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