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