Skip to main content

miden_node_store/state/
sync_state.rs

1use std::ops::RangeInclusive;
2
3use miden_node_utils::tracing::miden_instrument;
4use miden_protocol::account::AccountId;
5use miden_protocol::block::{BlockHeader, BlockNumber, BlockSignatures};
6use miden_protocol::crypto::merkle::mmr::{Forest, MmrDelta, MmrProof};
7
8use super::State;
9use crate::COMPONENT;
10use crate::db::models::queries::StorageMapValuesPage;
11use crate::db::{AccountVaultValue, NoteSyncUpdate, NullifierInfo};
12use crate::errors::{DatabaseError, NoteSyncError, StateSyncError};
13
14// STATE SYNCHRONIZATION ENDPOINTS
15// ================================================================================================
16
17impl State {
18    /// Returns the complete transaction records for the specified accounts within the specified
19    /// block range, including state commitments and note IDs.
20    pub async fn sync_transactions(
21        &self,
22        account_ids: Vec<AccountId>,
23        block_range: RangeInclusive<BlockNumber>,
24    ) -> Result<(BlockNumber, Vec<crate::db::TransactionRecord>), DatabaseError> {
25        self.db.select_transactions_records(account_ids, block_range).await
26    }
27
28    /// Returns the chain MMR delta and the `block_to` block header for the specified block range.
29    #[miden_instrument(
30        level = "debug",
31        target = COMPONENT,
32        skip_all,
33        err,
34    )]
35    pub async fn sync_chain_mmr(
36        &self,
37        block_range: RangeInclusive<BlockNumber>,
38    ) -> Result<(MmrDelta, BlockHeader, BlockSignatures), StateSyncError> {
39        let block_from = *block_range.start();
40        let block_to = *block_range.end();
41
42        // SAFETY: block_to has been validated to be <= the effective tip (chain tip or latest
43        // proven block) by the caller, so it must exist in the database.
44        let (block_header, signatures) = self
45            .db
46            .select_block_header_and_signatures_by_block_num(block_to)
47            .await?
48            .expect("block_to should exist in the database");
49
50        if block_from == block_to {
51            return Ok((
52                MmrDelta {
53                    forest: Forest::new(block_from.as_usize()).expect("block index fits in u32"),
54                    data: vec![],
55                },
56                block_header,
57                signatures,
58            ));
59        }
60
61        // Important notes about the boundary conditions:
62        //
63        // - The Mmr forest is 1-indexed whereas the block number is 0-indexed. The Mmr root
64        //   contained in the block header always lag behind by one block, this is because the Mmr
65        //   leaves are hashes of block headers, and we can't have self-referential hashes. These
66        //   two points cancel out and don't require adjusting.
67        // - Mmr::get_delta is inclusive, whereas the sync request block_from is defined to be the
68        //   last block already present in the caller's MMR. The delta should therefore start at the
69        //   next block, so the from_forest has to be adjusted with a +1.
70        let from_forest = (block_from + 1).as_usize();
71        let to_forest = block_to.as_usize();
72
73        let mmr_delta = self
74            .inner
75            .read()
76            .await
77            .blockchain
78            .as_mmr()
79            .get_delta(
80                Forest::new(from_forest).expect("from_forest fits in u32"),
81                Forest::new(to_forest).expect("to_forest fits in u32"),
82            )
83            .map_err(StateSyncError::FailedToBuildMmrDelta)?;
84
85        Ok((mmr_delta, block_header, signatures))
86    }
87
88    /// Loads data to synchronize a client's notes.
89    ///
90    /// Returns as many blocks with matching notes as fit within the response payload limit
91    /// ([`MAX_RESPONSE_PAYLOAD_BYTES`](miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES)).
92    /// Each block includes its header and MMR proof at forest `block_range.end() + 1`.
93    ///
94    /// Also returns the last block number checked. If this equals `block_range.end()`, the
95    /// sync is complete.
96    #[miden_instrument(
97        level = "debug",
98        target = COMPONENT,
99        skip_all,
100        err,
101    )]
102    pub async fn sync_notes(
103        &self,
104        note_tags: Vec<u32>,
105        block_range: RangeInclusive<BlockNumber>,
106    ) -> Result<(Vec<(NoteSyncUpdate, MmrProof)>, BlockNumber), NoteSyncError> {
107        let block_end = *block_range.end();
108        // The MMR at forest N contains proofs for blocks 0..N-1, so we use block_end + 1 to include
109        // the proof for block_end. SAFETY: it is ensured that block_end <= chain_tip, and the
110        // blockchain MMR always has at least chain_tip + 1 leaves.
111        let mmr_checkpoint = block_end + 1;
112
113        let note_syncs = self.db.get_note_sync_multi(block_range, note_tags.into()).await?;
114
115        let mut results = Vec::new();
116
117        {
118            let inner = self.inner.read().await;
119
120            for note_sync in note_syncs {
121                let mmr_proof =
122                    inner.blockchain.open_at(note_sync.block_header.block_num(), mmr_checkpoint)?;
123                results.push((note_sync, mmr_proof));
124            }
125        }
126
127        // if results is empty, return `block_end` since the sync is complete.
128        let last_block_checked =
129            results.last().map_or(block_end, |(update, _)| update.block_header.block_num());
130
131        Ok((results, last_block_checked))
132    }
133
134    pub async fn sync_nullifiers(
135        &self,
136        prefix_len: u32,
137        nullifier_prefixes: Vec<u32>,
138        block_range: RangeInclusive<BlockNumber>,
139    ) -> Result<(Vec<NullifierInfo>, BlockNumber), DatabaseError> {
140        self.db
141            .select_nullifiers_by_prefix(prefix_len, nullifier_prefixes, block_range)
142            .await
143    }
144
145    // ACCOUNT STATE SYNCHRONIZATION
146    // --------------------------------------------------------------------------------------------
147
148    /// Returns account vault updates for specified account within a block range.
149    pub async fn sync_account_vault(
150        &self,
151        account_id: AccountId,
152        block_range: RangeInclusive<BlockNumber>,
153    ) -> Result<(BlockNumber, Vec<AccountVaultValue>), DatabaseError> {
154        self.db.get_account_vault_sync(account_id, block_range).await
155    }
156
157    /// Returns storage map values for syncing within a block range.
158    pub async fn sync_account_storage_maps(
159        &self,
160        account_id: AccountId,
161        block_range: RangeInclusive<BlockNumber>,
162    ) -> Result<StorageMapValuesPage, DatabaseError> {
163        self.db.select_storage_map_sync_values(account_id, block_range, None).await
164    }
165}