miden_client/sync/
block_header.rs

1use alloc::vec::Vec;
2
3use crypto::merkle::{InOrderIndex, MmrDelta, MmrPeaks, PartialMmr};
4use miden_objects::{
5    Digest,
6    block::{BlockHeader, BlockNumber},
7    crypto::{self, merkle::MerklePath},
8};
9use tracing::warn;
10
11use super::NoteUpdates;
12use crate::{
13    Client, ClientError,
14    note::NoteScreener,
15    store::{ChainMmrNodeFilter, NoteFilter, StoreError},
16};
17
18/// Maximum number of blocks the client can be behind the network for transactions and account
19/// proofs to be considered valid.
20pub(crate) const MAX_BLOCK_NUMBER_DELTA: u32 = 256;
21
22/// Network information management methods.
23impl Client {
24    /// Updates committed notes with no MMR data. These could be notes that were
25    /// imported with an inclusion proof, but its block header isn't tracked.
26    pub(crate) async fn update_mmr_data(&mut self) -> Result<(), ClientError> {
27        let mut current_partial_mmr = self.build_current_partial_mmr(true).await?;
28
29        let mut changed_notes = vec![];
30        for mut note in self.store.get_input_notes(NoteFilter::Unverified).await? {
31            let block_num = note
32                .inclusion_proof()
33                .expect("Commited notes should have inclusion proofs")
34                .location()
35                .block_num();
36            let block_header = self
37                .get_and_store_authenticated_block(block_num, &mut current_partial_mmr)
38                .await?;
39
40            if note.block_header_received(&block_header)? {
41                changed_notes.push(note);
42            }
43        }
44
45        self.store.upsert_input_notes(&changed_notes).await?;
46
47        Ok(())
48    }
49
50    /// Attempts to retrieve the genesis block from the store. If not found,
51    /// it requests it from the node and store it.
52    pub async fn ensure_genesis_in_place(&mut self) -> Result<BlockHeader, ClientError> {
53        let genesis = self.store.get_block_header_by_num(0.into()).await?;
54
55        match genesis {
56            Some((block, _)) => Ok(block),
57            None => self.retrieve_and_store_genesis().await,
58        }
59    }
60
61    /// Calls `get_block_header_by_number` requesting the genesis block and storing it
62    /// in the local database.
63    async fn retrieve_and_store_genesis(&mut self) -> Result<BlockHeader, ClientError> {
64        let (genesis_block, _) = self
65            .rpc_api
66            .get_block_header_by_number(Some(BlockNumber::GENESIS), false)
67            .await?;
68
69        let blank_mmr_peaks =
70            MmrPeaks::new(0, vec![]).expect("Blank MmrPeaks should not fail to instantiate");
71        // We specify that we want to store the MMR data from the genesis block as we might use it
72        // as an anchor for created accounts.
73        self.store.insert_block_header(&genesis_block, blank_mmr_peaks, true).await?;
74        Ok(genesis_block)
75    }
76
77    // HELPERS
78    // --------------------------------------------------------------------------------------------
79
80    /// Checks the relevance of the block by verifying if any of the input notes in the block are
81    /// relevant to the client. If any of the notes are relevant, the function returns `true`.
82    pub(crate) async fn check_block_relevance(
83        &mut self,
84        committed_notes: &NoteUpdates,
85    ) -> Result<bool, ClientError> {
86        // We'll only do the check for either incoming public notes or expected input notes as
87        // output notes are not really candidates to be consumed here.
88
89        let note_screener = NoteScreener::new(self.store.clone());
90
91        // Find all relevant Input Notes using the note checker
92        for input_note in committed_notes.updated_input_notes() {
93            if !note_screener
94                .check_relevance(&input_note.try_into().map_err(ClientError::NoteRecordError)?)
95                .await?
96                .is_empty()
97            {
98                return Ok(true);
99            }
100        }
101
102        Ok(false)
103    }
104
105    /// Builds the current view of the chain's [`PartialMmr`]. Because we want to add all new
106    /// authentication nodes that could come from applying the MMR updates, we need to track all
107    /// known leaves thus far.
108    ///
109    /// As part of the syncing process, we add the current block number so we don't need to
110    /// track it here.
111    pub(crate) async fn build_current_partial_mmr(
112        &self,
113        include_current_block: bool,
114    ) -> Result<PartialMmr, ClientError> {
115        let current_block_num = self.store.get_sync_height().await?;
116
117        let tracked_nodes = self.store.get_chain_mmr_nodes(ChainMmrNodeFilter::All).await?;
118        let current_peaks = self.store.get_chain_mmr_peaks_by_block_num(current_block_num).await?;
119
120        let track_latest = if current_block_num.as_u32() != 0 {
121            match self
122                .store
123                .get_block_header_by_num(BlockNumber::from(current_block_num.as_u32() - 1))
124                .await?
125            {
126                Some((_, previous_block_had_notes)) => previous_block_had_notes,
127                None => false,
128            }
129        } else {
130            false
131        };
132
133        let mut current_partial_mmr =
134            PartialMmr::from_parts(current_peaks, tracked_nodes, track_latest);
135
136        if include_current_block {
137            let (current_block, has_client_notes) = self
138                .store
139                .get_block_header_by_num(current_block_num)
140                .await?
141                .expect("Current block should be in the store");
142
143            current_partial_mmr.add(current_block.commitment(), has_client_notes);
144        }
145
146        Ok(current_partial_mmr)
147    }
148
149    /// Retrieves and stores a [`BlockHeader`] by number, and stores its authentication data as
150    /// well.
151    ///
152    /// If the store already contains MMR data for the requested block number, the request isn't
153    /// done and the stored block header is returned.
154    pub(crate) async fn get_and_store_authenticated_block(
155        &self,
156        block_num: BlockNumber,
157        current_partial_mmr: &mut PartialMmr,
158    ) -> Result<BlockHeader, ClientError> {
159        if current_partial_mmr.is_tracked(block_num.as_usize()) {
160            warn!("Current partial MMR already contains the requested data");
161            let (block_header, _) = self
162                .store
163                .get_block_header_by_num(block_num)
164                .await?
165                .expect("Block header should be tracked");
166            return Ok(block_header);
167        }
168        let (block_header, mmr_proof) = self.rpc_api.get_block_header_with_proof(block_num).await?;
169
170        // Trim merkle path to keep nodes relevant to our current PartialMmr since the node's MMR
171        // might be of a forest arbitrarily higher
172        let path_nodes = adjust_merkle_path_for_forest(
173            &mmr_proof.merkle_path,
174            block_num,
175            current_partial_mmr.forest(),
176        );
177
178        let merkle_path = MerklePath::new(path_nodes.iter().map(|(_, n)| *n).collect());
179
180        current_partial_mmr
181            .track(block_num.as_usize(), block_header.commitment(), &merkle_path)
182            .map_err(StoreError::MmrError)?;
183
184        // Insert header and MMR nodes
185        self.store
186            .insert_block_header(&block_header, current_partial_mmr.peaks(), true)
187            .await?;
188        self.store.insert_chain_mmr_nodes(&path_nodes).await?;
189
190        Ok(block_header)
191    }
192
193    /// Returns the epoch block for the specified block number.
194    ///
195    /// If the epoch block header is not stored, it will be retrieved and stored.
196    pub async fn get_epoch_block(
197        &mut self,
198        block_num: BlockNumber,
199    ) -> Result<BlockHeader, ClientError> {
200        let epoch = block_num.block_epoch();
201        let epoch_block_number = BlockNumber::from_epoch(epoch);
202
203        if let Some((epoch_block, _)) =
204            self.store.get_block_header_by_num(epoch_block_number).await?
205        {
206            return Ok(epoch_block);
207        }
208
209        if epoch_block_number == 0.into() {
210            return self.ensure_genesis_in_place().await;
211        }
212
213        let mut current_partial_mmr = self.build_current_partial_mmr(true).await?;
214        let anchor_block = self
215            .get_and_store_authenticated_block(epoch_block_number, &mut current_partial_mmr)
216            .await?;
217
218        Ok(anchor_block)
219    }
220
221    /// Returns the epoch block for the latest tracked block.
222    ///
223    /// If the epoch block header is not stored, it will be retrieved and stored.
224    pub async fn get_latest_epoch_block(&mut self) -> Result<BlockHeader, ClientError> {
225        let current_block_num = self.store.get_sync_height().await?;
226        self.get_epoch_block(current_block_num).await
227    }
228}
229
230// UTILS
231// --------------------------------------------------------------------------------------------
232
233/// Returns a merkle path nodes for a specific block adjusted for a defined forest size.
234/// This function trims the merkle path to include only the nodes that are relevant for
235/// the MMR forest.
236///
237/// # Parameters
238/// - `merkle_path`: Original merkle path.
239/// - `block_num`: The block number for which the path is computed.
240/// - `forest`: The target size of the forest.
241fn adjust_merkle_path_for_forest(
242    merkle_path: &MerklePath,
243    block_num: BlockNumber,
244    forest: usize,
245) -> Vec<(InOrderIndex, Digest)> {
246    assert!(
247        forest > block_num.as_usize(),
248        "Can't adjust merkle path for a forest that does not include the block number"
249    );
250
251    let rightmost_index = InOrderIndex::from_leaf_pos(forest - 1);
252
253    let mut idx = InOrderIndex::from_leaf_pos(block_num.as_usize());
254    let mut path_nodes = vec![];
255    for node in merkle_path.iter() {
256        idx = idx.sibling();
257        // Rightmost index is always the biggest value, so if the path contains any node
258        // past it, we can discard it for our version of the forest
259        if idx <= rightmost_index {
260            path_nodes.push((idx, *node));
261        }
262        idx = idx.parent();
263    }
264
265    path_nodes
266}
267
268/// Applies changes to the Mmr structure, storing authentication nodes for leaves we track
269/// and returns the updated [`PartialMmr`].
270pub(crate) fn apply_mmr_changes(
271    current_partial_mmr: PartialMmr,
272    mmr_delta: MmrDelta,
273    current_block_header: &BlockHeader,
274    current_block_has_relevant_notes: bool,
275) -> Result<(MmrPeaks, Vec<(InOrderIndex, Digest)>), StoreError> {
276    let mut partial_mmr: PartialMmr = current_partial_mmr;
277
278    // First, apply curent_block to the Mmr
279    let new_authentication_nodes = partial_mmr
280        .add(current_block_header.commitment(), current_block_has_relevant_notes)
281        .into_iter();
282
283    // Apply the Mmr delta to bring Mmr to forest equal to chain tip
284    let new_authentication_nodes: Vec<(InOrderIndex, Digest)> = partial_mmr
285        .apply(mmr_delta)
286        .map_err(StoreError::MmrError)?
287        .into_iter()
288        .chain(new_authentication_nodes)
289        .collect();
290
291    Ok((partial_mmr.peaks(), new_authentication_nodes))
292}