//! Error types for problems that may arise when reading or storing wallet data to SQLite. use std::error; use std::fmt; use shardtree::error::ShardTreeError; use zcash_address::ParseError; use zcash_client_backend::PoolType; use zcash_keys::keys::AddressGenerationError; use zcash_primitives::zip32; use zcash_primitives::{consensus::BlockHeight, transaction::components::amount::BalanceError}; use crate::wallet::commitment_tree; use crate::AccountId; use crate::PRUNING_DEPTH; #[cfg(feature = "transparent-inputs")] use { zcash_client_backend::encoding::TransparentCodecError, zcash_primitives::{legacy::TransparentAddress, transaction::TxId}, }; /// The primary error type for the SQLite wallet backend. #[derive(Debug)] pub enum SqliteClientError { /// Decoding of a stored value from its serialized form has failed. CorruptedData(String), /// An error occurred decoding a protobuf message. Protobuf(prost::DecodeError), /// The rcm value for a note cannot be decoded to a valid JubJub point. InvalidNote, /// Illegal attempt to reinitialize an already-initialized wallet database. TableNotEmpty, /// A Zcash key or address decoding error DecodingError(ParseError), /// An error produced in legacy transparent address derivation #[cfg(feature = "transparent-inputs")] TransparentDerivation(bip32::Error), /// An error encountered in decoding a transparent address from its /// serialized form. #[cfg(feature = "transparent-inputs")] TransparentAddress(TransparentCodecError), /// Wrapper for rusqlite errors. DbError(rusqlite::Error), /// Wrapper for errors from the IO subsystem Io(std::io::Error), /// A received memo cannot be interpreted as a UTF-8 string. InvalidMemo(zcash_primitives::memo::Error), /// An attempt to update block data would overwrite the current hash for a block with a /// different hash. This indicates that a required rewind was not performed. BlockConflict(BlockHeight), /// A range of blocks provided to the database as a unit was non-sequential NonSequentialBlocks, /// A requested rewind would violate invariants of the storage layer. The payload returned with /// this error is (safe rewind height, requested height). RequestedRewindInvalid(BlockHeight, BlockHeight), /// An error occurred in generating a Zcash address. AddressGeneration(AddressGenerationError), /// The account for which information was requested does not belong to the wallet. AccountUnknown, /// The account being added collides with an existing account in the wallet with the given ID. /// The collision can be on the seed and ZIP-32 account index, or a shared FVK component. AccountCollision(AccountId), /// The account was imported, and ZIP-32 derivation information is not known for it. UnknownZip32Derivation, /// An error occurred deriving a spending key from a seed and a ZIP-32 account index. KeyDerivationError(zip32::AccountId), /// An error occurred while processing an account due to a failure in deriving the account's keys. BadAccountData(String), /// A caller attempted to initialize the accounts table with a discontinuous /// set of account identifiers. AccountIdDiscontinuity, /// A caller attempted to construct a new account with an invalid account identifier. AccountIdOutOfRange, /// The address associated with a record being inserted was not recognized as /// belonging to the wallet. #[cfg(feature = "transparent-inputs")] AddressNotRecognized(TransparentAddress), /// An error occurred in inserting data into or accessing data from one of the wallet's note /// commitment trees. CommitmentTree(ShardTreeError), /// The block at the specified height was not available from the block cache. CacheMiss(BlockHeight), /// The height of the chain was not available; a call to [`WalletWrite::update_chain_tip`] is /// required before the requested operation can succeed. /// /// [`WalletWrite::update_chain_tip`]: /// zcash_client_backend::data_api::WalletWrite::update_chain_tip ChainHeightUnknown, /// Unsupported pool type UnsupportedPoolType(PoolType), /// An error occurred in computing wallet balance BalanceError(BalanceError), /// The proposal cannot be constructed until transactions with previously reserved /// ephemeral address outputs have been mined. The parameters are the account id and /// the index that could not safely be reserved. #[cfg(feature = "transparent-inputs")] ReachedGapLimit(AccountId, u32), /// An ephemeral address would be reused. The parameters are the address in string /// form, and the txid of the earliest transaction in which it is known to have been /// used. #[cfg(feature = "transparent-inputs")] EphemeralAddressReuse(String, TxId), } impl error::Error for SqliteClientError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match &self { SqliteClientError::InvalidMemo(e) => Some(e), SqliteClientError::DbError(e) => Some(e), SqliteClientError::Io(e) => Some(e), SqliteClientError::BalanceError(e) => Some(e), SqliteClientError::AddressGeneration(e) => Some(e), _ => None, } } } impl fmt::Display for SqliteClientError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { SqliteClientError::CorruptedData(reason) => { write!(f, "Data DB is corrupted: {}", reason) } SqliteClientError::Protobuf(e) => write!(f, "Failed to parse protobuf-encoded record: {}", e), SqliteClientError::InvalidNote => write!(f, "Invalid note"), SqliteClientError::RequestedRewindInvalid(h, r) => write!(f, "A rewind must be either of less than {} blocks, or at least back to block {} for your wallet; the requested height was {}.", PRUNING_DEPTH, h, r), SqliteClientError::DecodingError(e) => write!(f, "{}", e), #[cfg(feature = "transparent-inputs")] SqliteClientError::TransparentDerivation(e) => write!(f, "{:?}", e), #[cfg(feature = "transparent-inputs")] SqliteClientError::TransparentAddress(e) => write!(f, "{}", e), SqliteClientError::TableNotEmpty => write!(f, "Table is not empty"), SqliteClientError::DbError(e) => write!(f, "{}", e), SqliteClientError::Io(e) => write!(f, "{}", e), SqliteClientError::InvalidMemo(e) => write!(f, "{}", e), SqliteClientError::BlockConflict(h) => write!(f, "A block hash conflict occurred at height {}; rewind required.", u32::from(*h)), SqliteClientError::NonSequentialBlocks => write!(f, "`put_blocks` requires that the provided block range be sequential"), SqliteClientError::AddressGeneration(e) => write!(f, "{}", e), SqliteClientError::AccountUnknown => write!(f, "The account with the given ID does not belong to this wallet."), SqliteClientError::UnknownZip32Derivation => write!(f, "ZIP-32 derivation information is not known for this account."), SqliteClientError::KeyDerivationError(acct_id) => write!(f, "Key derivation failed for account {}", u32::from(*acct_id)), SqliteClientError::BadAccountData(e) => write!(f, "Failed to add account: {}", e), SqliteClientError::AccountIdDiscontinuity => write!(f, "Wallet account identifiers must be sequential."), SqliteClientError::AccountIdOutOfRange => write!(f, "Wallet account identifiers must be less than 0x7FFFFFFF."), SqliteClientError::AccountCollision(id) => write!(f, "An account corresponding to the data provided already exists in the wallet with internal identifier {}.", id.0), #[cfg(feature = "transparent-inputs")] SqliteClientError::AddressNotRecognized(_) => write!(f, "The address associated with a received txo is not identifiable as belonging to the wallet."), SqliteClientError::CommitmentTree(err) => write!(f, "An error occurred accessing or updating note commitment tree data: {}.", err), SqliteClientError::CacheMiss(height) => write!(f, "Requested height {} does not exist in the block cache.", height), SqliteClientError::ChainHeightUnknown => write!(f, "Chain height unknown; please call `update_chain_tip`"), SqliteClientError::UnsupportedPoolType(t) => write!(f, "Pool type is not currently supported: {}", t), SqliteClientError::BalanceError(e) => write!(f, "Balance error: {}", e), #[cfg(feature = "transparent-inputs")] SqliteClientError::ReachedGapLimit(account_id, bad_index) => write!(f, "The proposal cannot be constructed until transactions with previously reserved ephemeral address outputs have been mined. \ The ephemeral address in account {account_id:?} at index {bad_index} could not be safely reserved.", ), #[cfg(feature = "transparent-inputs")] SqliteClientError::EphemeralAddressReuse(address_str, txid) => write!(f, "The ephemeral address {address_str} previously used in txid {txid} would be reused."), } } } impl From for SqliteClientError { fn from(e: rusqlite::Error) -> Self { SqliteClientError::DbError(e) } } impl From for SqliteClientError { fn from(e: std::io::Error) -> Self { SqliteClientError::Io(e) } } impl From for SqliteClientError { fn from(e: ParseError) -> Self { SqliteClientError::DecodingError(e) } } impl From for SqliteClientError { fn from(e: prost::DecodeError) -> Self { SqliteClientError::Protobuf(e) } } #[cfg(feature = "transparent-inputs")] impl From for SqliteClientError { fn from(e: bip32::Error) -> Self { SqliteClientError::TransparentDerivation(e) } } #[cfg(feature = "transparent-inputs")] impl From for SqliteClientError { fn from(e: TransparentCodecError) -> Self { SqliteClientError::TransparentAddress(e) } } impl From for SqliteClientError { fn from(e: zcash_primitives::memo::Error) -> Self { SqliteClientError::InvalidMemo(e) } } impl From> for SqliteClientError { fn from(e: ShardTreeError) -> Self { SqliteClientError::CommitmentTree(e) } } impl From for SqliteClientError { fn from(e: BalanceError) -> Self { SqliteClientError::BalanceError(e) } } impl From for SqliteClientError { fn from(e: AddressGenerationError) -> Self { SqliteClientError::AddressGeneration(e) } }