light_client/consensus/
namespace.rs1use anyhow::{Context, Result, bail, ensure};
2use espresso_types::{Header, NamespaceId, NsProof, Transaction};
3use hotshot_types::data::VidCommon;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
8
9pub struct NamespaceProof {
10 proof: Option<NonEmptyNamespaceProof>,
11}
12
13#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
14struct NonEmptyNamespaceProof {
15 proof: NsProof,
16 common: VidCommon,
17}
18
19impl NamespaceProof {
20 pub fn new(proof: NsProof, common: VidCommon) -> Self {
25 Self {
26 proof: Some(NonEmptyNamespaceProof { proof, common }),
27 }
28 }
29
30 pub fn not_present() -> Self {
32 Self { proof: None }
33 }
34
35 pub fn verify(&self, header: &Header, namespace: NamespaceId) -> Result<Vec<Transaction>> {
40 let Some(proof) = &self.proof else {
41 if let Some(ix) = header.ns_table().find_ns_id(&namespace) {
44 bail!(
45 "received trivial proof for missing namespace, but requested namespace \
46 {namespace} is present at position {ix:?}"
47 );
48 }
49 return Ok(vec![]);
50 };
51
52 let (txs, ns) = proof
53 .proof
54 .verify(
55 header.ns_table(),
56 &header.payload_commitment(),
57 &proof.common,
58 )
59 .context("invalid namespace proof")?;
60 ensure!(
61 ns == namespace,
62 "proof is for wrong namespace {ns}, expected namespace {namespace}"
63 );
64 Ok(txs)
65 }
66}
67
68#[cfg(test)]
69mod test {
70 use espresso_types::{Leaf2, NodeState};
71 use hotshot_query_service::availability::TransactionIndex;
72 use versions::FEE_VERSION;
73
74 use super::*;
75 use crate::testing::TestClient;
76
77 #[tokio::test]
78 #[test_log::test]
79 async fn test_namespace_proof_non_empty() {
80 let client = TestClient::default();
81 let leaf = client.leaf(1).await;
82 let payload = client.payload(1).await;
83 let common = client.vid_common(1).await;
84
85 let tx = payload
86 .transaction(&TransactionIndex {
87 ns_index: 0.into(),
88 position: 0,
89 })
90 .unwrap();
91 let proof =
92 NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
93 assert_eq!(
94 proof.verify(leaf.header(), tx.namespace()).unwrap(),
95 vec![tx.clone()]
96 );
97
98 let err = NamespaceProof::not_present()
100 .verify(leaf.header(), tx.namespace())
101 .unwrap_err();
102 assert!(
103 err.to_string()
104 .contains("received trivial proof for missing namespace"),
105 "{err:#}"
106 );
107 }
108
109 #[tokio::test]
110 #[test_log::test]
111 async fn test_namespace_proof_empty() {
112 let leaf = Leaf2::genesis(&Default::default(), &NodeState::mock(), FEE_VERSION).await;
113 let proof = NamespaceProof::not_present();
114
115 assert_eq!(
116 proof.verify(leaf.block_header(), 0u64.into()).unwrap(),
117 vec![]
118 );
119 }
120
121 #[tokio::test]
122 #[test_log::test]
123 async fn test_namespace_proof_invalid_wrong_payload() {
124 let client = TestClient::default();
125
126 let payload1 = client.payload(1).await;
127 let common1 = client.vid_common(1).await;
128 let tx = payload1
129 .transaction(&TransactionIndex {
130 ns_index: 0.into(),
131 position: 0,
132 })
133 .unwrap();
134 let proof = NamespaceProof::new(
135 NsProof::new(&payload1, &0.into(), &common1).unwrap(),
136 common1,
137 );
138
139 let leaf2 = client.leaf(2).await;
140 let err = proof.verify(leaf2.header(), tx.namespace()).unwrap_err();
141 assert!(
142 err.to_string().contains("invalid namespace proof"),
143 "{err:#}"
144 );
145 }
146
147 #[tokio::test]
148 #[test_log::test]
149 async fn test_namespace_proof_invalid_wrong_namespace() {
150 let client = TestClient::default();
151 let leaf = client.leaf(1).await;
152 let payload = client.payload(1).await;
153 let common = client.vid_common(1).await;
154
155 let tx = payload
156 .transaction(&TransactionIndex {
157 ns_index: 0.into(),
158 position: 0,
159 })
160 .unwrap();
161 let proof =
162 NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
163 let err = proof
164 .verify(
165 leaf.header(),
166 NamespaceId::from(u64::from(tx.namespace()) + 1),
167 )
168 .unwrap_err();
169 assert!(
170 err.to_string().contains("proof is for wrong namespace"),
171 "{err:#}"
172 );
173 }
174}