Skip to content

Commit 37b4647

Browse files
committed
Add BumpTransaction event handler
1 parent 4f806f5 commit 37b4647

File tree

3 files changed

+390
-4
lines changed

3 files changed

+390
-4
lines changed

lightning/src/events/bump_transaction.rs

Lines changed: 381 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,28 @@
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;
16+
use crate::chain::keysinterface::{ChannelSigner, EcdsaChannelSigner, SignerProvider};
17+
use crate::io_extras::sink;
1218
use crate::ln::PaymentPreimage;
1319
use crate::ln::chan_utils;
14-
use crate::ln::chan_utils::{ChannelTransactionParameters, HTLCOutputInCommitment};
20+
use crate::ln::chan_utils::{
21+
ANCHOR_INPUT_WITNESS_WEIGHT, HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT,
22+
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT, ChannelTransactionParameters, HTLCOutputInCommitment
23+
};
24+
use crate::events::Event;
25+
use crate::prelude::HashMap;
26+
use crate::util::logger::Logger;
27+
use crate::util::ser::Writeable;
1528

16-
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;
32+
use bitcoin::hashes::{Hash, HashEngine};
33+
use bitcoin::hashes::sha256::Hash as Sha256Hash;
1734
use bitcoin::secp256k1;
1835
use bitcoin::secp256k1::{PublicKey, Secp256k1};
1936
use bitcoin::secp256k1::ecdsa::Signature;
@@ -231,3 +248,365 @@ pub enum BumpTransactionEvent {
231248
tx_lock_time: PackedLockTime,
232249
},
233250
}
251+
252+
/// A unique identifier used to track bumps for a transaction. The same identifier is always used
253+
/// for all bumps of the same transaction.
254+
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
255+
pub struct BumpId([u8; 32]);
256+
257+
impl BumpId {
258+
fn from_txid(txid: Txid) -> Self {
259+
Self(txid.into_inner())
260+
}
261+
262+
fn from_htlc_descriptors(htlc_descriptors: &[HTLCDescriptor]) -> Self {
263+
let mut engine = Sha256Hash::engine();
264+
for htlc_descriptor in htlc_descriptors {
265+
let htlc_outpoint = OutPoint {
266+
txid: htlc_descriptor.commitment_txid,
267+
vout: htlc_descriptor.htlc.transaction_output_index.unwrap()
268+
};
269+
engine.input(&htlc_outpoint.encode());
270+
}
271+
Self(Sha256Hash::from_engine(engine).into_inner())
272+
}
273+
}
274+
275+
/// An input that must be included in a transaction when performing coin selection through
276+
/// [`CoinSelectionSource::select_confirmed_utxos`].
277+
pub struct Input {
278+
/// The unique identifier of the input.
279+
pub outpoint: OutPoint,
280+
/// The upper-bound weight consumed by the input's full witness required to satisfy its
281+
/// corresponding output's script.
282+
pub witness_weight: u64,
283+
}
284+
285+
/// An unspent transaction output that is available to spend resulting from a successful
286+
/// [`CoinSelection`] attempt.
287+
#[derive(Clone, Debug)]
288+
pub struct Utxo {
289+
/// The unique identifier of the output.
290+
pub outpoint: OutPoint,
291+
/// The output to spend.
292+
pub output: TxOut,
293+
/// The upper-bound weight consumed by the corresponding input's full witness required to
294+
/// satisfy the output's script.
295+
pub witness_weight: u64,
296+
}
297+
298+
/// The result of a successful coin selection attempt for a transaction requiring additional UTXOs
299+
/// to cover its fees.
300+
pub struct CoinSelection {
301+
/// The set of UTXOs (with at least 1 confirmation) to spend and use within a transaction
302+
/// requiring additional fees.
303+
confirmed_utxos: Vec<Utxo>,
304+
/// An additional output tracking whether any change remained after coin selection. This output
305+
/// should always have a value above dust for its given `script_pubkey`.
306+
change_output: Option<TxOut>,
307+
}
308+
309+
/// An abstraction over a bitcoin wallet that can perform coin selection over a set of UTXOs and can
310+
/// sign for them. The coin selection method aims to mimic Bitcoin Core's `fundrawtransaction` RPC,
311+
/// which most wallets should be able to satisfy.
312+
pub trait CoinSelectionSource {
313+
/// Performs coin selection of a set of UTXOs, with at least 1 confirmation each, that are
314+
/// available to spend. Implementations are free to pick their coin selection algorithm of
315+
/// choice, as long as the following requirements are met:
316+
///
317+
/// 1. `must_spend` contains a set of [`Input`]s that must be included in the transaction
318+
/// throughout coin selection.
319+
/// 2. `must_pay_to` contains a set of [`TxOut`]s that must be included in the transaction
320+
/// throughout coin selection.
321+
/// 3. Enough inputs must be selected/contributed for the resulting transaction (including the
322+
/// inputs and outputs noted above) to meet `target_feerate_sat_per_1000_weight`.
323+
///
324+
/// Implementations must realize that [`Input::witness_weight`] only tracks the weight of the
325+
/// input's witness. Some wallets, like Bitcoin Core's, may require providing the full input
326+
/// weight. Failing to do so may lead to underestimating fee bumps and delaying block inclusion.
327+
fn select_confirmed_utxos(
328+
&self, bump_id: BumpId, must_spend: Vec<Input>, must_pay_to: Vec<TxOut>,
329+
target_feerate_sat_per_1000_weight: u32,
330+
) -> Result<CoinSelection, ()>;
331+
/// Returns a script to use for change above dust resulting from a successful coin selection
332+
/// attempt.
333+
fn change_script(&self) -> Result<Script, ()>;
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, bump_id: BumpId, target_feerate_sat_per_1000_weight: u32,
398+
commitment_tx: &Transaction, anchor_descriptor: &AnchorDescriptor,
399+
) -> Result<Transaction, ()> {
400+
// Most wallets that support funding a transaction also require an output, e.g. see
401+
// bitcoind's `fundrawtransaction`. Since we're just interested in spending the anchor
402+
// input, without caring where the change goes, we use an output just above dust backed by
403+
// the wallet's change script. If the wallet ends up producing its own change output when
404+
// funding the transaction, we'll join them into one, saving the user a few satoshis.
405+
// TODO: Prevent change address inflation.
406+
let change_script = self.utxo_source.change_script()?;
407+
let dust_change_output = TxOut {
408+
value: change_script.dust_value().to_sat(),
409+
script_pubkey: change_script,
410+
};
411+
412+
let must_spend = vec![Input {
413+
outpoint: anchor_descriptor.outpoint,
414+
witness_weight: commitment_tx.weight() as u64 + ANCHOR_INPUT_WITNESS_WEIGHT,
415+
}];
416+
let must_pay_to = vec![dust_change_output.clone()];
417+
let coin_selection = self.utxo_source.select_confirmed_utxos(
418+
bump_id, must_spend, must_pay_to, target_feerate_sat_per_1000_weight,
419+
)?;
420+
let override_change_output = |tx: &mut Transaction, coin_selection: &mut CoinSelection| {
421+
if let Some(mut change_output) = coin_selection.change_output.take() {
422+
// Replace the change output we initially added to `must_spend` with the one given
423+
// to us by the user.
424+
let dust_change_output_weight = dust_change_output.consensus_encode(&mut sink())
425+
.unwrap() as u64;
426+
let dust_change_output_fee = dust_change_output_weight *
427+
target_feerate_sat_per_1000_weight as u64;
428+
change_output.value += dust_change_output_fee + dust_change_output.value;
429+
tx.output.push(change_output);
430+
} else {
431+
tx.output.push(dust_change_output);
432+
}
433+
};
434+
435+
let mut tx = Transaction {
436+
version: 2,
437+
lock_time: PackedLockTime::ZERO, // TODO: Use next best height.
438+
input: vec![TxIn {
439+
previous_output: anchor_descriptor.outpoint,
440+
script_sig: Script::new(),
441+
sequence: Sequence::ZERO,
442+
witness: Witness::new(),
443+
}],
444+
output: vec![],
445+
};
446+
self.process_coin_selection(&mut tx, coin_selection, Some(override_change_output));
447+
Ok(tx)
448+
}
449+
450+
/// Handles a [`BumpTransactionEvent::ChannelClose`] event variant by producing a fully-signed
451+
/// transaction spending an anchor output of the commitment transaction to bump its fee and
452+
/// broadcasts them to the network as a package.
453+
fn handle_channel_close(
454+
&self, package_target_feerate_sat_per_1000_weight: u32, commitment_tx: &Transaction,
455+
commitment_tx_fee_sat: u64, anchor_descriptor: &AnchorDescriptor,
456+
) -> Result<(), ()> {
457+
// Compute the feerate the anchor transaction must meet to meet the overall feerate for the
458+
// package (commitment + anchor transactions).
459+
let commitment_tx_feerate: u32 = (commitment_tx_fee_sat * 1000 / commitment_tx.weight() as u64)
460+
.try_into().unwrap_or(u32::max_value());
461+
let feerate_diff = package_target_feerate_sat_per_1000_weight.saturating_sub(commitment_tx_feerate);
462+
if feerate_diff == 0 {
463+
// If the commitment transaction already has a feerate high enough on its own, broadcast
464+
// it as is without a child.
465+
self.broadcaster.broadcast_transaction(commitment_tx);
466+
return Ok(());
467+
}
468+
469+
let bump_id = BumpId::from_txid(commitment_tx.txid());
470+
// TODO: Use the one in `crate::chain::chaininterface` once it's correct.
471+
const MIN_RELAY_FEERATE: u32 = bitcoin::policy::DEFAULT_MIN_RELAY_TX_FEE /
472+
WITNESS_SCALE_FACTOR as u32;
473+
let target_anchor_tx_feerate = if feerate_diff < MIN_RELAY_FEERATE {
474+
// Transactions generally won't propagate if the minimum feerate is not met, so use it
475+
// as a lower bound.
476+
MIN_RELAY_FEERATE
477+
} else {
478+
feerate_diff
479+
};
480+
let mut anchor_tx = self.build_anchor_tx(
481+
bump_id, target_anchor_tx_feerate, commitment_tx, anchor_descriptor,
482+
)?;
483+
484+
debug_assert_eq!(anchor_tx.output.len(), 1);
485+
self.utxo_source.sign_tx(&mut anchor_tx)?;
486+
let signer = self.signer_provider.derive_channel_signer(
487+
anchor_descriptor.channel_value_satoshis, anchor_descriptor.channel_keys_id,
488+
);
489+
let anchor_sig = signer.sign_holder_anchor_input(&anchor_tx, 0, &self.secp)?;
490+
anchor_tx.input[0].witness =
491+
chan_utils::build_anchor_input_witness(&signer.pubkeys().funding_pubkey, &anchor_sig);
492+
493+
// TODO: Broadcast as transaction package once supported.
494+
self.broadcaster.broadcast_transaction(commitment_tx);
495+
self.broadcaster.broadcast_transaction(&anchor_tx);
496+
Ok(())
497+
}
498+
499+
/// Returns an unsigned, fee-bumped HTLC transaction, along with the set of signers required to
500+
/// fulfill the witness for each HTLC input within it.
501+
fn build_htlc_tx(
502+
&self, bump_id: BumpId, target_feerate_sat_per_1000_weight: u32,
503+
htlc_descriptors: &[HTLCDescriptor], tx_lock_time: PackedLockTime,
504+
) -> Result<(Transaction, HashMap<[u8; 32], <SP::Target as SignerProvider>::Signer>), ()> {
505+
let mut tx = Transaction {
506+
version: 2,
507+
lock_time: tx_lock_time,
508+
input: vec![],
509+
output: vec![],
510+
};
511+
// Unfortunately, we need to derive the signer for each HTLC ahead of time to obtain its
512+
// input.
513+
let mut signers = HashMap::new();
514+
for htlc_descriptor in htlc_descriptors {
515+
let signer = signers.entry(htlc_descriptor.channel_keys_id)
516+
.or_insert_with(||
517+
self.signer_provider.derive_channel_signer(
518+
htlc_descriptor.channel_value_satoshis, htlc_descriptor.channel_keys_id,
519+
)
520+
);
521+
let per_commitment_point = signer.get_per_commitment_point(
522+
htlc_descriptor.per_commitment_number, &self.secp
523+
);
524+
tx.input.push(htlc_descriptor.unsigned_tx_input());
525+
let htlc_output = htlc_descriptor.tx_output(&per_commitment_point, &self.secp);
526+
tx.output.push(htlc_output);
527+
}
528+
529+
let must_spend = htlc_descriptors.iter().map(|htlc_descriptor| Input {
530+
outpoint: OutPoint {
531+
txid: htlc_descriptor.commitment_txid,
532+
vout: htlc_descriptor.htlc.transaction_output_index.unwrap(),
533+
},
534+
witness_weight: if htlc_descriptor.preimage.is_some() {
535+
HTLC_SUCCESS_INPUT_ANCHOR_WITNESS_WEIGHT
536+
} else {
537+
HTLC_TIMEOUT_INPUT_ANCHOR_WITNESS_WEIGHT
538+
},
539+
}).collect();
540+
let must_pay_to = tx.output.clone();
541+
let coin_selection = self.utxo_source.select_confirmed_utxos(
542+
bump_id, must_spend, must_pay_to, target_feerate_sat_per_1000_weight,
543+
)?;
544+
545+
self.process_coin_selection(
546+
&mut tx, coin_selection, None::<fn(&mut Transaction, &mut CoinSelection)>
547+
);
548+
Ok((tx, signers))
549+
}
550+
551+
/// Handles a [`BumpTransactionEvent::HTLCResolution`] event variant by producing a
552+
/// fully-signed, fee-bumped HTLC transaction that is broadcast to the network.
553+
fn handle_htlc_resolution(
554+
&self, target_feerate_sat_per_1000_weight: u32, htlc_descriptors: &[HTLCDescriptor],
555+
tx_lock_time: PackedLockTime,
556+
) -> Result<(), ()> {
557+
let bump_id = BumpId::from_htlc_descriptors(htlc_descriptors);
558+
let (mut htlc_tx, signers) = self.build_htlc_tx(
559+
bump_id, target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
560+
)?;
561+
562+
debug_assert_eq!(htlc_tx.output.len(), htlc_descriptors.len() + 1);
563+
self.utxo_source.sign_tx(&mut htlc_tx)?;
564+
for (idx, htlc_descriptor) in htlc_descriptors.iter().enumerate() {
565+
let signer = signers.get(&htlc_descriptor.channel_keys_id).unwrap();
566+
let htlc_sig = signer.sign_holder_htlc_transaction(
567+
&htlc_tx, idx, htlc_descriptor, &self.secp
568+
)?;
569+
let per_commitment_point = signer.get_per_commitment_point(
570+
htlc_descriptor.per_commitment_number, &self.secp
571+
);
572+
let witness_script = htlc_descriptor.witness_script(&per_commitment_point, &self.secp);
573+
htlc_tx.input[idx].witness = htlc_descriptor.tx_input_witness(&htlc_sig, &witness_script);
574+
}
575+
576+
self.broadcaster.broadcast_transaction(&htlc_tx);
577+
Ok(())
578+
}
579+
580+
/// Handles all variants of [`BumpTransactionEvent`], immediately returning otherwise.
581+
pub fn handle_event(&self, event: Event) {
582+
let event = if let Event::BumpTransaction(event) = event {
583+
event
584+
} else {
585+
return;
586+
};
587+
match event {
588+
BumpTransactionEvent::ChannelClose {
589+
package_target_feerate_sat_per_1000_weight, commitment_tx, anchor_descriptor,
590+
commitment_tx_fee_satoshis, ..
591+
} => {
592+
if let Err(_) = self.handle_channel_close(
593+
package_target_feerate_sat_per_1000_weight, &commitment_tx,
594+
commitment_tx_fee_satoshis, &anchor_descriptor,
595+
) {
596+
log_error!(self.logger, "Failed bumping commitment transaction fee for {}",
597+
commitment_tx.txid());
598+
}
599+
}
600+
BumpTransactionEvent::HTLCResolution {
601+
target_feerate_sat_per_1000_weight, htlc_descriptors, tx_lock_time,
602+
} => {
603+
if let Err(_) = self.handle_htlc_resolution(
604+
target_feerate_sat_per_1000_weight, &htlc_descriptors, tx_lock_time,
605+
) {
606+
log_error!(self.logger, "Failed bumping HTLC transaction fee for commitment {}",
607+
htlc_descriptors[0].commitment_txid);
608+
}
609+
}
610+
}
611+
}
612+
}

0 commit comments

Comments
 (0)