Skip to main content

miden_client/sync/
block_header.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_protocol::Word;
5use miden_protocol::block::{BlockHeader, BlockNumber};
6use miden_protocol::crypto::merkle::MerklePath;
7use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, MmrPeaks, PartialMmr};
8use tracing::warn;
9
10use crate::rpc::NodeRpcClient;
11use crate::store::StoreError;
12use crate::{Client, ClientError};
13
14/// Network information management methods.
15impl<AUTH> Client<AUTH> {
16    /// Attempts to retrieve the genesis block from the store. If not found,
17    /// it requests it from the node and store it.
18    pub async fn ensure_genesis_in_place(&mut self) -> Result<BlockHeader, ClientError> {
19        let genesis = if let Some((block, _)) = self.store.get_block_header_by_num(0.into()).await?
20        {
21            block
22        } else {
23            let genesis = self.retrieve_and_store_genesis().await?;
24            self.rpc_api.set_genesis_commitment(genesis.commitment()).await?;
25            genesis
26        };
27
28        Ok(genesis)
29    }
30
31    /// Calls `get_block_header_by_number` requesting the genesis block and storing it
32    /// in the local database.
33    async fn retrieve_and_store_genesis(&mut self) -> Result<BlockHeader, ClientError> {
34        let (genesis_block, _) = self
35            .rpc_api
36            .get_block_header_by_number(Some(BlockNumber::GENESIS), false)
37            .await?;
38
39        let blank_mmr_peaks = MmrPeaks::new(Forest::empty(), vec![])
40            .expect("Blank MmrPeaks should not fail to instantiate");
41        self.store.insert_block_header(&genesis_block, blank_mmr_peaks, false).await?;
42        Ok(genesis_block)
43    }
44
45    /// Fetches from the store the current view of the chain's [`PartialMmr`].
46    pub async fn get_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
47        self.store.get_current_partial_mmr().await.map_err(Into::into)
48    }
49
50    // HELPERS
51    // --------------------------------------------------------------------------------------------
52
53    /// Retrieves and stores a [`BlockHeader`] by number, and stores its authentication data as
54    /// well.
55    ///
56    /// If the store already contains MMR data for the requested block number, the request isn't
57    /// done and the stored block header is returned.
58    pub(crate) async fn get_and_store_authenticated_block(
59        &self,
60        block_num: BlockNumber,
61        current_partial_mmr: &mut PartialMmr,
62    ) -> Result<BlockHeader, ClientError> {
63        if current_partial_mmr.is_tracked(block_num.as_usize()) {
64            warn!("Current partial MMR already contains the requested data");
65            let (block_header, _) = self
66                .store
67                .get_block_header_by_num(block_num)
68                .await?
69                .expect("Block header should be tracked");
70            return Ok(block_header);
71        }
72
73        // Fetch the block header and MMR proof from the node
74        let (block_header, path_nodes) =
75            fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
76
77        // Insert header and MMR nodes
78        self.store
79            .insert_block_header(&block_header, current_partial_mmr.peaks(), true)
80            .await?;
81        self.store.insert_partial_blockchain_nodes(&path_nodes).await?;
82
83        Ok(block_header)
84    }
85}
86
87// UTILS
88// --------------------------------------------------------------------------------------------
89
90/// Returns a merkle path nodes for a specific block adjusted for a defined forest size.
91/// This function trims the merkle path to include only the nodes that are relevant for
92/// the MMR forest.
93///
94/// # Parameters
95/// - `merkle_path`: Original merkle path.
96/// - `block_num`: The block number for which the path is computed.
97/// - `forest`: The target size of the forest.
98pub(crate) fn adjust_merkle_path_for_forest(
99    merkle_path: &MerklePath,
100    block_num: BlockNumber,
101    forest: Forest,
102) -> Vec<(InOrderIndex, Word)> {
103    let expected_path_len = forest
104        .leaf_to_corresponding_tree(block_num.as_usize())
105        .expect("forest includes block number") as usize;
106
107    let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
108    let mut path_nodes = Vec::with_capacity(expected_path_len);
109
110    for node in merkle_path.nodes().iter().take(expected_path_len) {
111        path_nodes.push((idx.sibling(), *node));
112        idx = idx.parent();
113    }
114
115    path_nodes
116}
117
118pub(crate) async fn fetch_block_header(
119    rpc_api: Arc<dyn NodeRpcClient>,
120    block_num: BlockNumber,
121    current_partial_mmr: &mut PartialMmr,
122) -> Result<(BlockHeader, Vec<(InOrderIndex, Word)>), ClientError> {
123    let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
124
125    // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR
126    // might be of a forest arbitrarily higher
127    let path_nodes = adjust_merkle_path_for_forest(
128        &mmr_proof.merkle_path,
129        block_num,
130        current_partial_mmr.forest(),
131    );
132
133    let merkle_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
134
135    current_partial_mmr
136        .track(block_num.as_usize(), block_header.commitment(), &merkle_path)
137        .map_err(StoreError::MmrError)?;
138
139    Ok((block_header, path_nodes))
140}
141
142#[cfg(test)]
143mod tests {
144    use miden_protocol::block::BlockNumber;
145    use miden_protocol::crypto::merkle::MerklePath;
146    use miden_protocol::crypto::merkle::mmr::{Forest, InOrderIndex, Mmr, PartialMmr};
147    use miden_protocol::{Felt, Word};
148
149    use super::adjust_merkle_path_for_forest;
150
151    fn word(n: u64) -> Word {
152        Word::new([Felt::new(n), Felt::new(0), Felt::new(0), Felt::new(0)])
153    }
154
155    #[test]
156    fn adjust_merkle_path_truncates_to_forest_bounds() {
157        let forest = Forest::new(5);
158        // Forest 5 <=> block 4 is rightmost leaf
159        let block_num = BlockNumber::from(4u32);
160        let path = MerklePath::new(vec![word(1), word(2), word(3)]);
161
162        let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
163        // Block 4 conforms a single leaf tree so it should be empty
164        assert!(adjusted.is_empty());
165    }
166
167    #[test]
168    fn adjust_merkle_path_keeps_proof_valid_for_smaller_forest() {
169        // Build a proof in a larger forest and ensure truncation does not keep siblings from a
170        // different tree in the smaller forest, which would invalidate the proof.
171        let mut mmr = Mmr::new();
172        for value in 0u64..8 {
173            mmr.add(word(value));
174        }
175
176        let large_forest = Forest::new(8);
177        let small_forest = Forest::new(5);
178        let leaf_pos = 4usize;
179        let block_num = BlockNumber::from(u32::try_from(leaf_pos).unwrap());
180
181        let proof = mmr.open_at(leaf_pos, large_forest).expect("valid proof");
182        let adjusted_nodes =
183            adjust_merkle_path_for_forest(&proof.merkle_path, block_num, small_forest);
184        let adjusted_path = MerklePath::new(adjusted_nodes.iter().map(|(_, n)| *n).collect());
185
186        let peaks = mmr.peaks_at(small_forest).unwrap();
187        let mut partial = PartialMmr::from_peaks(peaks);
188        let leaf = mmr.get(leaf_pos).expect("leaf exists");
189
190        partial
191            .track(leaf_pos, leaf, &adjusted_path)
192            .expect("adjusted path should verify against smaller forest peaks");
193    }
194
195    #[test]
196    fn adjust_merkle_path_correct_indices() {
197        // Forest 6 has trees of size 2 and 4
198        let forest = Forest::new(6);
199        // Block 1 is on tree with size 4 (merkle path should have 2 nodes)
200        let block_num = BlockNumber::from(1u32);
201        let nodes = vec![word(10), word(11), word(12), word(13)];
202        let path = MerklePath::new(nodes.clone());
203
204        let adjusted = adjust_merkle_path_for_forest(&path, block_num, forest);
205
206        assert_eq!(adjusted.len(), 2);
207        assert_eq!(adjusted[0].1, nodes[0]);
208        assert_eq!(adjusted[1].1, nodes[1]);
209
210        let mut idx = InOrderIndex::from_leaf_pos(1);
211        let expected0 = idx.sibling();
212        idx = idx.parent();
213        let expected1 = idx.sibling();
214
215        assert_eq!(adjusted[0].0, expected0);
216        assert_eq!(adjusted[1].0, expected1);
217    }
218
219    #[test]
220    fn adjust_path_limit_correct_when_siblings_in_bounds() {
221        // Ensure the expected depth limit matters even when the next sibling
222        // is "in-bounds" (but not part of the leaf's subtree for that forest)
223        let large_leaves = 8usize;
224        let small_leaves = 7usize;
225        let leaf_pos = 2usize;
226        let mut mmr = Mmr::new();
227        for value in 0u64..large_leaves as u64 {
228            mmr.add(word(value));
229        }
230
231        let small_forest = Forest::new(small_leaves);
232        let proof = mmr.open_at(leaf_pos, Forest::new(large_leaves)).expect("valid proof");
233        let expected_depth =
234            small_forest.leaf_to_corresponding_tree(leaf_pos).expect("leaf is in forest") as usize;
235
236        // Confirm the next sibling after the expected depth is still in bounds, which would
237        // create an overlong path without the depth cap.
238        let mut idx = InOrderIndex::from_leaf_pos(leaf_pos);
239        for _ in 0..expected_depth {
240            idx = idx.parent();
241        }
242        let next_sibling = idx.sibling();
243        let rightmost = InOrderIndex::from_leaf_pos(small_leaves - 1);
244        assert!(next_sibling <= rightmost);
245        assert!(proof.merkle_path.depth() as usize > expected_depth);
246
247        let adjusted = adjust_merkle_path_for_forest(
248            &proof.merkle_path,
249            BlockNumber::from(u32::try_from(leaf_pos).unwrap()),
250            small_forest,
251        );
252        assert_eq!(adjusted.len(), expected_depth);
253    }
254}