Skip to main content

miden_node_store/state/
sync_state.rs

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