Skip to content

Commit 8973cbd

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 98e7e35 commit 8973cbd

File tree

3 files changed

+372
-4
lines changed

3 files changed

+372
-4
lines changed

lightning/src/events/bump_transaction.rs

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

lightning/src/events/mod.rs

-2
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;

0 commit comments

Comments
 (0)