Skip to content

Commit 0832312

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 cb45d43 commit 0832312

File tree

2 files changed

+136
-16
lines changed

2 files changed

+136
-16
lines changed

src/ln/channelmonitor.rs

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2217,13 +2217,6 @@ impl ChannelMonitor {
22172217
}
22182218
}
22192219
}
2220-
for claim in pending_claims {
2221-
log_trace!(self, "Outpoint {}:{} is under claiming process, if it doesn't succeed, a bumped claiming txn is going to be broadcast at height {}", (claim.0).vout, (claim.0).txid, (claim.1).0);
2222-
match self.our_claim_txn_waiting_first_conf.entry(claim.0) {
2223-
hash_map::Entry::Occupied(_) => {},
2224-
hash_map::Entry::Vacant(entry) => { entry.insert(claim.1); }
2225-
}
2226-
}
22272220
if let Some(events) = self.onchain_events_waiting_threshold_conf.remove(&height) {
22282221
for ev in events {
22292222
match ev {
@@ -2238,12 +2231,22 @@ impl ChannelMonitor {
22382231
}
22392232
}
22402233
}
2241-
{
2242-
for (claimed_txid, cached_claim_datas) in self.our_claim_txn_waiting_first_conf.iter_mut() {
2243-
if cached_claim_datas.0 == height {
2244-
}
2234+
let mut bump_candidates = Vec::new();
2235+
for (claimed_txid, ref mut cached_claim_datas) in self.our_claim_txn_waiting_first_conf.iter_mut() {
2236+
if cached_claim_datas.0 == height {
2237+
bump_candidates.push((claimed_txid.clone(), cached_claim_datas.clone()));
2238+
}
2239+
}
2240+
for (claimed_txid, cached_claim_datas) in bump_candidates.iter_mut() {
2241+
if let Some((new_timer, new_feerate, bump_tx)) = self.bump_claim_tx(height, &claimed_txid, &cached_claim_datas, fee_estimator) {
2242+
cached_claim_datas.0 = new_timer;
2243+
cached_claim_datas.2 = new_feerate;
2244+
broadcaster.broadcast_transaction(&bump_tx);
22452245
}
22462246
}
2247+
for (claimed_txid, cached_claim_datas) in bump_candidates.drain(..) {
2248+
self.our_claim_txn_waiting_first_conf.insert(claimed_txid, cached_claim_datas);
2249+
}
22472250
self.last_block_hash = block_hash.clone();
22482251
(watch_outputs, spendable_outputs, htlc_updated)
22492252
}
@@ -2457,6 +2460,122 @@ impl ChannelMonitor {
24572460
}
24582461
htlc_updated
24592462
}
2463+
2464+
/// 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
2465+
/// (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.
2466+
fn bump_claim_tx(&self, height: u32, txid: &Sha256dHash, cached_claim_datas: &(u32, u32, u64, u32, HashMap<u32, TxMaterial>), fee_estimator: &FeeEstimator) -> Option<(u32, u64, Transaction)> {
2467+
let mut inputs = Vec::new();
2468+
for vout in cached_claim_datas.4.keys() {
2469+
inputs.push(TxIn {
2470+
previous_output: BitcoinOutPoint {
2471+
txid: txid.clone(),
2472+
vout: *vout,
2473+
},
2474+
script_sig: Script::new(),
2475+
sequence: 0xfffffffd,
2476+
witness: Vec::new(),
2477+
});
2478+
}
2479+
let mut bumped_tx = Transaction {
2480+
version: 2,
2481+
lock_time: 0,
2482+
input: inputs,
2483+
output: vec![TxOut {
2484+
script_pubkey: self.destination_script.clone(),
2485+
value: 0
2486+
}],
2487+
};
2488+
2489+
macro_rules! RBF_bump {
2490+
($amount: expr, $old_feerate: expr, $fee_estimator: expr, $predicted_weight: expr, $txid: expr) => {
2491+
{
2492+
let mut used_feerate;
2493+
// If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
2494+
let new_fee = if $old_feerate < $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) {
2495+
let mut value = $amount;
2496+
if subtract_high_prio_fee!(self, $fee_estimator, value, $predicted_weight, txid, used_feerate) {
2497+
$amount - value
2498+
} else {
2499+
log_trace!(self, "Can't new-estimation bump claiming on {}, amount {} is too small", $txid, $amount);
2500+
return None;
2501+
}
2502+
// ...else just increase the previous feerate by 25% (because that's a nice number)
2503+
} else {
2504+
let fee = $old_feerate * $predicted_weight / 750;
2505+
if $amount <= fee {
2506+
log_trace!(self, "Can't 25% bump claiming on {}, amount {} is too small", $txid, $amount);
2507+
return None;
2508+
}
2509+
fee
2510+
};
2511+
2512+
let previous_fee = $old_feerate * $predicted_weight / 1000;
2513+
let min_relay_fee = $fee_estimator.get_min_relay_sat_per_1000_weight() * $predicted_weight / 1000;
2514+
// BIP 125 Opt-in Full Replace-by-Fee Signaling
2515+
// * 3. The replacement transaction pays an absolute fee of at least the sum paid by the original transactions.
2516+
// * 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.
2517+
let new_fee = if new_fee < previous_fee + min_relay_fee {
2518+
new_fee + previous_fee + min_relay_fee - new_fee
2519+
} else {
2520+
new_fee
2521+
};
2522+
Some((new_fee, new_fee * 1000 / $predicted_weight))
2523+
}
2524+
}
2525+
}
2526+
2527+
let new_timer = Self::get_height_timer(height, cached_claim_datas.3);
2528+
let mut inputs_witnesses_weight = 0;
2529+
let mut amt = 0;
2530+
for per_outp_material in cached_claim_datas.4.values() {
2531+
match per_outp_material {
2532+
&TxMaterial::Revoked { ref script, ref is_htlc, ref amount, .. } => {
2533+
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 { &[] });
2534+
amt = *amount;
2535+
},
2536+
&TxMaterial::RemoteHTLC { .. } => {},
2537+
&TxMaterial::LocalHTLC { .. } => {},
2538+
}
2539+
}
2540+
assert!(amt != 0);
2541+
2542+
let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight;
2543+
let mut new_feerate = 0;
2544+
if let Some((new_fee, feerate)) = RBF_bump!(amt, cached_claim_datas.2, fee_estimator, predicted_weight, txid) {
2545+
bumped_tx.output[0].value = amt - new_fee;
2546+
new_feerate = feerate;
2547+
} else {
2548+
return None;
2549+
}
2550+
2551+
for (i, (vout, per_outp_material)) in cached_claim_datas.4.iter().enumerate() {
2552+
match per_outp_material {
2553+
&TxMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount } => {
2554+
let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
2555+
let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
2556+
let sig = self.secp_ctx.sign(&sighash, &key);
2557+
bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
2558+
bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
2559+
if *is_htlc {
2560+
bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec());
2561+
} else {
2562+
bumped_tx.input[i].witness.push(vec!(1));
2563+
}
2564+
bumped_tx.input[i].witness.push(script.clone().into_bytes());
2565+
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, txid, new_feerate);
2566+
},
2567+
&TxMaterial::RemoteHTLC { .. } => {},
2568+
&TxMaterial::LocalHTLC { .. } => {
2569+
//TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't
2570+
// RBF them. Need a Lightning specs change and package relay modification :
2571+
// https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
2572+
return None;
2573+
}
2574+
}
2575+
}
2576+
assert!(predicted_weight >= bumped_tx.get_weight());
2577+
Some((new_timer, new_feerate, bumped_tx))
2578+
}
24602579
}
24612580

24622581
const MAX_ALLOC_SIZE: usize = 64*1024;

src/ln/functional_tests.rs

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

17571757
check_spends!(node_txn[0], revoked_local_txn[0].clone());
17581758
node_txn.swap_remove(0);
1759+
node_txn.truncate(1);
17591760
}
17601761
test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
17611762

@@ -1773,6 +1774,10 @@ fn test_justice_tx() {
17731774
// We test justice_tx build by A on B's revoked HTLC-Success tx
17741775
// Create some new channels:
17751776
let chan_6 = create_announced_chan_between_nodes(&nodes, 0, 1);
1777+
{
1778+
let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
1779+
node_txn.clear();
1780+
}
17761781

17771782
// A pending HTLC which will be revoked:
17781783
let payment_preimage_4 = route_payment(&nodes[0], &vec!(&nodes[1])[..], 3000000).0;
@@ -1950,7 +1955,7 @@ fn claim_htlc_outputs_single_tx() {
19501955
}
19511956

19521957
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
1953-
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)
1958+
assert_eq!(node_txn.len(), 26); // 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)
19541959

19551960
assert_eq!(node_txn[0], node_txn[7]);
19561961
assert_eq!(node_txn[1], node_txn[8]);
@@ -1960,10 +1965,6 @@ fn claim_htlc_outputs_single_tx() {
19601965
assert_eq!(node_txn[3], node_txn[5]); //local commitment tx + htlc timeout tx broadcasted by ChannelManger
19611966
assert_eq!(node_txn[4], node_txn[6]);
19621967

1963-
for i in 12..22 {
1964-
if i % 2 == 0 { assert_eq!(node_txn[3], node_txn[i]); } else { assert_eq!(node_txn[4], node_txn[i]); }
1965-
}
1966-
19671968
assert_eq!(node_txn[0].input.len(), 1);
19681969
assert_eq!(node_txn[1].input.len(), 1);
19691970
assert_eq!(node_txn[2].input.len(), 1);

0 commit comments

Comments
 (0)