From d6672b6d73e3e80a11bcbe786f7c19bac61284dc Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 13 Jan 2025 13:34:45 +0100 Subject: [PATCH] Allow to override fee rates for onchain payments We allow to override our fee estimator in the `send_to_address` and `send_all_to_address` API methods. To this end, we implement a bindings-compatible wrapper around `bitcoin::FeeRate`. --- bindings/ldk_node.udl | 14 ++++++++++++-- src/payment/onchain.rs | 34 +++++++++++++++++++++++++++++---- src/payment/unified_qr.rs | 7 +++++-- src/uniffi_types.rs | 2 +- src/wallet/mod.rs | 8 ++++++-- tests/integration_tests_rust.rs | 9 +++++---- 6 files changed, 59 insertions(+), 15 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 5ea0e4173..898351290 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -161,9 +161,19 @@ interface OnchainPayment { [Throws=NodeError] Address new_address(); [Throws=NodeError] - Txid send_to_address([ByRef]Address address, u64 amount_sats); + Txid send_to_address([ByRef]Address address, u64 amount_sats, FeeRate? fee_rate); [Throws=NodeError] - Txid send_all_to_address([ByRef]Address address, boolean retain_reserve); + Txid send_all_to_address([ByRef]Address address, boolean retain_reserve, FeeRate? fee_rate); +}; + +interface FeeRate { + [Name=from_sat_per_kwu] + constructor(u64 sat_kwu); + [Name=from_sat_per_vb_unchecked] + constructor(u64 sat_vb); + u64 to_sat_per_kwu(); + u64 to_sat_per_vb_floor(); + u64 to_sat_per_vb_ceil(); }; interface UnifiedQrPayment { diff --git a/src/payment/onchain.rs b/src/payment/onchain.rs index d46eba2b5..23d6b6a0b 100644 --- a/src/payment/onchain.rs +++ b/src/payment/onchain.rs @@ -17,6 +17,24 @@ use bitcoin::{Address, Txid}; use std::sync::{Arc, RwLock}; +#[cfg(not(feature = "uniffi"))] +type FeeRate = bitcoin::FeeRate; +#[cfg(feature = "uniffi")] +type FeeRate = Arc; + +macro_rules! maybe_map_fee_rate_opt { + ($fee_rate_opt: expr) => {{ + #[cfg(not(feature = "uniffi"))] + { + $fee_rate_opt + } + #[cfg(feature = "uniffi")] + { + $fee_rate_opt.map(|f| *f) + } + }}; +} + /// A payment handler allowing to send and receive on-chain payments. /// /// Should be retrieved by calling [`Node::onchain_payment`]. @@ -50,9 +68,12 @@ impl OnchainPayment { /// This will respect any on-chain reserve we need to keep, i.e., won't allow to cut into /// [`BalanceDetails::total_anchor_channels_reserve_sats`]. /// + /// If `fee_rate` is set it will be used on the resulting transaction. Otherwise we'll retrieve + /// a reasonable estimate from the configured chain source. + /// /// [`BalanceDetails::total_anchor_channels_reserve_sats`]: crate::BalanceDetails::total_anchor_channels_reserve_sats pub fn send_to_address( - &self, address: &bitcoin::Address, amount_sats: u64, + &self, address: &bitcoin::Address, amount_sats: u64, fee_rate: Option, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -63,7 +84,8 @@ impl OnchainPayment { crate::total_anchor_channels_reserve_sats(&self.channel_manager, &self.config); let send_amount = OnchainSendAmount::ExactRetainingReserve { amount_sats, cur_anchor_reserve_sats }; - self.wallet.send_to_address(address, send_amount) + let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); + self.wallet.send_to_address(address, send_amount, fee_rate_opt) } /// Send an on-chain payment to the given address, draining the available funds. @@ -77,9 +99,12 @@ impl OnchainPayment { /// will try to send all spendable onchain funds, i.e., /// [`BalanceDetails::spendable_onchain_balance_sats`]. /// + /// If `fee_rate` is set it will be used on the resulting transaction. Otherwise a reasonable + /// we'll retrieve an estimate from the configured chain source. + /// /// [`BalanceDetails::spendable_onchain_balance_sats`]: crate::balance::BalanceDetails::spendable_onchain_balance_sats pub fn send_all_to_address( - &self, address: &bitcoin::Address, retain_reserves: bool, + &self, address: &bitcoin::Address, retain_reserves: bool, fee_rate: Option, ) -> Result { let rt_lock = self.runtime.read().unwrap(); if rt_lock.is_none() { @@ -94,6 +119,7 @@ impl OnchainPayment { OnchainSendAmount::AllDrainingReserve }; - self.wallet.send_to_address(address, send_amount) + let fee_rate_opt = maybe_map_fee_rate_opt!(fee_rate); + self.wallet.send_to_address(address, send_amount, fee_rate_opt) } } diff --git a/src/payment/unified_qr.rs b/src/payment/unified_qr.rs index 029416b84..0c242d729 100644 --- a/src/payment/unified_qr.rs +++ b/src/payment/unified_qr.rs @@ -156,8 +156,11 @@ impl UnifiedQrPayment { }, }; - let txid = - self.onchain_payment.send_to_address(&uri_network_checked.address, amount.to_sat())?; + let txid = self.onchain_payment.send_to_address( + &uri_network_checked.address, + amount.to_sat(), + None, + )?; Ok(QrPaymentResult::Onchain { txid }) } diff --git a/src/uniffi_types.rs b/src/uniffi_types.rs index 89e11efc5..81384358e 100644 --- a/src/uniffi_types.rs +++ b/src/uniffi_types.rs @@ -30,7 +30,7 @@ pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; pub use lightning_invoice::Bolt11Invoice; -pub use bitcoin::{Address, BlockHash, Network, OutPoint, Txid}; +pub use bitcoin::{Address, BlockHash, FeeRate, Network, OutPoint, Txid}; pub use bip39::Mnemonic; diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 3533e6fef..b52d937a3 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -39,7 +39,8 @@ use bitcoin::secp256k1::ecdh::SharedSecret; use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature}; use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing}; use bitcoin::{ - Amount, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, WitnessVersion, + Amount, FeeRate, ScriptBuf, Transaction, TxOut, Txid, WPubkeyHash, WitnessProgram, + WitnessVersion, }; use std::ops::Deref; @@ -239,9 +240,12 @@ where pub(crate) fn send_to_address( &self, address: &bitcoin::Address, send_amount: OnchainSendAmount, + fee_rate: Option, ) -> Result { + // Use the set fee_rate or default to fee estimation. let confirmation_target = ConfirmationTarget::OnchainPayment; - let fee_rate = self.fee_estimator.estimate_fee_rate(confirmation_target); + let fee_rate = + fee_rate.unwrap_or_else(|| self.fee_estimator.estimate_fee_rate(confirmation_target)); let tx = { let mut locked_wallet = self.inner.lock().unwrap(); diff --git a/tests/integration_tests_rust.rs b/tests/integration_tests_rust.rs index 2737a3d18..e0f1823ae 100644 --- a/tests/integration_tests_rust.rs +++ b/tests/integration_tests_rust.rs @@ -315,11 +315,12 @@ fn onchain_spend_receive() { assert_eq!( Err(NodeError::InsufficientFunds), - node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1) + node_a.onchain_payment().send_to_address(&addr_b, expected_node_a_balance + 1, None) ); let amount_to_send_sats = 1000; - let txid = node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats).unwrap(); + let txid = + node_b.onchain_payment().send_to_address(&addr_a, amount_to_send_sats, None).unwrap(); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); wait_for_tx(&electrsd.client, txid); @@ -334,7 +335,7 @@ fn onchain_spend_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); let addr_b = node_b.onchain_payment().new_address().unwrap(); - let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true).unwrap(); + let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).unwrap(); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); wait_for_tx(&electrsd.client, txid); @@ -350,7 +351,7 @@ fn onchain_spend_receive() { assert!(node_b.list_balances().spendable_onchain_balance_sats < expected_node_b_balance_upper); let addr_b = node_b.onchain_payment().new_address().unwrap(); - let txid = node_a.onchain_payment().send_all_to_address(&addr_b, false).unwrap(); + let txid = node_a.onchain_payment().send_all_to_address(&addr_b, false, None).unwrap(); generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6); wait_for_tx(&electrsd.client, txid);