Skip to content

Commit 8bbc5ec

Browse files
authored
Merge pull request #4382 from jkczyz/2026-01-wallet-utils
2 parents 2203e2d + 9f6c678 commit 8bbc5ec

File tree

16 files changed

+1169
-1112
lines changed

16 files changed

+1169
-1112
lines changed

fuzz/src/chanmon_consistency.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ use lightning::chain::{
4747
chainmonitor, channelmonitor, BestBlock, ChannelMonitorUpdateStatus, Confirm, Watch,
4848
};
4949
use lightning::events;
50-
use lightning::events::bump_transaction::sync::{WalletSourceSync, WalletSync};
5150
use lightning::ln::channel::{
5251
FEE_SPIKE_BUFFER_FEE_INCREASE_MULTIPLE, MAX_STD_OUTPUT_DUST_LIMIT_SATOSHIS,
5352
};
@@ -83,6 +82,7 @@ use lightning::util::logger::Logger;
8382
use lightning::util::ser::{LengthReadable, ReadableArgs, Writeable, Writer};
8483
use lightning::util::test_channel_signer::{EnforcementState, SignerOp, TestChannelSigner};
8584
use lightning::util::test_utils::TestWalletSource;
85+
use lightning::util::wallet_utils::{WalletSourceSync, WalletSync};
8686

8787
use lightning_invoice::RawBolt11Invoice;
8888

fuzz/src/full_stack.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ use lightning::chain::chaininterface::{
4040
use lightning::chain::chainmonitor;
4141
use lightning::chain::transaction::OutPoint;
4242
use lightning::chain::{BestBlock, ChannelMonitorUpdateStatus, Confirm, Listen};
43-
use lightning::events::bump_transaction::sync::{WalletSourceSync, WalletSync};
4443
use lightning::events::Event;
4544
use lightning::ln::channel_state::ChannelDetails;
4645
use lightning::ln::channelmanager::{ChainParameters, ChannelManager, InterceptId, PaymentId};
@@ -71,6 +70,7 @@ use lightning::util::logger::Logger;
7170
use lightning::util::ser::{Readable, Writeable};
7271
use lightning::util::test_channel_signer::{EnforcementState, TestChannelSigner};
7372
use lightning::util::test_utils::TestWalletSource;
73+
use lightning::util::wallet_utils::{WalletSourceSync, WalletSync};
7474

7575
use lightning_invoice::RawBolt11Invoice;
7676

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ use lightning_0_0_125::routing::router as router_0_0_125;
4646
use lightning_0_0_125::util::ser::Writeable as _;
4747

4848
use lightning::chain::channelmonitor::{ANTI_REORG_DELAY, HTLC_FAIL_BACK_BUFFER};
49-
use lightning::events::bump_transaction::sync::WalletSourceSync;
5049
use lightning::events::{ClosureReason, Event, HTLCHandlingFailureType};
5150
use lightning::ln::functional_test_utils::*;
5251
use lightning::ln::msgs::BaseMessageHandler as _;
@@ -55,6 +54,7 @@ use lightning::ln::msgs::MessageSendEvent;
5554
use lightning::ln::splicing_tests::*;
5655
use lightning::ln::types::ChannelId;
5756
use lightning::sign::OutputSpender;
57+
use lightning::util::wallet_utils::WalletSourceSync;
5858

5959
use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
6060

lightning/src/events/bump_transaction/mod.rs

Lines changed: 15 additions & 523 deletions
Large diffs are not rendered by default.

lightning/src/events/bump_transaction/sync.rs

Lines changed: 3 additions & 249 deletions
Original file line numberDiff line numberDiff line change
@@ -15,258 +15,12 @@ use core::pin::pin;
1515
use core::task;
1616

1717
use crate::chain::chaininterface::BroadcasterInterface;
18-
use crate::chain::ClaimId;
19-
use crate::prelude::*;
2018
use crate::sign::SignerProvider;
21-
use crate::util::async_poll::{dummy_waker, MaybeSend, MaybeSync};
19+
use crate::util::async_poll::dummy_waker;
2220
use crate::util::logger::Logger;
21+
use crate::util::wallet_utils::{CoinSelectionSourceSync, CoinSelectionSourceSyncWrapper};
2322

24-
use bitcoin::{OutPoint, Psbt, ScriptBuf, Transaction, TxOut};
25-
26-
use super::BumpTransactionEvent;
27-
use super::{
28-
BumpTransactionEventHandler, CoinSelection, CoinSelectionSource, Input, Utxo, Wallet,
29-
WalletSource,
30-
};
31-
32-
/// An alternative to [`CoinSelectionSourceSync`] that can be implemented and used along
33-
/// [`WalletSync`] to provide a default implementation to [`CoinSelectionSourceSync`].
34-
///
35-
/// For an asynchronous version of this trait, see [`WalletSource`].
36-
// Note that updates to documentation on this trait should be copied to the asynchronous version.
37-
pub trait WalletSourceSync {
38-
/// Returns all UTXOs, with at least 1 confirmation each, that are available to spend.
39-
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()>;
40-
41-
/// Returns the previous transaction containing the UTXO referenced by the outpoint.
42-
fn get_prevtx(&self, outpoint: OutPoint) -> Result<Transaction, ()>;
43-
44-
/// Returns a script to use for change above dust resulting from a successful coin selection
45-
/// attempt.
46-
fn get_change_script(&self) -> Result<ScriptBuf, ()>;
47-
48-
/// Signs and provides the full [`TxIn::script_sig`] and [`TxIn::witness`] for all inputs within
49-
/// the transaction known to the wallet (i.e., any provided via
50-
/// [`WalletSource::list_confirmed_utxos`]).
51-
///
52-
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
53-
/// unsigned transaction and then sign it with your wallet.
54-
///
55-
/// [`TxIn::script_sig`]: bitcoin::TxIn::script_sig
56-
/// [`TxIn::witness`]: bitcoin::TxIn::witness
57-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
58-
}
59-
60-
pub(crate) struct WalletSourceSyncWrapper<T: Deref>(T)
61-
where
62-
T::Target: WalletSourceSync;
63-
64-
// Implement `Deref` directly on WalletSourceSyncWrapper so that it can be used directly
65-
// below, rather than via a wrapper.
66-
impl<T: Deref> Deref for WalletSourceSyncWrapper<T>
67-
where
68-
T::Target: WalletSourceSync,
69-
{
70-
type Target = Self;
71-
fn deref(&self) -> &Self {
72-
self
73-
}
74-
}
75-
76-
impl<T: Deref> WalletSource for WalletSourceSyncWrapper<T>
77-
where
78-
T::Target: WalletSourceSync,
79-
{
80-
fn list_confirmed_utxos<'a>(
81-
&'a self,
82-
) -> impl Future<Output = Result<Vec<Utxo>, ()>> + MaybeSend + 'a {
83-
let utxos = self.0.list_confirmed_utxos();
84-
async move { utxos }
85-
}
86-
87-
fn get_prevtx<'a>(
88-
&'a self, outpoint: OutPoint,
89-
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
90-
let prevtx = self.0.get_prevtx(outpoint);
91-
Box::pin(async move { prevtx })
92-
}
93-
94-
fn get_change_script<'a>(
95-
&'a self,
96-
) -> impl Future<Output = Result<ScriptBuf, ()>> + MaybeSend + 'a {
97-
let script = self.0.get_change_script();
98-
async move { script }
99-
}
100-
101-
fn sign_psbt<'a>(
102-
&'a self, psbt: Psbt,
103-
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
104-
let signed_psbt = self.0.sign_psbt(psbt);
105-
async move { signed_psbt }
106-
}
107-
}
108-
109-
/// A wrapper over [`WalletSourceSync`] that implements [`CoinSelectionSourceSync`] by preferring
110-
/// UTXOs that would avoid conflicting double spends. If not enough UTXOs are available to do so,
111-
/// conflicting double spends may happen.
112-
///
113-
/// For an asynchronous version of this wrapper, see [`Wallet`].
114-
// Note that updates to documentation on this struct should be copied to the asynchronous version.
115-
pub struct WalletSync<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend>
116-
where
117-
W::Target: WalletSourceSync + MaybeSend,
118-
{
119-
wallet: Wallet<WalletSourceSyncWrapper<W>, L>,
120-
}
121-
122-
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> WalletSync<W, L>
123-
where
124-
W::Target: WalletSourceSync + MaybeSend,
125-
{
126-
/// Constructs a new [`WalletSync`] instance.
127-
pub fn new(source: W, logger: L) -> Self {
128-
Self { wallet: Wallet::new(WalletSourceSyncWrapper(source), logger) }
129-
}
130-
}
131-
132-
impl<W: Deref + MaybeSync + MaybeSend, L: Logger + MaybeSync + MaybeSend> CoinSelectionSourceSync
133-
for WalletSync<W, L>
134-
where
135-
W::Target: WalletSourceSync + MaybeSend + MaybeSync,
136-
{
137-
fn select_confirmed_utxos(
138-
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
139-
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
140-
) -> Result<CoinSelection, ()> {
141-
let fut = self.wallet.select_confirmed_utxos(
142-
claim_id,
143-
must_spend,
144-
must_pay_to,
145-
target_feerate_sat_per_1000_weight,
146-
max_tx_weight,
147-
);
148-
let mut waker = dummy_waker();
149-
let mut ctx = task::Context::from_waker(&mut waker);
150-
match pin!(fut).poll(&mut ctx) {
151-
task::Poll::Ready(result) => result,
152-
task::Poll::Pending => {
153-
unreachable!(
154-
"Wallet::select_confirmed_utxos should not be pending in a sync context"
155-
);
156-
},
157-
}
158-
}
159-
160-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()> {
161-
let fut = self.wallet.sign_psbt(psbt);
162-
let mut waker = dummy_waker();
163-
let mut ctx = task::Context::from_waker(&mut waker);
164-
match pin!(fut).poll(&mut ctx) {
165-
task::Poll::Ready(result) => result,
166-
task::Poll::Pending => {
167-
unreachable!("Wallet::sign_psbt should not be pending in a sync context");
168-
},
169-
}
170-
}
171-
}
172-
173-
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
174-
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
175-
/// which most wallets should be able to satisfy. Otherwise, consider implementing
176-
/// [`WalletSourceSync`], which can provide a default implementation of this trait when used with
177-
/// [`WalletSync`].
178-
///
179-
/// For an asynchronous version of this trait, see [`CoinSelectionSource`].
180-
// Note that updates to documentation on this trait should be copied to the asynchronous version.
181-
pub trait CoinSelectionSourceSync {
182-
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
183-
/// available to spend. Implementations are free to pick their coin selection algorithm of
184-
/// choice, as long as the following requirements are met:
185-
///
186-
/// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
187-
/// throughout coin selection, but must not be returned as part of the result.
188-
/// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
189-
/// throughout coin selection. In some cases, like when funding an anchor transaction, this
190-
/// set is empty. Implementations should ensure they handle this correctly on their end,
191-
/// e.g., Bitcoin Core's `fundrawtransaction` RPC requires at least one output to be
192-
/// provided, in which case a zero-value empty OP_RETURN output can be used instead.
193-
/// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
194-
/// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
195-
/// 4. The final transaction must have a weight smaller than `max_tx_weight`; if this
196-
/// constraint can't be met, return an `Err`. In the case of counterparty-signed HTLC
197-
/// transactions, we will remove a chunk of HTLCs and try your algorithm again. As for
198-
/// anchor transactions, we will try your coin selection again with the same input-output
199-
/// set when you call [`ChannelMonitor::rebroadcast_pending_claims`], as anchor transactions
200-
/// cannot be downsized.
201-
///
202-
/// Implementations must take note that [`Input::satisfaction_weight`] only tracks the weight of
203-
/// the input's `script_sig` and `witness`. Some wallets, like Bitcoin Core's, may require
204-
/// providing the full input weight. Failing to do so may lead to underestimating fee bumps and
205-
/// delaying block inclusion.
206-
///
207-
/// The `claim_id` must map to the set of external UTXOs assigned to the claim, such that they
208-
/// can be re-used within new fee-bumped iterations of the original claiming transaction,
209-
/// ensuring that claims don't double spend each other. If a specific `claim_id` has never had a
210-
/// transaction associated with it, and all of the available UTXOs have already been assigned to
211-
/// other claims, implementations must be willing to double spend their UTXOs. The choice of
212-
/// which UTXOs to double spend is left to the implementation, but it must strive to keep the
213-
/// set of other claims being double spent to a minimum.
214-
///
215-
/// [`ChannelMonitor::rebroadcast_pending_claims`]: crate::chain::channelmonitor::ChannelMonitor::rebroadcast_pending_claims
216-
fn select_confirmed_utxos(
217-
&self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &[TxOut],
218-
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
219-
) -> Result<CoinSelection, ()>;
220-
221-
/// Signs and provides the full witness for all inputs within the transaction known to the
222-
/// trait (i.e., any provided via [`CoinSelectionSourceSync::select_confirmed_utxos`]).
223-
///
224-
/// If your wallet does not support signing PSBTs you can call `psbt.extract_tx()` to get the
225-
/// unsigned transaction and then sign it with your wallet.
226-
fn sign_psbt(&self, psbt: Psbt) -> Result<Transaction, ()>;
227-
}
228-
229-
struct CoinSelectionSourceSyncWrapper<T: Deref>(T)
230-
where
231-
T::Target: CoinSelectionSourceSync;
232-
233-
// Implement `Deref` directly on CoinSelectionSourceSyncWrapper so that it can be used directly
234-
// below, rather than via a wrapper.
235-
impl<T: Deref> Deref for CoinSelectionSourceSyncWrapper<T>
236-
where
237-
T::Target: CoinSelectionSourceSync,
238-
{
239-
type Target = Self;
240-
fn deref(&self) -> &Self {
241-
self
242-
}
243-
}
244-
245-
impl<T: Deref> CoinSelectionSource for CoinSelectionSourceSyncWrapper<T>
246-
where
247-
T::Target: CoinSelectionSourceSync,
248-
{
249-
fn select_confirmed_utxos<'a>(
250-
&'a self, claim_id: Option<ClaimId>, must_spend: Vec<Input>, must_pay_to: &'a [TxOut],
251-
target_feerate_sat_per_1000_weight: u32, max_tx_weight: u64,
252-
) -> impl Future<Output = Result<CoinSelection, ()>> + MaybeSend + 'a {
253-
let coins = self.0.select_confirmed_utxos(
254-
claim_id,
255-
must_spend,
256-
must_pay_to,
257-
target_feerate_sat_per_1000_weight,
258-
max_tx_weight,
259-
);
260-
async move { coins }
261-
}
262-
263-
fn sign_psbt<'a>(
264-
&'a self, psbt: Psbt,
265-
) -> impl Future<Output = Result<Transaction, ()>> + MaybeSend + 'a {
266-
let psbt = self.0.sign_psbt(psbt);
267-
async move { psbt }
268-
}
269-
}
23+
use super::{BumpTransactionEvent, BumpTransactionEventHandler};
27024

27125
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
27226
/// [`CoinSelectionSourceSync`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or

lightning/src/ln/async_signer_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//! Tests for asynchronous signing. These tests verify that the channel state machine behaves
1111
//! properly with a signer implementation that asynchronously derives signatures.
1212
13-
use crate::events::bump_transaction::sync::WalletSourceSync;
1413
use crate::ln::splicing_tests::{initiate_splice_out, negotiate_splice_tx};
1514
use crate::prelude::*;
1615
use crate::util::ser::Writeable;
@@ -31,6 +30,7 @@ use crate::sign::ecdsa::EcdsaChannelSigner;
3130
use crate::sign::SignerProvider;
3231
use crate::util::logger::Logger;
3332
use crate::util::test_channel_signer::SignerOp;
33+
use crate::util::wallet_utils::WalletSourceSync;
3434

3535
#[test]
3636
fn test_open_channel() {

lightning/src/ln/channel.rs

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ use crate::chain::channelmonitor::{
3636
};
3737
use crate::chain::transaction::{OutPoint, TransactionData};
3838
use crate::chain::BestBlock;
39-
use crate::events::bump_transaction::Input;
4039
use crate::events::{ClosureReason, FundingInfo};
4140
use crate::ln::chan_utils;
4241
use crate::ln::chan_utils::{
@@ -84,6 +83,7 @@ use crate::util::errors::APIError;
8483
use crate::util::logger::{Logger, Record, WithContext};
8584
use crate::util::scid_utils::{block_from_scid, scid_from_parts};
8685
use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, Writeable, Writer};
86+
use crate::util::wallet_utils::Input;
8787
use crate::{impl_readable_for_vec, impl_writeable_for_vec};
8888

8989
use alloc::collections::{btree_map, BTreeMap};
@@ -12162,7 +12162,7 @@ where
1216212162
}
1216312163

1216412164
/// Initiate splicing.
12165-
pub fn splice_channel(&mut self, feerate: FeeRate) -> Result<FundingTemplate, APIError> {
12165+
pub fn splice_channel(&self, feerate: FeeRate) -> Result<FundingTemplate, APIError> {
1216612166
if self.holder_commitment_point.current_point().is_none() {
1216712167
return Err(APIError::APIMisuseError {
1216812168
err: format!(
@@ -12213,17 +12213,18 @@ where
1221312213
satisfaction_weight: EMPTY_SCRIPT_SIG_WEIGHT + FUNDING_TRANSACTION_WITNESS_WEIGHT,
1221412214
};
1221512215

12216-
Ok(FundingTemplate::new(Some(shared_input), feerate, true))
12216+
Ok(FundingTemplate::new(Some(shared_input), feerate))
1221712217
}
1221812218

1221912219
pub fn funding_contributed<L: Logger>(
1222012220
&mut self, contribution: FundingContribution, locktime: LockTime, logger: &L,
1222112221
) -> Result<Option<msgs::Stfu>, SpliceFundingFailed> {
1222212222
debug_assert!(contribution.is_splice());
1222312223

12224-
if let Err(e) = contribution.net_value().and_then(|our_funding_contribution| {
12224+
if let Err(e) = contribution.validate().and_then(|()| {
1222512225
// For splice-out, our_funding_contribution is adjusted to cover fees if there
1222612226
// aren't any inputs.
12227+
let our_funding_contribution = contribution.net_value();
1222712228
self.validate_splice_contributions(our_funding_contribution, SignedAmount::ZERO)
1222812229
}) {
1222912230
log_error!(logger, "Channel {} cannot be funded: {}", self.context.channel_id(), e);
@@ -13566,24 +13567,12 @@ where
1356613567
}
1356713568

1356813569
let prev_funding_input = self.funding.to_splice_funding_input();
13569-
let is_initiator = contribution.is_initiator();
13570-
let our_funding_contribution = match contribution.net_value() {
13571-
Ok(net_value) => net_value,
13572-
Err(e) => {
13573-
debug_assert!(false);
13574-
return Err(ChannelError::WarnAndDisconnect(
13575-
format!(
13576-
"Internal Error: Insufficient funding contribution: {}",
13577-
e,
13578-
)
13579-
));
13580-
},
13581-
};
13570+
let our_funding_contribution = contribution.net_value();
1358213571
let funding_feerate_per_kw = contribution.feerate().to_sat_per_kwu() as u32;
1358313572
let (our_funding_inputs, our_funding_outputs) = contribution.into_tx_parts();
1358413573

1358513574
let context = FundingNegotiationContext {
13586-
is_initiator,
13575+
is_initiator: true,
1358713576
our_funding_contribution,
1358813577
funding_tx_locktime: locktime,
1358913578
funding_feerate_sat_per_1000_weight: funding_feerate_per_kw,

0 commit comments

Comments
 (0)