Skip to content

Commit d99e4a5

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

File tree

4 files changed

+116
-36
lines changed

4 files changed

+116
-36
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: 93 additions & 24 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,61 @@ 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 [`TxBuilder::finish`]
206+
pub enum CreateTxError<P> {
207+
/// There was a 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+
#[cfg(feature = "std")]
218+
impl<P> fmt::Display for CreateTxError<P>
219+
where
220+
P: fmt::Display,
221+
{
222+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223+
match self {
224+
Self::Descriptor(e) => e.fmt(f),
225+
Self::Persist(e) => {
226+
write!(
227+
f,
228+
"failed to write wallet data to persistence backend: {}",
229+
e
230+
)
231+
}
232+
Self::Bdk(e) => e.fmt(f),
233+
Self::Policy(e) => e.fmt(f),
234+
}
235+
}
236+
}
237+
238+
impl<P> From<descriptor::error::Error> for CreateTxError<P> {
239+
fn from(err: descriptor::error::Error) -> Self {
240+
CreateTxError::Descriptor(err)
241+
}
242+
}
243+
244+
impl<P> From<PolicyError> for CreateTxError<P> {
245+
fn from(err: PolicyError) -> Self {
246+
CreateTxError::Policy(err)
247+
}
248+
}
249+
250+
impl<P> From<Error> for CreateTxError<P> {
251+
fn from(err: Error) -> Self {
252+
CreateTxError::Bdk(err)
253+
}
254+
}
255+
256+
#[cfg(feature = "std")]
257+
impl<P: core::fmt::Display + core::fmt::Debug> std::error::Error for CreateTxError<P> {}
258+
203259
impl<D> Wallet<D> {
204260
/// Create a wallet from a `descriptor` (and an optional `change_descriptor`) and load related
205261
/// transaction data from `db`.
@@ -596,6 +652,8 @@ impl<D> Wallet<D> {
596652
/// # use std::str::FromStr;
597653
/// # use bitcoin::*;
598654
/// # use bdk::*;
655+
/// # use bdk::wallet::{ChangeSet,CreateTxError};
656+
/// # use bdk_chain::PersistBackend;
599657
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
600658
/// # let mut wallet = doctest_wallet!();
601659
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -607,7 +665,7 @@ impl<D> Wallet<D> {
607665
/// };
608666
///
609667
/// // sign and broadcast ...
610-
/// # Ok::<(), bdk::Error>(())
668+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
611669
/// ```
612670
///
613671
/// [`TxBuilder`]: crate::TxBuilder
@@ -624,7 +682,7 @@ impl<D> Wallet<D> {
624682
&mut self,
625683
coin_selection: Cs,
626684
params: TxParams,
627-
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error>
685+
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), CreateTxError<D::WriteError>>
628686
where
629687
D: PersistBackend<ChangeSet>,
630688
{
@@ -659,15 +717,15 @@ impl<D> Wallet<D> {
659717
&& external_policy.requires_path()
660718
&& params.external_policy_path.is_none()
661719
{
662-
return Err(Error::SpendingPolicyRequired(KeychainKind::External));
720+
return Err(Error::SpendingPolicyRequired(KeychainKind::External).into());
663721
};
664722
// Same for the internal_policy path, if present
665723
if let Some(internal_policy) = &internal_policy {
666724
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
667725
&& internal_policy.requires_path()
668726
&& params.internal_policy_path.is_none()
669727
{
670-
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal));
728+
return Err(Error::SpendingPolicyRequired(KeychainKind::Internal).into());
671729
};
672730
}
673731

@@ -696,13 +754,14 @@ impl<D> Wallet<D> {
696754

697755
let version = match params.version {
698756
Some(tx_builder::Version(0)) => {
699-
return Err(Error::Generic("Invalid version `0`".into()))
757+
return Err(Error::Generic("Invalid version `0`".into()).into())
700758
}
701759
Some(tx_builder::Version(1)) if requirements.csv.is_some() => {
702760
return Err(Error::Generic(
703761
"TxBuilder requested version `1`, but at least `2` is needed to use OP_CSV"
704762
.into(),
705-
))
763+
)
764+
.into())
706765
}
707766
Some(tx_builder::Version(x)) => x,
708767
None if requirements.csv.is_some() => 2,
@@ -745,7 +804,7 @@ impl<D> Wallet<D> {
745804
// Specific nLockTime required and it's compatible with the constraints
746805
Some(x) if requirements.timelock.unwrap().is_same_unit(x) && x >= requirements.timelock.unwrap() => x,
747806
// 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())))
807+
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())
749808
};
750809

751810
let n_sequence = match (params.rbf, requirements.csv) {
@@ -763,7 +822,8 @@ impl<D> Wallet<D> {
763822
(Some(tx_builder::RbfValue::Value(rbf)), _) if !rbf.is_rbf() => {
764823
return Err(Error::Generic(
765824
"Cannot enable RBF with a nSequence >= 0xFFFFFFFE".into(),
766-
))
825+
)
826+
.into())
767827
}
768828
// RBF with a specific value requested, but the value is incompatible with CSV
769829
(Some(tx_builder::RbfValue::Value(rbf)), Some(csv))
@@ -772,7 +832,8 @@ impl<D> Wallet<D> {
772832
return Err(Error::Generic(format!(
773833
"Cannot enable RBF with nSequence `{:?}` given a required OP_CSV of `{:?}`",
774834
rbf, csv
775-
)))
835+
))
836+
.into())
776837
}
777838

778839
// RBF enabled with the default value with CSV also enabled. CSV takes precedence
@@ -793,7 +854,8 @@ impl<D> Wallet<D> {
793854
if *fee < previous_fee.absolute {
794855
return Err(Error::FeeTooLow {
795856
required: previous_fee.absolute,
796-
});
857+
}
858+
.into());
797859
}
798860
}
799861
(FeeRate::from_sat_per_vb(0.0), *fee)
@@ -804,7 +866,8 @@ impl<D> Wallet<D> {
804866
if *rate < required_feerate {
805867
return Err(Error::FeeRateTooLow {
806868
required: required_feerate,
807-
});
869+
}
870+
.into());
808871
}
809872
}
810873
(*rate, 0)
@@ -819,7 +882,7 @@ impl<D> Wallet<D> {
819882
};
820883

821884
if params.manually_selected_only && params.utxos.is_empty() {
822-
return Err(Error::NoUtxosSelected);
885+
return Err(Error::NoUtxosSelected.into());
823886
}
824887

825888
// we keep it as a float while we accumulate it, and only round it at the end
@@ -833,7 +896,7 @@ impl<D> Wallet<D> {
833896
&& value.is_dust(script_pubkey)
834897
&& !script_pubkey.is_provably_unspendable()
835898
{
836-
return Err(Error::OutputBelowDustLimit(index));
899+
return Err(Error::OutputBelowDustLimit(index).into());
837900
}
838901

839902
if self.is_mine(script_pubkey) {
@@ -868,7 +931,8 @@ impl<D> Wallet<D> {
868931
{
869932
return Err(Error::Generic(
870933
"The `change_policy` can be set only if the wallet has a change_descriptor".into(),
871-
));
934+
)
935+
.into());
872936
}
873937

874938
let (required_utxos, optional_utxos) = self.preselect_utxos(
@@ -892,7 +956,7 @@ impl<D> Wallet<D> {
892956
self.indexed_graph.index.mark_used(&change_keychain, index);
893957
self.persist
894958
.stage(ChangeSet::from(IndexedAdditions::from(index_additions)));
895-
self.persist.commit().expect("TODO");
959+
self.persist.commit().map_err(CreateTxError::Persist)?;
896960
spk
897961
}
898962
};
@@ -936,10 +1000,11 @@ impl<D> Wallet<D> {
9361000
return Err(Error::InsufficientFunds {
9371001
needed: *dust_threshold,
9381002
available: remaining_amount.saturating_sub(*change_fee),
939-
});
1003+
}
1004+
.into());
9401005
}
9411006
} else {
942-
return Err(Error::NoRecipients);
1007+
return Err(Error::NoRecipients.into());
9431008
}
9441009
}
9451010

@@ -998,6 +1063,8 @@ impl<D> Wallet<D> {
9981063
/// # use std::str::FromStr;
9991064
/// # use bitcoin::*;
10001065
/// # use bdk::*;
1066+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
1067+
/// # use bdk_chain::PersistBackend;
10011068
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
10021069
/// # let mut wallet = doctest_wallet!();
10031070
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -1021,7 +1088,7 @@ impl<D> Wallet<D> {
10211088
/// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
10221089
/// let fee_bumped_tx = psbt.extract_tx();
10231090
/// // broadcast fee_bumped_tx to replace original
1024-
/// # Ok::<(), bdk::Error>(())
1091+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
10251092
/// ```
10261093
// TODO: support for merging multiple transactions while bumping the fees
10271094
pub fn build_fee_bump(
@@ -1168,6 +1235,8 @@ impl<D> Wallet<D> {
11681235
/// # use std::str::FromStr;
11691236
/// # use bitcoin::*;
11701237
/// # use bdk::*;
1238+
/// # use bdk::wallet::{ChangeSet, CreateTxError};
1239+
/// # use bdk_chain::PersistBackend;
11711240
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
11721241
/// # let mut wallet = doctest_wallet!();
11731242
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
@@ -1178,7 +1247,7 @@ impl<D> Wallet<D> {
11781247
/// };
11791248
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
11801249
/// assert!(finalized, "we should have signed all the inputs");
1181-
/// # Ok::<(), bdk::Error>(())
1250+
/// # Ok::<(), CreateTxError<<() as PersistBackend<ChangeSet>>::WriteError>>(())
11821251
pub fn sign(
11831252
&self,
11841253
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)