From 75d155c67a3d5f5fd3f5ee5e043cdfe3a8a62029 Mon Sep 17 00:00:00 2001 From: Matthew Date: Wed, 1 May 2024 13:02:50 -0500 Subject: [PATCH] chore: add error tests --- bdk-ffi/src/bdk.udl | 182 ++--- bdk-ffi/src/error.rs | 1788 ++++++++++++++++++++++++++---------------- 2 files changed, 1201 insertions(+), 769 deletions(-) diff --git a/bdk-ffi/src/bdk.udl b/bdk-ffi/src/bdk.udl index d62edd1..c5d63ca 100644 --- a/bdk-ffi/src/bdk.udl +++ b/bdk-ffi/src/bdk.udl @@ -4,6 +4,34 @@ namespace bdk {}; // bdk crate - error module // ------------------------------------------------------------------------ +[Error] +interface AddressError { + Base58(); + Bech32(); + WitnessVersion(string error_message); + WitnessProgram(string error_message); + UncompressedPubkey(); + ExcessiveScriptSize(); + UnrecognizedScript(); + NetworkValidation(Network required, Network found, string address); + OtherAddressErr(); +}; + +[Error] +interface Bip32Error { + CannotDeriveFromHardenedKey(); + Secp256k1(string error_message); + InvalidChildNumber(u32 child_number); + InvalidChildNumberFormat(); + InvalidDerivationPathFormat(); + UnknownVersion(string version); + WrongExtendedKeyLength(u32 length); + Base58(string error_message); + Hex(string error_message); + InvalidPublicKeyHexLength(u32 length); + UnknownError(string error_message); +}; + [Error] interface Bip39Error { BadWordCount(u64 word_count); @@ -13,28 +41,6 @@ interface Bip39Error { AmbiguousLanguages(string languages); }; -[Error] -interface Bip32Error { - CannotDeriveFromHardenedKey(); - Secp256k1(string e); - InvalidChildNumber(u32 child_number); - InvalidChildNumberFormat(); - InvalidDerivationPathFormat(); - UnknownVersion(string version); - WrongExtendedKeyLength(u32 length); - Base58(string e); - Hex(string e); - InvalidPublicKeyHexLength(u32 length); - UnknownError(string e); -}; - -[Error] -interface DescriptorKeyError { - Parse(string e); - InvalidKeyType(); - Bip32(string e); -}; - [Error] interface CalculateFeeError { MissingTxOut(sequence out_points); @@ -42,22 +48,15 @@ interface CalculateFeeError { }; [Error] -interface WalletCreationError { - Io(string e); - InvalidMagicBytes(sequence got, sequence expected); - Descriptor(); - Write(); - Load(); - NotInitialized(); - LoadedGenesisDoesNotMatch(); - LoadedNetworkDoesNotMatch(Network expected, Network? got); +interface CannotConnectError { + Include(u32 height); }; [Error] interface CreateTxError { - Descriptor(string e); - Persist(string e); - Policy(string e); + Descriptor(string error_message); + Persist(string error_message); + Policy(string error_message); SpendingPolicyRequired(string kind); Version0(); Version1Csv(); @@ -69,19 +68,37 @@ interface CreateTxError { NoUtxosSelected(); OutputBelowDustLimit(u64 index); ChangePolicyDescriptor(); - CoinSelection(string e); + CoinSelection(string error_message); InsufficientFunds(u64 needed, u64 available); NoRecipients(); - Psbt(string e); + Psbt(string error_message); MissingKeyOrigin(string key); UnknownUtxo(string outpoint); MissingNonWitnessUtxo(string outpoint); - MiniscriptPsbt(string e); + MiniscriptPsbt(string error_message); }; [Error] -interface PersistenceError { - Write(string e); +interface DescriptorError { + InvalidHdKeyPath(); + InvalidDescriptorChecksum(); + HardenedDerivationXpub(); + MultiPath(); + Key(string error_message); + Policy(string error_message); + InvalidDescriptorCharacter(string char); + Bip32(string error_message); + Base58(string error_message); + Pk(string error_message); + Miniscript(string error_message); + Hex(string error_message); +}; + +[Error] +interface DescriptorKeyError { + Parse(string error_message); + InvalidKeyType(); + Bip32(string error_message); }; [Error] @@ -100,55 +117,28 @@ interface EsploraError { InvalidHttpHeaderValue(string value); }; +[Error] +interface ExtractTxError { + AbsurdFeeRate(u64 fee_rate); + MissingInputValue(); + SendingTooMuch(); + OtherExtractTxErr(); +}; + [Error] enum FeeRateError { "ArithmeticOverflow" }; [Error] -interface AddressError { - Base58(); - Bech32(); - WitnessVersion(string error_message); - WitnessProgram(string error_message); - UncompressedPubkey(); - ExcessiveScriptSize(); - UnrecognizedScript(); - NetworkValidation(Network required, Network found, string address); - OtherAddressErr(); -}; - -[Error] -interface TransactionError { - Io(); - OversizedVectorAllocation(); - InvalidChecksum(string expected, string actual); - NonMinimalVarInt(); - ParseFailed(); - UnsupportedSegwitFlag(u8 flag); - OtherTransactionErr(); +interface PersistenceError { + Write(string error_message); }; [Error] interface PsbtParseError { - PsbtEncoding(string e); - Base64Encoding(string e); -}; - -[Error] -interface DescriptorError { - InvalidHdKeyPath(); - InvalidDescriptorChecksum(); - HardenedDerivationXpub(); - MultiPath(); - Key(string e); - Policy(string e); - InvalidDescriptorCharacter(string char); - Bip32(string e); - Base58(string e); - Pk(string e); - Miniscript(string e); - Hex(string e); + PsbtEncoding(string error_message); + Base64Encoding(string error_message); }; [Error] @@ -164,9 +154,20 @@ interface SignerError { MissingHdKeypath(); NonStandardSighash(); InvalidSighash(); - SighashError(string e); - MiniscriptPsbt(string e); - External(string e); + SighashError(string error_message); + MiniscriptPsbt(string error_message); + External(string error_message); +}; + +[Error] +interface TransactionError { + Io(); + OversizedVectorAllocation(); + InvalidChecksum(string expected, string actual); + NonMinimalVarInt(); + ParseFailed(); + UnsupportedSegwitFlag(u8 flag); + OtherTransactionErr(); }; [Error] @@ -175,16 +176,15 @@ interface TxidParseError { }; [Error] -interface ExtractTxError { - AbsurdFeeRate(u64 fee_rate); - MissingInputValue(); - SendingTooMuch(); - OtherExtractTxErr(); -}; - -[Error] -interface CannotConnectError { - Include(u32 height); +interface WalletCreationError { + Io(string error_message); + InvalidMagicBytes(sequence got, sequence expected); + Descriptor(); + Write(); + Load(); + NotInitialized(); + LoadedGenesisDoesNotMatch(); + LoadedNetworkDoesNotMatch(Network expected, Network? got); }; // ------------------------------------------------------------------------ diff --git a/bdk-ffi/src/error.rs b/bdk-ffi/src/error.rs index 343315f..443e6c3 100644 --- a/bdk-ffi/src/error.rs +++ b/bdk-ffi/src/error.rs @@ -23,13 +23,52 @@ use bdk::bitcoin::bip32; use bdk::wallet::error::CreateTxError as BdkCreateTxError; use std::convert::TryInto; +// ------------------------------------------------------------------------ +// error definitions +// ------------------------------------------------------------------------ + +#[derive(Debug, thiserror::Error)] +pub enum AddressError { + #[error("base58 address encoding error")] + Base58, + + #[error("bech32 address encoding error")] + Bech32, + + #[error("witness version conversion/parsing error: {error_message}")] + WitnessVersion { error_message: String }, + + #[error("witness program error: {error_message}")] + WitnessProgram { error_message: String }, + + #[error("an uncompressed pubkey was used where it is not allowed")] + UncompressedPubkey, + + #[error("script size exceed 520 bytes")] + ExcessiveScriptSize, + + #[error("script is not p2pkh, p2sh, or witness program")] + UnrecognizedScript, + + #[error("address {address} is not valid on {required}")] + NetworkValidation { + required: Network, + found: Network, + address: String, + }, + + // This is required because the bdk::bitcoin::address::Error is non-exhaustive + #[error("other address error")] + OtherAddressErr, +} + #[derive(Debug, thiserror::Error)] pub enum Bip32Error { #[error("Cannot derive from a hardened key")] CannotDeriveFromHardenedKey, - #[error("Secp256k1 error: {e}")] - Secp256k1 { e: String }, + #[error("Secp256k1 error: {error_message}")] + Secp256k1 { error_message: String }, #[error("Invalid child number: {child_number}")] InvalidChildNumber { child_number: u32 }, @@ -46,29 +85,62 @@ pub enum Bip32Error { #[error("Wrong extended key length: {length}")] WrongExtendedKeyLength { length: u32 }, - #[error("Base58 error: {e}")] - Base58 { e: String }, + #[error("Base58 error: {error_message}")] + Base58 { error_message: String }, - #[error("Hexadecimal conversion error: {e}")] - Hex { e: String }, + #[error("Hexadecimal conversion error: {error_message}")] + Hex { error_message: String }, #[error("Invalid public key hex length: {length}")] InvalidPublicKeyHexLength { length: u32 }, - #[error("Unknown error: {e}")] - UnknownError { e: String }, + #[error("Unknown error: {error_message}")] + UnknownError { error_message: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum Bip39Error { + #[error("the word count {word_count} is not supported")] + BadWordCount { word_count: u64 }, + + #[error("unknown word at index {index}")] + UnknownWord { index: u64 }, + + #[error("entropy bit count {bit_count} is invalid")] + BadEntropyBitCount { bit_count: u64 }, + + #[error("checksum is invalid")] + InvalidChecksum, + + #[error("ambiguous languages detected: {languages}")] + AmbiguousLanguages { languages: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum CalculateFeeError { + #[error("missing transaction output: {out_points:?}")] + MissingTxOut { out_points: Vec }, + + #[error("negative fee value: {fee}")] + NegativeFee { fee: i64 }, +} + +#[derive(Debug, thiserror::Error)] +pub enum CannotConnectError { + #[error("cannot include height: {height}")] + Include { height: u32 }, } #[derive(Debug, thiserror::Error)] pub enum CreateTxError { - #[error("Descriptor error: {e}")] - Descriptor { e: String }, + #[error("Descriptor error: {error_message}")] + Descriptor { error_message: String }, - #[error("Persistence failure: {e}")] - Persist { e: String }, + #[error("Persistence failure: {error_message}")] + Persist { error_message: String }, - #[error("Policy error: {e}")] - Policy { e: String }, + #[error("Policy error: {error_message}")] + Policy { error_message: String }, #[error("Spending policy required for {kind}")] SpendingPolicyRequired { kind: String }, @@ -103,8 +175,8 @@ pub enum CreateTxError { #[error("Change policy descriptor error")] ChangePolicyDescriptor, - #[error("Coin selection failed: {e}")] - CoinSelection { e: String }, + #[error("Coin selection failed: {error_message}")] + CoinSelection { error_message: String }, #[error("Insufficient funds: needed {needed} sat, available {available} sat")] InsufficientFunds { needed: u64, available: u64 }, @@ -112,8 +184,8 @@ pub enum CreateTxError { #[error("Transaction has no recipients")] NoRecipients, - #[error("PSBT creation error: {e}")] - Psbt { e: String }, + #[error("PSBT creation error: {error_message}")] + Psbt { error_message: String }, #[error("Missing key origin for: {key}")] MissingKeyOrigin { key: String }, @@ -124,85 +196,59 @@ pub enum CreateTxError { #[error("Missing non-witness UTXO for outpoint: {outpoint}")] MissingNonWitnessUtxo { outpoint: String }, - #[error("Miniscript PSBT error: {e}")] - MiniscriptPsbt { e: String }, + #[error("Miniscript PSBT error: {error_message}")] + MiniscriptPsbt { error_message: String }, +} + +#[derive(Debug, thiserror::Error)] +pub enum DescriptorError { + #[error("invalid hd key path")] + InvalidHdKeyPath, + + #[error("the provided descriptor doesn't match its checksum")] + InvalidDescriptorChecksum, + + #[error("the descriptor contains hardened derivation steps on public extended keys")] + HardenedDerivationXpub, + + #[error("the descriptor contains multipath keys, which are not supported yet")] + MultiPath, + + #[error("key error: {error_message}")] + Key { error_message: String }, + + #[error("policy error: {error_message}")] + Policy { error_message: String }, + + #[error("invalid descriptor character: {char}")] + InvalidDescriptorCharacter { char: String }, + + #[error("BIP32 error: {error_message}")] + Bip32 { error_message: String }, + + #[error("Base58 error: {error_message}")] + Base58 { error_message: String }, + + #[error("Key-related error: {error_message}")] + Pk { error_message: String }, + + #[error("Miniscript error: {error_message}")] + Miniscript { error_message: String }, + + #[error("Hex decoding error: {error_message}")] + Hex { error_message: String }, } #[derive(Debug, thiserror::Error)] pub enum DescriptorKeyError { - #[error("error parsing descriptor key: {e}")] - Parse { e: String }, + #[error("error parsing descriptor key: {error_message}")] + Parse { error_message: String }, #[error("error invalid key type")] InvalidKeyType, - #[error("error bip 32 related: {e}")] - Bip32 { e: String }, -} - -#[derive(Debug, thiserror::Error)] -pub enum CalculateFeeError { - #[error("missing transaction output: {out_points:?}")] - MissingTxOut { out_points: Vec }, - - #[error("negative fee value: {fee}")] - NegativeFee { fee: i64 }, -} - -#[derive(Debug, thiserror::Error)] -pub enum WalletCreationError { - // Errors coming from the FileError enum - #[error("io error trying to read file: {e}")] - Io { e: String }, - - #[error("file has invalid magic bytes: expected={expected:?} got={got:?}")] - InvalidMagicBytes { got: Vec, expected: Vec }, - - // Errors coming from the NewOrLoadError enum - #[error("error with descriptor")] - Descriptor, - - #[error("failed to write to persistence")] - Write, - - #[error("failed to load from persistence")] - Load, - - #[error("wallet is not initialized, persistence backend is empty")] - NotInitialized, - - #[error("loaded genesis hash does not match the expected one")] - LoadedGenesisDoesNotMatch, - - #[error("loaded network type is not {expected}, got {got:?}")] - LoadedNetworkDoesNotMatch { - expected: Network, - got: Option, - }, -} - -#[derive(Debug, thiserror::Error)] -pub enum Bip39Error { - #[error("the word count {word_count} is not supported")] - BadWordCount { word_count: u64 }, - - #[error("unknown word at index {index}")] - UnknownWord { index: u64 }, - - #[error("entropy bit count {bit_count} is invalid")] - BadEntropyBitCount { bit_count: u64 }, - - #[error("checksum is invalid")] - InvalidChecksum, - - #[error("ambiguous languages detected: {languages}")] - AmbiguousLanguages { languages: String }, -} - -#[derive(Debug, thiserror::Error)] -pub enum PersistenceError { - #[error("writing to persistence error: {e}")] - Write { e: String }, + #[error("error bip 32 related: {error_message}")] + Bip32 { error_message: String }, } #[derive(Debug, thiserror::Error)] @@ -244,6 +290,23 @@ pub enum EsploraError { InvalidHttpHeaderValue { value: String }, } +#[derive(Debug, thiserror::Error)] +pub enum ExtractTxError { + #[error("an absurdly high fee rate of {fee_rate} sat/vbyte")] + AbsurdFeeRate { fee_rate: u64 }, + + #[error("one of the inputs lacked value information (witness_utxo or non_witness_utxo)")] + MissingInputValue, + + #[error("transaction would be invalid due to output value being greater than input value")] + SendingTooMuch, + + #[error( + "this error is required because the bdk::bitcoin::psbt::ExtractTxError is non-exhaustive" + )] + OtherExtractTxErr, +} + #[derive(Debug, thiserror::Error)] pub enum FeeRateError { #[error("arithmetic overflow on feerate")] @@ -251,129 +314,18 @@ pub enum FeeRateError { } #[derive(Debug, thiserror::Error)] -pub enum TxidParseError { - #[error("invalid txid: {txid}")] - InvalidTxid { txid: String }, -} - -#[derive(Debug, thiserror::Error)] -pub enum CannotConnectError { - #[error("cannot include height: {height}")] - Include { height: u32 }, -} - -#[derive(Debug, thiserror::Error)] -pub enum AddressError { - // Errors coming from the ParseError enum - #[error("base58 address encoding error")] - Base58, - - #[error("bech32 address encoding error")] - Bech32, - - // Errors coming from the bitcoin::address::Error enum - #[error("witness version conversion/parsing error: {error_message}")] - WitnessVersion { error_message: String }, - - #[error("witness program error: {error_message}")] - WitnessProgram { error_message: String }, - - #[error("an uncompressed pubkey was used where it is not allowed")] - UncompressedPubkey, - - #[error("script size exceed 520 bytes")] - ExcessiveScriptSize, - - #[error("script is not p2pkh, p2sh, or witness program")] - UnrecognizedScript, - - #[error("address {address} is not valid on {required}")] - NetworkValidation { - /// Network that was required. - required: Network, - /// Network on which the address was found to be valid. - found: Network, - /// The address itself - address: String, - }, - - // This is required because the bdk::bitcoin::address::Error is non-exhaustive - #[error("other address error")] - OtherAddressErr, -} - -// Mapping https://docs.rs/bitcoin/latest/src/bitcoin/consensus/encode.rs.html#40-63 -#[derive(Debug, thiserror::Error)] -pub enum TransactionError { - #[error("IO error")] - Io, - - #[error("allocation of oversized vector")] - OversizedVectorAllocation, - - #[error("invalid checksum: expected={expected} actual={actual}")] - InvalidChecksum { expected: String, actual: String }, - - #[error("non-minimal varint")] - NonMinimalVarInt, - - #[error("parse failed")] - ParseFailed, - - #[error("unsupported segwit version: {flag}")] - UnsupportedSegwitFlag { flag: u8 }, - - // This is required because the bdk::bitcoin::consensus::encode::Error is non-exhaustive - #[error("other transaction error")] - OtherTransactionErr, +pub enum PersistenceError { + #[error("writing to persistence error: {error_message}")] + Write { error_message: String }, } #[derive(Debug, thiserror::Error)] pub enum PsbtParseError { - #[error("error in internal PSBT data structure: {e}")] - PsbtEncoding { e: String }, + #[error("error in internal PSBT data structure: {error_message}")] + PsbtEncoding { error_message: String }, - #[error("error in PSBT base64 encoding: {e}")] - Base64Encoding { e: String }, -} - -#[derive(Debug, thiserror::Error)] -pub enum DescriptorError { - #[error("invalid hd key path")] - InvalidHdKeyPath, - - #[error("the provided descriptor doesn't match its checksum")] - InvalidDescriptorChecksum, - - #[error("the descriptor contains hardened derivation steps on public extended keys")] - HardenedDerivationXpub, - - #[error("the descriptor contains multipath keys, which are not supported yet")] - MultiPath, - - #[error("key error: {e}")] - Key { e: String }, - - #[error("policy error: {e}")] - Policy { e: String }, - - #[error("invalid descriptor character: {char}")] - InvalidDescriptorCharacter { char: String }, - - #[error("BIP32 error: {e}")] - Bip32 { e: String }, - - #[error("Base58 error: {e}")] - Base58 { e: String }, - - #[error("Key-related error: {e}")] - Pk { e: String }, - - #[error("Miniscript error: {e}")] - Miniscript { e: String }, - - #[error("Hex decoding error: {e}")] - Hex { e: String }, + #[error("error in PSBT base64 encoding: {error_message}")] + Base64Encoding { error_message: String }, } #[derive(Debug, thiserror::Error)] @@ -411,331 +363,80 @@ pub enum SignerError { #[error("Invalid sighash type provided")] InvalidSighash, - #[error("Error with sighash computation: {e}")] - SighashError { e: String }, + #[error("Error with sighash computation: {error_message}")] + SighashError { error_message: String }, - #[error("Miniscript Psbt error: {e}")] - MiniscriptPsbt { e: String }, + #[error("Miniscript Psbt error: {error_message}")] + MiniscriptPsbt { error_message: String }, - #[error("External error: {e}")] - External { e: String }, + #[error("External error: {error_message}")] + External { error_message: String }, } #[derive(Debug, thiserror::Error)] -pub enum ExtractTxError { - #[error("an absurdly high fee rate of {fee_rate} sat/vbyte")] - AbsurdFeeRate { fee_rate: u64 }, +pub enum TransactionError { + #[error("IO error")] + Io, - #[error("one of the inputs lacked value information (witness_utxo or non_witness_utxo)")] - MissingInputValue, + #[error("allocation of oversized vector")] + OversizedVectorAllocation, - #[error("transaction would be invalid due to output value being greater than input value")] - SendingTooMuch, + #[error("invalid checksum: expected={expected} actual={actual}")] + InvalidChecksum { expected: String, actual: String }, - #[error( - "this error is required because the bdk::bitcoin::psbt::ExtractTxError is non-exhaustive" - )] - OtherExtractTxErr, + #[error("non-minimal varint")] + NonMinimalVarInt, + + #[error("parse failed")] + ParseFailed, + + #[error("unsupported segwit version: {flag}")] + UnsupportedSegwitFlag { flag: u8 }, + + // This is required because the bdk::bitcoin::consensus::encode::Error is non-exhaustive + #[error("other transaction error")] + OtherTransactionErr, } -impl From> for CreateTxError { - fn from(error: BdkCreateTxError) -> Self { - match error { - BdkCreateTxError::Descriptor(e) => CreateTxError::Descriptor { e: e.to_string() }, - BdkCreateTxError::Persist(e) => CreateTxError::Persist { e: e.to_string() }, - BdkCreateTxError::Policy(e) => CreateTxError::Policy { e: e.to_string() }, - BdkCreateTxError::SpendingPolicyRequired(kind) => { - CreateTxError::SpendingPolicyRequired { - kind: format!("{:?}", kind), - } - } - BdkCreateTxError::Version0 => CreateTxError::Version0, - BdkCreateTxError::Version1Csv => CreateTxError::Version1Csv, - BdkCreateTxError::LockTime { - requested, - required, - } => CreateTxError::LockTime { - requested: requested.to_string(), - required: required.to_string(), - }, - BdkCreateTxError::RbfSequence => CreateTxError::RbfSequence, - BdkCreateTxError::RbfSequenceCsv { rbf, csv } => CreateTxError::RbfSequenceCsv { - rbf: rbf.to_string(), - csv: csv.to_string(), - }, - BdkCreateTxError::FeeTooLow { required } => CreateTxError::FeeTooLow { required }, - BdkCreateTxError::FeeRateTooLow { required } => CreateTxError::FeeRateTooLow { - required: required.to_string(), - }, - BdkCreateTxError::NoUtxosSelected => CreateTxError::NoUtxosSelected, - BdkCreateTxError::OutputBelowDustLimit(index) => CreateTxError::OutputBelowDustLimit { - index: index as u64, - }, - BdkCreateTxError::ChangePolicyDescriptor => CreateTxError::ChangePolicyDescriptor, - BdkCreateTxError::CoinSelection(e) => CreateTxError::CoinSelection { e: e.to_string() }, - BdkCreateTxError::InsufficientFunds { needed, available } => { - CreateTxError::InsufficientFunds { needed, available } - } - BdkCreateTxError::NoRecipients => CreateTxError::NoRecipients, - BdkCreateTxError::Psbt(e) => CreateTxError::Psbt { e: e.to_string() }, - BdkCreateTxError::MissingKeyOrigin(key) => CreateTxError::MissingKeyOrigin { key }, - BdkCreateTxError::UnknownUtxo => CreateTxError::UnknownUtxo { - outpoint: "Unknown".to_string(), - }, - BdkCreateTxError::MissingNonWitnessUtxo(outpoint) => { - CreateTxError::MissingNonWitnessUtxo { - outpoint: outpoint.to_string(), - } - } - BdkCreateTxError::MiniscriptPsbt(e) => { - CreateTxError::MiniscriptPsbt { e: e.to_string() } - } - } - } +#[derive(Debug, thiserror::Error)] +pub enum TxidParseError { + #[error("invalid txid: {txid}")] + InvalidTxid { txid: String }, } -impl From for CreateTxError { - fn from(error: AddUtxoError) -> Self { - match error { - AddUtxoError::UnknownUtxo(outpoint) => CreateTxError::UnknownUtxo { - outpoint: outpoint.to_string(), - }, - } - } +#[derive(Debug, thiserror::Error)] +pub enum WalletCreationError { + #[error("io error trying to read file: {error_message}")] + Io { error_message: String }, + + #[error("file has invalid magic bytes: expected={expected:?} got={got:?}")] + InvalidMagicBytes { got: Vec, expected: Vec }, + + #[error("error with descriptor")] + Descriptor, + + #[error("failed to write to persistence")] + Write, + + #[error("failed to load from persistence")] + Load, + + #[error("wallet is not initialized, persistence backend is empty")] + NotInitialized, + + #[error("loaded genesis hash does not match the expected one")] + LoadedGenesisDoesNotMatch, + + #[error("loaded network type is not {expected}, got {got:?}")] + LoadedNetworkDoesNotMatch { + expected: Network, + got: Option, + }, } -impl From for CreateTxError { - fn from(error: BuildFeeBumpError) -> Self { - match error { - BuildFeeBumpError::UnknownUtxo(outpoint) => CreateTxError::UnknownUtxo { - outpoint: outpoint.to_string(), - }, - BuildFeeBumpError::TransactionNotFound(txid) => CreateTxError::UnknownUtxo { - outpoint: txid.to_string(), - }, - BuildFeeBumpError::TransactionConfirmed(txid) => CreateTxError::UnknownUtxo { - outpoint: txid.to_string(), - }, - BuildFeeBumpError::IrreplaceableTransaction(txid) => CreateTxError::UnknownUtxo { - outpoint: txid.to_string(), - }, - BuildFeeBumpError::FeeRateUnavailable => CreateTxError::FeeRateTooLow { - required: "unavailable".to_string(), - }, - } - } -} - -impl From for DescriptorError { - fn from(error: BdkDescriptorError) -> Self { - match error { - BdkDescriptorError::InvalidHdKeyPath => DescriptorError::InvalidHdKeyPath, - BdkDescriptorError::InvalidDescriptorChecksum => { - DescriptorError::InvalidDescriptorChecksum - } - BdkDescriptorError::HardenedDerivationXpub => DescriptorError::HardenedDerivationXpub, - BdkDescriptorError::MultiPath => DescriptorError::MultiPath, - BdkDescriptorError::Key(e) => DescriptorError::Key { e: e.to_string() }, - BdkDescriptorError::Policy(e) => DescriptorError::Policy { e: e.to_string() }, - BdkDescriptorError::InvalidDescriptorCharacter(char) => { - DescriptorError::InvalidDescriptorCharacter { - char: char.to_string(), - } - } - BdkDescriptorError::Bip32(e) => DescriptorError::Bip32 { e: e.to_string() }, - BdkDescriptorError::Base58(e) => DescriptorError::Base58 { e: e.to_string() }, - BdkDescriptorError::Pk(e) => DescriptorError::Pk { e: e.to_string() }, - BdkDescriptorError::Miniscript(e) => DescriptorError::Miniscript { e: e.to_string() }, - BdkDescriptorError::Hex(e) => DescriptorError::Hex { e: e.to_string() }, - } - } -} - -impl From for SignerError { - fn from(error: BdkSignerError) -> Self { - match error { - BdkSignerError::MissingKey => SignerError::MissingKey, - BdkSignerError::InvalidKey => SignerError::InvalidKey, - BdkSignerError::UserCanceled => SignerError::UserCanceled, - BdkSignerError::InputIndexOutOfRange => SignerError::InputIndexOutOfRange, - BdkSignerError::MissingNonWitnessUtxo => SignerError::MissingNonWitnessUtxo, - BdkSignerError::InvalidNonWitnessUtxo => SignerError::InvalidNonWitnessUtxo, - BdkSignerError::MissingWitnessUtxo => SignerError::MissingWitnessUtxo, - BdkSignerError::MissingWitnessScript => SignerError::MissingWitnessScript, - BdkSignerError::MissingHdKeypath => SignerError::MissingHdKeypath, - BdkSignerError::NonStandardSighash => SignerError::NonStandardSighash, - BdkSignerError::InvalidSighash => SignerError::InvalidSighash, - BdkSignerError::SighashError(e) => SignerError::SighashError { e: e.to_string() }, - BdkSignerError::MiniscriptPsbt(e) => SignerError::MiniscriptPsbt { - e: format!("{:?}", e), - }, - BdkSignerError::External(e) => SignerError::External { e }, - } - } -} - -impl From for DescriptorKeyError { - fn from(err: BdkDescriptorKeyParseError) -> DescriptorKeyError { - DescriptorKeyError::Parse { - e: format!("DescriptorKeyError error: {:?}", err), - } - } -} - -impl From for DescriptorKeyError { - fn from(err: bdk::bitcoin::bip32::Error) -> DescriptorKeyError { - DescriptorKeyError::Bip32 { - e: format!("BIP32 derivation error: {:?}", err), - } - } -} - -impl From for PsbtParseError { - fn from(error: BdkPsbtParseError) -> Self { - match error { - BdkPsbtParseError::PsbtEncoding(e) => PsbtParseError::PsbtEncoding { e: e.to_string() }, - BdkPsbtParseError::Base64Encoding(e) => { - PsbtParseError::Base64Encoding { e: e.to_string() } - } - _ => { - unreachable!("this is required because of the non-exhaustive enum in rust-bitcoin") - } - } - } -} - -impl From for WalletCreationError { - fn from(error: BdkFileError) -> Self { - match error { - BdkFileError::Io(e) => WalletCreationError::Io { e: e.to_string() }, - BdkFileError::InvalidMagicBytes { got, expected } => { - WalletCreationError::InvalidMagicBytes { got, expected } - } - } - } -} - -impl From> for WalletCreationError { - fn from(error: NewOrLoadError) -> Self { - match error { - NewOrLoadError::Descriptor(_) => WalletCreationError::Descriptor, - NewOrLoadError::Write(_) => WalletCreationError::Write, - NewOrLoadError::Load(_) => WalletCreationError::Load, - NewOrLoadError::NotInitialized => WalletCreationError::NotInitialized, - NewOrLoadError::LoadedGenesisDoesNotMatch { .. } => { - WalletCreationError::LoadedGenesisDoesNotMatch - } - NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => { - WalletCreationError::LoadedNetworkDoesNotMatch { expected, got } - } - } - } -} - -impl From for Bip39Error { - fn from(error: BdkBip39Error) -> Self { - match error { - BdkBip39Error::BadWordCount(word_count) => Bip39Error::BadWordCount { - word_count: word_count.try_into().expect("word count exceeds u64"), - }, - BdkBip39Error::UnknownWord(index) => Bip39Error::UnknownWord { - index: index.try_into().expect("index exceeds u64"), - }, - BdkBip39Error::BadEntropyBitCount(bit_count) => Bip39Error::BadEntropyBitCount { - bit_count: bit_count.try_into().expect("bit count exceeds u64"), - }, - BdkBip39Error::InvalidChecksum => Bip39Error::InvalidChecksum, - BdkBip39Error::AmbiguousLanguages(info) => Bip39Error::AmbiguousLanguages { - languages: format!("{:?}", info), - }, - } - } -} - -impl From for PersistenceError { - fn from(error: std::io::Error) -> Self { - PersistenceError::Write { - e: error.to_string(), - } - } -} - -impl From for Bip32Error { - fn from(error: BdkBip32Error) -> Self { - match error { - BdkBip32Error::CannotDeriveFromHardenedKey => Bip32Error::CannotDeriveFromHardenedKey, - BdkBip32Error::Secp256k1(err) => Bip32Error::Secp256k1 { e: err.to_string() }, - BdkBip32Error::InvalidChildNumber(num) => { - Bip32Error::InvalidChildNumber { child_number: num } - } - BdkBip32Error::InvalidChildNumberFormat => Bip32Error::InvalidChildNumberFormat, - BdkBip32Error::InvalidDerivationPathFormat => Bip32Error::InvalidDerivationPathFormat, - BdkBip32Error::UnknownVersion(bytes) => Bip32Error::UnknownVersion { - version: bytes.to_lower_hex_string(), - }, - BdkBip32Error::WrongExtendedKeyLength(len) => { - Bip32Error::WrongExtendedKeyLength { length: len as u32 } - } - BdkBip32Error::Base58(err) => Bip32Error::Base58 { e: err.to_string() }, - BdkBip32Error::Hex(err) => Bip32Error::Hex { e: err.to_string() }, - BdkBip32Error::InvalidPublicKeyHexLength(len) => { - Bip32Error::InvalidPublicKeyHexLength { length: len as u32 } - } - _ => Bip32Error::UnknownError { - e: format!("Unhandled error: {:?}", error), - }, - } - } -} - -impl From for CalculateFeeError { - fn from(error: BdkCalculateFeeError) -> Self { - match error { - BdkCalculateFeeError::MissingTxOut(out_points) => CalculateFeeError::MissingTxOut { - out_points: out_points.iter().map(|op| op.into()).collect(), - }, - BdkCalculateFeeError::NegativeFee(fee) => CalculateFeeError::NegativeFee { fee }, - } - } -} - -impl From for EsploraError { - fn from(error: BdkEsploraError) -> Self { - match error { - BdkEsploraError::Minreq(e) => EsploraError::Minreq { - error_message: e.to_string(), - }, - BdkEsploraError::HttpResponse { status, message } => EsploraError::HttpResponse { - status, - error_message: message, - }, - BdkEsploraError::Parsing(e) => EsploraError::Parsing { - error_message: e.to_string(), - }, - Error::StatusCode(e) => EsploraError::StatusCode { - error_message: e.to_string(), - }, - BdkEsploraError::BitcoinEncoding(e) => EsploraError::BitcoinEncoding { - error_message: e.to_string(), - }, - BdkEsploraError::HexToArray(e) => EsploraError::HexToArray { - error_message: e.to_string(), - }, - BdkEsploraError::HexToBytes(e) => EsploraError::HexToBytes { - error_message: e.to_string(), - }, - BdkEsploraError::TransactionNotFound(_) => EsploraError::TransactionNotFound, - BdkEsploraError::HeaderHeightNotFound(height) => { - EsploraError::HeaderHeightNotFound { height } - } - BdkEsploraError::HeaderHashNotFound(_) => EsploraError::HeaderHashNotFound, - Error::InvalidHttpHeaderName(name) => EsploraError::InvalidHttpHeaderName { name }, - BdkEsploraError::InvalidHttpHeaderValue(value) => { - EsploraError::InvalidHttpHeaderValue { value } - } - } - } -} +// ------------------------------------------------------------------------ +// error conversions +// ------------------------------------------------------------------------ impl From for AddressError { fn from(error: bdk::bitcoin::address::Error) -> Self { @@ -781,6 +482,331 @@ impl From for AddressError { } } +impl From for Bip32Error { + fn from(error: BdkBip32Error) -> Self { + match error { + BdkBip32Error::CannotDeriveFromHardenedKey => Bip32Error::CannotDeriveFromHardenedKey, + BdkBip32Error::Secp256k1(e) => Bip32Error::Secp256k1 { + error_message: e.to_string(), + }, + BdkBip32Error::InvalidChildNumber(num) => { + Bip32Error::InvalidChildNumber { child_number: num } + } + BdkBip32Error::InvalidChildNumberFormat => Bip32Error::InvalidChildNumberFormat, + BdkBip32Error::InvalidDerivationPathFormat => Bip32Error::InvalidDerivationPathFormat, + BdkBip32Error::UnknownVersion(bytes) => Bip32Error::UnknownVersion { + version: bytes.to_lower_hex_string(), + }, + BdkBip32Error::WrongExtendedKeyLength(len) => { + Bip32Error::WrongExtendedKeyLength { length: len as u32 } + } + BdkBip32Error::Base58(e) => Bip32Error::Base58 { + error_message: e.to_string(), + }, + BdkBip32Error::Hex(e) => Bip32Error::Hex { + error_message: e.to_string(), + }, + BdkBip32Error::InvalidPublicKeyHexLength(len) => { + Bip32Error::InvalidPublicKeyHexLength { length: len as u32 } + } + _ => Bip32Error::UnknownError { + error_message: format!("Unhandled error: {:?}", error), + }, + } + } +} + +impl From for Bip39Error { + fn from(error: BdkBip39Error) -> Self { + match error { + BdkBip39Error::BadWordCount(word_count) => Bip39Error::BadWordCount { + word_count: word_count.try_into().expect("word count exceeds u64"), + }, + BdkBip39Error::UnknownWord(index) => Bip39Error::UnknownWord { + index: index.try_into().expect("index exceeds u64"), + }, + BdkBip39Error::BadEntropyBitCount(bit_count) => Bip39Error::BadEntropyBitCount { + bit_count: bit_count.try_into().expect("bit count exceeds u64"), + }, + BdkBip39Error::InvalidChecksum => Bip39Error::InvalidChecksum, + BdkBip39Error::AmbiguousLanguages(info) => Bip39Error::AmbiguousLanguages { + languages: format!("{:?}", info), + }, + } + } +} + +impl From for CalculateFeeError { + fn from(error: BdkCalculateFeeError) -> Self { + match error { + BdkCalculateFeeError::MissingTxOut(out_points) => CalculateFeeError::MissingTxOut { + out_points: out_points.iter().map(|op| op.into()).collect(), + }, + BdkCalculateFeeError::NegativeFee(fee) => CalculateFeeError::NegativeFee { fee }, + } + } +} + +impl From> for CreateTxError { + fn from(error: BdkCreateTxError) -> Self { + match error { + BdkCreateTxError::Descriptor(e) => CreateTxError::Descriptor { + error_message: e.to_string(), + }, + BdkCreateTxError::Persist(e) => CreateTxError::Persist { + error_message: e.to_string(), + }, + BdkCreateTxError::Policy(e) => CreateTxError::Policy { + error_message: e.to_string(), + }, + BdkCreateTxError::SpendingPolicyRequired(kind) => { + CreateTxError::SpendingPolicyRequired { + kind: format!("{:?}", kind), + } + } + BdkCreateTxError::Version0 => CreateTxError::Version0, + BdkCreateTxError::Version1Csv => CreateTxError::Version1Csv, + BdkCreateTxError::LockTime { + requested, + required, + } => CreateTxError::LockTime { + requested: requested.to_string(), + required: required.to_string(), + }, + BdkCreateTxError::RbfSequence => CreateTxError::RbfSequence, + BdkCreateTxError::RbfSequenceCsv { rbf, csv } => CreateTxError::RbfSequenceCsv { + rbf: rbf.to_string(), + csv: csv.to_string(), + }, + BdkCreateTxError::FeeTooLow { required } => CreateTxError::FeeTooLow { required }, + BdkCreateTxError::FeeRateTooLow { required } => CreateTxError::FeeRateTooLow { + required: required.to_string(), + }, + BdkCreateTxError::NoUtxosSelected => CreateTxError::NoUtxosSelected, + BdkCreateTxError::OutputBelowDustLimit(index) => CreateTxError::OutputBelowDustLimit { + index: index as u64, + }, + BdkCreateTxError::ChangePolicyDescriptor => CreateTxError::ChangePolicyDescriptor, + BdkCreateTxError::CoinSelection(e) => CreateTxError::CoinSelection { + error_message: e.to_string(), + }, + BdkCreateTxError::InsufficientFunds { needed, available } => { + CreateTxError::InsufficientFunds { needed, available } + } + BdkCreateTxError::NoRecipients => CreateTxError::NoRecipients, + BdkCreateTxError::Psbt(e) => CreateTxError::Psbt { + error_message: e.to_string(), + }, + BdkCreateTxError::MissingKeyOrigin(key) => CreateTxError::MissingKeyOrigin { key }, + BdkCreateTxError::UnknownUtxo => CreateTxError::UnknownUtxo { + outpoint: "Unknown".to_string(), + }, + BdkCreateTxError::MissingNonWitnessUtxo(outpoint) => { + CreateTxError::MissingNonWitnessUtxo { + outpoint: outpoint.to_string(), + } + } + BdkCreateTxError::MiniscriptPsbt(e) => CreateTxError::MiniscriptPsbt { + error_message: e.to_string(), + }, + } + } +} + +impl From for CreateTxError { + fn from(error: AddUtxoError) -> Self { + match error { + AddUtxoError::UnknownUtxo(outpoint) => CreateTxError::UnknownUtxo { + outpoint: outpoint.to_string(), + }, + } + } +} + +impl From for CreateTxError { + fn from(error: BuildFeeBumpError) -> Self { + match error { + BuildFeeBumpError::UnknownUtxo(outpoint) => CreateTxError::UnknownUtxo { + outpoint: outpoint.to_string(), + }, + BuildFeeBumpError::TransactionNotFound(txid) => CreateTxError::UnknownUtxo { + outpoint: txid.to_string(), + }, + BuildFeeBumpError::TransactionConfirmed(txid) => CreateTxError::UnknownUtxo { + outpoint: txid.to_string(), + }, + BuildFeeBumpError::IrreplaceableTransaction(txid) => CreateTxError::UnknownUtxo { + outpoint: txid.to_string(), + }, + BuildFeeBumpError::FeeRateUnavailable => CreateTxError::FeeRateTooLow { + required: "unavailable".to_string(), + }, + } + } +} + +impl From for DescriptorError { + fn from(error: BdkDescriptorError) -> Self { + match error { + BdkDescriptorError::InvalidHdKeyPath => DescriptorError::InvalidHdKeyPath, + BdkDescriptorError::InvalidDescriptorChecksum => { + DescriptorError::InvalidDescriptorChecksum + } + BdkDescriptorError::HardenedDerivationXpub => DescriptorError::HardenedDerivationXpub, + BdkDescriptorError::MultiPath => DescriptorError::MultiPath, + BdkDescriptorError::Key(e) => DescriptorError::Key { + error_message: e.to_string(), + }, + BdkDescriptorError::Policy(e) => DescriptorError::Policy { + error_message: e.to_string(), + }, + BdkDescriptorError::InvalidDescriptorCharacter(char) => { + DescriptorError::InvalidDescriptorCharacter { + char: char.to_string(), + } + } + BdkDescriptorError::Bip32(e) => DescriptorError::Bip32 { + error_message: e.to_string(), + }, + BdkDescriptorError::Base58(e) => DescriptorError::Base58 { + error_message: e.to_string(), + }, + BdkDescriptorError::Pk(e) => DescriptorError::Pk { + error_message: e.to_string(), + }, + BdkDescriptorError::Miniscript(e) => DescriptorError::Miniscript { + error_message: e.to_string(), + }, + BdkDescriptorError::Hex(e) => DescriptorError::Hex { + error_message: e.to_string(), + }, + } + } +} + +impl From for DescriptorKeyError { + fn from(err: BdkDescriptorKeyParseError) -> DescriptorKeyError { + DescriptorKeyError::Parse { + error_message: format!("DescriptorKeyError error: {:?}", err), + } + } +} + +impl From for DescriptorKeyError { + fn from(error: bdk::bitcoin::bip32::Error) -> DescriptorKeyError { + DescriptorKeyError::Bip32 { + error_message: format!("BIP32 derivation error: {:?}", error), + } + } +} + +impl From for EsploraError { + fn from(error: BdkEsploraError) -> Self { + match error { + BdkEsploraError::Minreq(e) => EsploraError::Minreq { + error_message: e.to_string(), + }, + BdkEsploraError::HttpResponse { status, message } => EsploraError::HttpResponse { + status, + error_message: message, + }, + BdkEsploraError::Parsing(e) => EsploraError::Parsing { + error_message: e.to_string(), + }, + Error::StatusCode(e) => EsploraError::StatusCode { + error_message: e.to_string(), + }, + BdkEsploraError::BitcoinEncoding(e) => EsploraError::BitcoinEncoding { + error_message: e.to_string(), + }, + BdkEsploraError::HexToArray(e) => EsploraError::HexToArray { + error_message: e.to_string(), + }, + BdkEsploraError::HexToBytes(e) => EsploraError::HexToBytes { + error_message: e.to_string(), + }, + BdkEsploraError::TransactionNotFound(_) => EsploraError::TransactionNotFound, + BdkEsploraError::HeaderHeightNotFound(height) => { + EsploraError::HeaderHeightNotFound { height } + } + BdkEsploraError::HeaderHashNotFound(_) => EsploraError::HeaderHashNotFound, + Error::InvalidHttpHeaderName(name) => EsploraError::InvalidHttpHeaderName { name }, + BdkEsploraError::InvalidHttpHeaderValue(value) => { + EsploraError::InvalidHttpHeaderValue { value } + } + } + } +} + +impl From for ExtractTxError { + fn from(error: bdk::bitcoin::psbt::ExtractTxError) -> Self { + match error { + bdk::bitcoin::psbt::ExtractTxError::AbsurdFeeRate { fee_rate, .. } => { + let sat_per_vbyte = fee_rate.to_sat_per_vb_ceil(); + ExtractTxError::AbsurdFeeRate { + fee_rate: sat_per_vbyte, + } + } + bdk::bitcoin::psbt::ExtractTxError::MissingInputValue { .. } => { + ExtractTxError::MissingInputValue + } + bdk::bitcoin::psbt::ExtractTxError::SendingTooMuch { .. } => { + ExtractTxError::SendingTooMuch + } + _ => ExtractTxError::OtherExtractTxErr, + } + } +} + +impl From for PersistenceError { + fn from(error: std::io::Error) -> Self { + PersistenceError::Write { + error_message: error.to_string(), + } + } +} + +impl From for PsbtParseError { + fn from(error: BdkPsbtParseError) -> Self { + match error { + BdkPsbtParseError::PsbtEncoding(e) => PsbtParseError::PsbtEncoding { + error_message: e.to_string(), + }, + BdkPsbtParseError::Base64Encoding(e) => PsbtParseError::Base64Encoding { + error_message: e.to_string(), + }, + _ => { + unreachable!("this is required because of the non-exhaustive enum in rust-bitcoin") + } + } + } +} + +impl From for SignerError { + fn from(error: BdkSignerError) -> Self { + match error { + BdkSignerError::MissingKey => SignerError::MissingKey, + BdkSignerError::InvalidKey => SignerError::InvalidKey, + BdkSignerError::UserCanceled => SignerError::UserCanceled, + BdkSignerError::InputIndexOutOfRange => SignerError::InputIndexOutOfRange, + BdkSignerError::MissingNonWitnessUtxo => SignerError::MissingNonWitnessUtxo, + BdkSignerError::InvalidNonWitnessUtxo => SignerError::InvalidNonWitnessUtxo, + BdkSignerError::MissingWitnessUtxo => SignerError::MissingWitnessUtxo, + BdkSignerError::MissingWitnessScript => SignerError::MissingWitnessScript, + BdkSignerError::MissingHdKeypath => SignerError::MissingHdKeypath, + BdkSignerError::NonStandardSighash => SignerError::NonStandardSighash, + BdkSignerError::InvalidSighash => SignerError::InvalidSighash, + BdkSignerError::SighashError(e) => SignerError::SighashError { + error_message: e.to_string(), + }, + BdkSignerError::MiniscriptPsbt(e) => SignerError::MiniscriptPsbt { + error_message: format!("{:?}", e), + }, + BdkSignerError::External(e) => SignerError::External { error_message: e }, + } + } +} + impl From for TransactionError { fn from(error: bdk::bitcoin::consensus::encode::Error) -> Self { match error { @@ -806,30 +832,46 @@ impl From for TransactionError { } } -impl From for ExtractTxError { - fn from(error: bdk::bitcoin::psbt::ExtractTxError) -> Self { +impl From for WalletCreationError { + fn from(error: BdkFileError) -> Self { match error { - bdk::bitcoin::psbt::ExtractTxError::AbsurdFeeRate { fee_rate, .. } => { - let sat_per_vbyte = fee_rate.to_sat_per_vb_ceil(); - ExtractTxError::AbsurdFeeRate { - fee_rate: sat_per_vbyte, - } + BdkFileError::Io(e) => WalletCreationError::Io { + error_message: e.to_string(), + }, + BdkFileError::InvalidMagicBytes { got, expected } => { + WalletCreationError::InvalidMagicBytes { got, expected } } - bdk::bitcoin::psbt::ExtractTxError::MissingInputValue { .. } => { - ExtractTxError::MissingInputValue - } - bdk::bitcoin::psbt::ExtractTxError::SendingTooMuch { .. } => { - ExtractTxError::SendingTooMuch - } - _ => ExtractTxError::OtherExtractTxErr, } } } + +impl From> for WalletCreationError { + fn from(error: NewOrLoadError) -> Self { + match error { + NewOrLoadError::Descriptor(_) => WalletCreationError::Descriptor, + NewOrLoadError::Write(_) => WalletCreationError::Write, + NewOrLoadError::Load(_) => WalletCreationError::Load, + NewOrLoadError::NotInitialized => WalletCreationError::NotInitialized, + NewOrLoadError::LoadedGenesisDoesNotMatch { .. } => { + WalletCreationError::LoadedGenesisDoesNotMatch + } + NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => { + WalletCreationError::LoadedNetworkDoesNotMatch { expected, got } + } + } + } +} + +// ------------------------------------------------------------------------ +// error tests +// ------------------------------------------------------------------------ + #[cfg(test)] mod test { use crate::error::{ - Bip32Error, Bip39Error, CannotConnectError, EsploraError, PersistenceError, - WalletCreationError, + AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, + DescriptorKeyError, EsploraError, ExtractTxError, FeeRateError, PersistenceError, + PsbtParseError, TransactionError, TxidParseError, WalletCreationError, }; use crate::CalculateFeeError; use crate::OutPoint; @@ -837,7 +879,145 @@ mod test { use bdk::bitcoin::Network; #[test] - fn test_error_missing_tx_out() { + fn test_error_address() { + let cases = vec![ + (AddressError::Base58, "base58 address encoding error"), + (AddressError::Bech32, "bech32 address encoding error"), + ( + AddressError::WitnessVersion { + error_message: "version error".to_string(), + }, + "witness version conversion/parsing error: version error", + ), + ( + AddressError::WitnessProgram { + error_message: "program error".to_string(), + }, + "witness program error: program error", + ), + ( + AddressError::UncompressedPubkey, + "an uncompressed pubkey was used where it is not allowed", + ), + ( + AddressError::ExcessiveScriptSize, + "script size exceed 520 bytes", + ), + ( + AddressError::UnrecognizedScript, + "script is not p2pkh, p2sh, or witness program", + ), + ( + AddressError::NetworkValidation { + required: Network::Bitcoin, + found: Network::Testnet, + address: "1BitcoinEaterAddressDontSendf59kuE".to_string(), + }, + "address 1BitcoinEaterAddressDontSendf59kuE is not valid on bitcoin", + ), + (AddressError::OtherAddressErr, "other address error"), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_bip32() { + let cases = vec![ + ( + Bip32Error::CannotDeriveFromHardenedKey, + "Cannot derive from a hardened key", + ), + ( + Bip32Error::Secp256k1 { + error_message: "failure".to_string(), + }, + "Secp256k1 error: failure", + ), + ( + Bip32Error::InvalidChildNumber { child_number: 123 }, + "Invalid child number: 123", + ), + ( + Bip32Error::InvalidChildNumberFormat, + "Invalid format for child number", + ), + ( + Bip32Error::InvalidDerivationPathFormat, + "Invalid derivation path format", + ), + ( + Bip32Error::UnknownVersion { + version: "0x123".to_string(), + }, + "Unknown version: 0x123", + ), + ( + Bip32Error::WrongExtendedKeyLength { length: 512 }, + "Wrong extended key length: 512", + ), + ( + Bip32Error::Base58 { + error_message: "error".to_string(), + }, + "Base58 error: error", + ), + ( + Bip32Error::Hex { + error_message: "error".to_string(), + }, + "Hexadecimal conversion error: error", + ), + ( + Bip32Error::InvalidPublicKeyHexLength { length: 66 }, + "Invalid public key hex length: 66", + ), + ( + Bip32Error::UnknownError { + error_message: "mystery".to_string(), + }, + "Unknown error: mystery", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_bip39() { + let cases = vec![ + ( + Bip39Error::BadWordCount { word_count: 15 }, + "the word count 15 is not supported", + ), + ( + Bip39Error::UnknownWord { index: 102 }, + "unknown word at index 102", + ), + ( + Bip39Error::BadEntropyBitCount { bit_count: 128 }, + "entropy bit count 128 is invalid", + ), + (Bip39Error::InvalidChecksum, "checksum is invalid"), + ( + Bip39Error::AmbiguousLanguages { + languages: "English, Spanish".to_string(), + }, + "ambiguous languages detected: English, Spanish", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_calculate_fee() { let out_points: Vec = vec![ OutPoint { txid: "0000000000000000000000000000000000000000000000000000000000000001" @@ -851,34 +1031,250 @@ mod test { }, ]; - let error = CalculateFeeError::MissingTxOut { out_points }; + let cases = vec![ + ( + CalculateFeeError::MissingTxOut { + out_points: out_points.clone(), + }, + format!( + "missing transaction output: [{:?}, {:?}]", + out_points[0], out_points[1] + ), + ), + ( + CalculateFeeError::NegativeFee { fee: -100 }, + "negative fee value: -100".to_string(), + ), + ]; - let expected_message: String = format!( - "missing transaction output: [{:?}, {:?}]", - OutPoint { - txid: "0000000000000000000000000000000000000000000000000000000000000001" - .to_string(), - vout: 0 - }, - OutPoint { - txid: "0000000000000000000000000000000000000000000000000000000000000002" - .to_string(), - vout: 1 - } - ); - - assert_eq!(error.to_string(), expected_message); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } } #[test] - fn test_error_negative_fee() { - let error = CalculateFeeError::NegativeFee { fee: -100 }; + fn test_error_cannot_connect() { + let error = CannotConnectError::Include { height: 42 }; - assert_eq!(error.to_string(), "negative fee value: -100"); + assert_eq!(format!("{}", error), "cannot include height: 42"); } #[test] - fn test_esplora_errors() { + fn test_error_create_tx() { + let cases = vec![ + ( + CreateTxError::Descriptor { + error_message: "Descriptor failure".to_string(), + }, + "Descriptor error: Descriptor failure", + ), + ( + CreateTxError::Persist { + error_message: "Persistence error".to_string(), + }, + "Persistence failure: Persistence error", + ), + ( + CreateTxError::Policy { + error_message: "Policy violation".to_string(), + }, + "Policy error: Policy violation", + ), + ( + CreateTxError::SpendingPolicyRequired { + kind: "multisig".to_string(), + }, + "Spending policy required for multisig", + ), + (CreateTxError::Version0, "Unsupported version 0"), + (CreateTxError::Version1Csv, "Unsupported version 1 with CSV"), + ( + CreateTxError::LockTime { + requested: "today".to_string(), + required: "tomorrow".to_string(), + }, + "Lock time conflict: requested today, but required tomorrow", + ), + ( + CreateTxError::RbfSequence, + "Transaction requires RBF sequence number", + ), + ( + CreateTxError::RbfSequenceCsv { + rbf: "123".to_string(), + csv: "456".to_string(), + }, + "RBF sequence: 123, CSV sequence: 456", + ), + ( + CreateTxError::FeeTooLow { required: 1000 }, + "Fee too low: 1000 sat required", + ), + ( + CreateTxError::FeeRateTooLow { + required: "5 sat/vB".to_string(), + }, + "Fee rate too low: 5 sat/vB", + ), + ( + CreateTxError::NoUtxosSelected, + "No UTXOs selected for the transaction", + ), + ( + CreateTxError::OutputBelowDustLimit { index: 2 }, + "Output value below dust limit at index 2", + ), + ( + CreateTxError::ChangePolicyDescriptor, + "Change policy descriptor error", + ), + ( + CreateTxError::CoinSelection { + error_message: "No suitable outputs".to_string(), + }, + "Coin selection failed: No suitable outputs", + ), + ( + CreateTxError::InsufficientFunds { + needed: 5000, + available: 3000, + }, + "Insufficient funds: needed 5000 sat, available 3000 sat", + ), + (CreateTxError::NoRecipients, "Transaction has no recipients"), + ( + CreateTxError::Psbt { + error_message: "PSBT creation failed".to_string(), + }, + "PSBT creation error: PSBT creation failed", + ), + ( + CreateTxError::MissingKeyOrigin { + key: "xpub...".to_string(), + }, + "Missing key origin for: xpub...", + ), + ( + CreateTxError::UnknownUtxo { + outpoint: "outpoint123".to_string(), + }, + "Reference to an unknown UTXO: outpoint123", + ), + ( + CreateTxError::MissingNonWitnessUtxo { + outpoint: "outpoint456".to_string(), + }, + "Missing non-witness UTXO for outpoint: outpoint456", + ), + ( + CreateTxError::MiniscriptPsbt { + error_message: "Miniscript error".to_string(), + }, + "Miniscript PSBT error: Miniscript error", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_descriptor() { + let cases = vec![ + (DescriptorError::InvalidHdKeyPath, "invalid hd key path"), + ( + DescriptorError::InvalidDescriptorChecksum, + "the provided descriptor doesn't match its checksum", + ), + ( + DescriptorError::HardenedDerivationXpub, + "the descriptor contains hardened derivation steps on public extended keys", + ), + ( + DescriptorError::MultiPath, + "the descriptor contains multipath keys, which are not supported yet", + ), + ( + DescriptorError::Key { + error_message: "Invalid key format".to_string(), + }, + "key error: Invalid key format", + ), + ( + DescriptorError::Policy { + error_message: "Policy rule failed".to_string(), + }, + "policy error: Policy rule failed", + ), + ( + DescriptorError::InvalidDescriptorCharacter { + char: "}".to_string(), + }, + "invalid descriptor character: }", + ), + ( + DescriptorError::Bip32 { + error_message: "Bip32 error".to_string(), + }, + "BIP32 error: Bip32 error", + ), + ( + DescriptorError::Base58 { + error_message: "Base58 decode error".to_string(), + }, + "Base58 error: Base58 decode error", + ), + ( + DescriptorError::Pk { + error_message: "Public key error".to_string(), + }, + "Key-related error: Public key error", + ), + ( + DescriptorError::Miniscript { + error_message: "Miniscript evaluation error".to_string(), + }, + "Miniscript error: Miniscript evaluation error", + ), + ( + DescriptorError::Hex { + error_message: "Hexadecimal decoding error".to_string(), + }, + "Hex decoding error: Hexadecimal decoding error", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_descriptor_key() { + let cases = vec![ + ( + DescriptorKeyError::Parse { + error_message: "Failed to parse descriptor key".to_string(), + }, + "error parsing descriptor key: Failed to parse descriptor key", + ), + (DescriptorKeyError::InvalidKeyType, "error invalid key type"), + ( + DescriptorKeyError::Bip32 { + error_message: "BIP32 derivation error".to_string(), + }, + "error bip 32 related: BIP32 derivation error", + ), + ]; + + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } + + #[test] + fn test_error_esplora() { let cases = vec![ ( EsploraError::Minreq { @@ -937,65 +1333,87 @@ mod test { } #[test] - fn test_persistence_error() { - let io_err = std::io::Error::new( - std::io::ErrorKind::Other, - "unable to persist the new address", - ); - let op_err: PersistenceError = io_err.into(); + fn test_error_extract_tx() { + let cases = vec![ + ( + ExtractTxError::AbsurdFeeRate { fee_rate: 10000 }, + "an absurdly high fee rate of 10000 sat/vbyte", + ), + ( + ExtractTxError::MissingInputValue, + "one of the inputs lacked value information (witness_utxo or non_witness_utxo)", + ), + ( + ExtractTxError::SendingTooMuch, + "transaction would be invalid due to output value being greater than input value", + ), + ( + ExtractTxError::OtherExtractTxErr, + "this error is required because the bdk::bitcoin::psbt::ExtractTxError is non-exhaustive" + ), + ]; - let PersistenceError::Write { e } = op_err; - assert_eq!(e, "unable to persist the new address"); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } } #[test] - fn test_error_wallet_creation() { - let io_error = WalletCreationError::Io { - e: "io error".to_string(), - }; - assert_eq!( - io_error.to_string(), - "io error trying to read file: io error" - ); + fn test_error_fee_rate() { + let cases = vec![( + FeeRateError::ArithmeticOverflow, + "arithmetic overflow on feerate", + )]; - let invalid_magic_bytes_error = WalletCreationError::InvalidMagicBytes { - got: vec![1, 2, 3, 4], - expected: vec![4, 3, 2, 1], - }; - assert_eq!( - invalid_magic_bytes_error.to_string(), - "file has invalid magic bytes: expected=[4, 3, 2, 1] got=[1, 2, 3, 4]" - ); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } - let descriptor_error = WalletCreationError::Descriptor; - assert_eq!(descriptor_error.to_string(), "error with descriptor"); + #[test] + fn test_persistence_error() { + let cases = vec![ + ( + std::io::Error::new( + std::io::ErrorKind::Other, + "unable to persist the new address", + ) + .into(), + "writing to persistence error: unable to persist the new address", + ), + ( + PersistenceError::Write { + error_message: "failed to write to storage".to_string(), + }, + "writing to persistence error: failed to write to storage", + ), + ]; - let write_error = WalletCreationError::Write; - assert_eq!(write_error.to_string(), "failed to write to persistence"); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } + } - let load_error = WalletCreationError::Load; - assert_eq!(load_error.to_string(), "failed to load from persistence"); + #[test] + fn test_error_psbt_parse() { + let cases = vec![ + ( + PsbtParseError::PsbtEncoding { + error_message: "invalid PSBT structure".to_string(), + }, + "error in internal PSBT data structure: invalid PSBT structure", + ), + ( + PsbtParseError::Base64Encoding { + error_message: "base64 decode error".to_string(), + }, + "error in PSBT base64 encoding: base64 decode error", + ), + ]; - let not_initialized_error = WalletCreationError::NotInitialized; - assert_eq!( - not_initialized_error.to_string(), - "wallet is not initialized, persistence backend is empty" - ); - - let loaded_genesis_does_not_match_error = WalletCreationError::LoadedGenesisDoesNotMatch; - assert_eq!( - loaded_genesis_does_not_match_error.to_string(), - "loaded genesis hash does not match the expected one" - ); - - let loaded_network_does_not_match_error = WalletCreationError::LoadedNetworkDoesNotMatch { - expected: Network::Bitcoin, - got: Some(Network::Testnet), - }; - assert_eq!( - loaded_network_does_not_match_error.to_string(), - "loaded network type is not bitcoin, got Some(Testnet)" - ); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } } #[test] @@ -1026,19 +1444,19 @@ mod test { (SignerError::InvalidSighash, "Invalid sighash type provided"), ( SignerError::SighashError { - e: "dummy error".into(), + error_message: "dummy error".into(), }, "Error with sighash computation: dummy error", ), ( SignerError::MiniscriptPsbt { - e: "psbt issue".into(), + error_message: "psbt issue".into(), }, "Miniscript Psbt error: psbt issue", ), ( SignerError::External { - e: "external error".into(), + error_message: "external error".into(), }, "External error: external error", ), @@ -1050,84 +1468,98 @@ mod test { } #[test] - fn test_cannot_connect_error_include() { - let error = CannotConnectError::Include { height: 42 }; + fn test_error_transaction() { + let cases = vec![ + (TransactionError::Io, "IO error"), + ( + TransactionError::OversizedVectorAllocation, + "allocation of oversized vector", + ), + ( + TransactionError::InvalidChecksum { + expected: "deadbeef".to_string(), + actual: "beadbeef".to_string(), + }, + "invalid checksum: expected=deadbeef actual=beadbeef", + ), + (TransactionError::NonMinimalVarInt, "non-minimal varint"), + (TransactionError::ParseFailed, "parse failed"), + ( + TransactionError::UnsupportedSegwitFlag { flag: 1 }, + "unsupported segwit version: 1", + ), + ( + TransactionError::OtherTransactionErr, + "other transaction error", + ), + ]; - assert_eq!(format!("{}", error), "cannot include height: 42"); + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } } #[test] - fn test_error_bip39() { - let error = Bip39Error::BadWordCount { word_count: 15 }; - assert_eq!(format!("{}", error), "the word count 15 is not supported"); + fn test_error_txid_parse() { + let cases = vec![( + TxidParseError::InvalidTxid { + txid: "123abc".to_string(), + }, + "invalid txid: 123abc", + )]; - let error = Bip39Error::UnknownWord { index: 102 }; - assert_eq!(format!("{}", error), "unknown word at index 102"); - - let error = Bip39Error::BadEntropyBitCount { bit_count: 128 }; - assert_eq!(format!("{}", error), "entropy bit count 128 is invalid"); - - let error = Bip39Error::InvalidChecksum; - assert_eq!(format!("{}", error), "checksum is invalid"); - - let error = Bip39Error::AmbiguousLanguages { - languages: "English, Spanish".to_string(), - }; - assert_eq!( - format!("{}", error), - "ambiguous languages detected: English, Spanish" - ) + for (error, expected_message) in cases { + assert_eq!(error.to_string(), expected_message); + } } #[test] - fn test_error_bip32() { - let error = Bip32Error::CannotDeriveFromHardenedKey; - assert_eq!(format!("{}", error), "Cannot derive from a hardened key"); + fn test_error_wallet_creation() { + let errors = vec![ + ( + WalletCreationError::Io { + error_message: "io error".to_string(), + }, + "io error trying to read file: io error".to_string(), + ), + ( + WalletCreationError::InvalidMagicBytes { + got: vec![1, 2, 3, 4], + expected: vec![4, 3, 2, 1], + }, + "file has invalid magic bytes: expected=[4, 3, 2, 1] got=[1, 2, 3, 4]".to_string(), + ), + ( + WalletCreationError::Descriptor, + "error with descriptor".to_string(), + ), + ( + WalletCreationError::Write, + "failed to write to persistence".to_string(), + ), + ( + WalletCreationError::Load, + "failed to load from persistence".to_string(), + ), + ( + WalletCreationError::NotInitialized, + "wallet is not initialized, persistence backend is empty".to_string(), + ), + ( + WalletCreationError::LoadedGenesisDoesNotMatch, + "loaded genesis hash does not match the expected one".to_string(), + ), + ( + WalletCreationError::LoadedNetworkDoesNotMatch { + expected: Network::Bitcoin, + got: Some(Network::Testnet), + }, + "loaded network type is not bitcoin, got Some(Testnet)".to_string(), + ), + ]; - let error = Bip32Error::Secp256k1 { - e: "Secp256k1 failure".to_string(), - }; - assert_eq!(format!("{}", error), "Secp256k1 error: Secp256k1 failure"); - - let error = Bip32Error::InvalidChildNumber { child_number: 42 }; - assert_eq!(format!("{}", error), "Invalid child number: 42"); - - let error = Bip32Error::InvalidChildNumberFormat; - assert_eq!(format!("{}", error), "Invalid format for child number"); - - let error = Bip32Error::InvalidDerivationPathFormat; - assert_eq!(format!("{}", error), "Invalid derivation path format"); - - let error = Bip32Error::UnknownVersion { - version: "deadbeef".to_string(), - }; - assert_eq!(format!("{}", error), "Unknown version: deadbeef"); - - let error = Bip32Error::WrongExtendedKeyLength { length: 128 }; - assert_eq!(format!("{}", error), "Wrong extended key length: 128"); - - let error = Bip32Error::Base58 { - e: "Base58 error".to_string(), - }; - assert_eq!(format!("{}", error), "Base58 error: Base58 error"); - - let error = Bip32Error::Hex { - e: "Hex error".to_string(), - }; - assert_eq!( - format!("{}", error), - "Hexadecimal conversion error: Hex error" - ); - - let error = Bip32Error::InvalidPublicKeyHexLength { length: 65 }; - assert_eq!(format!("{}", error), "Invalid public key hex length: 65"); - - let error = Bip32Error::UnknownError { - e: "An unknown error occurred".to_string(), - }; - assert_eq!( - format!("{}", error), - "Unknown error: An unknown error occurred" - ); + for (error, expected) in errors { + assert_eq!(error.to_string(), expected); + } } }