miden_client/sync/
block_header.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use crypto::merkle::{InOrderIndex, MmrPeaks, PartialMmr};
4use miden_objects::{
5    Digest,
6    block::{BlockHeader, BlockNumber},
7    crypto::{self, merkle::MerklePath},
8};
9use tracing::warn;
10
11use crate::{
12    Client, ClientError,
13    rpc::NodeRpcClient,
14    store::{PartialBlockchainFilter, StoreError},
15};
16
17/// Network information management methods.
18impl Client {
19    /// Attempts to retrieve the genesis block from the store. If not found,
20    /// it requests it from the node and store it.
21    pub async fn ensure_genesis_in_place(&mut self) -> Result<BlockHeader, ClientError> {
22        let genesis = self.store.get_block_header_by_num(0.into()).await?;
23
24        match genesis {
25            Some((block, _)) => Ok(block),
26            None => self.retrieve_and_store_genesis().await,
27        }
28    }
29
30    /// Calls `get_block_header_by_number` requesting the genesis block and storing it
31    /// in the local database.
32    async fn retrieve_and_store_genesis(&mut self) -> Result<BlockHeader, ClientError> {
33        let (genesis_block, _) = self
34            .rpc_api
35            .get_block_header_by_number(Some(BlockNumber::GENESIS), false)
36            .await?;
37
38        let blank_mmr_peaks =
39            MmrPeaks::new(0, vec![]).expect("Blank MmrPeaks should not fail to instantiate");
40        self.store.insert_block_header(&genesis_block, blank_mmr_peaks, false).await?;
41        Ok(genesis_block)
42    }
43
44    // HELPERS
45    // --------------------------------------------------------------------------------------------
46
47    /// Builds the current view of the chain's [`PartialMmr`]. Because we want to add all new
48    /// authentication nodes that could come from applying the MMR updates, we need to track all
49    /// known leaves thus far.
50    ///
51    /// As part of the syncing process, we add the current block number so we don't need to
52    /// track it here.
53    pub(crate) async fn build_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
54        let current_block_num = self.store.get_sync_height().await?;
55
56        let tracked_nodes =
57            self.store.get_partial_blockchain_nodes(PartialBlockchainFilter::All).await?;
58        let current_peaks =
59            self.store.get_partial_blockchain_peaks_by_block_num(current_block_num).await?;
60
61        let track_latest = if current_block_num.as_u32() != 0 {
62            match self
63                .store
64                .get_block_header_by_num(BlockNumber::from(current_block_num.as_u32() - 1))
65                .await?
66            {
67                Some((_, previous_block_had_notes)) => previous_block_had_notes,
68                None => false,
69            }
70        } else {
71            false
72        };
73
74        let mut current_partial_mmr =
75            PartialMmr::from_parts(current_peaks, tracked_nodes, track_latest);
76
77        let (current_block, has_client_notes) = self
78            .store
79            .get_block_header_by_num(current_block_num)
80            .await?
81            .expect("Current block should be in the store");
82
83        current_partial_mmr.add(current_block.commitment(), has_client_notes);
84
85        Ok(current_partial_mmr)
86    }
87
88    /// Retrieves and stores a [`BlockHeader`] by number, and stores its authentication data as
89    /// well.
90    ///
91    /// If the store already contains MMR data for the requested block number, the request isn't
92    /// done and the stored block header is returned.
93    pub(crate) async fn get_and_store_authenticated_block(
94        &self,
95        block_num: BlockNumber,
96        current_partial_mmr: &mut PartialMmr,
97    ) -> Result<BlockHeader, ClientError> {
98        if current_partial_mmr.is_tracked(block_num.as_usize()) {
99            warn!("Current partial MMR already contains the requested data");
100            let (block_header, _) = self
101                .store
102                .get_block_header_by_num(block_num)
103                .await?
104                .expect("Block header should be tracked");
105            return Ok(block_header);
106        }
107
108        // Fetch the block header and MMR proof from the node
109        let (block_header, path_nodes) =
110            fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
111
112        // Insert header and MMR nodes
113        self.store
114            .insert_block_header(&block_header, current_partial_mmr.peaks(), true)
115            .await?;
116        self.store.insert_partial_blockchain_nodes(&path_nodes).await?;
117
118        Ok(block_header)
119    }
120}
121
122// UTILS
123// --------------------------------------------------------------------------------------------
124
125/// Returns a merkle path nodes for a specific block adjusted for a defined forest size.
126/// This function trims the merkle path to include only the nodes that are relevant for
127/// the MMR forest.
128///
129/// # Parameters
130/// - `merkle_path`: Original merkle path.
131/// - `block_num`: The block number for which the path is computed.
132/// - `forest`: The target size of the forest.
133pub(crate) fn adjust_merkle_path_for_forest(
134    merkle_path: &MerklePath,
135    block_num: BlockNumber,
136    forest: usize,
137) -> Vec<(InOrderIndex, Digest)> {
138    assert!(
139        forest > block_num.as_usize(),
140        "Can't adjust merkle path for a forest that does not include the block number"
141    );
142
143    let rightmost_index = InOrderIndex::from_leaf_pos(forest - 1);
144
145    let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
146    let mut path_nodes = vec![];
147    for node in merkle_path.iter() {
148        idx = idx.sibling();
149        // Rightmost index is always the biggest value, so if the path contains any node
150        // past it, we can discard it for our version of the forest
151        if idx <= rightmost_index {
152            path_nodes.push((idx, *node));
153        }
154        idx = idx.parent();
155    }
156
157    path_nodes
158}
159
160pub(crate) async fn fetch_block_header(
161    rpc_api: Arc<dyn NodeRpcClient>,
162    block_num: BlockNumber,
163    current_partial_mmr: &mut PartialMmr,
164) -> Result<(BlockHeader, Vec<(InOrderIndex, Digest)>), ClientError> {
165    let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
166
167    // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR
168    // might be of a forest arbitrarily higher
169    let path_nodes = adjust_merkle_path_for_forest(
170        &mmr_proof.merkle_path,
171        block_num,
172        current_partial_mmr.forest(),
173    );
174
175    let merkle_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
176
177    current_partial_mmr
178        .track(block_num.as_usize(), block_header.commitment(), &merkle_path)
179        .map_err(StoreError::MmrError)?;
180
181    Ok((block_header, path_nodes))
182}