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 contents(&self) -> Option<(&NsProof, &VidCommon)> {
40 self.proof
41 .as_ref()
42 .map(|proof| (&proof.proof, &proof.common))
43 }
44
45 pub fn verify(&self, header: &Header, namespace: NamespaceId) -> Result<Vec<Transaction>> {
50 let Some(proof) = &self.proof else {
51 if let Some(ix) = header.ns_table().find_ns_id(&namespace) {
54 bail!(
55 "received trivial proof for missing namespace, but requested namespace \
56 {namespace} is present at position {ix:?}"
57 );
58 }
59 return Ok(vec![]);
60 };
61
62 let (txs, ns) = proof
63 .proof
64 .verify(
65 header.ns_table(),
66 &header.payload_commitment(),
67 &proof.common,
68 )
69 .context("invalid namespace proof")?;
70 ensure!(
71 ns == namespace,
72 "proof is for wrong namespace {ns}, expected namespace {namespace}"
73 );
74 Ok(txs)
75 }
76}
77
78#[cfg(test)]
79mod test {
80 use espresso_types::{Leaf2, NodeState};
81 use hotshot_query_service_types::availability::TransactionIndex;
82 use versions::FEE_VERSION;
83
84 use super::*;
85 use crate::testing::TestClient;
86
87 #[tokio::test]
88 #[test_log::test]
89 async fn test_namespace_proof_non_empty() {
90 let client = TestClient::default();
91 let leaf = client.leaf(1).await;
92 let payload = client.payload(1).await;
93 let common = client.vid_common(1).await;
94
95 let tx = payload
96 .transaction(&TransactionIndex {
97 ns_index: 0.into(),
98 position: 0,
99 })
100 .unwrap();
101 let proof =
102 NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
103 assert_eq!(
104 proof.verify(leaf.header(), tx.namespace()).unwrap(),
105 vec![tx.clone()]
106 );
107
108 let err = NamespaceProof::not_present()
110 .verify(leaf.header(), tx.namespace())
111 .unwrap_err();
112 assert!(
113 err.to_string()
114 .contains("received trivial proof for missing namespace"),
115 "{err:#}"
116 );
117 }
118
119 #[tokio::test]
120 #[test_log::test]
121 async fn test_namespace_proof_empty() {
122 let leaf = Leaf2::genesis(&Default::default(), &NodeState::mock(), FEE_VERSION).await;
123 let proof = NamespaceProof::not_present();
124
125 assert_eq!(
126 proof.verify(leaf.block_header(), 0u64.into()).unwrap(),
127 vec![]
128 );
129 }
130
131 #[tokio::test]
132 #[test_log::test]
133 async fn test_namespace_proof_invalid_wrong_payload() {
134 let client = TestClient::default();
135
136 let payload1 = client.payload(1).await;
137 let common1 = client.vid_common(1).await;
138 let tx = payload1
139 .transaction(&TransactionIndex {
140 ns_index: 0.into(),
141 position: 0,
142 })
143 .unwrap();
144 let proof = NamespaceProof::new(
145 NsProof::new(&payload1, &0.into(), &common1).unwrap(),
146 common1,
147 );
148
149 let leaf2 = client.leaf(2).await;
150 let err = proof.verify(leaf2.header(), tx.namespace()).unwrap_err();
151 assert!(
152 err.to_string().contains("invalid namespace proof"),
153 "{err:#}"
154 );
155 }
156
157 #[tokio::test]
158 #[test_log::test]
159 async fn test_namespace_proof_invalid_wrong_namespace() {
160 let client = TestClient::default();
161 let leaf = client.leaf(1).await;
162 let payload = client.payload(1).await;
163 let common = client.vid_common(1).await;
164
165 let tx = payload
166 .transaction(&TransactionIndex {
167 ns_index: 0.into(),
168 position: 0,
169 })
170 .unwrap();
171 let proof =
172 NamespaceProof::new(NsProof::new(&payload, &0.into(), &common).unwrap(), common);
173 let err = proof
174 .verify(
175 leaf.header(),
176 NamespaceId::from(u64::from(tx.namespace()) + 1),
177 )
178 .unwrap_err();
179 assert!(
180 err.to_string().contains("proof is for wrong namespace"),
181 "{err:#}"
182 );
183 }
184}