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("data corrupted: {0}")]
75 DataCorrupted(String),
76 #[error(transparent)]
77 SqlValueConversion(#[from] DatabaseTypeConversionError),
78 #[error("storage root not found for account {account_id}, slot {slot_name}, block {block_num}")]
79 StorageRootNotFound {
80 account_id: AccountId,
81 slot_name: String,
82 block_num: BlockNumber,
83 },
84}
85
86#[derive(Error, Debug)]
90pub enum StateInitializationError {
91 #[error("account tree IO error: {0}")]
92 AccountTreeIoError(String),
93 #[error("nullifier tree IO error: {0}")]
94 NullifierTreeIoError(String),
95 #[error("account state forest IO error: {0}")]
96 AccountStateForestIoError(String),
97 #[error("database error")]
98 DatabaseError(#[from] DatabaseError),
99 #[error("failed to create nullifier tree")]
100 FailedToCreateNullifierTree(#[from] NullifierTreeError),
101 #[error("failed to create accounts tree")]
102 FailedToCreateAccountsTree(#[source] AccountTreeError),
103 #[error("failed to load data directory")]
104 DataDirectoryLoadError(#[source] std::io::Error),
105 #[error("failed to load block store")]
106 BlockStoreLoadError(#[source] std::io::Error),
107 #[error("failed to load proven tip")]
108 ProvenTipLoadError(#[source] std::io::Error),
109 #[error("failed to load database")]
110 DatabaseLoadError(#[source] DatabaseError),
111 #[error("account state forest error")]
112 AccountStateForestError(#[from] AccountStateForestError),
113 #[error(
114 "{tree_name} SMT root ({tree_root:?}) does not match expected root from block {block_num} \
115 ({block_root:?}). Delete the tree storage directories and restart the node to rebuild \
116 from the database."
117 )]
118 TreeStorageDiverged {
119 tree_name: &'static str,
120 block_num: BlockNumber,
121 tree_root: Word,
122 block_root: Word,
123 },
124 #[error(
125 "account state forest root ({forest_root}) does not match SQLite root \
126 ({database_root}) for account {account_id}, slot {slot_name:?}. Delete the account \
127 state forest storage directory and restart the node to rebuild from the database."
128 )]
129 AccountStateForestStorageDiverged {
130 account_id: AccountId,
131 slot_name: Option<String>,
132 forest_root: Word,
133 database_root: Word,
134 },
135 #[error("public account {0} is missing details in database")]
136 PublicAccountMissingDetails(AccountId),
137 #[error("failed to convert account to delta: {0}")]
138 AccountToDeltaConversionFailed(String),
139}
140
141#[derive(Error, Debug)]
144pub enum InvalidBlockError {
145 #[error("duplicated nullifiers {0:?}")]
146 DuplicatedNullifiers(Vec<Nullifier>),
147 #[error("invalid output note type: {0:?}")]
148 InvalidOutputNoteType(Box<OutputNote>),
149 #[error("invalid block tx commitment: expected {expected}, but got {actual}")]
150 InvalidBlockTxCommitment { expected: Word, actual: Word },
151 #[error("received invalid account tree root")]
152 NewBlockInvalidAccountRoot,
153 #[error("new block number must be 1 greater than the current block number")]
154 NewBlockInvalidBlockNum {
155 expected: BlockNumber,
156 submitted: BlockNumber,
157 },
158 #[error("new block chain commitment is not consistent with chain MMR")]
159 NewBlockInvalidChainCommitment,
160 #[error("received invalid note root")]
161 NewBlockInvalidNoteRoot,
162 #[error("received invalid nullifier root")]
163 NewBlockInvalidNullifierRoot,
164 #[error("new block `prev_block_commitment` must match the chain's tip")]
165 NewBlockInvalidPrevCommitment,
166 #[error("nullifier in new block is already spent")]
167 NewBlockNullifierAlreadySpent(#[source] NullifierTreeError),
168 #[error("duplicate account ID prefix in new block")]
169 NewBlockDuplicateAccountIdPrefix(#[source] AccountTreeError),
170 #[error("failed to build note tree: {0}")]
171 FailedToBuildNoteTree(String),
172}
173
174#[derive(Error, Debug)]
175pub enum ApplyBlockError {
176 #[error("database error")]
179 DatabaseError(#[from] DatabaseError),
180 #[error("I/O error")]
181 IoError(#[from] io::Error),
182 #[error("task join error")]
183 TokioJoinError(#[from] tokio::task::JoinError),
184 #[error("invalid block error")]
185 InvalidBlockError(#[from] InvalidBlockError),
186 #[error("account state forest error")]
187 AccountStateForestError(#[from] AccountStateForestError),
188
189 #[error("block applying was cancelled because of closed channel on database side")]
192 ClosedChannel(#[from] RecvError),
193 #[error("concurrent write detected")]
194 ConcurrentWrite,
195 #[error("database doesn't have any block header data")]
196 DbBlockHeaderEmpty,
197 #[error("database update failed: {0}")]
198 DbUpdateTaskFailed(String),
199}
200
201#[derive(Error, Debug)]
202pub enum ApplyBlockWithProvingInputsError {
203 #[error("failed to save block proving inputs")]
204 SaveProvingInputs(#[source] io::Error),
205 #[error("failed to apply block")]
206 ApplyBlock(#[source] ApplyBlockError),
207}
208
209#[derive(Error, Debug)]
210pub enum GetBlockHeaderError {
211 #[error("database error")]
212 DatabaseError(#[from] DatabaseError),
213 #[error("error retrieving the merkle proof for the block")]
214 MmrError(#[from] MmrError),
215}
216
217#[derive(Error, Debug)]
218pub enum GetBlockInputsError {
219 #[error("failed to select note inclusion proofs")]
220 SelectNoteInclusionProofError(#[source] DatabaseError),
221 #[error("failed to select block headers")]
222 SelectBlockHeaderError(#[source] DatabaseError),
223 #[error(
224 "highest block number {highest_block_number} referenced by a batch is newer than the latest block {latest_block_number}"
225 )]
226 UnknownBatchBlockReference {
227 highest_block_number: BlockNumber,
228 latest_block_number: BlockNumber,
229 },
230}
231
232#[derive(Error, Debug)]
233pub enum StateSyncError {
234 #[error("database error")]
235 DatabaseError(#[from] DatabaseError),
236 #[error("block headers table is empty")]
237 EmptyBlockHeadersTable,
238 #[error("failed to build MMR delta")]
239 FailedToBuildMmrDelta(#[from] MmrError),
240}
241
242impl From<diesel::result::Error> for StateSyncError {
243 fn from(value: diesel::result::Error) -> Self {
244 Self::DatabaseError(DatabaseError::from(value))
245 }
246}
247
248#[derive(Error, Debug)]
249pub enum NoteSyncError {
250 #[error("database error")]
251 DatabaseError(#[from] DatabaseError),
252 #[error("database error")]
253 UnderlyingDatabaseError(#[from] miden_node_db::DatabaseError),
254 #[error("block headers table is empty")]
255 EmptyBlockHeadersTable,
256 #[error("error retrieving the merkle proof for the block")]
257 MmrError(#[from] MmrError),
258 #[error("invalid block range")]
259 InvalidBlockRange(#[from] InvalidBlockRange),
260 #[error("block_to ({block_to}) is greater than chain tip ({chain_tip})")]
261 FutureBlock {
262 chain_tip: BlockNumber,
263 block_to: BlockNumber,
264 },
265 #[error("malformed note tags")]
266 DeserializationFailed(#[from] ConversionError),
267}
268
269impl From<diesel::result::Error> for NoteSyncError {
270 fn from(value: diesel::result::Error) -> Self {
271 Self::DatabaseError(DatabaseError::from(value))
272 }
273}
274
275#[derive(Error, Debug)]
276pub enum GetBatchInputsError {
277 #[error("failed to select note inclusion proofs")]
278 SelectNoteInclusionProofError(#[source] DatabaseError),
279 #[error("failed to select block headers")]
280 SelectBlockHeaderError(#[source] DatabaseError),
281 #[error("set of blocks referenced by transactions is empty")]
282 TransactionBlockReferencesEmpty,
283 #[error(
284 "highest block number {highest_block_num} referenced by a transaction is newer than the latest block {latest_block_num}"
285 )]
286 UnknownTransactionBlockReference {
287 highest_block_num: BlockNumber,
288 latest_block_num: BlockNumber,
289 },
290}
291
292#[derive(Debug, Error)]
296pub enum GetAccountError {
297 #[error("database error")]
298 DatabaseError(#[from] DatabaseError),
299 #[error("malformed request")]
300 DeserializationFailed(#[from] ConversionError),
301 #[error("account {0} not found at block {1}")]
302 AccountNotFound(AccountId, BlockNumber),
303 #[error("account {0} is not public")]
304 AccountNotPublic(AccountId),
305 #[error("block {0} is unknown")]
306 UnknownBlock(BlockNumber),
307 #[error("block {0} has been pruned")]
308 BlockPruned(BlockNumber),
309}
310
311mod compile_tests {
315 use std::marker::PhantomData;
316
317 use super::{
318 AccountDeltaError,
319 AccountError,
320 DatabaseError,
321 DeserializationError,
322 NoteError,
323 RecvError,
324 StateInitializationError,
325 };
326
327 #[expect(dead_code)]
330 fn assumed_trait_bounds_upheld() {
331 fn ensure_is_error<E>(_phony: PhantomData<E>)
332 where
333 E: std::error::Error + Send + Sync + 'static,
334 {
335 }
336
337 ensure_is_error::<AccountError>(PhantomData);
338 ensure_is_error::<AccountDeltaError>(PhantomData);
339 ensure_is_error::<RecvError>(PhantomData);
340 ensure_is_error::<DeserializationError>(PhantomData);
341 ensure_is_error::<NoteError>(PhantomData);
342 ensure_is_error::<hex::FromHexError>(PhantomData);
343 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
344 ensure_is_error::<diesel::result::Error>(PhantomData);
345 ensure_is_error::<deadpool_diesel::Error>(PhantomData);
346 ensure_is_error::<deadpool::managed::RecycleError<deadpool_diesel::Error>>(PhantomData);
347
348 ensure_is_error::<DatabaseError>(PhantomData);
349 ensure_is_error::<diesel::result::Error>(PhantomData);
350 ensure_is_error::<StateInitializationError>(PhantomData);
351 ensure_is_error::<deadpool::managed::PoolError<deadpool_diesel::Error>>(PhantomData);
352 }
353}