Skip to content

Commit e46173b

Browse files
valentinewallaceAntoine Riard
and
Antoine Riard
committed
Claim HTLC output on-chain if preimage is recv'd after force-close
If we receive a preimage for an outgoing HTLC on a force-closed channel, we need to claim the HTLC output on-chain. Co-authored-by: Antoine Riard <[email protected]> Co-authored-by: Valentine Wallace <[email protected]>
1 parent 6ee96d1 commit e46173b

File tree

3 files changed

+211
-21
lines changed

3 files changed

+211
-21
lines changed

lightning/src/chain/channelmonitor.rs

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,25 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
11501150
L::Target: Logger,
11511151
{
11521152
self.payment_preimages.insert(payment_hash.clone(), payment_preimage.clone());
1153+
// If the channel is force closed, try to claim the output from this preimage
1154+
if self.lockdown_from_offchain || self.holder_tx_signed {
1155+
if let Some(txid) = self.current_counterparty_commitment_txid {
1156+
if let Some(commitment_number) = self.counterparty_commitment_txn_on_chain.get(&txid) {
1157+
let (htlc_claim_reqs, set_script) = self.get_counterparty_htlc_output_claim_reqs(*commitment_number, txid, None);
1158+
if set_script {
1159+
self.counterparty_payment_script = {
1160+
// Note that the Network here is ignored as we immediately drop the address for the
1161+
// script_pubkey version
1162+
let payment_hash160 = WPubkeyHash::hash(&self.keys.pubkeys().payment_point.serialize());
1163+
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&payment_hash160[..]).into_script()
1164+
};
1165+
}
1166+
for req in htlc_claim_reqs {
1167+
self.onchain_tx_handler.claim_counterparty_htlc(req, broadcaster, fee_estimator, logger);
1168+
}
1169+
}
1170+
}
1171+
}
11531172
}
11541173

11551174
pub(crate) fn broadcast_latest_holder_commitment_txn<B: Deref, L: Deref>(&mut self, broadcaster: &B, logger: &L)
@@ -1171,7 +1190,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
11711190
F::Target: FeeEstimator,
11721191
L::Target: Logger,
11731192
{
1174-
if self.latest_update_id + 1 != updates.update_id {
1193+
// ChannelMonitor updates may be applied after force close if we receive a
1194+
// preimage for a broadcasted counterparty HTLC output that we'd like to
1195+
// claim on-chain. If this is the case, we no longer have guaranteed access
1196+
// to the monitor's update ID, so we use a sentinel value instead.
1197+
if updates.update_id != std::u64::MAX && self.latest_update_id + 1 != updates.update_id {
11751198
panic!("Attempted to apply ChannelMonitorUpdates out of order, check the update_id before passing an update to update_monitor!");
11761199
}
11771200
for update in updates.updates.iter() {
@@ -1439,39 +1462,61 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
14391462
check_htlc_fails!(txid, "previous", 'prev_loop);
14401463
}
14411464

1465+
let (htlc_claim_reqs, set_script) = self.get_counterparty_htlc_output_claim_reqs(commitment_number, commitment_txid, Some(tx));
1466+
if set_script {
1467+
self.counterparty_payment_script = {
1468+
// Note that the Network here is ignored as we immediately drop the address for the
1469+
// script_pubkey version
1470+
let payment_hash160 = WPubkeyHash::hash(&self.keys.pubkeys().payment_point.serialize());
1471+
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&payment_hash160[..]).into_script()
1472+
};
1473+
}
1474+
for req in htlc_claim_reqs {
1475+
claimable_outpoints.push(req);
1476+
}
1477+
1478+
}
1479+
(claimable_outpoints, (commitment_txid, watch_outputs))
1480+
}
1481+
1482+
fn get_counterparty_htlc_output_claim_reqs(&self, commitment_number: u64, commitment_txid: Txid, tx: Option<&Transaction>) -> (Vec<ClaimRequest>, bool) {
1483+
let mut claims = Vec::new();
1484+
let mut set_counterparty_payment_script = false;
1485+
if let Some(htlc_outputs) = self.counterparty_claimable_outpoints.get(&commitment_txid) {
14421486
if let Some(revocation_points) = self.their_cur_revocation_points {
14431487
let revocation_point_option =
14441488
if revocation_points.0 == commitment_number { Some(&revocation_points.1) }
1445-
else if let Some(point) = revocation_points.2.as_ref() {
1446-
if revocation_points.0 == commitment_number + 1 { Some(point) } else { None }
1447-
} else { None };
1489+
else if let Some(point) = revocation_points.2.as_ref() {
1490+
if revocation_points.0 == commitment_number + 1 { Some(point) } else { None }
1491+
} else { None };
14481492
if let Some(revocation_point) = revocation_point_option {
1449-
self.counterparty_payment_script = {
1450-
// Note that the Network here is ignored as we immediately drop the address for the
1451-
// script_pubkey version
1452-
let payment_hash160 = WPubkeyHash::hash(&self.keys.pubkeys().payment_point.serialize());
1453-
Builder::new().push_opcode(opcodes::all::OP_PUSHBYTES_0).push_slice(&payment_hash160[..]).into_script()
1454-
};
1493+
set_counterparty_payment_script = true;
14551494

14561495
// Then, try to find htlc outputs
1457-
for (_, &(ref htlc, _)) in per_commitment_data.iter().enumerate() {
1496+
for (_, &(ref htlc, _)) in htlc_outputs.iter().enumerate() {
14581497
if let Some(transaction_output_index) = htlc.transaction_output_index {
1459-
if transaction_output_index as usize >= tx.output.len() ||
1460-
tx.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 {
1461-
return (claimable_outpoints, (commitment_txid, watch_outputs)); // Corrupted per_commitment_data, fuck this user
1498+
if let Some(transaction) = tx {
1499+
if transaction_output_index as usize >= transaction.output.len() ||
1500+
transaction.output[transaction_output_index as usize].value != htlc.amount_msat / 1000 {
1501+
return (claims, set_counterparty_payment_script) // Corrupted per_commitment_data, fuck this user
1502+
}
14621503
}
1463-
let preimage = if htlc.offered { if let Some(p) = self.payment_preimages.get(&htlc.payment_hash) { Some(*p) } else { None } } else { None };
1504+
let preimage = if htlc.offered {
1505+
if let Some(p) = self.payment_preimages.get(&htlc.payment_hash) {
1506+
Some(*p)
1507+
} else { None }
1508+
} else { None };
14641509
let aggregable = if !htlc.offered { false } else { true };
14651510
if preimage.is_some() || !htlc.offered {
14661511
let witness_data = InputMaterial::CounterpartyHTLC { per_commitment_point: *revocation_point, counterparty_delayed_payment_base_key: self.counterparty_tx_cache.counterparty_delayed_payment_base_key, counterparty_htlc_base_key: self.counterparty_tx_cache.counterparty_htlc_base_key, preimage, htlc: htlc.clone() };
1467-
claimable_outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
1512+
claims.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
14681513
}
14691514
}
14701515
}
14711516
}
14721517
}
14731518
}
1474-
(claimable_outpoints, (commitment_txid, watch_outputs))
1519+
(claims, set_counterparty_payment_script)
14751520
}
14761521

14771522
/// Attempts to claim a counterparty HTLC-Success/HTLC-Timeout's outputs using the revocation key

lightning/src/ln/functional_tests.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8503,3 +8503,119 @@ fn test_htlc_no_detection() {
85038503
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1, 201, true, header_201.block_hash());
85048504
expect_payment_failed!(nodes[0], our_payment_hash, true);
85058505
}
8506+
8507+
#[test]
8508+
fn test_unordered_onchain_htlc_settlement() {
8509+
// If we route an HTLC, then learn the HTLC's preimage after the upstream
8510+
// channel has been force-closed by our counterparty, we must claim that HTLC
8511+
// on-chain. (Given an HTLC forwarded from Alice --> Bob --> Carol, Alice
8512+
// would be the upstream node, and Carol the downstream.)
8513+
//
8514+
// Steps of the test:
8515+
// 1) Alice sends a HTLC to Carol through Bob.
8516+
// 2) Carol doesn't settle the HTLC.
8517+
// 3) Alice force-closes her channel with Bob.
8518+
// 4) Bob sees the Alice's commitment on his chain. An offered output is present but can't
8519+
// be claimed as Bob doesn't have yet knowledge of the preimage.
8520+
// 5) Carol release the preimage to Bob off-chain.
8521+
// 6) Bob claims the offered output on Alice's commitment.
8522+
// TODO: reverse the test with Bob's commitment reaching the chain instead of Alice's one.
8523+
8524+
let chanmon_cfgs = create_chanmon_cfgs(3);
8525+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
8526+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
8527+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
8528+
8529+
// Create some initial channels
8530+
let chan_ab = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 10001, InitFeatures::known(), InitFeatures::known());
8531+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 100000, 10001, InitFeatures::known(), InitFeatures::known());
8532+
8533+
// Steps (1) and (2):
8534+
// Send an HTLC Alice --> Bob --> Carol, but Carol doesn't settle the HTLC back.
8535+
let (payment_preimage, _payment_hash) = route_payment(&nodes[0], &vec!(&nodes[1], &nodes[2]), 3_000_000);
8536+
8537+
// Check that Alice's commitment transaction now contains an output for this HTLC.
8538+
let alice_txn = get_local_commitment_txn!(nodes[0], chan_ab.2);
8539+
check_spends!(alice_txn[0], chan_ab.3);
8540+
assert_eq!(alice_txn[0].output.len(), 2);
8541+
check_spends!(alice_txn[1], alice_txn[0]); // 2nd transaction is a non-final HTLC-timeout
8542+
assert_eq!(alice_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
8543+
assert_eq!(alice_txn.len(), 2);
8544+
8545+
// Steps (3) and (4):
8546+
// Broadcast Alice's commitment transaction and check that Bob responds by (1) broadcasting
8547+
// a channel update and (2) adding a new ChannelMonitor.
8548+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42};
8549+
connect_block(&nodes[1], &Block { header, txdata: vec![alice_txn[0].clone()]}, 1);
8550+
check_closed_broadcast!(nodes[1], false);
8551+
check_added_monitors!(nodes[1], 1);
8552+
{
8553+
let mut bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap(); // ChannelManager : 1 (commitment tx)
8554+
assert_eq!(bob_txn.len(), 1);
8555+
check_spends!(bob_txn[0], chan_ab.3);
8556+
bob_txn.clear();
8557+
}
8558+
8559+
// Step (5):
8560+
// Carol then claims the funds and sends an update_fulfill message to Bob, and they go
8561+
// through the process of removing the HTLC from their commitment transactions.
8562+
assert!(nodes[2].node.claim_funds(payment_preimage, &None, 3_000_000));
8563+
check_added_monitors!(nodes[2], 1);
8564+
let carol_updates = get_htlc_update_msgs!(nodes[2], nodes[1].node.get_our_node_id());
8565+
assert!(carol_updates.update_add_htlcs.is_empty());
8566+
assert!(carol_updates.update_fail_htlcs.is_empty());
8567+
assert!(carol_updates.update_fail_malformed_htlcs.is_empty());
8568+
assert!(carol_updates.update_fee.is_none());
8569+
assert_eq!(carol_updates.update_fulfill_htlcs.len(), 1);
8570+
8571+
nodes[1].node.handle_update_fulfill_htlc(&nodes[2].node.get_our_node_id(), &carol_updates.update_fulfill_htlcs[0]);
8572+
nodes[1].node.handle_commitment_signed(&nodes[2].node.get_our_node_id(), &carol_updates.commitment_signed);
8573+
// One monitor update for the preimage, one monitor update for the new commitment tx info
8574+
check_added_monitors!(nodes[1], 2);
8575+
8576+
let events = nodes[1].node.get_and_clear_pending_msg_events();
8577+
assert_eq!(events.len(), 2);
8578+
let bob_revocation = match events[0] {
8579+
MessageSendEvent::SendRevokeAndACK { ref node_id, ref msg } => {
8580+
assert_eq!(*node_id, nodes[2].node.get_our_node_id());
8581+
(*msg).clone()
8582+
},
8583+
_ => panic!("Unexpected event"),
8584+
};
8585+
let bob_updates = match events[1] {
8586+
MessageSendEvent::UpdateHTLCs { ref node_id, ref updates } => {
8587+
assert_eq!(*node_id, nodes[2].node.get_our_node_id());
8588+
(*updates).clone()
8589+
},
8590+
_ => panic!("Unexpected event"),
8591+
};
8592+
8593+
nodes[2].node.handle_revoke_and_ack(&nodes[1].node.get_our_node_id(), &bob_revocation);
8594+
check_added_monitors!(nodes[2], 1);
8595+
nodes[2].node.handle_commitment_signed(&nodes[1].node.get_our_node_id(), &bob_updates.commitment_signed);
8596+
check_added_monitors!(nodes[2], 1);
8597+
8598+
let events = nodes[2].node.get_and_clear_pending_msg_events();
8599+
assert_eq!(events.len(), 1);
8600+
let carol_revocation = match events[0] {
8601+
MessageSendEvent::SendRevokeAndACK { ref node_id, ref msg } => {
8602+
assert_eq!(*node_id, nodes[1].node.get_our_node_id());
8603+
(*msg).clone()
8604+
},
8605+
_ => panic!("Unexpected event"),
8606+
};
8607+
nodes[1].node.handle_revoke_and_ack(&nodes[2].node.get_our_node_id(), &carol_revocation);
8608+
check_added_monitors!(nodes[1], 1);
8609+
8610+
let header = BlockHeader { version: 0x20000000, prev_blockhash: Default::default(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
8611+
connect_block(&nodes[1], &Block { header, txdata: vec![] }, 1);
8612+
// Step (6):
8613+
// Finally, check that Bob broadcasted a preimage-claiming transaction for the HTLC
8614+
// output on Alice's broadcasted commitment transaction.
8615+
{
8616+
let bob_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone(); // ChannelMonitor : 1 (htlc-preimage tx)
8617+
assert_eq!(bob_txn.len(), 1);
8618+
check_spends!(bob_txn[0], alice_txn[0]);
8619+
assert_eq!(bob_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
8620+
}
8621+
}

lightning/src/ln/onchaintx.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ pub struct OnchainTxHandler<ChanSigner: ChannelKeys> {
282282

283283
onchain_events_waiting_threshold_conf: HashMap<u32, Vec<OnchainEvent>>,
284284

285+
latest_height: u32,
286+
285287
secp_ctx: Secp256k1<secp256k1::All>,
286288
}
287289

@@ -328,6 +330,7 @@ impl<ChanSigner: ChannelKeys + Writeable> OnchainTxHandler<ChanSigner> {
328330
}
329331
}
330332
}
333+
self.latest_height.write(writer)?;
331334
Ok(())
332335
}
333336
}
@@ -387,6 +390,7 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for OnchainTxHandler<ChanSigne
387390
}
388391
onchain_events_waiting_threshold_conf.insert(height_target, events);
389392
}
393+
let latest_height = Readable::read(reader)?;
390394

391395
Ok(OnchainTxHandler {
392396
destination_script,
@@ -399,6 +403,7 @@ impl<ChanSigner: ChannelKeys + Readable> Readable for OnchainTxHandler<ChanSigne
399403
claimable_outpoints,
400404
pending_claim_requests,
401405
onchain_events_waiting_threshold_conf,
406+
latest_height,
402407
secp_ctx: Secp256k1::new(),
403408
})
404409
}
@@ -420,6 +425,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
420425
pending_claim_requests: HashMap::new(),
421426
claimable_outpoints: HashMap::new(),
422427
onchain_events_waiting_threshold_conf: HashMap::new(),
428+
latest_height: 0,
423429

424430
secp_ctx: Secp256k1::new(),
425431
}
@@ -471,7 +477,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
471477

472478
/// 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
473479
/// (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.
474-
fn generate_claim_tx<F: Deref, L: Deref>(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: F, logger: L) -> Option<(Option<u32>, u32, Transaction)>
480+
fn generate_claim_tx<F: Deref, L: Deref>(&mut self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: &F, logger: &L) -> Option<(Option<u32>, u32, Transaction)>
475481
where F::Target: FeeEstimator,
476482
L::Target: Logger,
477483
{
@@ -657,6 +663,29 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
657663
None
658664
}
659665

666+
pub(crate) fn claim_counterparty_htlc<B: Deref, F: Deref, L: Deref>(&mut self, claim: ClaimRequest, broadcaster: &B, fee_estimator: &F, logger: &L)
667+
where B::Target: BroadcasterInterface,
668+
F::Target: FeeEstimator,
669+
L::Target: Logger,
670+
{
671+
let height = self.latest_height;
672+
let mut agg_claim = HashMap::new();
673+
agg_claim.insert(claim.outpoint, claim.witness_data);
674+
let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock: claim.absolute_timelock, per_input_material: agg_claim };
675+
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, fee_estimator, logger) {
676+
claim_material.height_timer = new_timer;
677+
claim_material.feerate_previous = new_feerate;
678+
let txid = tx.txid();
679+
for k in claim_material.per_input_material.keys() {
680+
log_trace!(logger, "Registering claiming request for {}:{}", k.txid, k.vout);
681+
self.claimable_outpoints.insert(k.clone(), (txid, height));
682+
}
683+
self.pending_claim_requests.insert(txid, claim_material);
684+
log_trace!(logger, "Broadcast onchain {}", log_tx!(tx));
685+
broadcaster.broadcast_transaction(&tx);
686+
}
687+
}
688+
660689
pub(crate) fn block_connected<B: Deref, F: Deref, L: Deref>(&mut self, txn_matched: &[&Transaction], claimable_outpoints: Vec<ClaimRequest>, height: u32, broadcaster: B, fee_estimator: F, logger: L)
661690
where B::Target: BroadcasterInterface,
662691
F::Target: FeeEstimator,
@@ -691,7 +720,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
691720
// height timer expiration (i.e in how many blocks we're going to take action).
692721
for (soonest_timelock, claim) in new_claims.drain(..) {
693722
let mut claim_material = ClaimTxBumpMaterial { height_timer: None, feerate_previous: 0, soonest_timelock, per_input_material: claim };
694-
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator, &*logger) {
723+
if let Some((new_timer, new_feerate, tx)) = self.generate_claim_tx(height, &claim_material, &&*fee_estimator, &&*logger) {
695724
claim_material.height_timer = new_timer;
696725
claim_material.feerate_previous = new_feerate;
697726
let txid = tx.txid();
@@ -818,7 +847,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
818847
// Build, bump and rebroadcast tx accordingly
819848
log_trace!(logger, "Bumping {} candidates", bump_candidates.len());
820849
for (first_claim_txid, claim_material) in bump_candidates.iter() {
821-
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator, &*logger) {
850+
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &&*fee_estimator, &&*logger) {
822851
log_trace!(logger, "Broadcast onchain {}", log_tx!(bump_tx));
823852
broadcaster.broadcast_transaction(&bump_tx);
824853
if let Some(claim_material) = self.pending_claim_requests.get_mut(first_claim_txid) {
@@ -855,7 +884,7 @@ impl<ChanSigner: ChannelKeys> OnchainTxHandler<ChanSigner> {
855884
}
856885
}
857886
for (_, claim_material) in bump_candidates.iter_mut() {
858-
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &*fee_estimator, &*logger) {
887+
if let Some((new_timer, new_feerate, bump_tx)) = self.generate_claim_tx(height, &claim_material, &&*fee_estimator, &&*logger) {
859888
claim_material.height_timer = new_timer;
860889
claim_material.feerate_previous = new_feerate;
861890
broadcaster.broadcast_transaction(&bump_tx);

0 commit comments

Comments
 (0)