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