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 StateSyncError {
274 #[error("database error")]
275 DatabaseError(#[from] DatabaseError),
276 #[error("block headers table is empty")]
277 EmptyBlockHeadersTable,
278 #[error("failed to build MMR delta")]
279 FailedToBuildMmrDelta(#[from] MmrError),
280 #[error(transparent)]
281 RangeBeyondTip(#[from] RangeBeyondTip),
282}
283
284impl From<diesel::result::Error> for StateSyncError {
285 fn from(value: diesel::result::Error) -> Self {
286 Self::DatabaseError(DatabaseError::from(value))
287 }
288}
289
290#[derive(Error, Debug)]
291pub enum NoteSyncError {
292 #[error("database error")]
293 DatabaseError(#[from] DatabaseError),
294 #[error("database error")]
295 UnderlyingDatabaseError(#[from] miden_node_db::DatabaseError),
296 #[error("block headers table is empty")]
297 EmptyBlockHeadersTable,
298 #[error("error retrieving the merkle proof for the block")]
299 MmrError(#[from] MmrError),
300 #[error("invalid block range")]
301 InvalidBlockRange(#[from] InvalidBlockRange),
302 #[error(transparent)]
303 RangeBeyondTip(#[from] RangeBeyondTip),
304 #[error("malformed note tags")]
305 DeserializationFailed(#[from] ConversionError),
306}
307
308impl From<diesel::result::Error> for NoteSyncError {
309 fn from(value: diesel::result::Error) -> Self {
310 Self::DatabaseError(DatabaseError::from(value))
311 }
312}
313
314#[derive(Error, Debug)]
315pub enum GetNoteInclusionProofsError {
316 #[error("failed to select note inclusion proofs")]
317 SelectNoteInclusionProofError(#[source] DatabaseError),
318 #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
319 ReferenceBlockAfterTip {
320 reference_block: BlockNumber,
321 latest_block_num: BlockNumber,
322 },
323}
324
325#[derive(Error, Debug)]
326pub enum GetBlockInclusionProofsError {
327 #[error("failed to select block headers")]
328 SelectBlockHeaderError(#[source] DatabaseError),
329 #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
330 ReferenceBlockAfterTip {
331 reference_block: BlockNumber,
332 latest_block_num: BlockNumber,
333 },
334 #[error("block {block_num} is newer than the reference block {reference_block}")]
335 BlockAfterReferenceBlock {
336 block_num: BlockNumber,
337 reference_block: BlockNumber,
338 },
339}
340
341#[derive(Debug, Error)]
345pub enum GetAccountError {
346 #[error("database error")]
347 DatabaseError(#[from] DatabaseError),
348 #[error("malformed request")]
349 DeserializationFailed(#[from] ConversionError),
350 #[error("account {0} not found at block {1}")]
351 AccountNotFound(AccountId, BlockNumber),
352 #[error("account {0} is not public")]
353 AccountNotPublic(AccountId),
354 #[error("block {0} is unknown")]
355 UnknownBlock(BlockNumber),
356 #[error("block {0} has been pruned")]
357 BlockPruned(BlockNumber),
358}
359
360mod compile_tests {
364 use std::marker::PhantomData;
365
366 use super::{
367 AccountDeltaError,
368 AccountError,
369 DatabaseError,
370 DeserializationError,
371 NoteError,
372 RecvError,
373 StateInitializationError,
374 };
375
376 #[expect(dead_code)]
379 fn assumed_trait_bounds_upheld() {
380 fn ensure_is_error<E>(_phony: PhantomData<E>)
381 where
382 E: std::error::Error + Send + Sync + 'static,
383 {
384 }
385
386 ensure_is_error::<AccountError>(PhantomData);
387 ensure_is_error::<AccountDeltaError>(PhantomData);
388 ensure_is_error::<RecvError>(PhantomData);
389 ensure_is_error::<DeserializationError>(PhantomData);
390 ensure_is_error::<NoteError>(PhantomData);
391 ensure_is_error::<hex::FromHexError>(PhantomData);
392 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
393 ensure_is_error::<diesel::result::Error>(PhantomData);
394 ensure_is_error::<deadpool_diesel::Error>(PhantomData);
395 ensure_is_error::<deadpool::managed::RecycleError<deadpool_diesel::Error>>(PhantomData);
396
397 ensure_is_error::<DatabaseError>(PhantomData);
398 ensure_is_error::<diesel::result::Error>(PhantomData);
399 ensure_is_error::<StateInitializationError>(PhantomData);
400 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
401 }
402}