Skip to main content

miden_node_store/
errors.rs

1use std::io;
2
3use miden_node_proto::domain::block::InvalidBlockRange;
4use miden_node_proto::errors::ConversionError;
5use miden_node_utils::limiter::QueryLimitError;
6use miden_protocol::Word;
7use miden_protocol::account::AccountId;
8use miden_protocol::block::BlockNumber;
9use miden_protocol::crypto::merkle::MerkleError;
10use miden_protocol::crypto::merkle::mmr::MmrError;
11use miden_protocol::crypto::merkle::smt::{LargeSmtForestError, LineageId};
12use miden_protocol::crypto::utils::DeserializationError;
13use miden_protocol::errors::{
14    AccountDeltaError,
15    AccountError,
16    AccountTreeError,
17    AssetError,
18    AssetVaultError,
19    NoteError,
20    NullifierTreeError,
21    StorageMapError,
22};
23use miden_protocol::note::Nullifier;
24use miden_protocol::transaction::OutputNote;
25use thiserror::Error;
26use tokio::sync::oneshot::error::RecvError;
27
28/// Errors produced while preparing or rebuilding account-state forest updates.
29///
30/// The underlying [`LargeSmtForestError`] is preserved so callers can distinguish fatal backend
31/// failures from invalid update preparation.
32#[derive(Debug, Error)]
33pub enum AccountStateForestUpdateError {
34    /// The patch of a new account attempted to create an existing vault lineage.
35    #[error("account {account_id} vault lineage already exists")]
36    VaultLineageAlreadyExists { account_id: AccountId },
37    /// The patch of a new account attempted to create an existing storage-map lineage.
38    #[error("account {account_id} storage map lineage for slot {slot_name} already exists")]
39    StorageLineageAlreadyExists {
40        account_id: AccountId,
41        slot_name: miden_protocol::account::StorageSlotName,
42    },
43    /// The forest mutation set did not contain a root for a lineage it was expected to update.
44    #[error("computed forest mutations are missing lineage {lineage}")]
45    MissingComputedRoot { lineage: LineageId },
46    /// The forest backend failed while computing, reading, or applying mutations.
47    #[error(transparent)]
48    Forest(#[from] LargeSmtForestError),
49}
50
51// DATABASE ERRORS
52// =================================================================================================
53
54#[derive(Debug, Error)]
55pub enum DatabaseError {
56    #[error("invalid persisted protobuf value")]
57    Persistence(#[from] miden_node_persistence::PersistenceError),
58    // ERRORS WITH AUTOMATIC CONVERSIONS FROM NESTED ERROR TYPES
59    // ---------------------------------------------------------------------------------------------
60    #[error("account error")]
61    AccountError(#[from] AccountError),
62    #[error("asset vault error")]
63    AssetVaultError(#[from] AssetVaultError),
64    #[error("asset error")]
65    AssetError(#[from] AssetError),
66    #[error("closed channel")]
67    ClosedChannel(#[from] RecvError),
68    #[error("database error")]
69    DatabaseError(#[from] miden_node_db::DatabaseError),
70    #[error("deserialization failed")]
71    DeserializationError(#[from] DeserializationError),
72    #[error("I/O error")]
73    IoError(#[from] io::Error),
74    #[error("merkle error")]
75    MerkleError(#[from] MerkleError),
76    #[error("note error")]
77    NoteError(#[from] NoteError),
78    #[error("storage map error")]
79    StorageMapError(#[from] StorageMapError),
80    #[error(transparent)]
81    QueryParamLimit(#[from] QueryLimitError),
82    #[error(transparent)]
83    RangeBeyondTip(#[from] RangeBeyondTip),
84
85    // OTHER ERRORS
86    // ---------------------------------------------------------------------------------------------
87    #[error("account commitment mismatch (expected {expected}, but calculated is {calculated})")]
88    AccountCommitmentsMismatch { expected: Word, calculated: Word },
89    #[error(
90        "protocol config commitment mismatch (expected {expected}, but calculated is {calculated})"
91    )]
92    ProtocolConfigCommitmentMismatch { expected: Word, calculated: Word },
93    #[error("protocol config {0} is missing")]
94    ProtocolConfigNotFound(Word),
95    #[error("account {0} not found")]
96    AccountNotFoundInDb(AccountId),
97    #[error("account {0} already exists")]
98    AccountAlreadyExistsInDb(AccountId),
99    #[error("accounts {0:?} not found")]
100    AccountsNotFoundInDb(Vec<AccountId>),
101    #[error("account {0} is not on the chain")]
102    AccountNotPublic(AccountId),
103    #[error("invalid block parameters: block_from ({from}) > block_to ({to})")]
104    InvalidBlockRange { from: BlockNumber, to: BlockNumber },
105    #[error(
106        "transactions for block {block_num} would exceed maximum response size, \
107         use a stricter filter to reduce the number of transactions returned"
108    )]
109    TransactionPageExceedsPayloadLimit { block_num: BlockNumber },
110    #[error("data corrupted: {0}")]
111    DataCorrupted(String),
112    #[error("storage root not found for account {account_id}, slot {slot_name}, block {block_num}")]
113    StorageRootNotFound {
114        account_id: AccountId,
115        slot_name: String,
116        block_num: BlockNumber,
117    },
118}
119
120// INITIALIZATION ERRORS
121// =================================================================================================
122
123#[derive(Error, Debug)]
124pub enum StateInitializationError {
125    #[error("account tree IO error: {0}")]
126    AccountTreeIoError(String),
127    #[error("nullifier tree IO error: {0}")]
128    NullifierTreeIoError(String),
129    #[error("account state forest IO error: {0}")]
130    AccountStateForestIoError(String),
131    #[error("failed to rebuild account state forest")]
132    AccountStateForestRebuild(#[source] AccountStateForestUpdateError),
133    #[error("database error")]
134    DatabaseError(#[from] DatabaseError),
135    #[error("failed to create nullifier tree")]
136    FailedToCreateNullifierTree(#[from] NullifierTreeError),
137    #[error("failed to create accounts tree")]
138    FailedToCreateAccountsTree(#[source] AccountTreeError),
139    #[error("failed to load data directory")]
140    DataDirectoryLoadError(#[source] std::io::Error),
141    #[error("failed to load block store")]
142    BlockStoreLoadError(#[source] std::io::Error),
143    #[error("failed to load proven tip")]
144    ProvenTipLoadError(#[source] std::io::Error),
145    #[error("failed to load database")]
146    DatabaseLoadError(#[source] DatabaseError),
147    #[error(
148        "{tree_name} SMT root ({tree_root:?}) does not match expected root from block {block_num} \
149         ({block_root:?}). Delete the tree storage directories and restart the node to rebuild \
150         from the database."
151    )]
152    TreeStorageDiverged {
153        tree_name: &'static str,
154        block_num: BlockNumber,
155        tree_root: Word,
156        block_root: Word,
157    },
158    #[error("loaded chain MMR cannot produce peaks for block {block_num}")]
159    ChainMmrLoadError {
160        block_num: BlockNumber,
161        #[source]
162        source: MmrError,
163    },
164    #[error(
165        "chain MMR commitment ({chain_mmr_commitment:?}) does not match expected chain commitment \
166         from block {block_num} ({block_header_commitment:?})"
167    )]
168    ChainMmrStorageDiverged {
169        block_num: BlockNumber,
170        chain_mmr_commitment: Word,
171        block_header_commitment: Word,
172    },
173    #[error(
174        "account state forest root ({forest_root}) does not match SQLite root \
175         ({database_root}) for account {account_id}, slot {slot_name:?}. Delete the account \
176         state forest storage directory and restart the node to rebuild from the database."
177    )]
178    AccountStateForestStorageDiverged {
179        account_id: AccountId,
180        slot_name: Option<String>,
181        forest_root: Word,
182        database_root: Word,
183    },
184    #[error("public account {0} is missing details in database")]
185    PublicAccountMissingDetails(AccountId),
186    #[error("failed to convert account to delta: {0}")]
187    AccountToDeltaConversionFailed(String),
188    #[error("genesis block missing. The database should be bootstrapped first.")]
189    GenesisBlockMissing,
190    #[error(
191        "genesis protocol config {commitment} is missing. Rebootstrap the database from genesis."
192    )]
193    GenesisProtocolConfigMissing { commitment: Word },
194}
195
196// ENDPOINT ERRORS
197// =================================================================================================
198#[derive(Error, Debug)]
199pub enum InvalidBlockError {
200    #[error("duplicated nullifiers {0:?}")]
201    DuplicatedNullifiers(Vec<Nullifier>),
202    #[error("invalid output note type: {0:?}")]
203    InvalidOutputNoteType(Box<OutputNote>),
204    #[error("invalid block tx commitment: expected {expected}, but got {actual}")]
205    InvalidBlockTxCommitment { expected: Word, actual: Word },
206    #[error("received invalid account tree root")]
207    NewBlockInvalidAccountRoot,
208    #[error("new block number must be 1 greater than the current block number")]
209    NewBlockInvalidBlockNum {
210        expected: BlockNumber,
211        submitted: BlockNumber,
212    },
213    #[error("new block chain commitment is not consistent with chain MMR")]
214    NewBlockInvalidChainCommitment,
215    #[error("received invalid note root")]
216    NewBlockInvalidNoteRoot,
217    #[error("received invalid nullifier root")]
218    NewBlockInvalidNullifierRoot,
219    #[error("new block `prev_block_commitment` must match the chain's tip")]
220    NewBlockInvalidPrevCommitment,
221    #[error("nullifier in new block is already spent")]
222    NewBlockNullifierAlreadySpent(#[source] NullifierTreeError),
223    #[error("duplicate account ID prefix in new block")]
224    NewBlockDuplicateAccountIdPrefix(#[source] AccountTreeError),
225    #[error("failed to build note tree: {0}")]
226    FailedToBuildNoteTree(String),
227}
228
229#[derive(Error, Debug)]
230pub enum ApplyBlockError {
231    // ERRORS WITH AUTOMATIC CONVERSIONS FROM NESTED ERROR TYPES
232    // ---------------------------------------------------------------------------------------------
233    #[error("database error")]
234    DatabaseError(#[from] DatabaseError),
235    #[error("I/O error")]
236    IoError(#[from] io::Error),
237    #[error("task join error")]
238    TokioJoinError(#[from] tokio::task::JoinError),
239    #[error("invalid block error")]
240    InvalidBlockError(#[from] InvalidBlockError),
241
242    // OTHER ERRORS
243    // ---------------------------------------------------------------------------------------------
244    #[error("block applying was cancelled because the writer task dropped the result channel")]
245    ClosedChannel(#[from] RecvError),
246    #[error("account state forest update preparation failed")]
247    AccountStateForestPreparation(#[source] AccountStateForestUpdateError),
248    #[error("database doesn't have any block header data")]
249    DbBlockHeaderEmpty,
250    #[error("database update failed: {0}")]
251    DbUpdateTaskFailed(String),
252    #[error("failed to send block to the writer task: {0}")]
253    WriterTaskSendFailed(String),
254}
255
256#[derive(Error, Debug)]
257pub enum ApplyBlockWithProvingInputsError {
258    #[error("failed to save block proving inputs")]
259    SaveProvingInputs(#[source] io::Error),
260    #[error("failed to apply block")]
261    ApplyBlock(#[source] ApplyBlockError),
262}
263
264/// A requested block range extends beyond the chain tip of the state view serving the request.
265#[derive(Error, Debug)]
266#[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")]
267pub struct RangeBeyondTip {
268    pub chain_tip: BlockNumber,
269    pub block_to: BlockNumber,
270}
271
272#[derive(Error, Debug)]
273pub enum GetBlockHeaderError {
274    #[error("database error")]
275    DatabaseError(#[from] DatabaseError),
276    #[error("error retrieving the merkle proof for the block")]
277    MmrError(#[from] MmrError),
278}
279
280#[derive(Error, Debug)]
281pub enum StateSyncError {
282    #[error("database error")]
283    DatabaseError(#[from] DatabaseError),
284    #[error("block headers table is empty")]
285    EmptyBlockHeadersTable,
286    #[error("failed to build MMR delta")]
287    FailedToBuildMmrDelta(#[from] MmrError),
288    #[error(transparent)]
289    RangeBeyondTip(#[from] RangeBeyondTip),
290}
291
292#[derive(Error, Debug)]
293pub enum NoteSyncError {
294    #[error("database error")]
295    DatabaseError(#[from] DatabaseError),
296    #[error("database error")]
297    UnderlyingDatabaseError(#[from] miden_node_db::DatabaseError),
298    #[error("block headers table is empty")]
299    EmptyBlockHeadersTable,
300    #[error("error retrieving the merkle proof for the block")]
301    MmrError(#[from] MmrError),
302    #[error("invalid block range")]
303    InvalidBlockRange(#[from] InvalidBlockRange),
304    #[error(transparent)]
305    RangeBeyondTip(#[from] RangeBeyondTip),
306    #[error("malformed note tags")]
307    DeserializationFailed(#[from] ConversionError),
308}
309
310#[derive(Error, Debug)]
311pub enum GetNoteInclusionProofsError {
312    #[error("failed to select note inclusion proofs")]
313    SelectNoteInclusionProofError(#[source] DatabaseError),
314    #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
315    ReferenceBlockAfterTip {
316        reference_block: BlockNumber,
317        latest_block_num: BlockNumber,
318    },
319}
320
321#[derive(Error, Debug)]
322pub enum GetBlockInclusionProofsError {
323    #[error("failed to select block headers")]
324    SelectBlockHeaderError(#[source] DatabaseError),
325    #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
326    ReferenceBlockAfterTip {
327        reference_block: BlockNumber,
328        latest_block_num: BlockNumber,
329    },
330    #[error("block {block_num} is newer than the reference block {reference_block}")]
331    BlockAfterReferenceBlock {
332        block_num: BlockNumber,
333        reference_block: BlockNumber,
334    },
335}
336
337// GET ACCOUNT ERRORS
338// ================================================================================================
339
340#[derive(Debug, Error)]
341pub enum GetAccountError {
342    #[error("database error")]
343    DatabaseError(#[from] DatabaseError),
344    #[error("malformed request")]
345    DeserializationFailed(#[from] ConversionError),
346    #[error("account {0} not found at block {1}")]
347    AccountNotFound(AccountId, BlockNumber),
348    #[error("account {0} is not public")]
349    AccountNotPublic(AccountId),
350    #[error("block {0} is unknown")]
351    UnknownBlock(BlockNumber),
352    #[error("block {0} has been pruned")]
353    BlockPruned(BlockNumber),
354}
355
356// Do not scope for `cfg(test)` - if it the traitbounds don't suffice the issue will already appear
357// in the compilation of the library or binary, which would prevent getting to compiling the
358// following code.
359mod compile_tests {
360    use std::marker::PhantomData;
361
362    use super::{
363        AccountDeltaError,
364        AccountError,
365        DatabaseError,
366        DeserializationError,
367        NoteError,
368        RecvError,
369        StateInitializationError,
370    };
371
372    /// Ensure all enum variants remain compat with the desired trait bounds. Otherwise one gets
373    /// very unwieldy errors.
374    #[expect(dead_code)]
375    fn assumed_trait_bounds_upheld() {
376        fn ensure_is_error<E>(_phony: PhantomData<E>)
377        where
378            E: std::error::Error + Send + Sync + 'static,
379        {
380        }
381
382        ensure_is_error::<AccountError>(PhantomData);
383        ensure_is_error::<AccountDeltaError>(PhantomData);
384        ensure_is_error::<RecvError>(PhantomData);
385        ensure_is_error::<DeserializationError>(PhantomData);
386        ensure_is_error::<NoteError>(PhantomData);
387        ensure_is_error::<hex::FromHexError>(PhantomData);
388
389        ensure_is_error::<DatabaseError>(PhantomData);
390        ensure_is_error::<StateInitializationError>(PhantomData);
391    }
392}