miden_client/sync/
block_header.rs

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