1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_protocol::block::{BlockHeader, BlockNumber};
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::store::{BlockRelevance, StoreError};
13#[cfg(feature = "testing")]
14use crate::test_utils::mock::MockRpcApi;
15use crate::{CachedPartialMmr, Client, ClientError};
16
17impl<AUTH> Client<AUTH> {
19 pub async fn get_block_header_by_num(
23 &self,
24 block_num: BlockNumber,
25 ) -> Result<Option<(BlockHeader, BlockRelevance)>, ClientError> {
26 self.store.get_block_header_by_num(block_num).await.map_err(Into::into)
27 }
28
29 pub async fn get_latest_block_header(&self) -> Result<BlockHeader, ClientError> {
31 let sync_height = self.store.get_sync_height().await?;
32 let Some((block_header, _)) = self.get_block_header_by_num(sync_height).await? else {
33 return Err(ClientError::DataStoreError(miden_tx::DataStoreError::BlockNotFound(
34 sync_height,
35 )));
36 };
37 Ok(block_header)
38 }
39
40 pub async fn ensure_genesis_in_place(&mut self) -> Result<(), ClientError> {
44 if self.rpc_api.has_genesis_commitment().is_some() {
45 return Ok(());
46 }
47
48 let (genesis, _) = self
49 .rpc_api
50 .get_block_header_by_number(Some(BlockNumber::GENESIS), false)
51 .await?;
52
53 self.store.insert_block_header(&genesis, &[], false).await?;
56 self.rpc_api.set_genesis_commitment(genesis.commitment()).await?;
57 Ok(())
58 }
59
60 #[cfg(feature = "testing")]
66 pub async fn prepare_offline_bootstrap(&mut self) -> Result<(), ClientError> {
67 let limits = self.store.get_rpc_limits().await?.unwrap_or_default();
68 self.store.set_rpc_limits(limits).await?;
69 self.rpc_api.set_rpc_limits(limits).await;
70
71 if let Some((genesis, _)) = self.store.get_block_header_by_num(BlockNumber::GENESIS).await?
72 {
73 self.rpc_api.set_genesis_commitment(genesis.commitment()).await?;
74 return Ok(());
75 }
76
77 *self.test_rpc_api() = Arc::new(MockRpcApi::default());
78 self.ensure_genesis_in_place().await?;
79 Ok(())
80 }
81
82 pub async fn get_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
85 if self.cache_partial_mmr_in_memory
86 && let Some(ref cached) = self.partial_mmr
87 && cached.store_peaks_hash == self.current_store_peaks_hash().await?
88 && cached.tracked_blocks_hash == self.current_tracked_blocks_hash().await?
89 {
90 return Ok(cached.mmr.clone());
91 }
92 self.store.get_current_partial_mmr().await.map_err(Into::into)
93 }
94
95 pub(crate) async fn cache_partial_mmr(&mut self, mmr: PartialMmr) -> Result<(), ClientError> {
99 if !self.cache_partial_mmr_in_memory {
100 return Ok(());
101 }
102
103 let store_peaks_hash = self.current_store_peaks_hash().await?;
104 let tracked_blocks_hash = self.current_tracked_blocks_hash().await?;
105 self.partial_mmr = Some(CachedPartialMmr {
106 store_peaks_hash,
107 tracked_blocks_hash,
108 mmr,
109 });
110 Ok(())
111 }
112
113 async fn current_store_peaks_hash(&self) -> Result<Word, ClientError> {
116 Ok(self.store.get_current_blockchain_peaks().await?.hash_peaks())
117 }
118
119 async fn current_tracked_blocks_hash(&self) -> Result<Word, ClientError> {
122 let tracked = self.store.get_tracked_block_header_numbers().await?;
124 let elements: Vec<Felt> = tracked
125 .iter()
126 .map(|&n| Felt::from(u32::try_from(n).expect("block number fits in u32")))
127 .collect();
128 Ok(Rpo256::hash_elements(&elements))
129 }
130
131 pub(crate) async fn get_and_store_authenticated_block(
140 &self,
141 block_num: BlockNumber,
142 current_partial_mmr: &mut PartialMmr,
143 ) -> Result<BlockHeader, ClientError> {
144 if current_partial_mmr.is_tracked(block_num.as_usize()) {
145 warn!("Current partial MMR already contains the requested data");
146 let (block_header, _) = self
147 .store
148 .get_block_header_by_num(block_num)
149 .await?
150 .expect("Block header should be tracked");
151 return Ok(block_header);
152 }
153
154 let (block_header, path_nodes) =
156 fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
157 let tracked_nodes = authenticated_block_nodes(&block_header, path_nodes);
158
159 self.store.insert_block_header(&block_header, &tracked_nodes, true).await?;
161
162 Ok(block_header)
163 }
164}
165
166pub(crate) fn adjust_merkle_path_for_forest(
178 merkle_path: &MerklePath,
179 block_num: BlockNumber,
180 forest: Forest,
181) -> Vec<(InOrderIndex, Word)> {
182 let expected_path_len = forest
183 .leaf_to_corresponding_tree(block_num.as_usize())
184 .expect("forest includes block number") as usize;
185
186 let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
187 let mut path_nodes = Vec::with_capacity(expected_path_len);
188
189 for node in merkle_path.nodes().iter().take(expected_path_len) {
190 path_nodes.push((idx.sibling(), *node));
191 idx = idx.parent();
192 }
193
194 path_nodes
195}
196
197pub(crate) fn track_block_in_mmr(
201 partial_mmr: &mut PartialMmr,
202 block_num: BlockNumber,
203 block_commitment: Word,
204 mmr_path: &MerklePath,
205) -> Result<Vec<(InOrderIndex, Word)>, ClientError> {
206 let path_nodes = adjust_merkle_path_for_forest(mmr_path, block_num, partial_mmr.forest());
207 let adjusted_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
208
209 partial_mmr
210 .track(block_num.as_usize(), block_commitment, &adjusted_path)
211 .map_err(StoreError::MmrError)?;
212
213 Ok(path_nodes)
214}
215
216fn authenticated_block_nodes(
217 block_header: &BlockHeader,
218 mut path_nodes: Vec<(InOrderIndex, Word)>,
219) -> Vec<(InOrderIndex, Word)> {
220 let mut nodes = Vec::with_capacity(path_nodes.len() + 1);
221 nodes.push((
222 InOrderIndex::from_leaf_pos(block_header.block_num().as_usize()),
223 block_header.commitment(),
224 ));
225 nodes.append(&mut path_nodes);
226 nodes
227}
228
229pub(crate) async fn fetch_block_header(
230 rpc_api: Arc<dyn NodeRpcClient>,
231 block_num: BlockNumber,
232 current_partial_mmr: &mut PartialMmr,
233) -> Result<(BlockHeader, Vec<(InOrderIndex, Word)>), ClientError> {
234 let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
235
236 let path_nodes = track_block_in_mmr(
237 current_partial_mmr,
238 block_num,
239 block_header.commitment(),
240 mmr_proof.merkle_path(),
241 )?;
242
243 Ok((block_header, path_nodes))
244}
245
246#[cfg(test)]
247mod tests {
248 use miden_protocol::block::{BlockHeader, BlockNumber};
249 use miden_protocol::crypto::merkle::MerklePath;
250 use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, Mmr, PartialMmr};
251 use miden_protocol::transaction::TransactionKernel;
252 use miden_protocol::{Felt, Word};
253
254 use super::{adjust_merkle_path_for_forest, authenticated_block_nodes};
255
256 fn word(n: u64) -> Word {
257 Word::new([
258 Felt::new(n).expect("test value should fit into the base field"),
259 Felt::new(0).expect("zero is a valid field element"),
260 Felt::new(0).expect("zero is a valid field element"),
261 Felt::new(0).expect("zero is a valid field element"),
262 ])
263 }
264
265 #[test]
266 fn adjust_merkle_path_truncates_to_forest_bounds() {
267 let forest = Forest::new(5).expect("valid forest");
268 let block_num = BlockNumber::from(4u32);
270 let path = MerklePath::new(vec![word(1), word(2), word(3)]);
271
272 let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
273 assert!(adjusted.is_empty());
275 }
276
277 #[test]
278 #[should_panic(expected = "forest includes block number")]
279 fn adjust_merkle_path_panics_for_block_beyond_forest() {
280 let forest = Forest::new(5).expect("valid forest");
284 let block_num = BlockNumber::from(5u32);
285 let path = MerklePath::new(vec![word(1), word(2), word(3)]);
286
287 adjust_merkle_path_for_forest(&path, block_num, forest);
288 }
289
290 #[test]
291 fn adjust_merkle_path_keeps_proof_valid_for_smaller_forest() {
292 let mut mmr = Mmr::new();
295 for value in 0u64..8 {
296 mmr.add(word(value)).expect("test MMR append should succeed");
297 }
298
299 let large_forest = Forest::new(8).expect("valid forest");
300 let small_forest = Forest::new(5).expect("valid forest");
301 let leaf_pos = 4usize;
302 let block_num = BlockNumber::from(u32::try_from(leaf_pos).unwrap());
303
304 let proof = mmr.open_at(leaf_pos, large_forest).expect("valid proof");
305 let adjusted_nodes =
306 adjust_merkle_path_for_forest(proof.merkle_path(), block_num, small_forest);
307 let adjusted_path = MerklePath::new(adjusted_nodes.iter().map(|(_, n)| *n).collect());
308
309 let peaks = mmr.peaks_at(small_forest).unwrap();
310 let mut partial = PartialMmr::from_peaks(peaks);
311 let leaf = mmr.get(leaf_pos).expect("leaf exists");
312
313 partial
314 .track(leaf_pos, leaf, &adjusted_path)
315 .expect("adjusted path should verify against smaller forest peaks");
316 }
317
318 #[test]
319 fn adjust_merkle_path_correct_indices() {
320 let forest = Forest::new(6).expect("valid forest");
322 let block_num = BlockNumber::from(1u32);
324 let nodes = vec![word(10), word(11), word(12), word(13)];
325 let path = MerklePath::new(nodes.clone());
326
327 let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
328
329 assert_eq!(adjusted.len(), 2);
330 assert_eq!(adjusted[0].1, nodes[0]);
331 assert_eq!(adjusted[1].1, nodes[1]);
332
333 let mut idx = InOrderIndex::from_leaf_pos(1);
334 let expected0 = idx.sibling();
335 idx = idx.parent();
336 let expected1 = idx.sibling();
337
338 assert_eq!(adjusted[0].0, expected0);
339 assert_eq!(adjusted[1].0, expected1);
340 }
341
342 #[test]
343 fn adjust_path_limit_correct_when_siblings_in_bounds() {
344 let large_leaves = 8usize;
347 let small_leaves = 7usize;
348 let leaf_pos = 2usize;
349 let mut mmr = Mmr::new();
350 for value in 0u64..large_leaves as u64 {
351 mmr.add(word(value)).expect("test MMR append should succeed");
352 }
353
354 let small_forest = Forest::new(small_leaves).expect("valid forest");
355 let proof = mmr
356 .open_at(leaf_pos, Forest::new(large_leaves).expect("valid forest"))
357 .expect("valid proof");
358 let expected_depth =
359 small_forest.leaf_to_corresponding_tree(leaf_pos).expect("leaf is in forest") as usize;
360
361 let mut idx = InOrderIndex::from_leaf_pos(leaf_pos);
364 for _ in 0..expected_depth {
365 idx = idx.parent();
366 }
367 let next_sibling = idx.sibling();
368 let rightmost = InOrderIndex::from_leaf_pos(small_leaves - 1);
369 assert!(next_sibling <= rightmost);
370 assert!(proof.merkle_path().depth() as usize > expected_depth);
371
372 let adjusted = adjust_merkle_path_for_forest(
373 proof.merkle_path(),
374 BlockNumber::from(u32::try_from(leaf_pos).unwrap()),
375 small_forest,
376 );
377 assert_eq!(adjusted.len(), expected_depth);
378 }
379
380 #[test]
381 fn authenticated_block_nodes_include_leaf_commitment() {
382 let block_header = BlockHeader::mock(4, None, None, &[], TransactionKernel.to_commitment());
383 let path_nodes = vec![
384 (InOrderIndex::from_leaf_pos(4).sibling(), word(10)),
385 (InOrderIndex::from_leaf_pos(4).parent().sibling(), word(11)),
386 ];
387
388 let nodes = authenticated_block_nodes(&block_header, path_nodes.clone());
389
390 assert_eq!(nodes[0], (InOrderIndex::from_leaf_pos(4), block_header.commitment()));
391 assert_eq!(&nodes[1..], path_nodes.as_slice());
392 }
393}