Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add calculate_fee and calculate_fee_rate on wallet #437

Merged
merged 1 commit into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,14 @@ class LiveWalletTest {
assertTrue(walletDidSign)

val tx: Transaction = psbt.extractTx()

println("Txid is: ${tx.txid()}")

val txFee: ULong = wallet.calculateFee(tx)
println("Tx fee is: ${txFee}")

val feeRate: FeeRate = wallet.calculateFeeRate(tx)
println("Tx fee rate is: ${feeRate.asSatPerVb()} sat/vB")

esploraClient.broadcast(tx)
}
}
19 changes: 18 additions & 1 deletion bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ enum BdkError {
"Psbt",
};

[Error]
interface CalculateFeeError {
reez marked this conversation as resolved.
Show resolved Hide resolved
MissingTxOut(sequence<OutPoint> out_points);
NegativeFee(i64 fee);
Comment on lines +87 to +88
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the first time we use this pattern of defining errors as interfaces in the UDL. Can you confirm that the errors print out what we expect them to? Just making sure it all works well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added two tests in the error.rs file to address this. Let me know if:

  • those two tests make sense to you
  • that is the best place to test these two errors

... or any other thoughts you had on it too. Thanks, great suggestion!

};

interface FeeRate {
f32 as_sat_per_vb();
f32 sat_per_kwu();
};

enum ChangeSpendPolicy {
"ChangeAllowed",
"OnlyChange",
Expand Down Expand Up @@ -111,6 +122,12 @@ interface Wallet {
SentAndReceivedValues sent_and_received([ByRef] Transaction tx);

sequence<Transaction> transactions();

[Throws=CalculateFeeError]
u64 calculate_fee([ByRef] Transaction tx);

[Throws=CalculateFeeError]
FeeRate calculate_fee_rate([ByRef] Transaction tx);
};

interface Update {};
Expand Down Expand Up @@ -350,4 +367,4 @@ interface PartiallySignedTransaction {
dictionary OutPoint {
string txid;
u32 vout;
};
};
21 changes: 18 additions & 3 deletions bdk-ffi/src/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,15 @@ impl From<BdkTransaction> for Transaction {
}
}

impl From<Transaction> for BdkTransaction {
fn from(tx: Transaction) -> Self {
tx.inner
impl From<&BdkTransaction> for Transaction {
fn from(tx: &BdkTransaction) -> Self {
Transaction { inner: tx.clone() }
}
}

impl From<&Transaction> for BdkTransaction {
fn from(tx: &Transaction) -> Self {
tx.inner.clone()
}
}

Expand Down Expand Up @@ -310,6 +316,15 @@ impl From<&OutPoint> for BdkOutPoint {
}
}

impl From<&BdkOutPoint> for OutPoint {
fn from(outpoint: &BdkOutPoint) -> Self {
OutPoint {
txid: outpoint.txid.to_string(),
vout: outpoint.vout,
}
}
}

#[derive(Debug, Clone)]
pub struct TxOut {
pub value: u64,
Expand Down
82 changes: 82 additions & 0 deletions bdk-ffi/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::bitcoin::OutPoint;

use bdk::chain::tx_graph::CalculateFeeError as BdkCalculateFeeError;

use std::fmt;

#[derive(Debug)]
pub enum CalculateFeeError {
MissingTxOut { out_points: Vec<OutPoint> },
NegativeFee { fee: i64 },
}

impl fmt::Display for CalculateFeeError {
reez marked this conversation as resolved.
Show resolved Hide resolved
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CalculateFeeError::MissingTxOut { out_points } => {
write!(f, "Missing transaction output: {:?}", out_points)
}
CalculateFeeError::NegativeFee { fee } => write!(f, "Negative fee value: {}", fee),
}
}
}

impl From<BdkCalculateFeeError> 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 std::error::Error for CalculateFeeError {}
reez marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod test {
use crate::CalculateFeeError;
use crate::OutPoint;

#[test]
fn test_error_missing_tx_out() {
let out_points: Vec<OutPoint> = vec![
OutPoint {
txid: "0000000000000000000000000000000000000000000000000000000000000001"
.to_string(),
vout: 0,
},
OutPoint {
txid: "0000000000000000000000000000000000000000000000000000000000000002"
.to_string(),
vout: 1,
},
];

let error = CalculateFeeError::MissingTxOut { out_points };

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);
}

#[test]
fn test_error_negative_fee() {
let error = CalculateFeeError::NegativeFee { fee: -100 };

assert_eq!(error.to_string(), "Negative fee value: -100");
}
}
2 changes: 1 addition & 1 deletion bdk-ffi/src/esplora.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl EsploraClient {
// pub fn sync();

pub fn broadcast(&self, transaction: &Transaction) -> Result<(), BdkError> {
let bdk_transaction: BdkTransaction = transaction.clone().into();
let bdk_transaction: BdkTransaction = transaction.into();
self.0
.broadcast(&bdk_transaction)
.map_err(|e| BdkError::Generic(e.to_string()))
Expand Down
3 changes: 3 additions & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod bitcoin;
mod descriptor;
mod error;
mod esplora;
mod keys;
mod types;
Expand All @@ -13,6 +14,7 @@ use crate::bitcoin::Script;
use crate::bitcoin::Transaction;
use crate::bitcoin::TxOut;
use crate::descriptor::Descriptor;
use crate::error::CalculateFeeError;
use crate::esplora::EsploraClient;
use crate::keys::DerivationPath;
use crate::keys::DescriptorPublicKey;
Expand All @@ -21,6 +23,7 @@ use crate::keys::Mnemonic;
use crate::types::AddressIndex;
use crate::types::AddressInfo;
use crate::types::Balance;
use crate::types::FeeRate;
use crate::types::LocalUtxo;
use crate::types::ScriptAmount;
use crate::wallet::BumpFeeTxBuilder;
Expand Down
14 changes: 14 additions & 0 deletions bdk-ffi/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,22 @@ use bdk::KeychainKind;

use bdk::LocalUtxo as BdkLocalUtxo;

use bdk::FeeRate as BdkFeeRate;

use std::sync::Arc;

pub struct FeeRate(pub BdkFeeRate);

impl FeeRate {
pub fn as_sat_per_vb(&self) -> f32 {
self.0.as_sat_per_vb()
}

pub fn sat_per_kwu(&self) -> f32 {
self.0.sat_per_kwu()
}
}
reez marked this conversation as resolved.
Show resolved Hide resolved

pub struct ScriptAmount {
pub script: Arc<Script>,
pub amount: u64,
Expand Down
26 changes: 20 additions & 6 deletions bdk-ffi/src/wallet.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::bitcoin::{OutPoint, PartiallySignedTransaction, Transaction};
use crate::descriptor::Descriptor;
use crate::types::Balance;
use crate::error::CalculateFeeError;
use crate::types::ScriptAmount;
use crate::types::{Balance, FeeRate};
use crate::Script;
use crate::{AddressIndex, AddressInfo, Network};

Expand All @@ -10,7 +11,7 @@ use bdk::bitcoin::psbt::PartiallySignedTransaction as BdkPartiallySignedTransact
use bdk::bitcoin::{OutPoint as BdkOutPoint, Sequence, Txid};
use bdk::wallet::tx_builder::ChangeSpendPolicy;
use bdk::wallet::Update as BdkUpdate;
use bdk::{Error as BdkError, FeeRate};
use bdk::{Error as BdkError, FeeRate as BdkFeeRate};
use bdk::{SignOptions, Wallet as BdkWallet};

use std::collections::HashSet;
Expand Down Expand Up @@ -88,16 +89,29 @@ impl Wallet {
}

pub fn sent_and_received(&self, tx: &Transaction) -> SentAndReceivedValues {
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.clone().into());
let (sent, received): (u64, u64) = self.get_wallet().sent_and_received(&tx.into());
SentAndReceivedValues { sent, received }
}

pub fn transactions(&self) -> Vec<Arc<Transaction>> {
self.get_wallet()
.transactions()
.map(|tx| Arc::new(tx.tx_node.tx.clone().into()))
.map(|tx| Arc::new(tx.tx_node.tx.into()))
.collect()
}

pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
self.get_wallet()
.calculate_fee(&tx.into())
.map_err(|e| e.into())
}

pub fn calculate_fee_rate(&self, tx: &Transaction) -> Result<Arc<FeeRate>, CalculateFeeError> {
self.get_wallet()
.calculate_fee_rate(&tx.into())
.map(|bdk_fee_rate| Arc::new(FeeRate(bdk_fee_rate)))
.map_err(|e| e.into())
}
}

pub struct SentAndReceivedValues {
Expand Down Expand Up @@ -473,7 +487,7 @@ impl TxBuilder {
tx_builder.manually_selected_only();
}
if let Some(sat_per_vb) = self.fee_rate {
tx_builder.fee_rate(FeeRate::from_sat_per_vb(sat_per_vb));
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(sat_per_vb));
reez marked this conversation as resolved.
Show resolved Hide resolved
}
if let Some(fee_amount) = self.fee_absolute {
tx_builder.fee_absolute(fee_amount);
Expand Down Expand Up @@ -551,7 +565,7 @@ impl BumpFeeTxBuilder {
Txid::from_str(self.txid.as_str()).map_err(|e| BdkError::Generic(e.to_string()))?;
let mut wallet = wallet.get_wallet();
let mut tx_builder = wallet.build_fee_bump(txid)?;
tx_builder.fee_rate(FeeRate::from_sat_per_vb(self.fee_rate));
tx_builder.fee_rate(BdkFeeRate::from_sat_per_vb(self.fee_rate));
if let Some(allow_shrinking) = &self.allow_shrinking {
tx_builder.allow_shrinking(allow_shrinking.0.clone())?;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ class LiveWalletTest {
assertTrue(walletDidSign)

val tx: Transaction = psbt.extractTx()

println("Txid is: ${tx.txid()}")

val txFee: ULong = wallet.calculateFee(tx)
println("Tx fee is: ${txFee}")

val feeRate: FeeRate = wallet.calculateFeeRate(tx)
println("Tx fee rate is: ${feeRate.asSatPerVb()} sat/vB")

esploraClient.broadcast(tx)
}
}
5 changes: 5 additions & 0 deletions bdk-python/tests/test_live_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ def test_broadcast_transaction(self):
walletDidSign = wallet.sign(psbt)
self.assertTrue(walletDidSign)
tx = psbt.extract_tx()
print(f"Transaction Id: {tx.txid}")
fee = wallet.calculate_fee(tx)
print(f"Transaction Fee: {fee}")
fee_rate = wallet.calculate_fee_rate(tx)
print(f"Transaction Fee Rate: {fee_rate.as_sat_per_vb()} sat/vB")

esploraClient.broadcast(tx)

Expand Down
5 changes: 5 additions & 0 deletions bdk-swift/Tests/BitcoinDevKitTests/LiveWalletTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ final class LiveWalletTests: XCTestCase {

let tx: Transaction = psbt.extractTx()
print(tx.txid())
let fee: UInt64 = try wallet.calculateFee(tx: tx)
print("Transaction Fee: \(fee)")
let feeRate: FeeRate = try wallet.calculateFeeRate(tx: tx)
print("Transaction Fee Rate: \(feeRate.asSatPerVb()) sat/vB")

try esploraClient.broadcast(transaction: tx)
}
}
Loading