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