miden_node_store/state/view/
inclusion_proofs.rs1use std::collections::{BTreeMap, BTreeSet};
7
8use miden_protocol::block::BlockNumber;
9use miden_protocol::note::{NoteId, NoteInclusionProof};
10use miden_protocol::transaction::PartialBlockchain;
11
12use super::StateView;
13use crate::errors::{GetBlockInclusionProofsError, GetNoteInclusionProofsError};
14
15impl StateView {
16 pub async fn get_note_inclusion_proofs(
18 &self,
19 reference_block: BlockNumber,
20 note_ids: BTreeSet<NoteId>,
21 ) -> Result<BTreeMap<NoteId, NoteInclusionProof>, GetNoteInclusionProofsError> {
22 let latest_block_num = self.tip();
23 let reference_block = self.scope_block(reference_block).ok_or(
24 GetNoteInclusionProofsError::ReferenceBlockAfterTip {
25 reference_block,
26 latest_block_num: *latest_block_num,
27 },
28 )?;
29
30 let note_commitments = note_ids.into_iter().map(|note_id| note_id.as_word()).collect();
31 self.db
32 .select_note_inclusion_proofs(note_commitments, reference_block)
33 .await
34 .map_err(GetNoteInclusionProofsError::SelectNoteInclusionProofError)
35 }
36
37 pub async fn get_block_inclusion_proofs(
41 &self,
42 reference_block: BlockNumber,
43 block_numbers: BTreeSet<BlockNumber>,
44 ) -> Result<PartialBlockchain, GetBlockInclusionProofsError> {
45 let latest_block_num = self.tip();
46 let reference_block = self.scope_block(reference_block).ok_or(
47 GetBlockInclusionProofsError::ReferenceBlockAfterTip {
48 reference_block,
49 latest_block_num: *latest_block_num,
50 },
51 )?;
52
53 if let Some(&block_num) = block_numbers.last()
54 && block_num > *reference_block
55 {
56 return Err(GetBlockInclusionProofsError::BlockAfterReferenceBlock {
57 block_num,
58 reference_block: *reference_block,
59 });
60 }
61
62 let mut blocks = block_numbers;
63
64 blocks.remove(&*reference_block);
66
67 let scoped_blocks = blocks
69 .iter()
70 .map(|&block| {
71 self.scope_block(block)
72 .expect("requested blocks must not exceed the reference block")
73 })
74 .collect::<Vec<_>>();
75
76 let partial_mmr = self
81 .blockchain()
82 .partial_mmr_from_blocks(&blocks, *reference_block)
83 .expect("all requested blocks must exist before the reference block");
84
85 let headers = self
86 .db
87 .select_block_headers(scoped_blocks.into_iter())
88 .await
89 .map_err(GetBlockInclusionProofsError::SelectBlockHeaderError)?;
90
91 Ok(PartialBlockchain::new_unchecked(partial_mmr, headers)
98 .expect("partial mmr and block headers should be consistent"))
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use miden_node_utils::fee::test_fee_params;
105 use miden_protocol::block::ValidatorKeys;
106 use miden_protocol::testing::random_secret_key::random_secret_key;
107
108 use super::*;
109 use crate::GenesisState;
110 use crate::state::State;
111
112 #[tokio::test]
113 async fn block_inclusion_proofs_use_the_requested_block() {
114 let data_directory = tempfile::tempdir().expect("tempdir should be created");
115 bootstrap_store(data_directory.path());
116 let (state, _block_writer, _proof_writer) = State::for_tests(data_directory.path()).await;
117
118 let partial_blockchain = state
119 .view()
120 .get_block_inclusion_proofs(BlockNumber::GENESIS, BTreeSet::new())
121 .await
122 .expect("block inclusion proofs should be returned");
123 assert_eq!(partial_blockchain.chain_length(), BlockNumber::GENESIS);
124
125 let error = state
126 .view()
127 .get_block_inclusion_proofs(
128 BlockNumber::GENESIS.child(),
129 BTreeSet::from([BlockNumber::GENESIS]),
130 )
131 .await
132 .expect_err("a reference block after the tip should fail");
133 assert!(matches!(
134 error,
135 GetBlockInclusionProofsError::ReferenceBlockAfterTip {
136 reference_block,
137 latest_block_num,
138 } if reference_block == BlockNumber::GENESIS.child()
139 && latest_block_num == BlockNumber::GENESIS
140 ));
141
142 let error = state
143 .view()
144 .get_block_inclusion_proofs(
145 BlockNumber::GENESIS,
146 BTreeSet::from([BlockNumber::GENESIS.child()]),
147 )
148 .await
149 .expect_err("a requested block after the reference block should fail");
150 assert!(matches!(
151 error,
152 GetBlockInclusionProofsError::BlockAfterReferenceBlock {
153 block_num,
154 reference_block,
155 } if block_num == BlockNumber::GENESIS.child()
156 && reference_block == BlockNumber::GENESIS
157 ));
158 }
159
160 #[tokio::test]
161 async fn note_inclusion_proofs_reject_a_reference_block_after_the_tip() {
162 let data_directory = tempfile::tempdir().expect("tempdir should be created");
163 bootstrap_store(data_directory.path());
164 let (state, _block_writer, _proof_writer) = State::for_tests(data_directory.path()).await;
165
166 let error = state
167 .view()
168 .get_note_inclusion_proofs(BlockNumber::GENESIS.child(), BTreeSet::new())
169 .await
170 .expect_err("a reference block after the tip should fail");
171 assert!(matches!(
172 error,
173 GetNoteInclusionProofsError::ReferenceBlockAfterTip {
174 reference_block,
175 latest_block_num,
176 } if reference_block == BlockNumber::GENESIS.child()
177 && latest_block_num == BlockNumber::GENESIS
178 ));
179 }
180
181 fn bootstrap_store(path: &std::path::Path) {
182 let signer = random_secret_key();
183 let genesis_state = GenesisState::new(
184 vec![],
185 test_fee_params(),
186 1,
187 1,
188 ValidatorKeys::new(vec![signer.public_key()]).expect("validator keys should be valid"),
189 );
190 let genesis_block = genesis_state.into_block().expect("genesis block should be created");
191
192 State::bootstrap(genesis_block, path).expect("store should bootstrap");
193 }
194}