Move sync wallet utils to util::wallet_utils
What changed, and why it matters
This commit is a straightforward code reorganization. It moves synchronous wallet helper code from one module (bump_transaction::sync) into a shared utility module (util::wallet_utils) so it can be reused by channel funding and splicing features. No security-relevant behavior is changed; only import paths and module locations are adjusted.
No security action needed. Treat as a normal code-quality refactor.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates the synchronous wallet traits and wrappers (WalletSourceSync, WalletSync, CoinSelectionSourceSync, and their wrappers) from lightning/src/events/bump_transaction/sync.rs to lightning/src/util/wallet_utils.rs. All call sites are updated to import from the new path. The implementation logic is preserved verbatim, including the use of dummy_waker and polling to bridge async code into sync contexts. This is a pure refactor with no functional or security changes.
Changed components
lightning/src/events/bump_transaction/sync.rslightning/src/util/wallet_utils.rslightning/src/ln/funding.rslightning/src/ln/splicing_tests.rslightning/src/ln/async_signer_tests.rslightning/src/ln/functional_test_utils.rslightning/src/util/test_utils.rslightning/src/events/bump_transaction/mod.rsfuzz/src/chanmon_consistency.rsfuzz/src/full_stack.rslightning-tests/src/upgrade_downgrade_tests.rsInspect captured patch +260 / −272
diff --git a/fuzz/src/chanmon_consistency.rs b/fuzz/src/chanmon_consistency.rs
index ced89f5..0abeea1 100644
--- a/fuzz/src/chanmon_consistency.rs
+++ b/fuzz/src/chanmon_consistency.rs
@@ -46,7 +46,6 @@ use lightning::chain::{
chainmonitor, channelmonitor, BestBlock, ChannelMonitorUpdateStatus, Confirm, Watch,
};
use lightning::events;
-use lightning::events::bump_transaction::sync::{WalletSourceSync, WalletSync};
use lightning::ln::channel::{
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS,
};
@@ -81,6 +80,7 @@ use lightning::util::logger::Logger;
use lightning::util::ser::{LengthReadable, ReadableArgs, Writeable, Writer};
use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner};
use lightning::util::test_utils::TestWalletSource;
+use lightning::util::wallet_utils::{WalletSourceSync, WalletSync};
use lightning_invoice::RawBolt11Invoice;
diff --git a/fuzz/src/full_stack.rs b/fuzz/src/full_stack.rs
index 6adb8f3..085165e 100644
--- a/fuzz/src/full_stack.rs
+++ b/fuzz/src/full_stack.rs
@@ -40,7 +40,6 @@ use lightning::chain::chaininterface::{
use lightning::chain::chainmonitor;
use lightning::chain::transaction::OutPoint;
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
-use lightning::events::bump_transaction::sync::{WalletSourceSync, WalletSync};
use lightning::events::Event;
use lightning::ln::channel_state::ChannelDetails;
use lightning::ln::channelmanager::{ChainParameters, ChannelManager, InterceptId, PaymentId};
@@ -71,6 +70,7 @@ use lightning::util::logger::Logger;
use lightning::util::ser::{Readable, Writeable};
use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner};
use lightning::util::test_utils::TestWalletSource;
+use lightning::util::wallet_utils::{WalletSourceSync, WalletSync};
use lightning_invoice::RawBolt11Invoice;
diff --git a/lightning-tests/src/upgrade_downgrade_tests.rs b/lightning-tests/src/upgrade_downgrade_tests.rs
index f18e0e5..93d671b 100644
--- a/lightning-tests/src/upgrade_downgrade_tests.rs
+++ b/lightning-tests/src/upgrade_downgrade_tests.rs
@@ -46,7 +46,6 @@ use lightning_0_0_125::routing::router as router_0_0_125;
use lightning_0_0_125::util::ser::Writeable as _;
use lightning::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER};
-use lightning::events::bump_transaction::sync::WalletSourceSync;
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
use lightning::ln::functional_test_utils::*;
use lightning::ln::msgs::BaseMessageHandler as _;
@@ -55,6 +54,7 @@ use lightning::ln::msgs::MessageSendEvent;
use lightning::ln::splicing_tests::*;
use lightning::ln::types::ChannelId;
use lightning::sign::OutputSpender;
+use lightning::util::wallet_utils::WalletSourceSync;
use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
diff --git a/lightning/src/events/bump_transaction/mod.rs b/lightning/src/events/bump_transaction/mod.rs
index 13a5a61..f1ba1fc 100644
--- a/lightning/src/events/bump_transaction/mod.rs
+++ b/lightning/src/events/bump_transaction/mod.rs
@@ -843,9 +843,7 @@ where
mod tests {
use super::*;
- use crate::events::bump_transaction::sync::{
- BumpTransactionEventHandlerSync, CoinSelectionSourceSync,
- };
+ use crate::events::bump_transaction::sync::BumpTransactionEventHandlerSync;
use crate::io::Cursor;
use crate::ln::chan_utils::ChannelTransactionParameters;
use crate::ln::channel::ANCHOR_OUTPUT_VALUE_SATOSHI;
@@ -854,6 +852,7 @@ mod tests {
use crate::types::features::ChannelTypeFeatures;
use crate::util::ser::Readable;
use crate::util::test_utils::{TestBroadcaster, TestLogger};
+ use crate::util::wallet_utils::CoinSelectionSourceSync;
use crate::util::wallet_utils::Utxo;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
diff --git a/lightning/src/events/bump_transaction/sync.rs b/lightning/src/events/bump_transaction/sync.rs
index 2d88b01..f2e1be1 100644
--- a/lightning/src/events/bump_transaction/sync.rs
+++ b/lightning/src/events/bump_transaction/sync.rs
@@ -15,258 +15,13 @@ use core::pin::pin;
use core::task;
use crate::chain::chaininterface::BroadcasterInterface;
-use crate::chain::ClaimId;
-use crate::prelude::*;
use crate::sign::SignerProvider;
-use crate::util::async_poll::{dummy_waker, MaybeSend, MaybeSync};
+use crate::util::async_poll::dummy_waker;
use crate::util::logger::Logger;
-use crate::util::wallet_utils::{
- CoinSelection, CoinSelectionSource, Input, Utxo, Wallet, WalletSource,
-};
-
-use bitcoin::{OutPoint, Psbt, ScriptBuf, Transaction, TxOut};
+use crate::util::wallet_utils::{CoinSelectionSourceSync, CoinSelectionSourceSyncWrapper};
use super::{BumpTransactionEvent, BumpTransactionEventHandler};
-/// An alternative to [`CoinSelectionSourceSync`] that can be implemented and used along
-/// [`WalletSync`] to provide a default implementation to [`CoinSelectionSourceSync`].
-///
-/// For an asynchronous version of this trait, see [`WalletSource`].
-// Note that updates to documentation on this trait should be copied to the asynchronous version.
-pub trait WalletSourceSync {
- /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
- fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
-
- /// Returns the previous transaction containing the UTXO referenced by the outpoint.
- fn get_prevtx(&self, outpoint: OutPoint) -> Result<Transaction, ()>;
-
- /// Returns a script to use for change above dust resulting from a successful coin selection
- /// attempt.
- fn get_change_script(&self) -> Result<ScriptBuf, ()>;
-
- /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
- /// the transaction known to the wallet (i.e., any provided via
- /// [`WalletSource::list_confirmed_utxos`]).
- ///
- /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
- /// unsigned transaction and then sign it with your wallet.
- ///
- /// [`TxIn::script_sig`]: bitcoin::TxIn::script_sig
- /// [`TxIn::witness`]: bitcoin::TxIn::witness
- fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
-}
-
-pub(crate) struct WalletSourceSyncWrapper<T: Deref>(T)
-where
- T::Target: WalletSourceSync;
-
-// Implement `Deref` directly on WalletSourceSyncWrapper so that it can be used directly
-// below, rather than via a wrapper.
-impl<T: Deref> Deref for WalletSourceSyncWrapper<T>
-where
- T::Target: WalletSourceSync,
-{
- type Target = Self;
- fn deref(&self) -> &Self {
- self
- }
-}
-
-impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
-where
- T::Target: WalletSourceSync,
-{
- fn list_confirmed_utxos<'a>(
- &'a self,
- ) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a {
- let utxos = self.0.list_confirmed_utxos();
- async move { utxos }
- }
-
- fn get_prevtx<'a>(
- &'a self, outpoint: OutPoint,
- ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
- let prevtx = self.0.get_prevtx(outpoint);
- Box::pin(async move { prevtx })
- }
-
- fn get_change_script<'a>(
- &'a self,
- ) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a {
- let script = self.0.get_change_script();
- async move { script }
- }
-
- fn sign_psbt<'a>(
- &'a self, psbt: Psbt,
- ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
- let signed_psbt = self.0.sign_psbt(psbt);
- async move { signed_psbt }
- }
-}
-
-/// A wrapper over [`WalletSourceSync`] that implements [`CoinSelectionSourceSync`] by preferring
-/// UTXOs that would avoid conflicting double spends. If not enough UTXOs are available to do so,
-/// conflicting double spends may happen.
-///
-/// For an asynchronous version of this wrapper, see [`Wallet`].
-// Note that updates to documentation on this struct should be copied to the asynchronous version.
-pub struct WalletSync<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
-where
- W::Target: WalletSourceSync + MaybeSend,
-{
- wallet: Wallet<WalletSourceSyncWrapper<W>, L>,
-}
-
-impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> WalletSync<W, L>
-where
- W::Target: WalletSourceSync + MaybeSend,
-{
- /// Constructs a new [`WalletSync`] instance.
- pub fn new(source: W, logger: L) -> Self {
- Self { wallet: Wallet::new(WalletSourceSyncWrapper(source), logger) }
- }
-}
-
-impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> CoinSelectionSourceSync
- for WalletSync<W, L>
-where
- W::Target: WalletSourceSync + MaybeSend + MaybeSync,
-{
- fn select_confirmed_utxos(
- &self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
- target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> Result<CoinSelection, ()> {
- let fut = self.wallet.select_confirmed_utxos(
- claim_id,
- must_spend,
- must_pay_to,
- target_feerate_sat_per_1000_weight,
- max_tx_weight,
- );
- let mut waker = dummy_waker();
- let mut ctx = task::Context::from_waker(&mut waker);
- match pin!(fut).poll(&mut ctx) {
- task::Poll::Ready(result) => result,
- task::Poll::Pending => {
- unreachable!(
- "Wallet::select_confirmed_utxos should not be pending in a sync context"
- );
- },
- }
- }
-
- fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
- let fut = self.wallet.sign_psbt(psbt);
- let mut waker = dummy_waker();
- let mut ctx = task::Context::from_waker(&mut waker);
- match pin!(fut).poll(&mut ctx) {
- task::Poll::Ready(result) => result,
- task::Poll::Pending => {
- unreachable!("Wallet::sign_psbt should not be pending in a sync context");
- },
- }
- }
-}
-
-/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
-/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
-/// which most wallets should be able to satisfy. Otherwise, consider implementing
-/// [`WalletSourceSync`], which can provide a default implementation of this trait when used with
-/// [`WalletSync`].
-///
-/// For an asynchronous version of this trait, see [`CoinSelectionSource`].
-// Note that updates to documentation on this trait should be copied to the asynchronous version.
-pub trait CoinSelectionSourceSync {
- /// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
- /// available to spend. Implementations are free to pick their coin selection algorithm of
- /// choice, as long as the following requirements are met:
- ///
- /// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
- /// throughout coin selection, but must not be returned as part of the result.
- /// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
- /// throughout coin selection. In some cases, like when funding an anchor transaction, this
- /// set is empty. Implementations should ensure they handle this correctly on their end,
- /// e.g., Bitcoin Core's `fundrawtransaction` RPC requires at least one output to be
- /// provided, in which case a zero-value empty OP_RETURN output can be used instead.
- /// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
- /// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
- /// 4. The final transaction must have a weight smaller than `max_tx_weight`; if this
- /// constraint can't be met, return an `Err`. In the case of counterparty-signed HTLC
- /// transactions, we will remove a chunk of HTLCs and try your algorithm again. As for
- /// anchor transactions, we will try your coin selection again with the same input-output
- /// set when you call [`ChannelMonitor::rebroadcast_pending_claims`], as anchor transactions
- /// cannot be downsized.
- ///
- /// Implementations must take note that [`Input::satisfaction_weight`] only tracks the weight of
- /// the input's `script_sig` and `witness`. Some wallets, like Bitcoin Core's, may require
- /// providing the full input weight. Failing to do so may lead to underestimating fee bumps and
- /// delaying block inclusion.
- ///
- /// The `claim_id` must map to the set of external UTXOs assigned to the claim, such that they
- /// can be re-used within new fee-bumped iterations of the original claiming transaction,
- /// ensuring that claims don't double spend each other. If a specific `claim_id` has never had a
- /// transaction associated with it, and all of the available UTXOs have already been assigned to
- /// other claims, implementations must be willing to double spend their UTXOs. The choice of
- /// which UTXOs to double spend is left to the implementation, but it must strive to keep the
- /// set of other claims being double spent to a minimum.
- ///
- /// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims
- fn select_confirmed_utxos(
- &self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
- target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> Result<CoinSelection, ()>;
-
- /// Signs and provides the full witness for all inputs within the transaction known to the
- /// trait (i.e., any provided via [`CoinSelectionSourceSync::select_confirmed_utxos`]).
- ///
- /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
- /// unsigned transaction and then sign it with your wallet.
- fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
-}
-
-struct CoinSelectionSourceSyncWrapper<T: Deref>(T)
-where
- T::Target: CoinSelectionSourceSync;
-
-// Implement `Deref` directly on CoinSelectionSourceSyncWrapper so that it can be used directly
-// below, rather than via a wrapper.
-impl<T: Deref> Deref for CoinSelectionSourceSyncWrapper<T>
-where
- T::Target: CoinSelectionSourceSync,
-{
- type Target = Self;
- fn deref(&self) -> &Self {
- self
- }
-}
-
-impl<T: Deref> CoinSelectionSource for CoinSelectionSourceSyncWrapper<T>
-where
- T::Target: CoinSelectionSourceSync,
-{
- fn select_confirmed_utxos<'a>(
- &'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
- target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
- ) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
- let coins = self.0.select_confirmed_utxos(
- claim_id,
- must_spend,
- must_pay_to,
- target_feerate_sat_per_1000_weight,
- max_tx_weight,
- );
- async move { coins }
- }
-
- fn sign_psbt<'a>(
- &'a self, psbt: Psbt,
- ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
- let psbt = self.0.sign_psbt(psbt);
- async move { psbt }
- }
-}
-
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
/// [`CoinSelectionSourceSync`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
/// Replace-By-Fee (RBF).
diff --git a/lightning/src/ln/async_signer_tests.rs b/lightning/src/ln/async_signer_tests.rs
index f34a2b3..558812a 100644
--- a/lightning/src/ln/async_signer_tests.rs
+++ b/lightning/src/ln/async_signer_tests.rs
@@ -10,7 +10,6 @@
//! Tests for asynchronous signing. These tests verify that the channel state machine behaves
//! properly with a signer implementation that asynchronously derives signatures.
-use crate::events::bump_transaction::sync::WalletSourceSync;
use crate::ln::splicing_tests::{initiate_splice_out, negotiate_splice_tx};
use crate::prelude::*;
use crate::util::ser::Writeable;
@@ -31,6 +30,7 @@ use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::SignerProvider;
use crate::util::logger::Logger;
use crate::util::test_channel_signer::SignerOp;
+use crate::util::wallet_utils::WalletSourceSync;
#[test]
fn test_open_channel() {
diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs
index 66a0147..5d5075d 100644
--- a/lightning/src/ln/functional_test_utils.rs
+++ b/lightning/src/ln/functional_test_utils.rs
@@ -14,9 +14,7 @@ use crate::blinded_path::payment::DummyTlvs;
use crate::chain::channelmonitor::ChannelMonitor;
use crate::chain::transaction::OutPoint;
use crate::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
-use crate::events::bump_transaction::sync::{
- BumpTransactionEventHandlerSync, WalletSourceSync, WalletSync,
-};
+use crate::events::bump_transaction::sync::BumpTransactionEventHandlerSync;
use crate::events::bump_transaction::BumpTransactionEvent;
use crate::events::{
ClaimedHTLC, ClosureReason, Event, HTLCHandlingFailureType, PaidBolt12Invoice, PathFailure,
@@ -54,6 +52,7 @@ use crate::util::test_channel_signer::SignerOp;
use crate::util::test_channel_signer::TestChannelSigner;
use crate::util::test_utils::{self, TestLogger};
use crate::util::test_utils::{TestChainMonitor, TestKeysInterface, TestScorer};
+use crate::util::wallet_utils::{WalletSourceSync, WalletSync};
use bitcoin::amount::Amount;
use bitcoin::block::{Block, Header, Version as BlockVersion};
diff --git a/lightning/src/ln/funding.rs b/lightning/src/ln/funding.rs
index c33ca8c..65b0715 100644
--- a/lightning/src/ln/funding.rs
+++ b/lightning/src/ln/funding.rs
@@ -18,7 +18,6 @@ use bitcoin::{
use core::ops::Deref;
-use crate::events::bump_transaction::sync::CoinSelectionSourceSync;
use crate::ln::chan_utils::{
make_funding_redeemscript, BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT,
FUNDING_TRANSACTION_WITNESS_WEIGHT,
@@ -30,7 +29,9 @@ use crate::ln::LN_MAX_MSG_LEN;
use crate::prelude::*;
use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
use crate::util::async_poll::MaybeSend;
-use crate::util::wallet_utils::{CoinSelection, CoinSelectionSource, Input, Utxo};
+use crate::util::wallet_utils::{
+ CoinSelection, CoinSelectionSource, CoinSelectionSourceSync, Input, Utxo,
+};
/// A template for contributing to a channel's splice funding transaction.
///
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index cc422d6..90190ee 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -13,7 +13,6 @@ use crate::chain::chaininterface::{TransactionType, FEERATE_FLOOR_SATS_PER_KW};
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS};
use crate::chain::transaction::OutPoint;
use crate::chain::ChannelMonitorUpdateStatus;
-use crate::events::bump_transaction::sync::{WalletSourceSync, WalletSync};
use crate::events::{ClosureReason, Event, FundingInfo, HTLCHandlingFailureType};
use crate::ln::chan_utils;
use crate::ln::channel::CHANNEL_ANNOUNCEMENT_PROPAGATION_DELAY;
@@ -26,6 +25,7 @@ use crate::ln::types::ChannelId;
use crate::routing::router::{PaymentParameters, RouteParameters};
use crate::util::errors::APIError;
use crate::util::ser::Writeable;
+use crate::util::wallet_utils::{WalletSourceSync, WalletSync};
use crate::sync::Arc;
diff --git a/lightning/src/util/test_utils.rs b/lightning/src/util/test_utils.rs
index 48f506e..22be436 100644
--- a/lightning/src/util/test_utils.rs
+++ b/lightning/src/util/test_utils.rs
@@ -21,7 +21,6 @@ use crate::chain::channelmonitor::{
};
use crate::chain::transaction::OutPoint;
use crate::chain::WatchedOutput;
-use crate::events::bump_transaction::sync::WalletSourceSync;
#[cfg(any(test, feature = "_externalize_tests"))]
use crate::ln::chan_utils::CommitmentTransaction;
use crate::ln::channel_state::ChannelDetails;
@@ -61,7 +60,7 @@ use crate::util::persist::{KVStore, KVStoreSync, MonitorName};
use crate::util::ser::{Readable, ReadableArgs, Writeable, Writer};
use crate::util::test_channel_signer::{EnforcementState, TestChannelSigner};
use crate::util::wakers::Notifier;
-use crate::util::wallet_utils::{ConfirmedUtxo, Utxo};
+use crate::util::wallet_utils::{ConfirmedUtxo, Utxo, WalletSourceSync};
use bitcoin::amount::Amount;
use bitcoin::block::Block;
diff --git a/lightning/src/util/wallet_utils.rs b/lightning/src/util/wallet_utils.rs
index cf754d1..f247a8a 100644
--- a/lightning/src/util/wallet_utils.rs
+++ b/lightning/src/util/wallet_utils.rs
@@ -11,6 +11,8 @@
use core::future::Future;
use core::ops::Deref;
+use core::pin::pin;
+use core::task;
use crate::chain::chaininterface::fee_for_weight;
use crate::chain::ClaimId;
@@ -23,7 +25,7 @@ use crate::ln::funding::FundingTxInput;
use crate::prelude::*;
use crate::sign::{P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
use crate::sync::Mutex;
-use crate::util::async_poll::{MaybeSend, MaybeSync};
+use crate::util::async_poll::{dummy_waker, MaybeSend, MaybeSync};
use crate::util::hash_tables::{new_hash_map, HashMap};
use crate::util::logger::Logger;
@@ -172,11 +174,9 @@ impl CoinSelection {
/// which most wallets should be able to satisfy. Otherwise, consider implementing [`WalletSource`],
/// which can provide a default implementation of this trait when used with [`Wallet`].
///
-/// For a synchronous version of this trait, see [`sync::CoinSelectionSourceSync`].
+/// For a synchronous version of this trait, see [`CoinSelectionSourceSync`].
///
/// This is not exported to bindings users as async is only supported in Rust.
-///
-/// [`sync::CoinSelectionSourceSync`]: crate::events::bump_transaction::sync::CoinSelectionSourceSync
// Note that updates to documentation on this trait should be copied to the synchronous version.
pub trait CoinSelectionSource {
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
@@ -233,11 +233,9 @@ pub trait CoinSelectionSource {
/// An alternative to [`CoinSelectionSource`] that can be implemented and used along [`Wallet`] to
/// provide a default implementation to [`CoinSelectionSource`].
///
-/// For a synchronous version of this trait, see [`sync::WalletSourceSync`].
+/// For a synchronous version of this trait, see [`WalletSourceSync`].
///
/// This is not exported to bindings users as async is only supported in Rust.
-///
-/// [`sync::WalletSourceSync`]: crate::events::bump_transaction::sync::WalletSourceSync
// Note that updates to documentation on this trait should be copied to the synchronous version.
pub trait WalletSource {
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
@@ -274,11 +272,9 @@ pub trait WalletSource {
/// that would avoid conflicting double spends. If not enough UTXOs are available to do so,
/// conflicting double spends may happen.
///
-/// For a synchronous version of this wrapper, see [`sync::WalletSync`].
+/// For a synchronous version of this wrapper, see [`WalletSync`].
///
/// This is not exported to bindings users as async is only supported in Rust.
-///
-/// [`sync::WalletSync`]: crate::events::bump_transaction::sync::WalletSync
// Note that updates to documentation on this struct should be copied to the synchronous version.
pub struct Wallet<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
where
@@ -544,3 +540,242 @@ where
self.source.sign_psbt(psbt)
}
}
+
+/// An alternative to [`CoinSelectionSourceSync`] that can be implemented and used along
+/// [`WalletSync`] to provide a default implementation to [`CoinSelectionSourceSync`].
+///
+/// For an asynchronous version of this trait, see [`WalletSource`].
+// Note that updates to documentation on this trait should be copied to the asynchronous version.
+pub trait WalletSourceSync {
+ /// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
+ fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
+
+ /// Returns the previous transaction containing the UTXO referenced by the outpoint.
+ fn get_prevtx(&self, outpoint: OutPoint) -> Result<Transaction, ()>;
+
+ /// Returns a script to use for change above dust resulting from a successful coin selection
+ /// attempt.
+ fn get_change_script(&self) -> Result<ScriptBuf, ()>;
+
+ /// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
+ /// the transaction known to the wallet (i.e., any provided via
+ /// [`WalletSource::list_confirmed_utxos`]).
+ ///
+ /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
+ /// unsigned transaction and then sign it with your wallet.
+ ///
+ /// [`TxIn::script_sig`]: bitcoin::TxIn::script_sig
+ /// [`TxIn::witness`]: bitcoin::TxIn::witness
+ fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
+}
+
+struct WalletSourceSyncWrapper<T: Deref>(T)
+where
+ T::Target: WalletSourceSync;
+
+// Implement `Deref` directly on WalletSourceSyncWrapper so that it can be used directly
+// below, rather than via a wrapper.
+impl<T: Deref> Deref for WalletSourceSyncWrapper<T>
+where
+ T::Target: WalletSourceSync,
+{
+ type Target = Self;
+ fn deref(&self) -> &Self {
+ self
+ }
+}
+
+impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
+where
+ T::Target: WalletSourceSync,
+{
+ fn list_confirmed_utxos<'a>(
+ &'a self,
+ ) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a {
+ let utxos = self.0.list_confirmed_utxos();
+ async move { utxos }
+ }
+
+ fn get_prevtx<'a>(
+ &'a self, outpoint: OutPoint,
+ ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
+ let prevtx = self.0.get_prevtx(outpoint);
+ Box::pin(async move { prevtx })
+ }
+
+ fn get_change_script<'a>(
+ &'a self,
+ ) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a {
+ let script = self.0.get_change_script();
+ async move { script }
+ }
+
+ fn sign_psbt<'a>(
+ &'a self, psbt: Psbt,
+ ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
+ let signed_psbt = self.0.sign_psbt(psbt);
+ async move { signed_psbt }
+ }
+}
+
+/// A wrapper over [`WalletSourceSync`] that implements [`CoinSelectionSourceSync`] by preferring
+/// UTXOs that would avoid conflicting double spends. If not enough UTXOs are available to do so,
+/// conflicting double spends may happen.
+///
+/// For an asynchronous version of this wrapper, see [`Wallet`].
+// Note that updates to documentation on this struct should be copied to the asynchronous version.
+pub struct WalletSync<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
+where
+ W::Target: WalletSourceSync + MaybeSend,
+{
+ wallet: Wallet<WalletSourceSyncWrapper<W>, L>,
+}
+
+impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> WalletSync<W, L>
+where
+ W::Target: WalletSourceSync + MaybeSend,
+{
+ /// Constructs a new [`WalletSync`] instance.
+ pub fn new(source: W, logger: L) -> Self {
+ Self { wallet: Wallet::new(WalletSourceSyncWrapper(source), logger) }
+ }
+}
+
+impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> CoinSelectionSourceSync
+ for WalletSync<W, L>
+where
+ W::Target: WalletSourceSync + MaybeSend + MaybeSync,
+{
+ fn select_confirmed_utxos(
+ &self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
+ target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
+ ) -> Result<CoinSelection, ()> {
+ let fut = self.wallet.select_confirmed_utxos(
+ claim_id,
+ must_spend,
+ must_pay_to,
+ target_feerate_sat_per_1000_weight,
+ max_tx_weight,
+ );
+ let mut waker = dummy_waker();
+ let mut ctx = task::Context::from_waker(&mut waker);
+ match pin!(fut).poll(&mut ctx) {
+ task::Poll::Ready(result) => result,
+ task::Poll::Pending => {
+ unreachable!(
+ "Wallet::select_confirmed_utxos should not be pending in a sync context"
+ );
+ },
+ }
+ }
+
+ fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
+ let fut = self.wallet.sign_psbt(psbt);
+ let mut waker = dummy_waker();
+ let mut ctx = task::Context::from_waker(&mut waker);
+ match pin!(fut).poll(&mut ctx) {
+ task::Poll::Ready(result) => result,
+ task::Poll::Pending => {
+ unreachable!("Wallet::sign_psbt should not be pending in a sync context");
+ },
+ }
+ }
+}
+
+/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
+/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
+/// which most wallets should be able to satisfy. Otherwise, consider implementing
+/// [`WalletSourceSync`], which can provide a default implementation of this trait when used with
+/// [`WalletSync`].
+///
+/// For an asynchronous version of this trait, see [`CoinSelectionSource`].
+// Note that updates to documentation on this trait should be copied to the asynchronous version.
+pub trait CoinSelectionSourceSync {
+ /// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
+ /// available to spend. Implementations are free to pick their coin selection algorithm of
+ /// choice, as long as the following requirements are met:
+ ///
+ /// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
+ /// throughout coin selection, but must not be returned as part of the result.
+ /// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
+ /// throughout coin selection. In some cases, like when funding an anchor transaction, this
+ /// set is empty. Implementations should ensure they handle this correctly on their end,
+ /// e.g., Bitcoin Core's `fundrawtransaction` RPC requires at least one output to be
+ /// provided, in which case a zero-value empty OP_RETURN output can be used instead.
+ /// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
+ /// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
+ /// 4. The final transaction must have a weight smaller than `max_tx_weight`; if this
+ /// constraint can't be met, return an `Err`. In the case of counterparty-signed HTLC
+ /// transactions, we will remove a chunk of HTLCs and try your algorithm again. As for
+ /// anchor transactions, we will try your coin selection again with the same input-output
+ /// set when you call [`ChannelMonitor::rebroadcast_pending_claims`], as anchor transactions
+ /// cannot be downsized.
+ ///
+ /// Implementations must take note that [`Input::satisfaction_weight`] only tracks the weight of
+ /// the input's `script_sig` and `witness`. Some wallets, like Bitcoin Core's, may require
+ /// providing the full input weight. Failing to do so may lead to underestimating fee bumps and
+ /// delaying block inclusion.
+ ///
+ /// The `claim_id` must map to the set of external UTXOs assigned to the claim, such that they
+ /// can be re-used within new fee-bumped iterations of the original claiming transaction,
+ /// ensuring that claims don't double spend each other. If a specific `claim_id` has never had a
+ /// transaction associated with it, and all of the available UTXOs have already been assigned to
+ /// other claims, implementations must be willing to double spend their UTXOs. The choice of
+ /// which UTXOs to double spend is left to the implementation, but it must strive to keep the
+ /// set of other claims being double spent to a minimum.
+ ///
+ /// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims
+ fn select_confirmed_utxos(
+ &self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
+ target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
+ ) -> Result<CoinSelection, ()>;
+
+ /// Signs and provides the full witness for all inputs within the transaction known to the
+ /// trait (i.e., any provided via [`CoinSelectionSourceSync::select_confirmed_utxos`]).
+ ///
+ /// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
+ /// unsigned transaction and then sign it with your wallet.
+ fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
+}
+
+pub(crate) struct CoinSelectionSourceSyncWrapper<T: Deref>(pub(crate) T)
+where
+ T::Target: CoinSelectionSourceSync;
+
+// Implement `Deref` directly on CoinSelectionSourceSyncWrapper so that it can be used directly
+// below, rather than via a wrapper.
+impl<T: Deref> Deref for CoinSelectionSourceSyncWrapper<T>
+where
+ T::Target: CoinSelectionSourceSync,
+{
+ type Target = Self;
+ fn deref(&self) -> &Self {
+ self
+ }
+}
+
+impl<T: Deref> CoinSelectionSource for CoinSelectionSourceSyncWrapper<T>
+where
+ T::Target: CoinSelectionSourceSync,
+{
+ fn select_confirmed_utxos<'a>(
+ &'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
+ target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
+ ) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
+ let coins = self.0.select_confirmed_utxos(
+ claim_id,
+ must_spend,
+ must_pay_to,
+ target_feerate_sat_per_1000_weight,
+ max_tx_weight,
+ );
+ async move { coins }
+ }
+
+ fn sign_psbt<'a>(
+ &'a self, psbt: Psbt,
+ ) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
+ let psbt = self.0.sign_psbt(psbt);
+ async move { psbt }
+ }
+}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.