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