Skip to content

Commit ff59871

Browse files
author
Antoine Riard
committed
Implement bumping engine in ChannelMonitor::block_connected
Add RBF-bumping of justice txn, given they are only signed by us we can RBF at wish. Aggregation of bump-candidates and more aggresive bumping heuristics are left open Fix tests broken by introduction of more txn broadcast. Some tests may have a relaxed check (claim_htlc_ouputs_single_tx) as broadcast bumped txn are now interwining in previous broadcast ones and breaking simple expectations
1 parent b976887 commit ff59871

File tree

2 files changed

+146
-6
lines changed

2 files changed

+146
-6
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use ln::chan_utils;
3434
use ln::chan_utils::HTLCOutputInCommitment;
3535
use ln::channelmanager::{HTLCSource, PaymentPreimage, PaymentHash};
3636
use ln::channel::{ACCEPTED_HTLC_SCRIPT_WEIGHT, OFFERED_HTLC_SCRIPT_WEIGHT};
37-
use chain::chaininterface::{ChainListener, ChainWatchInterface, BroadcasterInterface, FeeEstimator, ConfirmationTarget};
37+
use chain::chaininterface::{ChainListener, ChainWatchInterface, BroadcasterInterface, FeeEstimator, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
3838
use chain::transaction::OutPoint;
3939
use chain::keysinterface::SpendableOutputDescriptor;
4040
use util::logger::Logger;
@@ -2297,6 +2297,22 @@ impl ChannelMonitor {
22972297
}
22982298
}
22992299
}
2300+
let mut bump_candidates = Vec::new();
2301+
for (lead_outpoint, ref mut cached_claim_datas) in self.our_claim_txn_waiting_first_conf.iter_mut() {
2302+
if cached_claim_datas.height_timer == height {
2303+
bump_candidates.push((lead_outpoint.clone(), cached_claim_datas.clone()));
2304+
}
2305+
}
2306+
for &mut (lead_outpoint, ref mut cached_claim_datas) in bump_candidates.iter_mut() {
2307+
if let Some((new_timer, new_feerate, bump_tx)) = self.bump_claim_tx(height, &lead_outpoint, &cached_claim_datas, fee_estimator) {
2308+
cached_claim_datas.height_timer = new_timer;
2309+
cached_claim_datas.feerate_previous = new_feerate;
2310+
broadcaster.broadcast_transaction(&bump_tx);
2311+
}
2312+
}
2313+
for (lead_outpoint, cached_claim_datas) in bump_candidates.drain(..) {
2314+
self.our_claim_txn_waiting_first_conf.insert(lead_outpoint, cached_claim_datas);
2315+
}
23002316
self.last_block_hash = block_hash.clone();
23012317
(watch_outputs, spendable_outputs, htlc_updated)
23022318
}
@@ -2510,6 +2526,129 @@ impl ChannelMonitor {
25102526
}
25112527
htlc_updated
25122528
}
2529+
2530+
/// Lightning security model (i.e being able to redeem/timeout HTLC or penalize coutnerparty onchain) lays on the assumption of claim transactions getting confirmed before timelock expiration
2531+
/// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent.
2532+
fn bump_claim_tx(&self, height: u32, outp: &BitcoinOutPoint, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: &FeeEstimator) -> Option<(u32, u64, Transaction)> {
2533+
let mut inputs = Vec::new();
2534+
for vout in cached_claim_datas.per_input_material.keys() {
2535+
inputs.push(TxIn {
2536+
previous_output: BitcoinOutPoint {
2537+
txid: outp.txid,
2538+
vout: *vout,
2539+
},
2540+
script_sig: Script::new(),
2541+
sequence: 0xfffffffd,
2542+
witness: Vec::new(),
2543+
});
2544+
}
2545+
let mut bumped_tx = Transaction {
2546+
version: 2,
2547+
lock_time: 0,
2548+
input: inputs,
2549+
output: vec![TxOut {
2550+
script_pubkey: self.destination_script.clone(),
2551+
value: 0
2552+
}],
2553+
};
2554+
2555+
macro_rules! RBF_bump {
2556+
($amount: expr, $old_feerate: expr, $fee_estimator: expr, $predicted_weight: expr, $txid: expr) => {
2557+
{
2558+
let mut used_feerate;
2559+
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
2560+
let new_fee = if $old_feerate < $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) {
2561+
let mut value = $amount;
2562+
if subtract_high_prio_fee!(self, $fee_estimator, value, $predicted_weight, outp.txid, used_feerate) {
2563+
// Overflow check is done in subtract_high_prio_fee
2564+
$amount - value
2565+
} else {
2566+
log_trace!(self, "Can't new-estimation bump claiming on {}, amount {} is too small", $txid, $amount);
2567+
return None;
2568+
}
2569+
// ...else just increase the previous feerate by 25% (because that's a nice number)
2570+
} else {
2571+
let fee = $old_feerate * $predicted_weight / 750;
2572+
if $amount <= fee {
2573+
log_trace!(self, "Can't 25% bump claiming on {}, amount {} is too small", $txid, $amount);
2574+
return None;
2575+
}
2576+
fee
2577+
};
2578+
2579+
let previous_fee = $old_feerate * $predicted_weight / 1000;
2580+
let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * $predicted_weight / 1000;
2581+
// BIP 125 Opt-in Full Replace-by-Fee Signaling
2582+
// * 3. The replacement transaction pays an absolute fee of at least the sum paid by the original transactions.
2583+
// * 4. The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting.
2584+
let new_fee = if new_fee < previous_fee + min_relay_fee {
2585+
new_fee + previous_fee + min_relay_fee - new_fee
2586+
} else {
2587+
new_fee
2588+
};
2589+
Some((new_fee, new_fee * 1000 / $predicted_weight))
2590+
}
2591+
}
2592+
}
2593+
2594+
let new_timer = Self::get_height_timer(height, cached_claim_datas.soonest_timelock);
2595+
let mut inputs_witnesses_weight = 0;
2596+
let mut amt = 0;
2597+
for per_outp_material in cached_claim_datas.per_input_material.values() {
2598+
match per_outp_material {
2599+
&InputMaterial::Revoked { ref script, ref is_htlc, ref amount, .. } => {
2600+
inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if script.len() == OFFERED_HTLC_SCRIPT_WEIGHT { &[InputDescriptors::RevokedOfferedHTLC] } else if script.len() == ACCEPTED_HTLC_SCRIPT_WEIGHT { &[InputDescriptors::RevokedReceivedHTLC] } else { &[] });
2601+
amt += *amount;
2602+
},
2603+
&InputMaterial::RemoteHTLC { .. } => { return None; },
2604+
&InputMaterial::LocalHTLC { .. } => { return None; }
2605+
}
2606+
}
2607+
assert!(amt != 0);
2608+
2609+
let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight;
2610+
let new_feerate;
2611+
if let Some((new_fee, feerate)) = RBF_bump!(amt, cached_claim_datas.feerate_previous, fee_estimator, predicted_weight as u64, outp.txid) {
2612+
// If new computed fee is superior at the whole claimable amount burn all in fees
2613+
if new_fee > amt {
2614+
bumped_tx.output[0].value = 0;
2615+
} else {
2616+
bumped_tx.output[0].value = amt - new_fee;
2617+
}
2618+
new_feerate = feerate;
2619+
} else {
2620+
return None;
2621+
}
2622+
assert!(new_feerate != 0);
2623+
2624+
for (i, (vout, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
2625+
match per_outp_material {
2626+
&InputMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount } => {
2627+
let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
2628+
let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
2629+
let sig = self.secp_ctx.sign(&sighash, &key);
2630+
bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
2631+
bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
2632+
if *is_htlc {
2633+
bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec());
2634+
} else {
2635+
bumped_tx.input[i].witness.push(vec!(1));
2636+
}
2637+
bumped_tx.input[i].witness.push(script.clone().into_bytes());
2638+
log_trace!(self, "Going to broadcast bumped Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}", bumped_tx.txid(), if !is_htlc { "to_local" } else if script.len() == OFFERED_HTLC_SCRIPT_WEIGHT { "offered" } else if script.len() == ACCEPTED_HTLC_SCRIPT_WEIGHT { "received" } else { "" }, vout, outp.txid, new_feerate);
2639+
},
2640+
&InputMaterial::RemoteHTLC { .. } => {},
2641+
&InputMaterial::LocalHTLC { .. } => {
2642+
//TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't
2643+
// RBF them. Need a Lightning specs change and package relay modification :
2644+
// https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
2645+
return None;
2646+
}
2647+
}
2648+
}
2649+
assert!(predicted_weight >= bumped_tx.get_weight());
2650+
Some((new_timer, new_feerate, bumped_tx))
2651+
}
25132652
}
25142653

25152654
const MAX_ALLOC_SIZE: usize = 64*1024;

lightning/src/ln/functional_tests.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,7 @@ fn test_justice_tx() {
18841884

18851885
check_spends!(node_txn[0], revoked_local_txn[0].clone());
18861886
node_txn.swap_remove(0);
1887+
node_txn.truncate(1);
18871888
}
18881889
test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
18891890

@@ -1901,6 +1902,10 @@ fn test_justice_tx() {
19011902
// We test justice_tx build by A on B's revoked HTLC-Success tx
19021903
// Create some new channels:
19031904
let chan_6 = create_announced_chan_between_nodes(&nodes, 0, 1, LocalFeatures::new(), LocalFeatures::new());
1905+
{
1906+
let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
1907+
node_txn.clear();
1908+
}
19041909

19051910
// A pending HTLC which will be revoked:
19061911
let payment_preimage_4 = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0;
@@ -2078,7 +2083,7 @@ fn claim_htlc_outputs_single_tx() {
20782083
}
20792084

20802085
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
2081-
assert_eq!(node_txn.len(), 22); // ChannelManager : 2, ChannelMontitor: 8 (1 standard revoked output, 2 revocation htlc tx, 1 local commitment tx + 1 htlc timeout tx) * 2 (block-rescan) + 5 * (1 local commitment tx + 1 htlc timeout tx)
2086+
assert_eq!(node_txn.len(), 24); // ChannelManager : 2, ChannelMontitor: 8 (1 standard revoked output, 2 revocation htlc tx, 1 local commitment tx + 1 htlc timeout tx) * 2 (block-rescan) + 5 * (1 local commitment tx + 1 htlc timeout tx)
20822087

20832088
assert_eq!(node_txn[0], node_txn[7]);
20842089
assert_eq!(node_txn[1], node_txn[8]);
@@ -2088,10 +2093,6 @@ fn claim_htlc_outputs_single_tx() {
20882093
assert_eq!(node_txn[3], node_txn[5]); //local commitment tx + htlc timeout tx broadcasted by ChannelManger
20892094
assert_eq!(node_txn[4], node_txn[6]);
20902095

2091-
for i in 12..22 {
2092-
if i % 2 == 0 { assert_eq!(node_txn[3], node_txn[i]); } else { assert_eq!(node_txn[4], node_txn[i]); }
2093-
}
2094-
20952096
assert_eq!(node_txn[0].input.len(), 1);
20962097
assert_eq!(node_txn[1].input.len(), 1);
20972098
assert_eq!(node_txn[2].input.len(), 1);

0 commit comments

Comments
 (0)