1use std::error;
4use std::fmt;
5use std::ops::Range;
6
7#[cfg(feature = "orchard")]
8use incrementalmerkletree::Position;
9use nonempty::NonEmpty;
10#[cfg(feature = "orchard")]
11use shardtree::error::InsertionError;
12use shardtree::error::ShardTreeError;
13
14#[cfg(feature = "transparent-key-import")]
15use uuid::Uuid;
16use zcash_address::ParseError;
17use zcash_client_backend::data_api::NoteFilter;
18use zcash_client_backend::data_api::error::RewindError;
19use zcash_client_backend::data_api::ll;
20use zcash_client_backend::data_api::ll::wallet::PutBlocksError;
21use zcash_client_backend::wallet::OutputRef;
22use zcash_keys::address::UnifiedAddress;
23use zcash_keys::keys::AddressGenerationError;
24use zcash_protocol::{PoolType, ShieldedPool, TxId, consensus::BlockHeight, value::BalanceError};
25use zip32::DiversifierIndex;
26
27use crate::{
28 AccountUuid,
29 wallet::{commitment_tree, common::ErrUnsupportedPool},
30};
31
32#[cfg(feature = "transparent-inputs")]
33use {
34 crate::wallet::transparent::SchedulingError,
35 ::transparent::{address::TransparentAddress, keys::TransparentKeyScope},
36 zcash_keys::{
37 encoding::TransparentCodecError, keys::transparent::gap_limits::GapAddressesError,
38 },
39};
40
41#[derive(Debug)]
43#[non_exhaustive]
44pub enum SqliteClientError {
45 CorruptedData(String),
47
48 Protobuf(prost::DecodeError),
50
51 InvalidNote,
53
54 TableNotEmpty,
56
57 DecodingError(ParseError),
59
60 #[cfg(feature = "transparent-inputs")]
62 TransparentDerivation(bip32::Error),
63
64 #[cfg(feature = "transparent-inputs")]
67 TransparentAddress(TransparentCodecError),
68
69 DbError(rusqlite::Error),
71
72 Io(std::io::Error),
74
75 InvalidMemo(zcash_protocol::memo::Error),
77
78 BlockConflict(BlockHeight),
81
82 NonSequentialBlocks,
84
85 RequestedRewindInvalid {
89 safe_rewind_height: Option<BlockHeight>,
92 requested_height: BlockHeight,
94 },
95
96 AddressGeneration(AddressGenerationError),
98
99 AccountUnknown,
101
102 AccountCollision(AccountUuid),
105
106 UnknownZip32Derivation,
108
109 KeyDerivationError(zip32::AccountId),
111
112 BadAccountData(String),
114
115 Zip32AccountIndexOutOfRange,
117
118 #[cfg(feature = "transparent-inputs")]
121 AddressNotRecognized(TransparentAddress),
122
123 CommitmentTree(ShardTreeError<commitment_tree::Error>),
126
127 PutBlocksCommitmentTree {
133 pool: ShieldedPool,
135 block_range: Range<BlockHeight>,
138 error: ShardTreeError<commitment_tree::Error>,
140 },
141
142 TruncateCommitmentTree {
147 pool: ShieldedPool,
149 height: BlockHeight,
151 error: ShardTreeError<commitment_tree::Error>,
153 },
154
155 #[cfg(feature = "orchard")]
164 HistoricalFrontierInvalid(InsertionError),
165
166 #[cfg(feature = "orchard")]
179 HistoricalWitnessUnavailable {
180 position: Position,
183 height: BlockHeight,
185 },
186
187 CacheMiss(BlockHeight),
189
190 ChainHeightUnknown,
196
197 UnsupportedPoolType(PoolType),
199
200 BalanceError(BalanceError),
202
203 NoteFilterInvalid(NoteFilter),
205
206 #[cfg(feature = "transparent-inputs")]
210 ReachedGapLimit(TransparentKeyScope, u32),
211
212 DiversifierIndexReuse(DiversifierIndex, Box<UnifiedAddress>),
216
217 AddressReuse(String, NonEmpty<TxId>),
222
223 IneligibleNotes,
226
227 #[cfg(feature = "transparent-inputs")]
229 Scheduling(SchedulingError),
230
231 #[cfg(feature = "transparent-inputs")]
237 NotificationMismatch {
238 expected: BlockHeight,
240 actual: BlockHeight,
242 },
243
244 #[cfg(feature = "transparent-key-import")]
247 StandaloneImportConflict(Uuid),
248
249 #[cfg(feature = "transparent-inputs")]
254 FeeRuleError(Box<dyn error::Error + Send + Sync>),
255
256 BackendError(BackendError),
265}
266
267#[derive(Debug)]
273#[non_exhaustive]
274pub enum BackendError {
275 PutBlocks(Box<PutBlocksError<SqliteClientError, commitment_tree::Error>>),
277 Rewind(Box<RewindError<AccountUuid, SqliteClientError>>),
279}
280
281impl fmt::Display for BackendError {
282 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
283 match self {
284 BackendError::PutBlocks(_) => write!(f, "block insertion"),
285 BackendError::Rewind(_) => write!(f, "rewind to a previous chain state"),
286 }
287 }
288}
289
290impl error::Error for SqliteClientError {
291 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
292 match &self {
293 SqliteClientError::InvalidMemo(e) => Some(e),
294 SqliteClientError::DbError(e) => Some(e),
295 SqliteClientError::Io(e) => Some(e),
296 SqliteClientError::BalanceError(e) => Some(e),
297 SqliteClientError::AddressGeneration(e) => Some(e),
298 #[cfg(feature = "orchard")]
299 SqliteClientError::HistoricalFrontierInvalid(e) => Some(e),
300 #[cfg(feature = "transparent-inputs")]
301 SqliteClientError::FeeRuleError(e) => Some(&**e),
302 _ => None,
303 }
304 }
305}
306
307#[cfg(feature = "transparent-inputs")]
308impl From<GapAddressesError<SqliteClientError>> for SqliteClientError {
309 fn from(err: GapAddressesError<SqliteClientError>) -> Self {
310 match err {
311 GapAddressesError::Storage(e) => e,
312 GapAddressesError::AddressGeneration(e) => SqliteClientError::AddressGeneration(e),
313 GapAddressesError::AccountUnknown => SqliteClientError::AccountUnknown,
314 }
315 }
316}
317
318impl fmt::Display for SqliteClientError {
319 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320 match &self {
321 SqliteClientError::CorruptedData(reason) => {
322 write!(f, "Data DB is corrupted: {reason}")
323 }
324 SqliteClientError::Protobuf(e) => {
325 write!(f, "Failed to parse protobuf-encoded record: {e}")
326 }
327 SqliteClientError::InvalidNote => write!(f, "Invalid note"),
328 SqliteClientError::RequestedRewindInvalid {
329 safe_rewind_height,
330 requested_height,
331 } => write!(
332 f,
333 "A rewind for your wallet may only target height {} or greater; the requested height was {}.",
334 safe_rewind_height.map_or("<unavailable>".to_owned(), |h0| format!("{h0}")),
335 requested_height
336 ),
337 SqliteClientError::DecodingError(e) => write!(f, "{e}"),
338 #[cfg(feature = "transparent-inputs")]
339 SqliteClientError::TransparentDerivation(e) => write!(f, "{e:?}"),
340 #[cfg(feature = "transparent-inputs")]
341 SqliteClientError::TransparentAddress(e) => write!(f, "{e}"),
342 SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"),
343 SqliteClientError::DbError(e) => write!(f, "{e}"),
344 SqliteClientError::Io(e) => write!(f, "{e}"),
345 SqliteClientError::InvalidMemo(e) => write!(f, "{e}"),
346 SqliteClientError::BlockConflict(h) => write!(
347 f,
348 "A block hash conflict occurred at height {}; rewind required.",
349 u32::from(*h)
350 ),
351 SqliteClientError::NonSequentialBlocks => write!(
352 f,
353 "`put_blocks` requires that the provided block range be sequential"
354 ),
355 SqliteClientError::AddressGeneration(e) => write!(f, "{e}"),
356 SqliteClientError::AccountUnknown => write!(
357 f,
358 "The account with the given ID does not belong to this wallet."
359 ),
360 SqliteClientError::UnknownZip32Derivation => write!(
361 f,
362 "ZIP-32 derivation information is not known for this account."
363 ),
364 SqliteClientError::KeyDerivationError(zip32_index) => write!(
365 f,
366 "Key derivation failed for ZIP 32 account index {}",
367 u32::from(*zip32_index)
368 ),
369 SqliteClientError::BadAccountData(e) => write!(f, "Failed to add account: {e}"),
370 SqliteClientError::Zip32AccountIndexOutOfRange => write!(
371 f,
372 "ZIP 32 account identifiers must be less than 0x7FFFFFFF."
373 ),
374 SqliteClientError::AccountCollision(account_uuid) => write!(
375 f,
376 "An account corresponding to the data provided already exists in the wallet with UUID {account_uuid:?}."
377 ),
378 #[cfg(feature = "transparent-inputs")]
379 SqliteClientError::AddressNotRecognized(_) => write!(
380 f,
381 "The address associated with a received txo is not identifiable as belonging to the wallet."
382 ),
383 SqliteClientError::CommitmentTree(err) => write!(
384 f,
385 "An error occurred accessing or updating note commitment tree data: {err}."
386 ),
387 SqliteClientError::PutBlocksCommitmentTree {
388 pool,
389 block_range,
390 error,
391 } => write!(
392 f,
393 "An error occurred updating the {pool:?} note commitment tree while adding blocks in the range {}..{}: {error}.",
394 u32::from(block_range.start),
395 u32::from(block_range.end),
396 ),
397 SqliteClientError::TruncateCommitmentTree {
398 pool,
399 height,
400 error,
401 } => write!(
402 f,
403 "An error occurred updating the {pool:?} note commitment tree while truncating the wallet to height {}: {error}.",
404 u32::from(*height),
405 ),
406 #[cfg(feature = "orchard")]
407 SqliteClientError::HistoricalFrontierInvalid(err) => write!(
408 f,
409 "The frontier supplied to historical witness generation is inconsistent with the wallet's shard data: {err}"
410 ),
411 #[cfg(feature = "orchard")]
412 SqliteClientError::HistoricalWitnessUnavailable { position, height } => write!(
413 f,
414 "No witness is available for position {} at height {height} (the wallet may need to sync through this height).",
415 u64::from(*position),
416 ),
417 SqliteClientError::CacheMiss(height) => write!(
418 f,
419 "Requested height {height} does not exist in the block cache."
420 ),
421 SqliteClientError::ChainHeightUnknown => {
422 write!(f, "Chain height unknown; please call `update_chain_tip`")
423 }
424 SqliteClientError::UnsupportedPoolType(t) => {
425 write!(f, "Pool type is not currently supported: {t}")
426 }
427 SqliteClientError::BalanceError(e) => write!(f, "Balance error: {e}"),
428 SqliteClientError::NoteFilterInvalid(s) => {
429 write!(f, "Could not evaluate filter query: {s:?}")
430 }
431 #[cfg(feature = "transparent-inputs")]
432 SqliteClientError::ReachedGapLimit(key_scope, bad_index) => write!(
433 f,
434 "The proposal cannot be constructed until a transaction with outputs to a previously reserved {} address has been mined. \
435 The address at index {bad_index} could not be safely reserved.",
436 match *key_scope {
437 TransparentKeyScope::EXTERNAL => "external transparent",
438 TransparentKeyScope::INTERNAL => "transparent change",
439 TransparentKeyScope::EPHEMERAL => "ephemeral transparent",
440 _ => panic!("Unsupported transparent key scope."),
441 }
442 ),
443 SqliteClientError::DiversifierIndexReuse(i, _) => {
444 write!(
445 f,
446 "An address has already been exposed for diversifier index {}",
447 u128::from(*i)
448 )
449 }
450 SqliteClientError::AddressReuse(address_str, txids) => {
451 write!(
452 f,
453 "The address {address_str} previously used in txid(s) {txids:?} would be reused."
454 )
455 }
456 #[cfg(feature = "transparent-inputs")]
457 SqliteClientError::Scheduling(err) => {
458 write!(f, "The wallet was unable to schedule an event: {err}")
459 }
460 #[cfg(feature = "transparent-inputs")]
461 SqliteClientError::NotificationMismatch { expected, actual } => {
462 write!(
463 f,
464 "The client performed an address check over a block range that did not match the requested range; expected as_of_height: {expected}, actual as_of_height: {actual}"
465 )
466 }
467 SqliteClientError::IneligibleNotes => {
468 write!(
469 f,
470 "Query found notes that are considered ineligible in its context"
471 )
472 }
473 #[cfg(feature = "transparent-key-import")]
474 SqliteClientError::StandaloneImportConflict(uuid) => {
475 write!(
476 f,
477 "The given standalone transparent address is already managed by account {uuid}"
478 )
479 }
480 #[cfg(feature = "transparent-inputs")]
481 SqliteClientError::FeeRuleError(e) => write!(f, "Fee rule error: {e}"),
482 SqliteClientError::BackendError(e) => write!(
483 f,
484 "The zcash_client_backend error reported for {e} is not one this version of \
485 zcash_client_sqlite recognizes; this crate must be updated to handle it."
486 ),
487 }
488 }
489}
490
491impl From<rusqlite::Error> for SqliteClientError {
492 fn from(e: rusqlite::Error) -> Self {
493 SqliteClientError::DbError(e)
494 }
495}
496
497impl From<std::io::Error> for SqliteClientError {
498 fn from(e: std::io::Error) -> Self {
499 SqliteClientError::Io(e)
500 }
501}
502impl From<ParseError> for SqliteClientError {
503 fn from(e: ParseError) -> Self {
504 SqliteClientError::DecodingError(e)
505 }
506}
507
508impl From<prost::DecodeError> for SqliteClientError {
509 fn from(e: prost::DecodeError) -> Self {
510 SqliteClientError::Protobuf(e)
511 }
512}
513
514#[cfg(feature = "transparent-inputs")]
515impl From<bip32::Error> for SqliteClientError {
516 fn from(e: bip32::Error) -> Self {
517 SqliteClientError::TransparentDerivation(e)
518 }
519}
520
521#[cfg(feature = "transparent-inputs")]
522impl From<TransparentCodecError> for SqliteClientError {
523 fn from(e: TransparentCodecError) -> Self {
524 SqliteClientError::TransparentAddress(e)
525 }
526}
527
528impl From<zcash_protocol::memo::Error> for SqliteClientError {
529 fn from(e: zcash_protocol::memo::Error) -> Self {
530 SqliteClientError::InvalidMemo(e)
531 }
532}
533
534impl From<ShardTreeError<commitment_tree::Error>> for SqliteClientError {
535 fn from(e: ShardTreeError<commitment_tree::Error>) -> Self {
536 SqliteClientError::CommitmentTree(e)
537 }
538}
539
540impl From<BalanceError> for SqliteClientError {
541 fn from(e: BalanceError) -> Self {
542 SqliteClientError::BalanceError(e)
543 }
544}
545
546impl From<AddressGenerationError> for SqliteClientError {
547 fn from(e: AddressGenerationError) -> Self {
548 SqliteClientError::AddressGeneration(e)
549 }
550}
551
552#[cfg(feature = "transparent-inputs")]
555#[derive(Debug)]
556struct FeeErrorWrapper(zcash_primitives::transaction::fees::zip317::FeeError);
557
558#[cfg(feature = "transparent-inputs")]
559impl fmt::Display for FeeErrorWrapper {
560 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
561 fmt::Display::fmt(&self.0, f)
562 }
563}
564
565#[cfg(feature = "transparent-inputs")]
566impl error::Error for FeeErrorWrapper {}
567
568#[cfg(feature = "transparent-inputs")]
569impl From<zcash_primitives::transaction::fees::zip317::FeeError> for SqliteClientError {
570 fn from(e: zcash_primitives::transaction::fees::zip317::FeeError) -> Self {
571 SqliteClientError::FeeRuleError(Box::new(FeeErrorWrapper(e)))
572 }
573}
574
575#[cfg(feature = "transparent-inputs")]
576impl From<SchedulingError> for SqliteClientError {
577 fn from(value: SchedulingError) -> Self {
578 SqliteClientError::Scheduling(value)
579 }
580}
581
582impl From<PutBlocksError<SqliteClientError, commitment_tree::Error>> for SqliteClientError {
583 fn from(value: PutBlocksError<SqliteClientError, commitment_tree::Error>) -> Self {
584 match value {
585 ll::wallet::PutBlocksError::NonSequentialBlocks { .. } => {
586 SqliteClientError::NonSequentialBlocks
587 }
588 ll::wallet::PutBlocksError::Storage(e) => e,
589 ll::wallet::PutBlocksError::ShardTree(e) => SqliteClientError::from(e),
590 ll::wallet::PutBlocksError::ShardTreeForBlockRange {
591 pool,
592 block_range,
593 error,
594 } => SqliteClientError::PutBlocksCommitmentTree {
595 pool,
596 block_range,
597 error,
598 },
599 #[cfg(feature = "transparent-inputs")]
600 ll::wallet::PutBlocksError::GapAddresses(e) => SqliteClientError::from(e),
601 other => SqliteClientError::BackendError(BackendError::PutBlocks(Box::new(other))),
605 }
606 }
607}
608
609impl ErrUnsupportedPool for SqliteClientError {
610 fn unsupported_pool_type(pool_type: PoolType) -> Self {
611 SqliteClientError::UnsupportedPoolType(pool_type)
612 }
613}
614
615pub(crate) enum LockError {
617 Storage(rusqlite::Error),
619 LockFailure(OutputRef),
621}
622
623impl From<rusqlite::Error> for LockError {
624 fn from(value: rusqlite::Error) -> Self {
625 LockError::Storage(value)
626 }
627}
628
629impl From<LockError> for zcash_client_backend::data_api::error::LockError<SqliteClientError> {
630 fn from(value: LockError) -> Self {
631 match value {
632 LockError::Storage(error) => zcash_client_backend::data_api::error::LockError::Storage(
633 SqliteClientError::from(error),
634 ),
635 LockError::LockFailure(output) => {
636 zcash_client_backend::data_api::error::LockError::LockFailure(output)
637 }
638 }
639 }
640}