Skip to content

Commit f8a67ef

Browse files
committed
Add BumpTransaction event handler
This allows users to bump their commitments and HTLC transactions without having to worry about all the little details to do so. Instead, we'll just require that they implement the `CoinSelectionSource` trait over their wallet/UTXO source, granting the event handler permission to spend confirmed UTXOs for the transactions it'll produce. While the event handler should in most cases produce valid transactions, assuming the provided confirmed UTXOs are valid, it may not produce relayable transactions due to not satisfying certain Replace-By-Fee (RBF) mempool policy requirements. Some of these require that the replacement transactions have a higher feerate and absolute fee than the conflicting transactions it aims to replace. To make sure we adhere to these requirements, we'd have to persist some state for all transactions the event handler has produced, greatly increasing its complexity. While we may consider implementing so in the future, we choose to go with a simple initial version that relies on the OnchainTxHandler's bumping frequency. For each new bumping attempt, the OnchainTxHandler proposes a 25% feerate increase to ensure transactions can propagate under constrained mempool circumstances.
1 parent a5d3fc5 commit f8a67ef

File tree

3 files changed

+356
-4
lines changed

3 files changed

+356
-4
lines changed

lightning/src/events/bump_transaction.rs

Lines changed: 347 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,26 @@
99

1010
//! Utitilies for bumping transactions originating from [`super::Event`]s.
1111
12+
use core::convert::TryInto;
13+
use core::ops::Deref;
14+
15+
use crate::chain::chaininterface::BroadcasterInterface;
1216
use crate::chain::ClaimId;
17+
use crate::sign::{ChannelSigner, EcdsaChannelSigner, SignerProvider};
18+
use crate::io_extras::sink;
1319
use crate::ln::PaymentPreimage;
1420
use crate::ln::chan_utils;
15-
use crate::ln::chan_utils::{ChannelTransactionParameters, HTLCOutputInCommitment};
21+
use crate::ln::chan_utils::{
22+
ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT,
23+
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, ChannelTransactionParameters, HTLCOutputInCommitment
24+
};
25+
use crate::events::Event;
26+
use crate::prelude::HashMap;
27+
use crate::util::logger::Logger;
1628

17-
use bitcoin::{OutPoint, PackedLockTime, Script, Transaction, Txid, TxIn, TxOut, Witness};
29+
use bitcoin::{OutPoint, PackedLockTime, Sequence, Script, Transaction, Txid, TxIn, TxOut, Witness};
30+
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
31+
use bitcoin::consensus::Encodable;
1832
use bitcoin::secp256k1;
1933
use bitcoin::secp256k1::{PublicKey, Secp256k1};
2034
use bitcoin::secp256k1::ecdsa::Signature;
@@ -245,3 +259,334 @@ pub enum BumpTransactionEvent {
245259
tx_lock_time: PackedLockTime,
246260
},
247261
}
262+
263+
/// An input that must be included in a transaction when performing coin selection through
264+
/// [`CoinSelectionSource::select_confirmed_utxos`].
265+
pub struct Input {
266+
/// The unique identifier of the input.
267+
pub outpoint: OutPoint,
268+
/// The upper-bound weight consumed by the input's full witness required to satisfy its
269+
/// corresponding output's script.
270+
pub witness_weight: u64,
271+
}
272+
273+
/// An unspent transaction output that is available to spend resulting from a successful
274+
/// [`CoinSelection`] attempt.
275+
#[derive(Clone, Debug)]
276+
pub struct Utxo {
277+
/// The unique identifier of the output.
278+
pub outpoint: OutPoint,
279+
/// The output to spend.
280+
pub output: TxOut,
281+
/// The upper-bound weight consumed by the corresponding input's full witness required to
282+
/// satisfy the output's script.
283+
pub witness_weight: u64,
284+
}
285+
286+
/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
287+
/// to cover its fees.
288+
pub struct CoinSelection {
289+
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
290+
/// requiring additional fees.
291+
confirmed_utxos: Vec<Utxo>,
292+
/// An additional output tracking whether any change remained after coin selection. This output
293+
/// should always have a value above dust for its given `script_pubkey`. Its `script_pubkey`
294+
/// should be solely controlled by the same entity from which the `confirmed_utxos` were
295+
/// sourced. It should not be spent until the transaction it belongs to confirms to ensure
296+
/// mempool descendant limits are not met.
297+
change_output: Option<TxOut>,
298+
}
299+
300+
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
301+
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
302+
/// which most wallets should be able to satisfy.
303+
pub trait CoinSelectionSource {
304+
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
305+
/// available to spend. Implementations are free to pick their coin selection algorithm of
306+
/// choice, as long as the following requirements are met:
307+
///
308+
/// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
309+
/// throughout coin selection.
310+
/// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
311+
/// throughout coin selection. In some cases, like when funding an anchor transaction, this
312+
/// set is empty. Implementations should ensure they handle this correctly on their end,
313+
/// e.g., Bitcoin Core's `fundrawtransaction` RPC requires at least one output to be
314+
/// provided, in which case a zero-value empty OP_RETURN output can be used instead.
315+
/// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
316+
/// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
317+
///
318+
/// Implementations must take note that [`Input::witness_weight`] only tracks the weight of the
319+
/// input's witness. Some wallets, like Bitcoin Core's, may require providing the full input
320+
/// weight. Failing to do so may lead to underestimating fee bumps and delaying block inclusion.
321+
///
322+
/// The `claim_id` must map to the set of external UTXOs assigned to the claim, such that they
323+
/// can be re-used within new fee-bumped iterations of the original claiming transaction,
324+
/// ensuring that claims don't double spend each other. If a specific `claim_id` has never had a
325+
/// transaction associated with it, and all of the available UTXOs have already been assigned to
326+
/// other claims, implementations must be willing to double spend their UTXOs. The choice of
327+
/// which UTXOs to double spend is left to the implementation (it may be willing to take
328+
/// different risks than others), but it must strive to keep the set of other claims being
329+
/// double spent to a minimum.
330+
fn select_confirmed_utxos(
331+
&self, claim_id: ClaimId, must_spend: &[Input], must_pay_to: &[TxOut],
332+
target_feerate_sat_per_1000_weight: u32,
333+
) -> Result<CoinSelection, ()>;
334+
/// Signs and provides the full witness for all inputs within the transaction known to the
335+
/// trait (i.e., any provided via [`CoinSelectionSource::select_confirmed_utxos`]).
336+
fn sign_tx(&self, tx: &mut Transaction) -> Result<(), ()>;
337+
}
338+
339+
/// A handler for [`Event::BumpTransaction`] events that sources confirmed UTXOs from a
340+
/// [`CoinSelectionSource`] to fee bump transactions via Child-Pays-For-Parent (CPFP) or
341+
/// Replace-By-Fee (RBF).
342+
pub struct BumpTransactionEventHandler<B: Deref, C: Deref, SP: Deref, L: Deref>
343+
where
344+
B::Target: BroadcasterInterface,
345+
C::Target: CoinSelectionSource,
346+
SP::Target: SignerProvider,
347+
L::Target: Logger,
348+
{
349+
broadcaster: B,
350+
utxo_source: C,
351+
signer_provider: SP,
352+
logger: L,
353+
secp: Secp256k1<secp256k1::All>,
354+
}
355+
356+
impl<B: Deref, C: Deref, SP: Deref, L: Deref> BumpTransactionEventHandler<B, C, SP, L>
357+
where
358+
B::Target: BroadcasterInterface,
359+
C::Target: CoinSelectionSource,
360+
SP::Target: SignerProvider,
361+
L::Target: Logger,
362+
{
363+
/// Returns a new instance capable of handling [`Event::BumpTransaction`] events.
364+
pub fn new(broadcaster: B, utxo_source: C, signer_provider: SP, logger: L) -> Self {
365+
Self {
366+
broadcaster,
367+
utxo_source,
368+
signer_provider,
369+
logger,
370+
secp: Secp256k1::new(),
371+
}
372+
}
373+
374+
/// Updates a transaction with the result of a successful coin selection attempt.
375+
fn process_coin_selection(
376+
&self, tx: &mut Transaction, mut coin_selection: CoinSelection,
377+
mut override_change_output: Option<impl FnOnce(&mut Transaction, &mut CoinSelection)>,
378+
) {
379+
for utxo in coin_selection.confirmed_utxos.drain(..) {
380+
tx.input.push(TxIn {
381+
previous_output: utxo.outpoint,
382+
script_sig: Script::new(),
383+
sequence: Sequence::ZERO,
384+
witness: Witness::new(),
385+
});
386+
}
387+
if let Some(override_change_output) = override_change_output.take() {
388+
override_change_output(tx, &mut coin_selection)
389+
} else if let Some(change_output) = coin_selection.change_output.take() {
390+
tx.output.push(change_output);
391+
}
392+
}
393+
394+
/// Returns an unsigned transaction spending an anchor output of the commitment transaction, and
395+
/// any additional UTXOs sourced, to bump the commitment transaction's fee.
396+
fn build_anchor_tx(
397+
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
398+
commitment_tx: &Transaction, anchor_descriptor: &AnchorDescriptor,
399+
) -> Result<Transaction, ()> {
400+
let must_spend = vec![Input {
401+
outpoint: anchor_descriptor.outpoint,
402+
witness_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT,
403+
}];
404+
let coin_selection = self.utxo_source.select_confirmed_utxos(
405+
claim_id, &must_spend, &[], target_feerate_sat_per_1000_weight,
406+
)?;
407+
let override_change_output = |tx: &mut Transaction, coin_selection: &mut CoinSelection| {
408+
if let Some(change_output) = coin_selection.change_output.take() {
409+
tx.output.push(change_output);
410+
} else {
411+
// We weren't provided a change output, likely because the input set was a perfect
412+
// match, but we still need to have at least one output in the transaction for it to
413+
// be considered standard. We choose to go with an empty OP_RETURN as it is the
414+
// cheapest way to include a dummy output.
415+
tx.output.push(TxOut {
416+
value: 0,
417+
script_pubkey: Script::new_op_return(&[]),
418+
});
419+
}
420+
};
421+
422+
let mut tx = Transaction {
423+
version: 2,
424+
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
425+
input: vec![TxIn {
426+
previous_output: anchor_descriptor.outpoint,
427+
script_sig: Script::new(),
428+
sequence: Sequence::ZERO,
429+
witness: Witness::new(),
430+
}],
431+
output: vec![],
432+
};
433+
self.process_coin_selection(&mut tx, coin_selection, Some(override_change_output));
434+
Ok(tx)
435+
}
436+
437+
/// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
438+
/// transaction spending an anchor output of the commitment transaction to bump its fee and
439+
/// broadcasts them to the network as a package.
440+
fn handle_channel_close(
441+
&self, claim_id: ClaimId, package_target_feerate_sat_per_1000_weight: u32,
442+
commitment_tx: &Transaction, commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
443+
) -> Result<(), ()> {
444+
// Compute the feerate the anchor transaction must meet to meet the overall feerate for the
445+
// package (commitment + anchor transactions).
446+
let commitment_tx_feerate: u32 = (commitment_tx_fee_sat * 1000 / commitment_tx.weight() as u64)
447+
.try_into().unwrap_or(u32::max_value());
448+
let feerate_diff = package_target_feerate_sat_per_1000_weight.saturating_sub(commitment_tx_feerate);
449+
if feerate_diff == 0 {
450+
// If the commitment transaction already has a feerate high enough on its own, broadcast
451+
// it as is without a child.
452+
self.broadcaster.broadcast_transactions(&[&commitment_tx]);
453+
return Ok(());
454+
}
455+
456+
// TODO: Use the one in `crate::chain::chaininterface` once it's correct.
457+
const MIN_RELAY_FEERATE: u32 = bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE /
458+
WITNESS_SCALE_FACTOR as u32;
459+
let target_anchor_tx_feerate = if feerate_diff < MIN_RELAY_FEERATE {
460+
// Transactions generally won't propagate if the minimum feerate is not met, so use it
461+
// as a lower bound.
462+
MIN_RELAY_FEERATE
463+
} else {
464+
feerate_diff
465+
};
466+
let mut anchor_tx = self.build_anchor_tx(
467+
claim_id, target_anchor_tx_feerate, commitment_tx, anchor_descriptor,
468+
)?;
469+
470+
debug_assert_eq!(anchor_tx.output.len(), 1);
471+
self.utxo_source.sign_tx(&mut anchor_tx)?;
472+
let signer = self.signer_provider.derive_channel_signer(
473+
anchor_descriptor.channel_value_satoshis, anchor_descriptor.channel_keys_id,
474+
);
475+
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
476+
anchor_tx.input[0].witness =
477+
chan_utils::build_anchor_input_witness(&signer.pubkeys().funding_pubkey, &anchor_sig);
478+
479+
self.broadcaster.broadcast_transactions(&[&commitment_tx, &anchor_tx]);
480+
Ok(())
481+
}
482+
483+
/// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to
484+
/// fulfill the witness for each HTLC input within it.
485+
fn build_htlc_tx(
486+
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
487+
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
488+
) -> Result<(Transaction, HashMap<[u8; 32], <SP::Target as SignerProvider>::Signer>), ()> {
489+
let mut tx = Transaction {
490+
version: 2,
491+
lock_time: tx_lock_time,
492+
input: vec![],
493+
output: vec![],
494+
};
495+
// Unfortunately, we need to derive the signer for each HTLC ahead of time to obtain its
496+
// input.
497+
let mut signers = HashMap::new();
498+
let mut must_spend = Vec::with_capacity(htlc_descriptors.len());
499+
for htlc_descriptor in htlc_descriptors {
500+
let signer = signers.entry(htlc_descriptor.channel_keys_id)
501+
.or_insert_with(||
502+
self.signer_provider.derive_channel_signer(
503+
htlc_descriptor.channel_value_satoshis, htlc_descriptor.channel_keys_id,
504+
)
505+
);
506+
let per_commitment_point = signer.get_per_commitment_point(
507+
htlc_descriptor.per_commitment_number, &self.secp
508+
);
509+
510+
let htlc_input = htlc_descriptor.unsigned_tx_input();
511+
must_spend.push(Input {
512+
outpoint: htlc_input.previous_output.clone(),
513+
witness_weight: if htlc_descriptor.preimage.is_some() {
514+
HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT
515+
} else {
516+
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
517+
},
518+
});
519+
tx.input.push(htlc_input);
520+
let htlc_output = htlc_descriptor.tx_output(&per_commitment_point, &self.secp);
521+
tx.output.push(htlc_output);
522+
}
523+
524+
let coin_selection = self.utxo_source.select_confirmed_utxos(
525+
claim_id, &must_spend, &tx.output, target_feerate_sat_per_1000_weight,
526+
)?;
527+
self.process_coin_selection(
528+
&mut tx, coin_selection, None::<fn(&mut Transaction, &mut CoinSelection)>
529+
);
530+
Ok((tx, signers))
531+
}
532+
533+
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
534+
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
535+
fn handle_htlc_resolution(
536+
&self, claim_id: ClaimId, target_feerate_sat_per_1000_weight: u32,
537+
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
538+
) -> Result<(), ()> {
539+
let (mut htlc_tx, signers) = self.build_htlc_tx(
540+
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
541+
)?;
542+
543+
self.utxo_source.sign_tx(&mut htlc_tx)?;
544+
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
545+
let signer = signers.get(&htlc_descriptor.channel_keys_id).unwrap();
546+
let htlc_sig = signer.sign_holder_htlc_transaction(
547+
&htlc_tx, idx, htlc_descriptor, &self.secp
548+
)?;
549+
let per_commitment_point = signer.get_per_commitment_point(
550+
htlc_descriptor.per_commitment_number, &self.secp
551+
);
552+
let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &self.secp);
553+
htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
554+
}
555+
556+
self.broadcaster.broadcast_transactions(&[&htlc_tx]);
557+
Ok(())
558+
}
559+
560+
/// Handles all variants of [`BumpTransactionEvent`], immediately returning otherwise.
561+
pub fn handle_event(&self, event: &Event) {
562+
let event = if let Event::BumpTransaction(event) = event {
563+
event
564+
} else {
565+
return;
566+
};
567+
match event {
568+
BumpTransactionEvent::ChannelClose {
569+
claim_id, package_target_feerate_sat_per_1000_weight, commitment_tx,
570+
anchor_descriptor, commitment_tx_fee_satoshis, ..
571+
} => {
572+
if let Err(_) = self.handle_channel_close(
573+
*claim_id, *package_target_feerate_sat_per_1000_weight, commitment_tx,
574+
*commitment_tx_fee_satoshis, anchor_descriptor,
575+
) {
576+
log_error!(self.logger, "Failed bumping commitment transaction fee for {}",
577+
commitment_tx.txid());
578+
}
579+
}
580+
BumpTransactionEvent::HTLCResolution {
581+
claim_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
582+
} => {
583+
if let Err(_) = self.handle_htlc_resolution(
584+
*claim_id, *target_feerate_sat_per_1000_weight, htlc_descriptors, *tx_lock_time,
585+
) {
586+
log_error!(self.logger, "Failed bumping HTLC transaction fee for commitment {}",
587+
htlc_descriptors[0].commitment_txid);
588+
}
589+
}
590+
}
591+
}
592+
}

lightning/src/events/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ use crate::util::string::UntrustedString;
3333
use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
3434

3535
use bitcoin::{PackedLockTime, Transaction, OutPoint};
36-
#[cfg(anchors)]
37-
use bitcoin::{Txid, TxIn, TxOut, Witness};
3836
use bitcoin::blockdata::script::Script;
3937
use bitcoin::hashes::Hash;
4038
use bitcoin::hashes::sha256::Hash as Sha256;

lightning/src/ln/chan_utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ pub(crate) const MIN_ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 136;
5757
/// This is the maximum post-anchor value.
5858
pub const MAX_ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 143;
5959

60+
/// The upper bound weight of an anchor input.
61+
pub const ANCHOR_INPUT_WITNESS_WEIGHT: u64 = 116;
62+
/// The upper bound weight of an HTLC timeout input from a commitment transaction with anchor
63+
/// outputs.
64+
pub const HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 288;
65+
/// The upper bound weight of an HTLC success input from a commitment transaction with anchor
66+
/// outputs.
67+
pub const HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT: u64 = 327;
68+
6069
/// Gets the weight for an HTLC-Success transaction.
6170
#[inline]
6271
pub fn htlc_success_tx_weight(opt_anchors: bool) -> u64 {

0 commit comments

Comments
 (0)