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(
92 "protocol config commitment mismatch (expected {expected}, but calculated is {calculated})"
93 )]
94 ProtocolConfigCommitmentMismatch { expected: Word, calculated: Word },
95 #[error("protocol config {0} is missing")]
96 ProtocolConfigNotFound(Word),
97 #[error("account {0} not found")]
98 AccountNotFoundInDb(AccountId),
99 #[error("accounts {0:?} not found")]
100 AccountsNotFoundInDb(Vec<AccountId>),
101 #[error("account {0} is not on the chain")]
102 AccountNotPublic(AccountId),
103 #[error("invalid block parameters: block_from ({from}) > block_to ({to})")]
104 InvalidBlockRange { from: BlockNumber, to: BlockNumber },
105 #[error(
106 "transactions for block {block_num} would exceed maximum response size, \
107 use a stricter filter to reduce the number of transactions returned"
108 )]
109 TransactionPageExceedsPayloadLimit { block_num: BlockNumber },
110 #[error("data corrupted: {0}")]
111 DataCorrupted(String),
112 #[error(transparent)]
113 SqlValueConversion(#[from] DatabaseTypeConversionError),
114 #[error("storage root not found for account {account_id}, slot {slot_name}, block {block_num}")]
115 StorageRootNotFound {
116 account_id: AccountId,
117 slot_name: String,
118 block_num: BlockNumber,
119 },
120}
121
122#[derive(Error, Debug)]
126pub enum StateInitializationError {
127 #[error("account tree IO error: {0}")]
128 AccountTreeIoError(String),
129 #[error("nullifier tree IO error: {0}")]
130 NullifierTreeIoError(String),
131 #[error("account state forest IO error: {0}")]
132 AccountStateForestIoError(String),
133 #[error("failed to rebuild account state forest")]
134 AccountStateForestRebuild(#[source] AccountStateForestUpdateError),
135 #[error("database error")]
136 DatabaseError(#[from] DatabaseError),
137 #[error("failed to create nullifier tree")]
138 FailedToCreateNullifierTree(#[from] NullifierTreeError),
139 #[error("failed to create accounts tree")]
140 FailedToCreateAccountsTree(#[source] AccountTreeError),
141 #[error("failed to load data directory")]
142 DataDirectoryLoadError(#[source] std::io::Error),
143 #[error("failed to load block store")]
144 BlockStoreLoadError(#[source] std::io::Error),
145 #[error("failed to load proven tip")]
146 ProvenTipLoadError(#[source] std::io::Error),
147 #[error("failed to load database")]
148 DatabaseLoadError(#[source] DatabaseError),
149 #[error(
150 "{tree_name} SMT root ({tree_root:?}) does not match expected root from block {block_num} \
151 ({block_root:?}). Delete the tree storage directories and restart the node to rebuild \
152 from the database."
153 )]
154 TreeStorageDiverged {
155 tree_name: &'static str,
156 block_num: BlockNumber,
157 tree_root: Word,
158 block_root: Word,
159 },
160 #[error("loaded chain MMR cannot produce peaks for block {block_num}")]
161 ChainMmrLoadError {
162 block_num: BlockNumber,
163 #[source]
164 source: MmrError,
165 },
166 #[error(
167 "chain MMR commitment ({chain_mmr_commitment:?}) does not match expected chain commitment \
168 from block {block_num} ({block_header_commitment:?})"
169 )]
170 ChainMmrStorageDiverged {
171 block_num: BlockNumber,
172 chain_mmr_commitment: Word,
173 block_header_commitment: Word,
174 },
175 #[error(
176 "account state forest root ({forest_root}) does not match SQLite root \
177 ({database_root}) for account {account_id}, slot {slot_name:?}. Delete the account \
178 state forest storage directory and restart the node to rebuild from the database."
179 )]
180 AccountStateForestStorageDiverged {
181 account_id: AccountId,
182 slot_name: Option<String>,
183 forest_root: Word,
184 database_root: Word,
185 },
186 #[error("public account {0} is missing details in database")]
187 PublicAccountMissingDetails(AccountId),
188 #[error("failed to convert account to delta: {0}")]
189 AccountToDeltaConversionFailed(String),
190 #[error("genesis block missing. The database should be bootstrapped first.")]
191 GenesisBlockMissing,
192 #[error(
193 "genesis protocol config {commitment} is missing. Rebootstrap the database from genesis."
194 )]
195 GenesisProtocolConfigMissing { commitment: Word },
196}
197
198#[derive(Error, Debug)]
201pub enum InvalidBlockError {
202 #[error("duplicated nullifiers {0:?}")]
203 DuplicatedNullifiers(Vec<Nullifier>),
204 #[error("invalid output note type: {0:?}")]
205 InvalidOutputNoteType(Box<OutputNote>),
206 #[error("invalid block tx commitment: expected {expected}, but got {actual}")]
207 InvalidBlockTxCommitment { expected: Word, actual: Word },
208 #[error("received invalid account tree root")]
209 NewBlockInvalidAccountRoot,
210 #[error("new block number must be 1 greater than the current block number")]
211 NewBlockInvalidBlockNum {
212 expected: BlockNumber,
213 submitted: BlockNumber,
214 },
215 #[error("new block chain commitment is not consistent with chain MMR")]
216 NewBlockInvalidChainCommitment,
217 #[error("received invalid note root")]
218 NewBlockInvalidNoteRoot,
219 #[error("received invalid nullifier root")]
220 NewBlockInvalidNullifierRoot,
221 #[error("new block `prev_block_commitment` must match the chain's tip")]
222 NewBlockInvalidPrevCommitment,
223 #[error("nullifier in new block is already spent")]
224 NewBlockNullifierAlreadySpent(#[source] NullifierTreeError),
225 #[error("duplicate account ID prefix in new block")]
226 NewBlockDuplicateAccountIdPrefix(#[source] AccountTreeError),
227 #[error("failed to build note tree: {0}")]
228 FailedToBuildNoteTree(String),
229}
230
231#[derive(Error, Debug)]
232pub enum ApplyBlockError {
233 #[error("database error")]
236 DatabaseError(#[from] DatabaseError),
237 #[error("I/O error")]
238 IoError(#[from] io::Error),
239 #[error("task join error")]
240 TokioJoinError(#[from] tokio::task::JoinError),
241 #[error("invalid block error")]
242 InvalidBlockError(#[from] InvalidBlockError),
243
244 #[error("block applying was cancelled because the writer task dropped the result channel")]
247 ClosedChannel(#[from] RecvError),
248 #[error("account state forest update preparation failed")]
249 AccountStateForestPreparation(#[source] AccountStateForestUpdateError),
250 #[error("database doesn't have any block header data")]
251 DbBlockHeaderEmpty,
252 #[error("database update failed: {0}")]
253 DbUpdateTaskFailed(String),
254 #[error("failed to send block to the writer task: {0}")]
255 WriterTaskSendFailed(String),
256}
257
258#[derive(Error, Debug)]
259pub enum ApplyBlockWithProvingInputsError {
260 #[error("failed to save block proving inputs")]
261 SaveProvingInputs(#[source] io::Error),
262 #[error("failed to apply block")]
263 ApplyBlock(#[source] ApplyBlockError),
264}
265
266#[derive(Error, Debug)]
268#[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")]
269pub struct RangeBeyondTip {
270 pub chain_tip: BlockNumber,
271 pub block_to: BlockNumber,
272}
273
274#[derive(Error, Debug)]
275pub enum GetBlockHeaderError {
276 #[error("database error")]
277 DatabaseError(#[from] DatabaseError),
278 #[error("error retrieving the merkle proof for the block")]
279 MmrError(#[from] MmrError),
280}
281
282#[derive(Error, Debug)]
283pub enum StateSyncError {
284 #[error("database error")]
285 DatabaseError(#[from] DatabaseError),
286 #[error("block headers table is empty")]
287 EmptyBlockHeadersTable,
288 #[error("failed to build MMR delta")]
289 FailedToBuildMmrDelta(#[from] MmrError),
290 #[error(transparent)]
291 RangeBeyondTip(#[from] RangeBeyondTip),
292}
293
294impl From<diesel::result::Error> for StateSyncError {
295 fn from(value: diesel::result::Error) -> Self {
296 Self::DatabaseError(DatabaseError::from(value))
297 }
298}
299
300#[derive(Error, Debug)]
301pub enum NoteSyncError {
302 #[error("database error")]
303 DatabaseError(#[from] DatabaseError),
304 #[error("database error")]
305 UnderlyingDatabaseError(#[from] miden_node_db::DatabaseError),
306 #[error("block headers table is empty")]
307 EmptyBlockHeadersTable,
308 #[error("error retrieving the merkle proof for the block")]
309 MmrError(#[from] MmrError),
310 #[error("invalid block range")]
311 InvalidBlockRange(#[from] InvalidBlockRange),
312 #[error(transparent)]
313 RangeBeyondTip(#[from] RangeBeyondTip),
314 #[error("malformed note tags")]
315 DeserializationFailed(#[from] ConversionError),
316}
317
318impl From<diesel::result::Error> for NoteSyncError {
319 fn from(value: diesel::result::Error) -> Self {
320 Self::DatabaseError(DatabaseError::from(value))
321 }
322}
323
324#[derive(Error, Debug)]
325pub enum GetNoteInclusionProofsError {
326 #[error("failed to select note inclusion proofs")]
327 SelectNoteInclusionProofError(#[source] DatabaseError),
328 #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
329 ReferenceBlockAfterTip {
330 reference_block: BlockNumber,
331 latest_block_num: BlockNumber,
332 },
333}
334
335#[derive(Error, Debug)]
336pub enum GetBlockInclusionProofsError {
337 #[error("failed to select block headers")]
338 SelectBlockHeaderError(#[source] DatabaseError),
339 #[error("reference block {reference_block} is newer than the latest block {latest_block_num}")]
340 ReferenceBlockAfterTip {
341 reference_block: BlockNumber,
342 latest_block_num: BlockNumber,
343 },
344 #[error("block {block_num} is newer than the reference block {reference_block}")]
345 BlockAfterReferenceBlock {
346 block_num: BlockNumber,
347 reference_block: BlockNumber,
348 },
349}
350
351#[derive(Debug, Error)]
355pub enum GetAccountError {
356 #[error("database error")]
357 DatabaseError(#[from] DatabaseError),
358 #[error("malformed request")]
359 DeserializationFailed(#[from] ConversionError),
360 #[error("account {0} not found at block {1}")]
361 AccountNotFound(AccountId, BlockNumber),
362 #[error("account {0} is not public")]
363 AccountNotPublic(AccountId),
364 #[error("block {0} is unknown")]
365 UnknownBlock(BlockNumber),
366 #[error("block {0} has been pruned")]
367 BlockPruned(BlockNumber),
368}
369
370mod compile_tests {
374 use std::marker::PhantomData;
375
376 use super::{
377 AccountDeltaError,
378 AccountError,
379 DatabaseError,
380 DeserializationError,
381 NoteError,
382 RecvError,
383 StateInitializationError,
384 };
385
386 #[expect(dead_code)]
389 fn assumed_trait_bounds_upheld() {
390 fn ensure_is_error<E>(_phony: PhantomData<E>)
391 where
392 E: std::error::Error + Send + Sync + 'static,
393 {
394 }
395
396 ensure_is_error::<AccountError>(PhantomData);
397 ensure_is_error::<AccountDeltaError>(PhantomData);
398 ensure_is_error::<RecvError>(PhantomData);
399 ensure_is_error::<DeserializationError>(PhantomData);
400 ensure_is_error::<NoteError>(PhantomData);
401 ensure_is_error::<hex::FromHexError>(PhantomData);
402 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
403 ensure_is_error::<diesel::result::Error>(PhantomData);
404 ensure_is_error::<deadpool_diesel::Error>(PhantomData);
405 ensure_is_error::<deadpool::managed::RecycleError<deadpool_diesel::Error>>(PhantomData);
406
407 ensure_is_error::<DatabaseError>(PhantomData);
408 ensure_is_error::<diesel::result::Error>(PhantomData);
409 ensure_is_error::<StateInitializationError>(PhantomData);
410 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
411 }
412}