Skip to content

Commit 6d5f15d

Browse files
author
Antoine Riard
committed
Conditional feerate-bumping of HolderCommitmentTx
We condition a CPFP triggering on observing a feerate increase as reported by our fee estimator. TODO: should we bump unconditionally after a timer expiration ? Griefing vector in case of commitment tx withholding ?
1 parent 87e5255 commit 6d5f15d

File tree

4 files changed

+67
-50
lines changed

4 files changed

+67
-50
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1809,7 +1809,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
18091809
let should_broadcast = self.would_broadcast_at_height(height, &logger);
18101810
if should_broadcast {
18111811
let holder_commitment_tx = PackageTemplate::build_holder_commitment_tx(self.funding_redeemscript.clone(), self.funding_info.0.txid.clone(), self.funding_info.0.index as u32);
1812-
claimable_outpoints.push(OnchainRequest { aggregation: false, bump_strategy: BumpStrategy::CPFP, feerate_previous: 0, height_timer: None, absolute_timelock: height, height_original: height, content: holder_commitment_tx });
1812+
claimable_outpoints.push(OnchainRequest { aggregation: false, bump_strategy: BumpStrategy::CPFP, feerate_previous: self.current_holder_commitment_tx.feerate_per_kw as u64, height_timer: None, absolute_timelock: height, height_original: height, content: holder_commitment_tx });
18131813
}
18141814
if should_broadcast {
18151815
self.pending_monitor_events.push(MonitorEvent::CommitmentTxBroadcasted(self.funding_info.0));

lightning/src/ln/functional_tests.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,11 +2744,12 @@ fn claim_htlc_outputs_single_tx() {
27442744
expect_payment_failed!(nodes[1], payment_hash_2, true);
27452745

27462746
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
2747-
assert_eq!(node_txn.len(), 9);
2747+
assert_eq!(node_txn.len(), 13);
27482748
// ChannelMonitor: justice tx revoked offered htlc, justice tx revoked received htlc, justice tx revoked to_local (3)
27492749
// ChannelManager: local commmitment + local HTLC-timeout (2)
27502750
// ChannelMonitor: bumped justice tx, after one increase, bumps on HTLC aren't generated not being substantial anymore, bump on revoked to_local isn't generated due to more room for expiration (2)
27512751
// ChannelMonitor: local commitment + local HTLC-timeout (2)
2752+
// ChannelMonitor: bumped local commitment with _no_ CPFPs as feerate didn't fluctuate (3)
27522753

27532754
// Check the pair local commitment and HTLC-timeout broadcast due to HTLC expiration
27542755
assert_eq!(node_txn[2].input.len(), 1);
@@ -2758,6 +2759,11 @@ fn claim_htlc_outputs_single_tx() {
27582759
assert_eq!(witness_script.len(), OFFERED_HTLC_SCRIPT_WEIGHT); //Spending an offered htlc output
27592760
check_spends!(node_txn[3], node_txn[2]);
27602761

2762+
// Bumped commitments TODO check more
2763+
check_spends!(node_txn[10], chan_1.3);
2764+
check_spends!(node_txn[11], chan_1.3);
2765+
check_spends!(node_txn[12], chan_1.3);
2766+
27612767
// Justice transactions are indices 1-2-4
27622768
assert_eq!(node_txn[0].input.len(), 1);
27632769
assert_eq!(node_txn[1].input.len(), 1);
@@ -8431,11 +8437,12 @@ fn test_concurrent_monitor_claim() {
84318437
// We confirm Bob's state Y on Alice, she should broadcast a HTLC-timeout
84328438
watchtower_alice.simple_monitor.block_connected(&header, 136, &vec![&bob_state_y][..], &vec![]);
84338439
{
8434-
let htlc_txn = chanmon_cfgs[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
8440+
let txn = chanmon_cfgs[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
84358441
// We broadcast twice the transaction, once due to the HTLC-timeout, once due
84368442
// the onchain detection of the HTLC output
8437-
assert_eq!(htlc_txn.len(), 2);
8438-
check_spends!(htlc_txn[0], bob_state_y);
8439-
check_spends!(htlc_txn[1], bob_state_y);
8443+
assert_eq!(txn.len(), 3);
8444+
check_spends!(txn[0], bob_state_y);
8445+
check_spends!(txn[1], bob_state_y);
8446+
check_spends!(txn[2], chan_1.3);
84408447
}
84418448
}

lightning/src/ln/onchain_utils.rs

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,9 @@ impl PackageTemplate {
582582

583583
// We sign our commitment transaction
584584
let signed_tx = onchain_handler.get_fully_signed_holder_tx(&input.1.funding_redeemscript).unwrap();
585+
let mut package_txn = Vec::with_capacity(2);
586+
log_trace!(logger, "Going to broadcast Holder Transaction {} claiming funding output {} from {}...", signed_tx.txid(), input.0.vout, input.0.txid);
587+
package_txn.push(signed_tx);
585588
let mut cpfp_tx = Transaction {
586589
version: 2,
587590
lock_time: 0,
@@ -591,49 +594,53 @@ impl PackageTemplate {
591594
value,
592595
}],
593596
};
594-
// TODO: make CPFP generation conditional on utxo input
595-
if let Some(ref holder_tx) = onchain_handler.holder_commitment.as_ref() {
596-
// We find & select our anchor output
597-
let our_anchor_output_script = chan_utils::get_anchor_redeemscript(&onchain_handler.key_storage.pubkeys().funding_pubkey);
598-
let mut vout = ::std::u32::MAX;
599-
for (idx, outp) in holder_tx.unsigned_tx.output.iter().enumerate() {
600-
if outp.script_pubkey == our_anchor_output_script.to_v0_p2wsh() {
601-
vout = idx as u32;
597+
// If commitment tx is still relying on its pre-signed fees to
598+
// confirm, don't attach a CPFP
599+
if utxo_input.is_some() {
600+
if let Some(ref holder_tx) = onchain_handler.holder_commitment.as_ref() {
601+
// We find & select our anchor output
602+
let our_anchor_output_script = chan_utils::get_anchor_redeemscript(&onchain_handler.key_storage.pubkeys().funding_pubkey);
603+
let mut vout = ::std::u32::MAX;
604+
for (idx, outp) in holder_tx.unsigned_tx.output.iter().enumerate() {
605+
if outp.script_pubkey == our_anchor_output_script.to_v0_p2wsh() {
606+
vout = idx as u32;
607+
}
602608
}
603-
}
604-
if vout == ::std::u32::MAX { return None; }
605-
let anchor_outpoint = BitcoinOutPoint {
606-
txid: holder_tx.unsigned_tx.txid(),
607-
vout,
608-
};
609-
// We take our bumping outpoint
610-
let bumping_outpoint = utxo_input.as_ref().unwrap().0;
611-
// We build our CPFP transaction
612-
cpfp_tx.input.push(TxIn {
613-
previous_output: anchor_outpoint,
614-
script_sig: Script::new(),
615-
sequence: 0xfffffffd,
616-
witness: Vec::new(),
617-
});
618-
cpfp_tx.input.push(TxIn {
619-
previous_output: bumping_outpoint,
620-
script_sig: Script::new(),
621-
sequence: 0xfffffffd,
622-
witness: Vec::new(),
623-
});
624-
// We sign and witness finalize anchor input
625-
if let Ok(anchor_sig) = onchain_handler.key_storage.sign_cpfp(&cpfp_tx, 0, ANCHOR_OUTPUT_VALUE, &onchain_handler.secp_ctx) {
626-
cpfp_tx.input[0].witness.push(anchor_sig.serialize_der().to_vec());
627-
cpfp_tx.input[0].witness[0].push(SigHashType::All as u8);
628-
cpfp_tx.input[0].witness.push(our_anchor_output_script.into_bytes());
629-
}
630-
//// We sign and witness finalize bumping input
631-
if let Ok(witness) = utxo_pool.provide_utxo_witness(&cpfp_tx, 1) {
632-
cpfp_tx.input[1].witness = witness;
609+
if vout == ::std::u32::MAX { return None; }
610+
let anchor_outpoint = BitcoinOutPoint {
611+
txid: holder_tx.unsigned_tx.txid(),
612+
vout,
613+
};
614+
// We take our bumping outpoint
615+
let bumping_outpoint = utxo_input.as_ref().unwrap().0;
616+
// We build our CPFP transaction
617+
cpfp_tx.input.push(TxIn {
618+
previous_output: anchor_outpoint,
619+
script_sig: Script::new(),
620+
sequence: 0xfffffffd,
621+
witness: Vec::new(),
622+
});
623+
cpfp_tx.input.push(TxIn {
624+
previous_output: bumping_outpoint,
625+
script_sig: Script::new(),
626+
sequence: 0xfffffffd,
627+
witness: Vec::new(),
628+
});
629+
// We sign and witness finalize anchor input
630+
if let Ok(anchor_sig) = onchain_handler.key_storage.sign_cpfp(&cpfp_tx, 0, ANCHOR_OUTPUT_VALUE, &onchain_handler.secp_ctx) {
631+
cpfp_tx.input[0].witness.push(anchor_sig.serialize_der().to_vec());
632+
cpfp_tx.input[0].witness[0].push(SigHashType::All as u8);
633+
cpfp_tx.input[0].witness.push(our_anchor_output_script.into_bytes());
634+
}
635+
//// We sign and witness finalize bumping input
636+
if let Ok(witness) = utxo_pool.provide_utxo_witness(&cpfp_tx, 1) {
637+
cpfp_tx.input[1].witness = witness;
638+
}
639+
log_trace!(logger, "Going to broadcast CPFP Transaction {} claiming anchor output from {}...", cpfp_tx.txid(), holder_tx.unsigned_tx.txid());
640+
package_txn.push(cpfp_tx);
633641
}
634642
}
635-
log_trace!(logger, "Going to broadcast Holder Transaction {} claiming funding output {} from {}...", signed_tx.txid(), input.0.vout, input.0.txid);
636-
return Some(vec![signed_tx, cpfp_tx]);
643+
return Some(package_txn);
637644
}
638645
}
639646
}
@@ -1016,7 +1023,7 @@ pub(crate) fn compute_output_value<F: Deref, L: Deref>(predicted_weight: usize,
10161023
// If transaction is still relying ont its pre-committed feerate to get confirmed return
10171024
// a 0-value output-value as it won't be consumed further
10181025
if input_amounts == 0 {
1019-
return Some((0, previous_feerate));
1026+
return Some((0, previous_feerate));
10201027
}
10211028

10221029
// If old feerate is 0, first iteration of this claim, use normal fee calculation

lightning/src/ln/onchaintx.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use ln::channelmanager::PaymentPreimage;
2727
use ln::chan_utils::HolderCommitmentTransaction;
2828
use ln::onchain_utils::{OnchainRequest, PackageTemplate, BumpStrategy};
2929
use ln::onchain_utils;
30-
use chain::chaininterface::{FeeEstimator, BroadcasterInterface};
30+
use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget};
3131
use chain::keysinterface::ChannelKeys;
3232
use chain::utxointerface::UtxoPool;
3333
use util::logger::Logger;
@@ -311,7 +311,11 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
311311
if cached_request.content.outpoints().len() == 0 { return None } // But don't prune pending claiming request yet, we may have to resurrect HTLCs
312312

313313
if cached_request.bump_strategy == BumpStrategy::CPFP {
314-
cached_request.content.package_cpfp(&utxo_pool);
314+
if cached_request.feerate_previous < fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) as u64 {
315+
// Bumping UTXO is allocated the first time we detect the pre-signed feerate
316+
// is our fee estimator confirmation target
317+
cached_request.content.package_cpfp(&utxo_pool);
318+
}
315319
}
316320

317321
// Compute new height timer to decide when we need to regenerate a new bumped version of the claim tx (if we
@@ -332,7 +336,6 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
332336
if predicted_weight == 706 || predicted_weight == 666 {
333337
new_timer = None;
334338
}
335-
336339
return Some((new_timer, new_feerate, txn))
337340
}
338341
None

0 commit comments

Comments
 (0)