diff --git a/Cargo.toml b/Cargo.toml index 681da7f07f..7755882088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } rand = "^0.8" +thiserror = "^1.0" # Optional dependencies sled = { version = "0.34", optional = true } diff --git a/src/blockchain/compact_filters/mod.rs b/src/blockchain/compact_filters/mod.rs index 9b47df9cf0..b25e4c137e 100644 --- a/src/blockchain/compact_filters/mod.rs +++ b/src/blockchain/compact_filters/mod.rs @@ -50,7 +50,6 @@ //! ``` use std::collections::HashSet; -use std::fmt; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex}; @@ -534,54 +533,62 @@ impl ConfigurableBlockchain for CompactFiltersBlockchain { } /// An error that can occur during sync with a [`CompactFiltersBlockchain`] -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum CompactFiltersError { /// A peer sent an invalid or unexpected response + #[error("A peer sent an invalid or unexpected response")] InvalidResponse, /// The headers returned are invalid + #[error("Invalid headers")] InvalidHeaders, /// The compact filter headers returned are invalid + #[error("Invalid filter header")] InvalidFilterHeader, /// The compact filter returned is invalid + #[error("Invalid filters")] InvalidFilter, /// The peer is missing a block in the valid chain + #[error("The peer is missing a block in the valid chain")] MissingBlock, /// Block hash at specified height not found + #[error("Block hash not found")] BlockHashNotFound, /// The data stored in the block filters storage are corrupted + #[error("The data stored in the block filters storage are corrupted")] DataCorruption, /// A peer is not connected + #[error("A peer is not connected")] NotConnected, /// A peer took too long to reply to one of our messages + #[error("A peer took too long to reply to one of our messages")] Timeout, /// The peer doesn't advertise the [`BLOOM`](bitcoin::network::constants::ServiceFlags::BLOOM) service flag + #[error("Peer doesn't advertise the BLOOM service flag")] PeerBloomDisabled, /// No peers have been specified + #[error("No peers have been specified")] NoPeers, /// Internal database error + #[error("Internal database error: {0}")] Db(rocksdb::Error), /// Internal I/O error + #[error("Internal I/O error: {0}")] Io(std::io::Error), /// Invalid BIP158 filter + #[error("Invalid BIP158 filter: {0}")] Bip158(bitcoin::util::bip158::Error), /// Internal system time error + #[error("Invalid system time: {0}")] Time(std::time::SystemTimeError), /// Wrapper for [`crate::error::Error`] + #[error("Generic error: {0}")] Global(Box), } -impl fmt::Display for CompactFiltersError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for CompactFiltersError {} - impl_error!(rocksdb::Error, Db, CompactFiltersError); impl_error!(std::io::Error, Io, CompactFiltersError); impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError); diff --git a/src/descriptor/error.rs b/src/descriptor/error.rs index 72141dcbbb..8bc8917ea9 100644 --- a/src/descriptor/error.rs +++ b/src/descriptor/error.rs @@ -12,32 +12,43 @@ //! Descriptor errors /// Errors related to the parsing and usage of descriptors -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Invalid HD Key path, such as having a wildcard but a length != 1 + #[error("Invalid HD key path")] InvalidHdKeyPath, /// The provided descriptor doesn't match its checksum + #[error("The provided descriptor doesn't match its checksum")] InvalidDescriptorChecksum, /// The descriptor contains hardened derivation steps on public extended keys + #[error("The descriptor contains hardened derivation steps on public extended keys")] HardenedDerivationXpub, /// Error thrown while working with [`keys`](crate::keys) + #[error("Key error: {0}")] Key(crate::keys::KeyError), /// Error while extracting and manipulating policies + #[error("Policy error: {0}")] Policy(crate::descriptor::policy::PolicyError), /// Invalid byte found in the descriptor checksum + #[error("Invalid descriptor character: {0}")] InvalidDescriptorCharacter(u8), /// BIP32 error + #[error("BIP32 error: {0}")] Bip32(bitcoin::util::bip32::Error), /// Error during base58 decoding + #[error("Base58 error: {0}")] Base58(bitcoin::util::base58::Error), /// Key-related error + #[error("Key-related error: {0}")] Pk(bitcoin::util::key::Error), /// Miniscript error + #[error("Miniscript error: {0}")] Miniscript(miniscript::Error), /// Hex decoding error + #[error("Hex decoding error: {0}")] Hex(bitcoin::hashes::hex::Error), } @@ -51,14 +62,6 @@ impl From for Error { } } -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - impl_error!(bitcoin::util::bip32::Error, Bip32); impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::util::key::Error, Pk); diff --git a/src/descriptor/policy.rs b/src/descriptor/policy.rs index 964ec29114..22d64119f5 100644 --- a/src/descriptor/policy.rs +++ b/src/descriptor/policy.rs @@ -38,7 +38,6 @@ use std::cmp::max; use std::collections::{BTreeMap, HashSet, VecDeque}; -use std::fmt; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; @@ -494,30 +493,28 @@ impl Condition { } /// Errors that can happen while extracting and manipulating policies -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, thiserror::Error)] pub enum PolicyError { /// Not enough items are selected to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`] + #[error("Not enought items selected: {0}")] NotEnoughItemsSelected(String), /// Index out of range for an item to satisfy a [`SatisfiableItem::Thresh`] or a [`SatisfiableItem::Multisig`] + #[error("Index out of range: {0}")] IndexOutOfRange(usize), /// Can not add to an item that is [`Satisfaction::None`] or [`Satisfaction::Complete`] + #[error("Add on leaf")] AddOnLeaf, /// Can not add to an item that is [`Satisfaction::PartialComplete`] + #[error("Add on partial complete")] AddOnPartialComplete, /// Can not merge CSV or timelock values unless both are less than or both are equal or greater than 500_000_000 + #[error("Mixed timelock units")] MixedTimelockUnits, /// Incompatible conditions (not currently used) + #[error("Incompatible conditions")] IncompatibleConditions, } -impl fmt::Display for PolicyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for PolicyError {} - impl Policy { fn new(item: SatisfiableItem) -> Self { Policy { diff --git a/src/error.rs b/src/error.rs index 4150eadc9e..31dcc4673e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -16,21 +16,28 @@ use crate::{descriptor, wallet}; use bitcoin::{OutPoint, Txid}; /// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet) -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum Error { /// Wrong number of bytes found when trying to convert to u32 + #[error("Wrong number of bytes found when trying to convert to u32")] InvalidU32Bytes(Vec), /// Generic error + #[error("Generic error: {0}")] Generic(String), /// This error is thrown when trying to convert Bare and Public key script to address + #[error("Script doesn't have address form")] ScriptDoesntHaveAddressForm, /// Cannot build a tx without recipients + #[error("Cannot build tx without recipients")] NoRecipients, /// `manually_selected_only` option is selected but no utxo has been passed + #[error("No UTXO selected")] NoUtxosSelected, /// Output created is under the dust limit, 546 satoshis + #[error("Output below the dust limit: {0}")] OutputBelowDustLimit(usize), /// Wallet's UTXO set is not enough to cover recipient's requested plus fee + #[error("Insufficient funds: {available} sat available of {needed} sat needed")] InsufficientFunds { /// Sats needed for some transaction needed: u64, @@ -39,47 +46,63 @@ pub enum Error { }, /// Branch and bound coin selection possible attempts with sufficiently big UTXO set could grow /// exponentially, thus a limit is set, and when hit, this error is thrown + #[error("Branch and bound coin selection: total tries exceeded")] BnBTotalTriesExceeded, /// Branch and bound coin selection tries to avoid needing a change by finding the right inputs for /// the desired outputs plus fee, if there is not such combination this error is thrown + #[error("Branch and bound coin selection: not exact match")] BnBNoExactMatch, /// Happens when trying to spend an UTXO that is not in the internal database + #[error("UTXO not found in the internal database")] UnknownUtxo, /// Thrown when a tx is not found in the internal database + #[error("Transaction not found in the internal database")] TransactionNotFound, /// Happens when trying to bump a transaction that is already confirmed + #[error("Transaction already confirmed")] TransactionConfirmed, /// Trying to replace a tx that has a sequence >= `0xFFFFFFFE` + #[error("Transaction can't be replaced")] IrreplaceableTransaction, /// When bumping a tx the fee rate requested is lower than required + #[error("Fee rate too low: required {} sat/vbyte", required.as_sat_per_vb())] FeeRateTooLow { /// Required fee rate (satoshi/vbyte) required: crate::types::FeeRate, }, /// When bumping a tx the absolute fee requested is lower than replaced tx absolute fee + #[error("Fee to low: required {required} sat")] FeeTooLow { /// Required fee absolute value (satoshi) required: u64, }, /// Node doesn't have data to estimate a fee rate + #[error("Fee rate unavailable")] FeeRateUnavailable, /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended /// key in the descriptor must either be a master key itself (having depth = 0) or have an /// explicit origin provided /// /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs + #[error("Missing key origin: {0}")] MissingKeyOrigin(String), /// Error while working with [`keys`](crate::keys) + #[error("Key error: {0}")] Key(crate::keys::KeyError), /// Descriptor checksum mismatch + #[error("Descriptor checksum mismatch")] ChecksumMismatch, /// Spending policy is not compatible with this [`KeychainKind`](crate::types::KeychainKind) + #[error("Spending policy required: {0:?}")] SpendingPolicyRequired(crate::types::KeychainKind), /// Error while extracting and manipulating policies + #[error("Invalid policy path: {0}")] InvalidPolicyPathError(crate::descriptor::policy::PolicyError), /// Signing error + #[error("Signer error: {0}")] Signer(crate::wallet::signer::SignerError), /// Invalid network + #[error("Invalid network: requested {} but found {}", requested, found)] InvalidNetwork { /// requested network, for example what is given as bdk-cli option requested: Network, @@ -88,34 +111,48 @@ pub enum Error { }, #[cfg(feature = "verify")] /// Transaction verification error + #[error("Verification error: {0}")] Verification(crate::wallet::verify::VerifyError), /// Progress value must be between `0.0` (included) and `100.0` (included) + #[error("Invalid progress value: {0}")] InvalidProgressValue(f32), /// Progress update error (maybe the channel has been closed) + #[error("Progress update error (maybe the channel has been closed)")] ProgressUpdateError, /// Requested outpoint doesn't exist in the tx (vout greater than available outputs) + #[error("Requested outpoint doesn't exist in the tx: {0}")] InvalidOutpoint(OutPoint), /// Error related to the parsing and usage of descriptors + #[error("Descriptor error: {0}")] Descriptor(crate::descriptor::error::Error), /// Encoding error + #[error("Encoding error: {0}")] Encode(bitcoin::consensus::encode::Error), /// Miniscript error + #[error("Miniscript error: {0}")] Miniscript(miniscript::Error), /// Miniscript PSBT error + #[error("Miniscript PSBT error: {0}")] MiniscriptPsbt(MiniscriptPsbtError), /// BIP32 error + #[error("BIP32 error: {0}")] Bip32(bitcoin::util::bip32::Error), /// An ECDSA error + #[error("ECDSA error: {0}")] Secp256k1(bitcoin::secp256k1::Error), /// Error serializing or deserializing JSON data + #[error("Serialize/Deserialize JSON error: {0}")] Json(serde_json::Error), /// Hex decoding error + #[error("Hex decoding error: {0}")] Hex(bitcoin::hashes::hex::Error), /// Partially signed bitcoin transaction error + #[error("PSBT error: {0}")] Psbt(bitcoin::util::psbt::Error), /// Partially signed bitcoin transaction parse error + #[error("Impossible to parse PSBT: {0}")] PsbtParse(bitcoin::util::psbt::PsbtParseError), //KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey), @@ -127,33 +164,43 @@ pub enum Error { //MissingCachedAddresses, /// [`crate::blockchain::WalletSync`] sync attempt failed due to missing scripts in cache which /// are needed to satisfy `stop_gap`. + #[error("Missing cached scripts: {0:?}")] MissingCachedScripts(MissingCachedScripts), #[cfg(feature = "electrum")] /// Electrum client error + #[error("Electrum client error: {0}")] Electrum(electrum_client::Error), #[cfg(feature = "esplora")] /// Esplora client error + #[error("Esplora client error: {0}")] Esplora(Box), #[cfg(feature = "compact_filters")] /// Compact filters client error) + #[error("Compact filters client error: {0}")] CompactFilters(crate::blockchain::compact_filters::CompactFiltersError), #[cfg(feature = "key-value-db")] /// Sled database error + #[error("Sled database error: {0}")] Sled(sled::Error), #[cfg(feature = "rpc")] /// Rpc client error + #[error("RPC client error: {0}")] Rpc(bitcoincore_rpc::Error), #[cfg(feature = "sqlite")] /// Rusqlite client error + #[error("SQLite error: {0}")] Rusqlite(rusqlite::Error), } /// Errors returned by miniscript when updating inconsistent PSBTs -#[derive(Debug, Clone)] +#[derive(Debug, Clone, thiserror::Error)] pub enum MiniscriptPsbtError { + #[error("Conversion error: {0}")] Conversion(miniscript::descriptor::ConversionError), + #[error("UTXO update error: {0}")] UtxoUpdate(miniscript::psbt::UtxoUpdateError), + #[error("Output update error: {0}")] OutputUpdate(miniscript::psbt::OutputUpdateError), } @@ -167,14 +214,6 @@ pub struct MissingCachedScripts { pub missing_count: usize, } -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - macro_rules! impl_error { ( $from:ty, $to:ident ) => { impl_error!($from, $to, Error); diff --git a/src/keys/mod.rs b/src/keys/mod.rs index da541e1c5c..98ec61a239 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -912,35 +912,33 @@ impl IntoDescriptorKey for PrivateKey { } /// Errors thrown while working with [`keys`](crate::keys) -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum KeyError { /// The key cannot exist in the given script context + #[error("Invalid script context")] InvalidScriptContext, /// The key is not valid for the given network + #[error("Invalid network")] InvalidNetwork, /// The key has an invalid checksum + #[error("Invalid checksum")] InvalidChecksum, /// Custom error message + #[error("{0}")] Message(String), /// BIP32 error + #[error("BIP32 error: {0}")] Bip32(bitcoin::util::bip32::Error), /// Miniscript error + #[error("Miniscript error: {0}")] Miniscript(miniscript::Error), } impl_error!(miniscript::Error, Miniscript, KeyError); impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError); -impl std::fmt::Display for KeyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for KeyError {} - #[cfg(test)] pub mod test { use bitcoin::util::bip32; diff --git a/src/wallet/signer.rs b/src/wallet/signer.rs index 84d38826a9..b8f8cc0feb 100644 --- a/src/wallet/signer.rs +++ b/src/wallet/signer.rs @@ -130,38 +130,53 @@ impl From for SignerId { } /// Signing error -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] pub enum SignerError { /// The private key is missing for the required public key + #[error("Missing private key")] MissingKey, /// The private key in use has the right fingerprint but derives differently than expected + #[error( + "The private key in use has the right fingerprint but derives differently than expected" + )] InvalidKey, /// The user canceled the operation + #[error("The user canceled the operation")] UserCanceled, /// Input index is out of range + #[error("Input index out of range")] InputIndexOutOfRange, /// The `non_witness_utxo` field of the transaction is required to sign this input + #[error("Missing non-witness UTXO")] MissingNonWitnessUtxo, /// The `non_witness_utxo` specified is invalid + #[error("Invalid non-witness UTXO")] InvalidNonWitnessUtxo, /// The `witness_utxo` field of the transaction is required to sign this input + #[error("Missing witness UTXO")] MissingWitnessUtxo, /// The `witness_script` field of the transaction is required to sign this input + #[error("Missing witness script")] MissingWitnessScript, /// The fingerprint and derivation path are missing from the psbt input + #[error("Missing fingerprint and derivation path")] MissingHdKeypath, /// The psbt contains a non-`SIGHASH_ALL` sighash in one of its input and the user hasn't /// explicitly allowed them /// /// To enable signing transactions with non-standard sighashes set /// [`SignOptions::allow_all_sighashes`] to `true`. + #[error("The psbt contains a non standard sighash")] NonStandardSighash, /// Invalid SIGHASH for the signing context in use + #[error("Invalid SIGHASH for the signing context in use")] InvalidSighash, /// Error while computing the hash to sign + #[error("Error while computing the hash to sign: {0}")] SighashError(sighash::Error), /// Error while signing using hardware wallets #[cfg(feature = "hardware-signer")] + #[error("Error while signing using hardware wallets: {0}")] HWIError(hwi::error::Error), } @@ -178,14 +193,6 @@ impl From for SignerError { } } -impl fmt::Display for SignerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for SignerError {} - /// Signing context /// /// Used by our software signers to determine the type of signatures to make diff --git a/src/wallet/verify.rs b/src/wallet/verify.rs index 084388b91a..92a0a54962 100644 --- a/src/wallet/verify.rs +++ b/src/wallet/verify.rs @@ -12,7 +12,6 @@ //! Verify transactions against the consensus rules use std::collections::HashMap; -use std::fmt; use bitcoin::consensus::serialize; use bitcoin::{OutPoint, Transaction, Txid}; @@ -73,30 +72,26 @@ pub fn verify_tx( } /// Error during validation of a tx agains the consensus rules -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum VerifyError { /// The transaction being spent is not available in the database or the blockchain client + #[error("The transaction being spent is not available in the database or the blockchain client: {0}")] MissingInputTx(Txid), /// The transaction being spent doesn't have the requested output + #[error("The transaction being spent doesn't have the requested output: {0}")] InvalidInput(OutPoint), /// Consensus error + #[error("Consensus error: {0:?}")] Consensus(bitcoinconsensus::Error), /// Generic error /// /// It has to be wrapped in a `Box` since `Error` has a variant that contains this enum + #[error("Generic error: {0}")] Global(Box), } -impl fmt::Display for VerifyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for VerifyError {} - impl From for VerifyError { fn from(other: Error) -> Self { VerifyError::Global(Box::new(other))