Skip to content

Commit f19ce86

Browse files
committed
Add CreateTxError as error type for TxBuilder::finish()
1 parent 81c7613 commit f19ce86

File tree

4 files changed

+114
-35
lines changed

4 files changed

+114
-35
lines changed

crates/bdk/src/wallet/coin_selection.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
//! ```
2727
//! # use std::str::FromStr;
2828
//! # use bitcoin::*;
29-
//! # use bdk::wallet::{self, coin_selection::*};
29+
//! # use bdk::wallet::{self, ChangeSet, coin_selection::*, CreateTxError};
30+
//! # use bdk_chain::PersistBackend;
3031
//! # use bdk::*;
3132
//! # use bdk::wallet::coin_selection::decide_change;
3233
//! # const TXIN_BASE_WEIGHT: usize = (32 + 4 + 4) * 4;
@@ -89,7 +90,7 @@
8990
//!
9091
//! // inspect, sign, broadcast, ...
9192
//!
92-
//! # Ok::<(), bdk::Error>(())
93+
//! # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
9394
//! ```
9495
9596
use crate::types::FeeRate;

crates/bdk/src/wallet/mod.rs

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,17 @@ pub mod hardwaresigner;
5454

5555
pub use utils::IsDust;
5656

57+
use crate::descriptor;
5758
#[allow(deprecated)]
5859
use coin_selection::DefaultCoinSelectionAlgorithm;
5960
use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
6061
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
6162
use utils::{check_nsequence_rbf, After, Older, SecpCtx};
6263

63-
use crate::descriptor::policy::BuildSatisfaction;
64+
use crate::descriptor::policy::{BuildSatisfaction, PolicyError};
6465
use crate::descriptor::{
65-
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
66-
ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
66+
calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorError,
67+
DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
6768
};
6869
use crate::error::{Error, MiniscriptPsbtError};
6970
use crate::psbt::PsbtUtils;
@@ -166,7 +167,7 @@ impl Wallet {
166167
pub enum NewError<P> {
167168
/// There was problem with the descriptors passed in
168169
Descriptor(crate::descriptor::DescriptorError),
169-
/// We were unable to load the wallet's data from the persistance backend
170+
/// We were unable to load the wallet's data from the persistence backend
170171
Persist(P),
171172
}
172173

@@ -178,7 +179,7 @@ where
178179
match self {
179180
NewError::Descriptor(e) => e.fmt(f),
180181
NewError::Persist(e) => {
181-
write!(f, "failed to load wallet from persistance backend: {}", e)
182+
write!(f, "failed to load wallet from persistence backend: {}", e)
182183
}
183184
}
184185
}
@@ -200,6 +201,60 @@ pub enum InsertTxError {
200201
#[cfg(feature = "std")]
201202
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for NewError<P> {}
202203

204+
#[derive(Debug)]
205+
/// Error returned from [`Wallet::create_tx`]
206+
pub enum CreateTxError<P> {
207+
/// There was problem with the descriptors passed in
208+
Descriptor(DescriptorError),
209+
/// We were unable to write wallet data to the persistence backend
210+
Persist(P),
211+
/// There was a problem while extracting and manipulating policies
212+
Policy(PolicyError),
213+
/// TODO: replace this with specific error types
214+
Bdk(Error),
215+
}
216+
217+
impl<P> fmt::Display for CreateTxError<P>
218+
where
219+
P: fmt::Display,
220+
{
221+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222+
match self {
223+
Self::Descriptor(e) => e.fmt(f),
224+
Self::Persist(e) => {
225+
write!(
226+
f,
227+
"failed to write wallet data to persistence backend: {}",
228+
e
229+
)
230+
}
231+
Self::Bdk(e) => e.fmt(f),
232+
Self::Policy(e) => e.fmt(f),
233+
}
234+
}
235+
}
236+
237+
impl<P> From<descriptor::error::Error> for CreateTxError<P> {
238+
fn from(err: descriptor::error::Error) -> Self {
239+
CreateTxError::Descriptor(err)
240+
}
241+
}
242+
243+
impl<P> From<PolicyError> for CreateTxError<P> {
244+
fn from(err: PolicyError) -> Self {
245+
CreateTxError::Policy(err)
246+
}
247+
}
248+
249+
impl<P> From<Error> for CreateTxError<P> {
250+
fn from(err: Error) -> Self {
251+
CreateTxError::Bdk(err)
252+
}
253+
}
254+
255+
#[cfg(feature = "std")]
256+
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for CreateTxError<P> {}
257+
203258
impl<D> Wallet<D> {
204259
/// Create a wallet from a `descriptor` (and an optional `change_descriptor`) and load related
205260
/// transaction data from `db`.
@@ -596,6 +651,8 @@ impl<D> Wallet<D> {
596651
/// # use std::str::FromStr;
597652
/// # use bitcoin::*;
598653
/// # use bdk::*;
654+
/// # use bdk::wallet::{ChangeSet,CreateTxError};
655+
/// # use bdk_chain::PersistBackend;
599656
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
600657
/// # let mut wallet = doctest_wallet!();
601658
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -607,7 +664,7 @@ impl<D> Wallet<D> {
607664
/// };
608665
///
609666
/// // sign and broadcast ...
610-
/// # Ok::<(), bdk::Error>(())
667+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
611668
/// ```
612669
///
613670
/// [`TxBuilder`]: crate::TxBuilder
@@ -624,7 +681,7 @@ impl<D> Wallet<D> {
624681
&mut self,
625682
coin_selection: Cs,
626683
params: TxParams,
627-
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
684+
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), CreateTxError<D::WriteError>>
628685
where
629686
D: PersistBackend<ChangeSet>,
630687
{
@@ -659,15 +716,15 @@ impl<D> Wallet<D> {
659716
&& external_policy.requires_path()
660717
&& params.external_policy_path.is_none()
661718
{
662-
return Err(Error::SpendingPolicyRequired(KeychainKind::External));
719+
return Err(Error::SpendingPolicyRequired(KeychainKind::External).into());
663720
};
664721
// Same for the internal_policy path, if present
665722
if let Some(internal_policy) = &internal_policy {
666723
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
667724
&& internal_policy.requires_path()
668725
&& params.internal_policy_path.is_none()
669726
{
670-
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal));
727+
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal).into());
671728
};
672729
}
673730

@@ -696,13 +753,14 @@ impl<D> Wallet<D> {
696753

697754
let version = match params.version {
698755
Some(tx_builder::Version(0)) => {
699-
return Err(Error::Generic("Invalid version `0`".into()))
756+
return Err(Error::Generic("Invalid version `0`".into()).into())
700757
}
701758
Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
702759
return Err(Error::Generic(
703760
"TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
704761
.into(),
705-
))
762+
)
763+
.into())
706764
}
707765
Some(tx_builder::Version(x)) => x,
708766
None if requirements.csv.is_some() => 2,
@@ -745,7 +803,7 @@ impl<D> Wallet<D> {
745803
// Specific nLockTime required and it's compatible with the constraints
746804
Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => x,
747805
// Invalid nLockTime required
748-
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", x, requirements.timelock.unwrap())))
806+
Some(x) => return Err(Error::Generic(format!("TxBuilder requested timelock of `{:?}`, but at least `{:?}` is required to spend from this script", x, requirements.timelock.unwrap())).into())
749807
};
750808

751809
let n_sequence = match (params.rbf, requirements.csv) {
@@ -763,7 +821,8 @@ impl<D> Wallet<D> {
763821
(Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
764822
return Err(Error::Generic(
765823
"Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(),
766-
))
824+
)
825+
.into())
767826
}
768827
// RBF with a specific value requested, but the value is incompatible with CSV
769828
(Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
@@ -772,7 +831,8 @@ impl<D> Wallet<D> {
772831
return Err(Error::Generic(format!(
773832
"Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
774833
rbf, csv
775-
)))
834+
))
835+
.into())
776836
}
777837

778838
// RBF enabled with the default value with CSV also enabled. CSV takes precedence
@@ -793,7 +853,8 @@ impl<D> Wallet<D> {
793853
if *fee < previous_fee.absolute {
794854
return Err(Error::FeeTooLow {
795855
required: previous_fee.absolute,
796-
});
856+
}
857+
.into());
797858
}
798859
}
799860
(FeeRate::from_sat_per_vb(0.0), *fee)
@@ -804,7 +865,8 @@ impl<D> Wallet<D> {
804865
if *rate < required_feerate {
805866
return Err(Error::FeeRateTooLow {
806867
required: required_feerate,
807-
});
868+
}
869+
.into());
808870
}
809871
}
810872
(*rate, 0)
@@ -819,7 +881,7 @@ impl<D> Wallet<D> {
819881
};
820882

821883
if params.manually_selected_only && params.utxos.is_empty() {
822-
return Err(Error::NoUtxosSelected);
884+
return Err(Error::NoUtxosSelected.into());
823885
}
824886

825887
// we keep it as a float while we accumulate it, and only round it at the end
@@ -833,7 +895,7 @@ impl<D> Wallet<D> {
833895
&& value.is_dust(script_pubkey)
834896
&& !script_pubkey.is_provably_unspendable()
835897
{
836-
return Err(Error::OutputBelowDustLimit(index));
898+
return Err(Error::OutputBelowDustLimit(index).into());
837899
}
838900

839901
if self.is_mine(script_pubkey) {
@@ -868,7 +930,8 @@ impl<D> Wallet<D> {
868930
{
869931
return Err(Error::Generic(
870932
"The `change_policy` can be set only if the wallet has a change_descriptor".into(),
871-
));
933+
)
934+
.into());
872935
}
873936

874937
let (required_utxos, optional_utxos) = self.preselect_utxos(
@@ -936,10 +999,11 @@ impl<D> Wallet<D> {
936999
return Err(Error::InsufficientFunds {
9371000
needed: *dust_threshold,
9381001
available: remaining_amount.saturating_sub(*change_fee),
939-
});
1002+
}
1003+
.into());
9401004
}
9411005
} else {
942-
return Err(Error::NoRecipients);
1006+
return Err(Error::NoRecipients.into());
9431007
}
9441008
}
9451009

@@ -998,6 +1062,8 @@ impl<D> Wallet<D> {
9981062
/// # use std::str::FromStr;
9991063
/// # use bitcoin::*;
10001064
/// # use bdk::*;
1065+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
1066+
/// # use bdk_chain::PersistBackend;
10011067
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
10021068
/// # let mut wallet = doctest_wallet!();
10031069
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -1021,7 +1087,7 @@ impl<D> Wallet<D> {
10211087
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
10221088
/// let fee_bumped_tx = psbt.extract_tx();
10231089
/// // broadcast fee_bumped_tx to replace original
1024-
/// # Ok::<(), bdk::Error>(())
1090+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
10251091
/// ```
10261092
// TODO: support for merging multiple transactions while bumping the fees
10271093
pub fn build_fee_bump(
@@ -1168,6 +1234,8 @@ impl<D> Wallet<D> {
11681234
/// # use std::str::FromStr;
11691235
/// # use bitcoin::*;
11701236
/// # use bdk::*;
1237+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
1238+
/// # use bdk_chain::PersistBackend;
11711239
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
11721240
/// # let mut wallet = doctest_wallet!();
11731241
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -1178,7 +1246,7 @@ impl<D> Wallet<D> {
11781246
/// };
11791247
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
11801248
/// assert!(finalized, "we should have signed all the inputs");
1181-
/// # Ok::<(), bdk::Error>(())
1249+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
11821250
pub fn sign(
11831251
&self,
11841252
psbt: &mut psbt::PartiallySignedTransaction,

crates/bdk/src/wallet/tx_builder.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
//! # use std::str::FromStr;
1818
//! # use bitcoin::*;
1919
//! # use bdk::*;
20+
//! # use bdk::wallet::{ChangeSet, CreateTxError};
2021
//! # use bdk::wallet::tx_builder::CreateTx;
22+
//! # use bdk_chain::PersistBackend;
2123
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
2224
//! # let mut wallet = doctest_wallet!();
2325
//! // create a TxBuilder from a wallet
@@ -33,7 +35,7 @@
3335
//! // Turn on RBF signaling
3436
//! .enable_rbf();
3537
//! let (psbt, tx_details) = tx_builder.finish()?;
36-
//! # Ok::<(), bdk::Error>(())
38+
//! # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
3739
//! ```
3840
3941
use crate::collections::BTreeMap;
@@ -48,6 +50,7 @@ use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
4850

4951
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
5052
use super::ChangeSet;
53+
use crate::wallet::CreateTxError;
5154
use crate::{
5255
types::{FeeRate, KeychainKind, LocalUtxo, WeightedUtxo},
5356
TransactionDetails,
@@ -81,6 +84,8 @@ impl TxBuilderContext for BumpFee {}
8184
/// # use bdk::wallet::tx_builder::*;
8285
/// # use bitcoin::*;
8386
/// # use core::str::FromStr;
87+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
88+
/// # use bdk_chain::PersistBackend;
8489
/// # let mut wallet = doctest_wallet!();
8590
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
8691
/// # let addr2 = addr1.clone();
@@ -105,7 +110,7 @@ impl TxBuilderContext for BumpFee {}
105110
/// };
106111
///
107112
/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]);
108-
/// # Ok::<(), bdk::Error>(())
113+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
109114
/// ```
110115
///
111116
/// At the moment [`coin_selection`] is an exception to the rule as it consumes `self`.
@@ -527,7 +532,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm, Ctx: TxBuilderContext> TxBuilder<'a, D,
527532
/// Returns the [`BIP174`] "PSBT" and summary details about the transaction.
528533
///
529534
/// [`BIP174`]: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
530-
pub fn finish(self) -> Result<(Psbt, TransactionDetails), Error>
535+
pub fn finish(self) -> Result<(Psbt, TransactionDetails), CreateTxError<D::WriteError>>
531536
where
532537
D: PersistBackend<ChangeSet>,
533538
{
@@ -625,7 +630,9 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> {
625630
/// # use std::str::FromStr;
626631
/// # use bitcoin::*;
627632
/// # use bdk::*;
633+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
628634
/// # use bdk::wallet::tx_builder::CreateTx;
635+
/// # use bdk_chain::PersistBackend;
629636
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
630637
/// # let mut wallet = doctest_wallet!();
631638
/// let mut tx_builder = wallet.build_tx();
@@ -638,7 +645,7 @@ impl<'a, D, Cs: CoinSelectionAlgorithm> TxBuilder<'a, D, Cs, CreateTx> {
638645
/// .fee_rate(FeeRate::from_sat_per_vb(5.0))
639646
/// .enable_rbf();
640647
/// let (psbt, tx_details) = tx_builder.finish()?;
641-
/// # Ok::<(), bdk::Error>(())
648+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
642649
/// ```
643650
///
644651
/// [`allow_shrinking`]: Self::allow_shrinking

0 commit comments

Comments
 (0)