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