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    pub(crate) async fn build_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
51        let current_block_num = self.store.get_sync_height().await?;
52
53        let tracked_nodes =
54            self.store.get_partial_blockchain_nodes(PartialBlockchainFilter::All).await?;
55        let current_peaks =
56            self.store.get_partial_blockchain_peaks_by_block_num(current_block_num).await?;
57
58        // FIXME: Because each block stores the peaks for the MMR for the leaf of pos `block_num-1`,
59        // we can get an MMR based on those peaks, add the current block number and align it with
60        // the set of all nodes in the store.
61        // Otherwise, by doing `PartialMmr::from_parts` we would effectively have more nodes than
62        // we need for the passed peaks. The alternative here is to truncate the set of all nodes
63        // before calling `from_parts`
64        //
65        // This is a bit hacky but it works. One alternative would be to _just_ get nodes required
66        // for tracked blocks in the MMR. This would however block us from the convenience of
67        // just getting all nodes from the store.
68
69        let (current_block, has_client_notes) = self
70            .store
71            .get_block_header_by_num(current_block_num)
72            .await?
73            .expect("Current block should be in the store");
74
75        let mut current_partial_mmr = PartialMmr::from_peaks(current_peaks);
76        current_partial_mmr.add(current_block.commitment(), has_client_notes);
77
78        let current_partial_mmr =
79            PartialMmr::from_parts(current_partial_mmr.peaks(), tracked_nodes, has_client_notes);
80
81        Ok(current_partial_mmr)
82    }
83
84    /// Retrieves and stores a [`BlockHeader`] by number, and stores its authentication data as
85    /// well.
86    ///
87    /// If the store already contains MMR data for the requested block number, the request isn't
88    /// done and the stored block header is returned.
89    pub(crate) async fn get_and_store_authenticated_block(
90        &self,
91        block_num: BlockNumber,
92        current_partial_mmr: &mut PartialMmr,
93    ) -> Result<BlockHeader, ClientError> {
94        if current_partial_mmr.is_tracked(block_num.as_usize()) {
95            warn!("Current partial MMR already contains the requested data");
96            let (block_header, _) = self
97                .store
98                .get_block_header_by_num(block_num)
99                .await?
100                .expect("Block header should be tracked");
101            return Ok(block_header);
102        }
103
104        // Fetch the block header and MMR proof from the node
105        let (block_header, path_nodes) =
106            fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
107
108        // Insert header and MMR nodes
109        self.store
110            .insert_block_header(&block_header, current_partial_mmr.peaks(), true)
111            .await?;
112        self.store.insert_partial_blockchain_nodes(&path_nodes).await?;
113
114        Ok(block_header)
115    }
116}
117
118// UTILS
119// --------------------------------------------------------------------------------------------
120
121/// Returns a merkle path nodes for a specific block adjusted for a defined forest size.
122/// This function trims the merkle path to include only the nodes that are relevant for
123/// the MMR forest.
124///
125/// # Parameters
126/// - `merkle_path`: Original merkle path.
127/// - `block_num`: The block number for which the path is computed.
128/// - `forest`: The target size of the forest.
129pub(crate) fn adjust_merkle_path_for_forest(
130    merkle_path: &MerklePath,
131    block_num: BlockNumber,
132    forest: usize,
133) -> Vec<(InOrderIndex, Digest)> {
134    assert!(
135        forest > block_num.as_usize(),
136        "Can't adjust merkle path for a forest that does not include the block number"
137    );
138
139    let rightmost_index = InOrderIndex::from_leaf_pos(forest - 1);
140
141    let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
142    let mut path_nodes = vec![];
143    for node in merkle_path.iter() {
144        idx = idx.sibling();
145        // Rightmost index is always the biggest value, so if the path contains any node
146        // past it, we can discard it for our version of the forest
147        if idx <= rightmost_index {
148            path_nodes.push((idx, *node));
149        }
150        idx = idx.parent();
151    }
152
153    path_nodes
154}
155
156pub(crate) async fn fetch_block_header(
157    rpc_api: Arc<dyn NodeRpcClient>,
158    block_num: BlockNumber,
159    current_partial_mmr: &mut PartialMmr,
160) -> Result<(BlockHeader, Vec<(InOrderIndex, Digest)>), ClientError> {
161    let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
162
163    // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR
164    // might be of a forest arbitrarily higher
165    let path_nodes = adjust_merkle_path_for_forest(
166        &mmr_proof.merkle_path,
167        block_num,
168        current_partial_mmr.forest(),
169    );
170
171    let merkle_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
172
173    current_partial_mmr
174        .track(block_num.as_usize(), block_header.commitment(), &merkle_path)
175        .map_err(StoreError::MmrError)?;
176
177    Ok((block_header, path_nodes))
178}