1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_protocol::block::{BlockHeader, BlockNumber, ValidatorConfig};
5use miden_protocol::crypto::hash::rpo::Rpo256;
6use miden_protocol::crypto::merkle::MerklePath;
7use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, PartialMmr};
8use miden_protocol::{Felt, Word};
9use tracing::warn;
10
11use crate::rpc::NodeRpcClient;
12use crate::rpc::domain::note::ResolvedSyncNotesBlock;
13use crate::store::{BlockRelevance, StoreError};
14#[cfg(feature = "testing")]
15use crate::test_utils::mock::MockRpcApi;
16use crate::{CachedPartialMmr, Client, ClientError};
17
18impl<AUTH> Client<AUTH> {
20 pub async fn get_block_header_by_num(
24 &self,
25 block_num: BlockNumber,
26 ) -> Result<Option<(BlockHeader, BlockRelevance)>, ClientError> {
27 self.store.get_block_header_by_num(block_num).await.map_err(Into::into)
28 }
29
30 pub async fn get_latest_block_header(&self) -> Result<BlockHeader, ClientError> {
32 let sync_height = self.store.get_sync_height().await?;
33 let Some((block_header, _)) = self.get_block_header_by_num(sync_height).await? else {
34 return Err(ClientError::DataStoreError(miden_tx::DataStoreError::BlockNotFound(
35 sync_height,
36 )));
37 };
38 Ok(block_header)
39 }
40
41 pub async fn get_validator_config(&self) -> Result<ValidatorConfig, ClientError> {
44 Ok(self.get_latest_block_header().await?.validator_config().clone())
45 }
46
47 pub async fn ensure_genesis_in_place(&mut self) -> Result<(), ClientError> {
51 if self.rpc_api.has_genesis_commitment().is_some() {
52 return Ok(());
53 }
54
55 let (genesis, _) = self
56 .rpc_api
57 .get_block_header_by_number(Some(BlockNumber::GENESIS), false)
58 .await?;
59
60 self.store.insert_block_header(&genesis, &[], false).await?;
63 self.rpc_api.set_genesis_commitment(genesis.commitment()).await?;
64 Ok(())
65 }
66
67 #[cfg(feature = "testing")]
73 pub async fn prepare_offline_bootstrap(&mut self) -> Result<(), ClientError> {
74 let limits = self.store.get_rpc_limits().await?.unwrap_or_default();
75 self.store.set_rpc_limits(limits).await?;
76 self.rpc_api.set_rpc_limits(limits).await;
77
78 if let Some((genesis, _)) = self.store.get_block_header_by_num(BlockNumber::GENESIS).await?
79 {
80 self.rpc_api.set_genesis_commitment(genesis.commitment()).await?;
81 return Ok(());
82 }
83
84 let rpc = MockRpcApi::default();
85 self.seed_protocol_config(rpc.protocol_config()).await?;
86 *self.test_rpc_api() = Arc::new(rpc);
87 self.ensure_genesis_in_place().await?;
88 Ok(())
89 }
90
91 pub async fn get_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
94 if self.cache_partial_mmr_in_memory
95 && let Some(ref cached) = self.partial_mmr
96 && cached.store_peaks_hash == self.current_store_peaks_hash().await?
97 && cached.tracked_blocks_hash == self.current_tracked_blocks_hash().await?
98 {
99 return Ok(cached.mmr.clone());
100 }
101 self.store.get_current_partial_mmr().await.map_err(Into::into)
102 }
103
104 pub(crate) async fn cache_partial_mmr(&mut self, mmr: PartialMmr) -> Result<(), ClientError> {
108 if !self.cache_partial_mmr_in_memory {
109 return Ok(());
110 }
111
112 let store_peaks_hash = self.current_store_peaks_hash().await?;
113 let tracked_blocks_hash = self.current_tracked_blocks_hash().await?;
114 self.partial_mmr = Some(CachedPartialMmr {
115 store_peaks_hash,
116 tracked_blocks_hash,
117 mmr,
118 });
119 Ok(())
120 }
121
122 async fn current_store_peaks_hash(&self) -> Result<Word, ClientError> {
125 Ok(self.store.get_current_blockchain_peaks().await?.hash_peaks())
126 }
127
128 async fn current_tracked_blocks_hash(&self) -> Result<Word, ClientError> {
131 let tracked = self.store.get_tracked_block_header_numbers().await?;
133 let elements: Vec<Felt> = tracked
134 .iter()
135 .map(|&n| Felt::from(u32::try_from(n).expect("block number fits in u32")))
136 .collect();
137 Ok(Rpo256::hash_elements(&elements))
138 }
139
140 pub(crate) async fn insert_note_blocks(
143 &mut self,
144 blocks: &[ResolvedSyncNotesBlock],
145 partial_mmr: &mut PartialMmr,
146 ) -> Result<(), ClientError> {
147 let mut authenticated_blocks = Vec::with_capacity(blocks.len());
148 for block in blocks {
149 let block_num = block.block_header.block_num();
150 if partial_mmr.is_tracked(block_num.as_usize()) {
153 continue;
154 }
155
156 let path_nodes = track_block_in_mmr(
157 partial_mmr,
158 block_num,
159 block.block_header.commitment(),
160 &block.mmr_path,
161 )?;
162 authenticated_blocks.push((block.block_header.clone(), path_nodes));
163 }
164
165 for (block_header, path_nodes) in authenticated_blocks {
166 let nodes = authenticated_block_nodes(&block_header, path_nodes);
167 self.store.insert_block_header(&block_header, &nodes, true).await?;
168 }
169
170 Ok(())
171 }
172
173 pub(crate) async fn get_and_store_authenticated_block(
182 &self,
183 block_num: BlockNumber,
184 current_partial_mmr: &mut PartialMmr,
185 ) -> Result<BlockHeader, ClientError> {
186 if current_partial_mmr.is_tracked(block_num.as_usize()) {
187 warn!("Current partial MMR already contains the requested data");
188 let (block_header, _) = self
189 .store
190 .get_block_header_by_num(block_num)
191 .await?
192 .expect("Block header should be tracked");
193 return Ok(block_header);
194 }
195
196 let (block_header, path_nodes) =
198 fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
199 let tracked_nodes = authenticated_block_nodes(&block_header, path_nodes);
200
201 self.store.insert_block_header(&block_header, &tracked_nodes, true).await?;
204
205 Ok(block_header)
206 }
207}
208
209pub(crate) fn adjust_merkle_path_for_forest(
220 merkle_path: &MerklePath,
221 block_num: BlockNumber,
222 forest: Forest,
223) -> Vec<(InOrderIndex, Word)> {
224 let expected_path_len = forest
225 .leaf_to_corresponding_tree(block_num.as_usize())
226 .expect("forest includes block number") as usize;
227
228 let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
229 let mut path_nodes = Vec::with_capacity(expected_path_len);
230
231 for node in merkle_path.nodes().iter().take(expected_path_len) {
232 path_nodes.push((idx.sibling(), *node));
233 idx = idx.parent();
234 }
235
236 path_nodes
237}
238
239pub(crate) fn track_block_in_mmr(
242 partial_mmr: &mut PartialMmr,
243 block_num: BlockNumber,
244 block_commitment: Word,
245 mmr_path: &MerklePath,
246) -> Result<Vec<(InOrderIndex, Word)>, ClientError> {
247 let path_nodes = adjust_merkle_path_for_forest(mmr_path, block_num, partial_mmr.forest());
248 let adjusted_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
249
250 partial_mmr
251 .track(block_num.as_usize(), block_commitment, &adjusted_path)
252 .map_err(StoreError::MmrError)?;
253
254 Ok(path_nodes)
255}
256
257fn authenticated_block_nodes(
258 block_header: &BlockHeader,
259 mut path_nodes: Vec<(InOrderIndex, Word)>,
260) -> Vec<(InOrderIndex, Word)> {
261 let mut nodes = Vec::with_capacity(path_nodes.len() + 1);
262 nodes.push((
263 InOrderIndex::from_leaf_pos(block_header.block_num().as_usize()),
264 block_header.commitment(),
265 ));
266 nodes.append(&mut path_nodes);
267 nodes
268}
269
270pub(crate) async fn fetch_block_header(
271 rpc_api: Arc<dyn NodeRpcClient>,
272 block_num: BlockNumber,
273 current_partial_mmr: &mut PartialMmr,
274) -> Result<(BlockHeader, Vec<(InOrderIndex, Word)>), ClientError> {
275 let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
276
277 let path_nodes = track_block_in_mmr(
278 current_partial_mmr,
279 block_num,
280 block_header.commitment(),
281 mmr_proof.merkle_path(),
282 )?;
283
284 Ok((block_header, path_nodes))
285}
286
287#[cfg(test)]
288mod tests {
289 use miden_protocol::block::{BlockHeader, BlockNumber};
290 use miden_protocol::crypto::merkle::MerklePath;
291 use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, Mmr, PartialMmr};
292 use miden_protocol::{Felt, Word};
293
294 use super::{adjust_merkle_path_for_forest, authenticated_block_nodes};
295
296 fn word(n: u64) -> Word {
297 Word::new([
298 Felt::new(n).expect("test value should fit into the base field"),
299 Felt::new(0).expect("zero is a valid field element"),
300 Felt::new(0).expect("zero is a valid field element"),
301 Felt::new(0).expect("zero is a valid field element"),
302 ])
303 }
304
305 #[test]
306 fn adjust_merkle_path_truncates_to_forest_bounds() {
307 let forest = Forest::new(5).expect("valid forest");
308 let block_num = BlockNumber::from(4u32);
310 let path = MerklePath::new(vec![word(1), word(2), word(3)]);
311
312 let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
313 assert!(adjusted.is_empty());
315 }
316
317 #[test]
318 #[should_panic(expected = "forest includes block number")]
319 fn adjust_merkle_path_panics_for_block_beyond_forest() {
320 let forest = Forest::new(5).expect("valid forest");
324 let block_num = BlockNumber::from(5u32);
325 let path = MerklePath::new(vec![word(1), word(2), word(3)]);
326
327 adjust_merkle_path_for_forest(&path, block_num, forest);
328 }
329
330 #[test]
331 fn adjust_merkle_path_keeps_proof_valid_for_smaller_forest() {
332 let mut mmr = Mmr::new();
335 for value in 0u64..8 {
336 mmr.add(word(value)).expect("test MMR append should succeed");
337 }
338
339 let large_forest = Forest::new(8).expect("valid forest");
340 let small_forest = Forest::new(5).expect("valid forest");
341 let leaf_pos = 4usize;
342 let block_num = BlockNumber::from(u32::try_from(leaf_pos).unwrap());
343
344 let proof = mmr.open_at(leaf_pos, large_forest).expect("valid proof");
345 let adjusted_nodes =
346 adjust_merkle_path_for_forest(proof.merkle_path(), block_num, small_forest);
347 let adjusted_path = MerklePath::new(adjusted_nodes.iter().map(|(_, n)| *n).collect());
348
349 let peaks = mmr.peaks_at(small_forest).unwrap();
350 let mut partial = PartialMmr::from_peaks(peaks);
351 let leaf = mmr.get(leaf_pos).expect("leaf exists");
352
353 partial
354 .track(leaf_pos, leaf, &adjusted_path)
355 .expect("adjusted path should verify against smaller forest peaks");
356 }
357
358 #[test]
359 fn adjust_merkle_path_correct_indices() {
360 let forest = Forest::new(6).expect("valid forest");
362 let block_num = BlockNumber::from(1u32);
364 let nodes = vec![word(10), word(11), word(12), word(13)];
365 let path = MerklePath::new(nodes.clone());
366
367 let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
368
369 assert_eq!(adjusted.len(), 2);
370 assert_eq!(adjusted[0].1, nodes[0]);
371 assert_eq!(adjusted[1].1, nodes[1]);
372
373 let mut idx = InOrderIndex::from_leaf_pos(1);
374 let expected0 = idx.sibling();
375 idx = idx.parent();
376 let expected1 = idx.sibling();
377
378 assert_eq!(adjusted[0].0, expected0);
379 assert_eq!(adjusted[1].0, expected1);
380 }
381
382 #[test]
383 fn adjust_path_limit_correct_when_siblings_in_bounds() {
384 let large_leaves = 8usize;
387 let small_leaves = 7usize;
388 let leaf_pos = 2usize;
389 let mut mmr = Mmr::new();
390 for value in 0u64..large_leaves as u64 {
391 mmr.add(word(value)).expect("test MMR append should succeed");
392 }
393
394 let small_forest = Forest::new(small_leaves).expect("valid forest");
395 let proof = mmr
396 .open_at(leaf_pos, Forest::new(large_leaves).expect("valid forest"))
397 .expect("valid proof");
398 let expected_depth =
399 small_forest.leaf_to_corresponding_tree(leaf_pos).expect("leaf is in forest") as usize;
400
401 let mut idx = InOrderIndex::from_leaf_pos(leaf_pos);
404 for _ in 0..expected_depth {
405 idx = idx.parent();
406 }
407 let next_sibling = idx.sibling();
408 let rightmost = InOrderIndex::from_leaf_pos(small_leaves - 1);
409 assert!(next_sibling <= rightmost);
410 assert!(proof.merkle_path().depth() as usize > expected_depth);
411
412 let adjusted = adjust_merkle_path_for_forest(
413 proof.merkle_path(),
414 BlockNumber::from(u32::try_from(leaf_pos).unwrap()),
415 small_forest,
416 );
417 assert_eq!(adjusted.len(), expected_depth);
418 }
419
420 #[test]
421 fn authenticated_block_nodes_include_leaf_commitment() {
422 let block_header = BlockHeader::mock(4, None, None, &[]);
423 let path_nodes = vec![
424 (InOrderIndex::from_leaf_pos(4).sibling(), word(10)),
425 (InOrderIndex::from_leaf_pos(4).parent().sibling(), word(11)),
426 ];
427
428 let nodes = authenticated_block_nodes(&block_header, path_nodes.clone());
429
430 assert_eq!(nodes[0], (InOrderIndex::from_leaf_pos(4), block_header.commitment()));
431 assert_eq!(&nodes[1..], path_nodes.as_slice());
432 }
433}