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