data_anchor_proofs/compound/
completeness.rs1use serde::{Deserialize, Serialize};
5use solana_sdk::{clock::Slot, hash::Hash, pubkey::Pubkey};
6use thiserror::Error;
7
8use crate::{
9 accounts_delta_hash::exclusion::{ExclusionProof, ExclusionProofError},
10 bank_hash::BankHashProof,
11};
12
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
24pub struct CompoundCompletenessProof {
25 slot: Slot,
26 blober_exclusion_proof: ExclusionProof,
27 pub bank_hash_proof: BankHashProof,
28}
29
30#[derive(Debug, Clone, Error)]
32pub enum CompoundCompletenessProofError {
33 #[error("The exclusion proof is not for the blober account")]
34 ExcludedAccountNotBlober,
35 #[error(
36 "The proof is for a different blockhash than the one provided, expected {expected:?}, found {found:?}"
37 )]
38 BlockHashMismatch { expected: Hash, found: Hash },
39 #[error(transparent)]
40 AccountsDeltaHash(#[from] ExclusionProofError),
41}
42
43impl CompoundCompletenessProof {
44 pub fn new(
46 slot: Slot,
47 blober_exclusion_proof: ExclusionProof,
48 bank_hash_proof: BankHashProof,
49 ) -> Self {
50 Self {
51 slot,
52 blober_exclusion_proof,
53 bank_hash_proof,
54 }
55 }
56
57 #[tracing::instrument(skip_all, err(Debug), fields(slot = %self.slot, blober = %blober, blockhash = %blockhash))]
59 pub fn verify(
60 &self,
61 blober: Pubkey,
62 blockhash: Hash,
63 ) -> Result<(), CompoundCompletenessProofError> {
64 if let Some(excluded) = self.blober_exclusion_proof.excluded() {
65 if excluded != &blober {
67 return Err(CompoundCompletenessProofError::ExcludedAccountNotBlober);
68 }
69 } if self.bank_hash_proof.blockhash != blockhash {
72 return Err(CompoundCompletenessProofError::BlockHashMismatch {
73 expected: blockhash,
74 found: self.bank_hash_proof.blockhash,
75 });
76 }
77
78 self.blober_exclusion_proof
79 .verify(self.bank_hash_proof.accounts_delta_hash)?;
80
81 Ok(())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use std::collections::HashSet;
88
89 use arbtest::arbtest;
90 use solana_sdk::{
91 account::Account,
92 hash::Hash,
93 slot_hashes::SlotHashes,
94 sysvar::{self, SysvarId},
95 };
96
97 use super::*;
98 use crate::{
99 accounts_delta_hash::{
100 AccountMerkleTree,
101 exclusion::{ExclusionProof, left::ExclusionLeftProof},
102 testing::{ArbAccount, ArbKeypair, UnwrapOrArbitrary, choose_or_generate},
103 },
104 testing::arbitrary_hash,
105 };
106
107 #[test]
108 fn completeness_construction() {
109 arbtest(|u| {
110 let accounts: Vec<(ArbKeypair, ArbAccount)> = u.arbitrary()?;
111 let (leftmost_index, leftmost) = choose_or_generate(u, &accounts)?;
112
113 let blober = u.arbitrary::<ArbKeypair>()?.pubkey();
114
115 let mut solana_accounts = accounts
116 .into_iter()
117 .map(|(keypair, account)| (keypair.pubkey(), account.into()))
118 .collect::<Vec<_>>();
119 let is_excluded = if u.ratio(1, 2)? {
120 solana_accounts.push((
121 blober,
122 Account {
123 ..Default::default()
126 },
127 ));
128 false
129 } else {
130 true
131 };
132 solana_accounts.sort_by_key(|(pubkey, _)| *pubkey);
133
134 let not_blober = u.arbitrary::<ArbKeypair>()?.pubkey();
136 let mut tree = AccountMerkleTree::builder([blober, not_blober].into_iter().collect());
137 for (pubkey, account) in solana_accounts.iter() {
138 tree.insert(*pubkey, account.clone());
139 }
140 let tree = tree.build();
141
142 let parent_bankhash = arbitrary_hash(u)?;
143 let signature_count = u.arbitrary()?;
144 let blockhash = arbitrary_hash(u)?;
145 let root = tree.root();
146 let bank_hash_proof =
147 BankHashProof::new(parent_bankhash, root, signature_count, blockhash);
148
149 let mut trusted_vote_authorities: Vec<ArbKeypair> = vec![
150 arbitrary::Arbitrary::arbitrary(u)?,
151 arbitrary::Arbitrary::arbitrary(u)?,
152 ];
153 trusted_vote_authorities.sort_by_key(|pk| pk.pubkey());
154
155 let required_votes = 1 + u.choose_index(trusted_vote_authorities.len())?;
156
157 let votes_valid =
158 required_votes <= trusted_vote_authorities.len() && required_votes > 0;
159
160 let proven_slot = u.arbitrary()?;
161 let proven_hash = bank_hash_proof.hash();
162
163 let slot_hashes = u
164 .arbitrary_iter::<(u64, [u8; 32])>()?
165 .map(|tup| Ok((tup?.0, Hash::new_from_array(tup?.1))))
166 .chain([Ok((proven_slot, proven_hash))].into_iter())
168 .collect::<Result<HashSet<_>, _>>()?
169 .into_iter()
170 .collect::<Vec<_>>();
171 if slot_hashes.is_empty() {
172 return Ok(());
173 }
174
175 let slot_hashes = SlotHashes::new(&slot_hashes);
176
177 let mut slot_hashes_account: Account = u.arbitrary::<ArbAccount>()?.into();
178 slot_hashes_account.data = bincode::serialize(&slot_hashes).unwrap();
179
180 let mut slot_hashes_tree =
181 AccountMerkleTree::builder([sysvar::slot_hashes::ID].into_iter().collect());
182 slot_hashes_tree.insert(SlotHashes::id(), slot_hashes_account);
183
184 if is_excluded {
185 let exclusion_proof = tree.prove_exclusion(blober).unwrap();
186 let proof = CompoundCompletenessProof::new(
187 proven_slot,
188 exclusion_proof.clone(),
189 bank_hash_proof,
190 );
191 if u.ratio(1, 5)? {
192 let accounts_delta_hash = arbitrary_hash(u)?;
194 let bank_hash_proof = BankHashProof::new(
195 parent_bankhash,
196 accounts_delta_hash,
197 signature_count,
198 blockhash,
199 );
200 let proof = CompoundCompletenessProof::new(
201 proven_slot,
202 exclusion_proof,
203 bank_hash_proof,
204 );
205 proof.verify(blober, bank_hash_proof.blockhash).unwrap_err();
206 roundtrip_serialization(proof);
207 } else if !solana_accounts.is_empty() && u.ratio(1, 5)? {
208 if not_blober != blober {
210 dbg!(&tree, ¬_blober.to_string());
211 if let Some(exclusion_proof) = tree.prove_exclusion(not_blober) {
212 let proof = CompoundCompletenessProof::new(
213 proven_slot,
214 exclusion_proof,
215 bank_hash_proof,
216 );
217 proof.verify(blober, bank_hash_proof.blockhash).unwrap_err();
218 roundtrip_serialization(proof);
219 }
220 }
221 } else if !votes_valid {
222 proof.verify(blober, bank_hash_proof.blockhash).unwrap_err();
224 roundtrip_serialization(proof);
225 } else {
226 dbg!(&proof);
227 proof
228 .verify(
229 blober,
230 bank_hash_proof.blockhash,
233 )
234 .unwrap();
235 roundtrip_serialization(proof);
236 };
237 } else {
238 let false_exclusion_proof = ExclusionProof::ExclusionLeft(ExclusionLeftProof {
241 excluded: blober,
242 leftmost: tree.unchecked_inclusion_proof(
243 leftmost_index.unwrap_or_arbitrary(u)?,
244 &leftmost.0.pubkey(),
245 &leftmost.1.clone().into(),
246 ),
247 });
248 let proof = CompoundCompletenessProof::new(
249 proven_slot,
250 false_exclusion_proof,
251 bank_hash_proof,
252 );
253 dbg!(&solana_accounts, &proof);
254 proof.verify(blober, bank_hash_proof.blockhash).unwrap_err();
255 roundtrip_serialization(proof);
256 }
257
258 Ok(())
259 })
260 .size_max(100_000_000);
261 }
262
263 fn roundtrip_serialization(proof: CompoundCompletenessProof) {
264 let serialized_json = serde_json::to_string(&proof).unwrap();
265 let deserialized_json: CompoundCompletenessProof =
266 serde_json::from_str(&serialized_json).unwrap();
267 assert_eq!(proof, deserialized_json);
268
269 let serialized_bincode = bincode::serialize(&proof).unwrap();
270 let deserialized_bincode: CompoundCompletenessProof =
271 bincode::deserialize(&serialized_bincode).unwrap();
272 assert_eq!(proof, deserialized_bincode);
273
274 let serialized_risc0_zkvm = risc0_zkvm::serde::to_vec(&proof).unwrap();
275 let deserialized_risc0_zkvm: CompoundCompletenessProof =
276 risc0_zkvm::serde::from_slice(&serialized_risc0_zkvm).unwrap();
277 assert_eq!(proof, deserialized_risc0_zkvm);
278 }
279}