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#[derive(Debug, Error)]
33pub enum DatabaseError {
34 #[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 #[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#[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#[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 #[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 #[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#[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
328mod 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 #[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}