Skip to content

Commit 0a2f829

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 025e938 commit 0a2f829

File tree

3 files changed

+360
-4
lines changed

3 files changed

+360
-4
lines changed

lightning/src/events/bump_transaction.rs

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

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)