Merge bitcoindevkit/bdk-ffi#208: Add FeeRate struct and PSBT fee_amount and fee_rate functions

ae1ea99ed3f26f33ead92d4a91588ae32d9e110b Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction (Steve Myers)
9a381f6d32321f2a726092bd2b54ab10d105e9c6 Rename PartiallySignedBitcoinTransaction to PartiallySignedTransaction (Steve Myers)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction.

  ### Notes to the reviewers

  This PR is dependent on https://github.com/bitcoindevkit/bdk/pull/782.

  ### Changelog notice

  - Breaking Changes
    - Renamed PartiallySignedBitcoinTransaction to PartiallySignedTransaction to be consistent with `rust-bitcoin`
  - APIs Added
    - Add FeeRate struct
    - Add fee_amount() and fee_rate() functions on PartiallySignedTransaction

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  thunderbiscuit:
    Re-ACK [ae1ea99](ae1ea99ed3).

Tree-SHA512: 2c3f792e9ef092cd3ba233601122f4960c496d132caad54ef2f7f41d7113dd16600a863bb8fd78d2e5b978adebdb7ddd9529c21b3d46cd0b16e0db4eb90de01d
This commit is contained in:
Steve Myers 2022-11-07 14:13:22 -06:00
commit a25fb1348d
No known key found for this signature in database
GPG Key ID: 8105A46B22C2D051
2 changed files with 76 additions and 21 deletions

View File

@ -137,7 +137,7 @@ interface Blockchain {
constructor(BlockchainConfig config); constructor(BlockchainConfig config);
[Throws=BdkError] [Throws=BdkError]
void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt); void broadcast([ByRef] PartiallySignedTransaction psbt);
[Throws=BdkError] [Throws=BdkError]
u32 get_height(); u32 get_height();
@ -188,7 +188,7 @@ interface Wallet {
Balance get_balance(); Balance get_balance();
[Throws=BdkError] [Throws=BdkError]
boolean sign([ByRef] PartiallySignedBitcoinTransaction psbt); boolean sign([ByRef] PartiallySignedTransaction psbt);
[Throws=BdkError] [Throws=BdkError]
sequence<TransactionDetails> list_transactions(); sequence<TransactionDetails> list_transactions();
@ -202,7 +202,14 @@ interface Wallet {
sequence<LocalUtxo> list_unspent(); sequence<LocalUtxo> list_unspent();
}; };
interface PartiallySignedBitcoinTransaction { interface FeeRate {
[Name=from_sat_per_vb]
constructor(float sat_per_vb);
float as_sat_per_vb();
};
interface PartiallySignedTransaction {
[Throws=BdkError] [Throws=BdkError]
constructor(string psbt_base64); constructor(string psbt_base64);
@ -213,11 +220,15 @@ interface PartiallySignedBitcoinTransaction {
sequence<u8> extract_tx(); sequence<u8> extract_tx();
[Throws=BdkError] [Throws=BdkError]
PartiallySignedBitcoinTransaction combine(PartiallySignedBitcoinTransaction other); PartiallySignedTransaction combine(PartiallySignedTransaction other);
u64? fee_amount();
FeeRate? fee_rate();
}; };
dictionary TxBuilderResult { dictionary TxBuilderResult {
PartiallySignedBitcoinTransaction psbt; PartiallySignedTransaction psbt;
TransactionDetails transaction_details; TransactionDetails transaction_details;
}; };
@ -270,7 +281,7 @@ interface BumpFeeTxBuilder {
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence); BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);
[Throws=BdkError] [Throws=BdkError]
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet); PartiallySignedTransaction finish([ByRef] Wallet wallet);
}; };
interface Mnemonic { interface Mnemonic {

View File

@ -3,7 +3,7 @@ use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath; use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
use bdk::bitcoin::util::psbt::serialize::Serialize; use bdk::bitcoin::util::psbt::serialize::Serialize;
use bdk::bitcoin::util::psbt::PartiallySignedTransaction; use bdk::bitcoin::util::psbt::PartiallySignedTransaction as BdkPartiallySignedTransaction;
use bdk::bitcoin::Sequence; use bdk::bitcoin::Sequence;
use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid}; use bdk::bitcoin::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig}; use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
@ -22,6 +22,7 @@ use bdk::keys::{
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey, DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
}; };
use bdk::miniscript::BareCtx; use bdk::miniscript::BareCtx;
use bdk::psbt::PsbtUtils;
use bdk::wallet::tx_builder::ChangeSpendPolicy; use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::AddressIndex as BdkAddressIndex; use bdk::wallet::AddressIndex as BdkAddressIndex;
use bdk::wallet::AddressInfo as BdkAddressInfo; use bdk::wallet::AddressInfo as BdkAddressInfo;
@ -210,7 +211,7 @@ impl Blockchain {
self.blockchain_mutex.lock().expect("blockchain") self.blockchain_mutex.lock().expect("blockchain")
} }
fn broadcast(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<(), BdkError> { fn broadcast(&self, psbt: &PartiallySignedTransaction) -> Result<(), BdkError> {
let tx = psbt.internal.lock().unwrap().clone().extract_tx(); let tx = psbt.internal.lock().unwrap().clone().extract_tx();
self.get_blockchain().broadcast(&tx) self.get_blockchain().broadcast(&tx)
} }
@ -341,14 +342,15 @@ impl fmt::Debug for ProgressHolder {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct PartiallySignedBitcoinTransaction { pub struct PartiallySignedTransaction {
internal: Mutex<PartiallySignedTransaction>, internal: Mutex<BdkPartiallySignedTransaction>,
} }
impl PartiallySignedBitcoinTransaction { impl PartiallySignedTransaction {
fn new(psbt_base64: String) -> Result<Self, BdkError> { fn new(psbt_base64: String) -> Result<Self, BdkError> {
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?; let psbt: BdkPartiallySignedTransaction =
Ok(PartiallySignedBitcoinTransaction { BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedTransaction {
internal: Mutex::new(psbt), internal: Mutex::new(psbt),
}) })
} }
@ -379,16 +381,30 @@ impl PartiallySignedBitcoinTransaction {
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)` /// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
fn combine( fn combine(
&self, &self,
other: Arc<PartiallySignedBitcoinTransaction>, other: Arc<PartiallySignedTransaction>,
) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> { ) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
let other_psbt = other.internal.lock().unwrap().clone(); let other_psbt = other.internal.lock().unwrap().clone();
let mut original_psbt = self.internal.lock().unwrap().clone(); let mut original_psbt = self.internal.lock().unwrap().clone();
original_psbt.combine(other_psbt)?; original_psbt.combine(other_psbt)?;
Ok(Arc::new(PartiallySignedBitcoinTransaction { Ok(Arc::new(PartiallySignedTransaction {
internal: Mutex::new(original_psbt), internal: Mutex::new(original_psbt),
})) }))
} }
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
/// If the PSBT is missing a TxOut for an input returns None.
fn fee_amount(&self) -> Option<u64> {
self.internal.lock().unwrap().fee_amount()
}
/// The transaction's fee rate. This value will only be accurate if calculated AFTER the
/// `PartiallySignedTransaction` is finalized and all witness/signature data is added to the
/// transaction.
/// If the PSBT is missing a TxOut for an input returns None.
fn fee_rate(&self) -> Option<Arc<FeeRate>> {
self.internal.lock().unwrap().fee_rate().map(Arc::new)
}
} }
/// A Bitcoin wallet. /// A Bitcoin wallet.
@ -460,7 +476,7 @@ impl Wallet {
} }
/// Sign a transaction with all the wallets signers. /// Sign a transaction with all the wallets signers.
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, BdkError> { fn sign(&self, psbt: &PartiallySignedTransaction) -> Result<bool, BdkError> {
let mut psbt = psbt.internal.lock().unwrap(); let mut psbt = psbt.internal.lock().unwrap();
self.get_wallet().sign(&mut psbt, SignOptions::default()) self.get_wallet().sign(&mut psbt, SignOptions::default())
} }
@ -532,7 +548,7 @@ enum RbfValue {
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and /// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
/// transaction details. /// transaction details.
pub struct TxBuilderResult { pub struct TxBuilderResult {
pub psbt: Arc<PartiallySignedBitcoinTransaction>, pub psbt: Arc<PartiallySignedTransaction>,
pub transaction_details: TransactionDetails, pub transaction_details: TransactionDetails,
} }
@ -770,7 +786,7 @@ impl TxBuilder {
tx_builder tx_builder
.finish() .finish()
.map(|(psbt, tx_details)| TxBuilderResult { .map(|(psbt, tx_details)| TxBuilderResult {
psbt: Arc::new(PartiallySignedBitcoinTransaction { psbt: Arc::new(PartiallySignedTransaction {
internal: Mutex::new(psbt), internal: Mutex::new(psbt),
}), }),
transaction_details: TransactionDetails::from(&tx_details), transaction_details: TransactionDetails::from(&tx_details),
@ -828,7 +844,7 @@ impl BumpFeeTxBuilder {
} }
/// Finish building the transaction. Returns the BIP174 PSBT. /// Finish building the transaction. Returns the BIP174 PSBT.
fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> { fn finish(&self, wallet: &Wallet) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
let wallet = wallet.get_wallet(); let wallet = wallet.get_wallet();
let txid = Txid::from_str(self.txid.as_str())?; let txid = Txid::from_str(self.txid.as_str())?;
let mut tx_builder = wallet.build_fee_bump(txid)?; let mut tx_builder = wallet.build_fee_bump(txid)?;
@ -851,7 +867,7 @@ impl BumpFeeTxBuilder {
} }
tx_builder tx_builder
.finish() .finish()
.map(|(psbt, _)| PartiallySignedBitcoinTransaction { .map(|(psbt, _)| PartiallySignedTransaction {
internal: Mutex::new(psbt), internal: Mutex::new(psbt),
}) })
.map(Arc::new) .map(Arc::new)
@ -1246,4 +1262,32 @@ mod test {
"e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936" "e93315d6ce401eb4db803a56232f0ed3e69b053774e6047df54f1bd00e5ea936"
) )
} }
#[test]
fn test_psbt_fee() {
let test_wpkh = "wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)";
let (funded_wallet, _, _) = get_funded_wallet(test_wpkh);
let test_wallet = Wallet {
wallet_mutex: Mutex::new(funded_wallet),
};
let drain_to_address = "tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt".to_string();
let tx_builder = TxBuilder::new()
.fee_rate(2.0)
.drain_wallet()
.drain_to(drain_to_address.clone());
//dbg!(&tx_builder);
assert!(tx_builder.drain_wallet);
assert_eq!(tx_builder.drain_to, Some(drain_to_address));
let tx_builder_result = tx_builder.finish(&test_wallet).unwrap();
assert!(tx_builder_result.psbt.fee_rate().is_some());
assert_eq!(
tx_builder_result.psbt.fee_rate().unwrap().as_sat_per_vb(),
2.682927
);
assert!(tx_builder_result.psbt.fee_amount().is_some());
assert_eq!(tx_builder_result.psbt.fee_amount().unwrap(), 220);
}
} }