Skip to content

Commit

Permalink
Merge #208: Add FeeRate struct and PSBT fee_amount and fee_rate funct…
Browse files Browse the repository at this point in the history
…ions

ae1ea99 Add FeeRate struct and fee_amount() and fee_rate() functions on PartiallySignedTransaction (Steve Myers)
9a381f6 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 bitcoindevkit/bdk#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](ae1ea99).

Tree-SHA512: 2c3f792e9ef092cd3ba233601122f4960c496d132caad54ef2f7f41d7113dd16600a863bb8fd78d2e5b978adebdb7ddd9529c21b3d46cd0b16e0db4eb90de01d
  • Loading branch information
notmandatory committed Nov 7, 2022
2 parents 3a07b48 + ae1ea99 commit a25fb13
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 21 deletions.
23 changes: 17 additions & 6 deletions src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ interface Blockchain {
constructor(BlockchainConfig config);

[Throws=BdkError]
void broadcast([ByRef] PartiallySignedBitcoinTransaction psbt);
void broadcast([ByRef] PartiallySignedTransaction psbt);

[Throws=BdkError]
u32 get_height();
Expand Down Expand Up @@ -188,7 +188,7 @@ interface Wallet {
Balance get_balance();

[Throws=BdkError]
boolean sign([ByRef] PartiallySignedBitcoinTransaction psbt);
boolean sign([ByRef] PartiallySignedTransaction psbt);

[Throws=BdkError]
sequence<TransactionDetails> list_transactions();
Expand All @@ -202,7 +202,14 @@ interface Wallet {
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]
constructor(string psbt_base64);

Expand All @@ -213,11 +220,15 @@ interface PartiallySignedBitcoinTransaction {
sequence<u8> extract_tx();

[Throws=BdkError]
PartiallySignedBitcoinTransaction combine(PartiallySignedBitcoinTransaction other);
PartiallySignedTransaction combine(PartiallySignedTransaction other);

u64? fee_amount();

FeeRate? fee_rate();
};

dictionary TxBuilderResult {
PartiallySignedBitcoinTransaction psbt;
PartiallySignedTransaction psbt;
TransactionDetails transaction_details;
};

Expand Down Expand Up @@ -270,7 +281,7 @@ interface BumpFeeTxBuilder {
BumpFeeTxBuilder enable_rbf_with_sequence(u32 nsequence);

[Throws=BdkError]
PartiallySignedBitcoinTransaction finish([ByRef] Wallet wallet);
PartiallySignedTransaction finish([ByRef] Wallet wallet);
};

interface Mnemonic {
Expand Down
74 changes: 59 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use bdk::bitcoin::hashes::hex::ToHex;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::DerivationPath as BdkDerivationPath;
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::{Address as BdkAddress, Network, OutPoint as BdkOutPoint, Txid};
use bdk::blockchain::any::{AnyBlockchain, AnyBlockchainConfig};
Expand All @@ -22,6 +22,7 @@ use bdk::keys::{
DescriptorSecretKey as BdkDescriptorSecretKey, ExtendedKey, GeneratableKey, GeneratedKey,
};
use bdk::miniscript::BareCtx;
use bdk::psbt::PsbtUtils;
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::AddressIndex as BdkAddressIndex;
use bdk::wallet::AddressInfo as BdkAddressInfo;
Expand Down Expand Up @@ -210,7 +211,7 @@ impl 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();
self.get_blockchain().broadcast(&tx)
}
Expand Down Expand Up @@ -341,14 +342,15 @@ impl fmt::Debug for ProgressHolder {
}

#[derive(Debug)]
pub struct PartiallySignedBitcoinTransaction {
internal: Mutex<PartiallySignedTransaction>,
pub struct PartiallySignedTransaction {
internal: Mutex<BdkPartiallySignedTransaction>,
}

impl PartiallySignedBitcoinTransaction {
impl PartiallySignedTransaction {
fn new(psbt_base64: String) -> Result<Self, BdkError> {
let psbt: PartiallySignedTransaction = PartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedBitcoinTransaction {
let psbt: BdkPartiallySignedTransaction =
BdkPartiallySignedTransaction::from_str(&psbt_base64)?;
Ok(PartiallySignedTransaction {
internal: Mutex::new(psbt),
})
}
Expand Down Expand Up @@ -379,16 +381,30 @@ impl PartiallySignedBitcoinTransaction {
/// In accordance with BIP 174 this function is commutative i.e., `A.combine(B) == B.combine(A)`
fn combine(
&self,
other: Arc<PartiallySignedBitcoinTransaction>,
) -> Result<Arc<PartiallySignedBitcoinTransaction>, BdkError> {
other: Arc<PartiallySignedTransaction>,
) -> Result<Arc<PartiallySignedTransaction>, BdkError> {
let other_psbt = other.internal.lock().unwrap().clone();
let mut original_psbt = self.internal.lock().unwrap().clone();

original_psbt.combine(other_psbt)?;
Ok(Arc::new(PartiallySignedBitcoinTransaction {
Ok(Arc::new(PartiallySignedTransaction {
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.
Expand Down Expand Up @@ -460,7 +476,7 @@ impl Wallet {
}

/// Sign a transaction with all the wallet’s signers.
fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result<bool, BdkError> {
fn sign(&self, psbt: &PartiallySignedTransaction) -> Result<bool, BdkError> {
let mut psbt = psbt.internal.lock().unwrap();
self.get_wallet().sign(&mut psbt, SignOptions::default())
}
Expand Down Expand Up @@ -532,7 +548,7 @@ enum RbfValue {
/// The result after calling the TxBuilder finish() function. Contains unsigned PSBT and
/// transaction details.
pub struct TxBuilderResult {
pub psbt: Arc<PartiallySignedBitcoinTransaction>,
pub psbt: Arc<PartiallySignedTransaction>,
pub transaction_details: TransactionDetails,
}

Expand Down Expand Up @@ -770,7 +786,7 @@ impl TxBuilder {
tx_builder
.finish()
.map(|(psbt, tx_details)| TxBuilderResult {
psbt: Arc::new(PartiallySignedBitcoinTransaction {
psbt: Arc::new(PartiallySignedTransaction {
internal: Mutex::new(psbt),
}),
transaction_details: TransactionDetails::from(&tx_details),
Expand Down Expand Up @@ -828,7 +844,7 @@ impl BumpFeeTxBuilder {
}

/// 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 txid = Txid::from_str(self.txid.as_str())?;
let mut tx_builder = wallet.build_fee_bump(txid)?;
Expand All @@ -851,7 +867,7 @@ impl BumpFeeTxBuilder {
}
tx_builder
.finish()
.map(|(psbt, _)| PartiallySignedBitcoinTransaction {
.map(|(psbt, _)| PartiallySignedTransaction {
internal: Mutex::new(psbt),
})
.map(Arc::new)
Expand Down Expand Up @@ -1246,4 +1262,32 @@ mod test {
"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);
}
}

0 comments on commit a25fb13

Please sign in to comment.