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        // We specify that we want to store the MMR data from the genesis block as we might use it
41        // as an anchor for created accounts.
42        self.store.insert_block_header(&genesis_block, blank_mmr_peaks, true).await?;
43        Ok(genesis_block)
44    }
45
46    // HELPERS
47    // --------------------------------------------------------------------------------------------
48
49    /// Builds the current view of the chain's [`PartialMmr`]. Because we want to add all new
50    /// authentication nodes that could come from applying the MMR updates, we need to track all
51    /// known leaves thus far.
52    ///
53    /// As part of the syncing process, we add the current block number so we don't need to
54    /// track it here.
55    pub(crate) async fn build_current_partial_mmr(&self) -> Result<PartialMmr, ClientError> {
56        let current_block_num = self.store.get_sync_height().await?;
57
58        let tracked_nodes =
59            self.store.get_partial_blockchain_nodes(PartialBlockchainFilter::All).await?;
60        let current_peaks =
61            self.store.get_partial_blockchain_peaks_by_block_num(current_block_num).await?;
62
63        let track_latest = if current_block_num.as_u32() != 0 {
64            match self
65                .store
66                .get_block_header_by_num(BlockNumber::from(current_block_num.as_u32() - 1))
67                .await?
68            {
69                Some((_, previous_block_had_notes)) => previous_block_had_notes,
70                None => false,
71            }
72        } else {
73            false
74        };
75
76        let mut current_partial_mmr =
77            PartialMmr::from_parts(current_peaks, tracked_nodes, track_latest);
78
79        let (current_block, has_client_notes) = self
80            .store
81            .get_block_header_by_num(current_block_num)
82            .await?
83            .expect("Current block should be in the store");
84
85        current_partial_mmr.add(current_block.commitment(), has_client_notes);
86
87        Ok(current_partial_mmr)
88    }
89
90    /// Retrieves and stores a [`BlockHeader`] by number, and stores its authentication data as
91    /// well.
92    ///
93    /// If the store already contains MMR data for the requested block number, the request isn't
94    /// done and the stored block header is returned.
95    pub(crate) async fn get_and_store_authenticated_block(
96        &self,
97        block_num: BlockNumber,
98        current_partial_mmr: &mut PartialMmr,
99    ) -> Result<BlockHeader, ClientError> {
100        if current_partial_mmr.is_tracked(block_num.as_usize()) {
101            warn!("Current partial MMR already contains the requested data");
102            let (block_header, _) = self
103                .store
104                .get_block_header_by_num(block_num)
105                .await?
106                .expect("Block header should be tracked");
107            return Ok(block_header);
108        }
109
110        // Fetch the block header and MMR proof from the node
111        let (block_header, path_nodes) =
112            fetch_block_header(self.rpc_api.clone(), block_num, current_partial_mmr).await?;
113
114        // Insert header and MMR nodes
115        self.store
116            .insert_block_header(&block_header, current_partial_mmr.peaks(), true)
117            .await?;
118        self.store.insert_partial_blockchain_nodes(&path_nodes).await?;
119
120        Ok(block_header)
121    }
122
123    /// Returns the epoch block for the specified block number.
124    ///
125    /// If the epoch block header is not stored, it will be retrieved and stored.
126    pub async fn get_epoch_block(
127        &mut self,
128        block_num: BlockNumber,
129    ) -> Result<BlockHeader, ClientError> {
130        let epoch = block_num.block_epoch();
131        let epoch_block_number = BlockNumber::from_epoch(epoch);
132
133        if let Some((epoch_block, _)) =
134            self.store.get_block_header_by_num(epoch_block_number).await?
135        {
136            return Ok(epoch_block);
137        }
138
139        if epoch_block_number == 0.into() {
140            return self.ensure_genesis_in_place().await;
141        }
142
143        let mut current_partial_mmr = self.build_current_partial_mmr().await?;
144        let anchor_block = self
145            .get_and_store_authenticated_block(epoch_block_number, &mut current_partial_mmr)
146            .await?;
147
148        Ok(anchor_block)
149    }
150
151    /// Returns the epoch block for the latest tracked block.
152    ///
153    /// If the epoch block header is not stored, it will be retrieved and stored.
154    pub async fn get_latest_epoch_block(&mut self) -> Result<BlockHeader, ClientError> {
155        let current_block_num = self.store.get_sync_height().await?;
156        self.get_epoch_block(current_block_num).await
157    }
158}
159
160// UTILS
161// --------------------------------------------------------------------------------------------
162
163/// Returns a merkle path nodes for a specific block adjusted for a defined forest size.
164/// This function trims the merkle path to include only the nodes that are relevant for
165/// the MMR forest.
166///
167/// # Parameters
168/// - `merkle_path`: Original merkle path.
169/// - `block_num`: The block number for which the path is computed.
170/// - `forest`: The target size of the forest.
171pub(crate) fn adjust_merkle_path_for_forest(
172    merkle_path: &MerklePath,
173    block_num: BlockNumber,
174    forest: usize,
175) -> Vec<(InOrderIndex, Digest)> {
176    assert!(
177        forest > block_num.as_usize(),
178        "Can't adjust merkle path for a forest that does not include the block number"
179    );
180
181    let rightmost_index = InOrderIndex::from_leaf_pos(forest - 1);
182
183    let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
184    let mut path_nodes = vec![];
185    for node in merkle_path.iter() {
186        idx = idx.sibling();
187        // Rightmost index is always the biggest value, so if the path contains any node
188        // past it, we can discard it for our version of the forest
189        if idx <= rightmost_index {
190            path_nodes.push((idx, *node));
191        }
192        idx = idx.parent();
193    }
194
195    path_nodes
196}
197
198pub(crate) async fn fetch_block_header(
199    rpc_api: Arc<dyn NodeRpcClient>,
200    block_num: BlockNumber,
201    current_partial_mmr: &mut PartialMmr,
202) -> Result<(BlockHeader, Vec<(InOrderIndex, Digest)>), ClientError> {
203    let (block_header, mmr_proof) = rpc_api.get_block_header_with_proof(block_num).await?;
204
205    // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR
206    // might be of a forest arbitrarily higher
207    let path_nodes = adjust_merkle_path_for_forest(
208        &mmr_proof.merkle_path,
209        block_num,
210        current_partial_mmr.forest(),
211    );
212
213    let merkle_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
214
215    current_partial_mmr
216        .track(block_num.as_usize(), block_header.commitment(), &merkle_path)
217        .map_err(StoreError::MmrError)?;
218
219    Ok((block_header, path_nodes))
220}