Skip to content

Commit 3a4100c

Browse files
author
Antoine Riard
committed
Structurify claim request handed between detection/reaction
1 parent 2d127a3 commit 3a4100c

File tree

2 files changed

+57
-31
lines changed

2 files changed

+57
-31
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,28 @@ impl<R: ::std::io::Read> Readable<R> for InputMaterial {
563563
}
564564
}
565565

566+
/// ClaimRequest is a descriptor structure to communicate between detection
567+
/// and reaction module. They are generated by ChannelMonitor while parsing
568+
/// onchain txn leaked from a channel and handed over to OnchainTxHandler which
569+
/// is responsible for opportunistic aggregation, selecting and enforcing
570+
/// bumping logic, building and signing transactions.
571+
pub(crate) struct ClaimRequest {
572+
// Block height before which claiming is exclusive to one party,
573+
// after reaching it, claiming may be contentious.
574+
pub(crate) absolute_timelock: u32,
575+
// Timeout tx must have nLocktime set which means aggregating multiple
576+
// ones must take the higher nLocktime among them to satisfy all of them.
577+
// Sadly it has few pitfalls, a) it takes longuer to get fund back b) CLTV_DELTA
578+
// of a sooner-HTLC could be swallowed by the highest nLocktime of the HTLC set.
579+
// Do simplify we mark them as non-aggregable.
580+
pub(crate) aggregable: bool,
581+
// Basic bitcoin outpoint (txid, vout)
582+
pub(crate) outpoint: BitcoinOutPoint,
583+
// Following outpoint type, set of data needed to generate transaction digest
584+
// and satisfy witness program.
585+
pub(crate) witness_data: InputMaterial
586+
}
587+
566588
/// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it
567589
/// once they mature to enough confirmations (ANTI_REORG_DELAY)
568590
#[derive(Clone, PartialEq)]
@@ -1387,7 +1409,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
13871409
/// HTLC-Success/HTLC-Timeout transactions.
13881410
/// Return updates for HTLC pending in the channel and failed automatically by the broadcast of
13891411
/// revoked remote commitment tx
1390-
fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32) -> (HashMap<Sha256dHash, Vec<(u32, bool, BitcoinOutPoint, InputMaterial)>>, (Sha256dHash, Vec<TxOut>), Option<SpendableOutputDescriptor>) {
1412+
fn check_spend_remote_transaction(&mut self, tx: &Transaction, height: u32) -> (HashMap<Sha256dHash, Vec<ClaimRequest>>, (Sha256dHash, Vec<TxOut>), Option<SpendableOutputDescriptor>) {
13911413
// Most secp and related errors trying to create keys means we have no hope of constructing
13921414
// a spend transaction...so we return no transactions to broadcast
13931415
let mut claimable_outpoints = HashMap::new();
@@ -1442,7 +1464,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
14421464
// First, process non-htlc outputs (to_local & to_remote)
14431465
for (idx, outp) in tx.output.iter().enumerate() {
14441466
if outp.script_pubkey == revokeable_p2wsh {
1445-
outpoints.push((height + self.our_to_self_delay as u32, true, BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, InputMaterial::Revoked { script: revokeable_redeemscript.clone(), pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: outp.value }));
1467+
let witness_data = InputMaterial::Revoked { script: revokeable_redeemscript.clone(), pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: outp.value };
1468+
outpoints.push(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, witness_data});
14461469
} else if Some(&outp.script_pubkey) == local_payment_p2wpkh.as_ref() {
14471470
spendable_descriptor = Some(SpendableOutputDescriptor::DynamicOutputP2WPKH {
14481471
outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 },
@@ -1462,7 +1485,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
14621485
tx.output[transaction_output_index as usize].script_pubkey != expected_script.to_v0_p2wsh() {
14631486
return (claimable_outpoints, (commitment_txid, watch_outputs), spendable_descriptor); // Corrupted per_commitment_data, fuck this user
14641487
}
1465-
outpoints.push((htlc.cltv_expiry, true, BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, InputMaterial::Revoked { script: expected_script, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: tx.output[transaction_output_index as usize].value }));
1488+
let witness_data = InputMaterial::Revoked { script: expected_script, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: tx.output[transaction_output_index as usize].value };
1489+
outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable: true, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
14661490
}
14671491
}
14681492
}
@@ -1625,7 +1649,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16251649
let preimage = if htlc.offered { if let Some(p) = self.payment_preimages.get(&htlc.payment_hash) { Some(*p) } else { None } } else { None };
16261650
let aggregable = if !htlc.offered { false } else { true };
16271651
if preimage.is_some() || !htlc.offered {
1628-
outpoints.push((htlc.cltv_expiry, aggregable, BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, InputMaterial::RemoteHTLC { script: expected_script, key: htlc_privkey, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry }));
1652+
let witness_data = InputMaterial::RemoteHTLC { script: expected_script, key: htlc_privkey, preimage, amount: htlc.amount_msat / 1000, locktime: htlc.cltv_expiry };
1653+
outpoints.push(ClaimRequest { absolute_timelock: htlc.cltv_expiry, aggregable, outpoint: BitcoinOutPoint { txid: commitment_txid, vout: transaction_output_index }, witness_data });
16291654
}
16301655
}
16311656
}
@@ -1647,7 +1672,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16471672
}
16481673

16491674
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
1650-
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> HashMap<Sha256dHash, Vec<(u32, bool, BitcoinOutPoint, InputMaterial)>> {
1675+
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> HashMap<Sha256dHash, Vec<ClaimRequest>> {
16511676
//TODO: send back new outputs to guarantee pending_claim_request consistency
16521677
if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 {
16531678
return HashMap::new()
@@ -1680,7 +1705,8 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16801705
let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!
16811706

16821707
log_trace!(self, "Remote HTLC broadcast {}:{}", htlc_txid, 0);
1683-
let outpoints = vec!((height + self.our_to_self_delay as u32, true, BitcoinOutPoint { txid: htlc_txid, vout: 0}, InputMaterial::Revoked { script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value }));
1708+
let witness_data = InputMaterial::Revoked { script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value };
1709+
let outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
16841710
let mut claimable_outpoints = HashMap::with_capacity(1);
16851711
claimable_outpoints.insert(htlc_txid, outpoints);
16861712
claimable_outpoints
@@ -1955,7 +1981,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
19551981
log_trace!(self, "Block {} at height {} connected with {} txn matched", block_hash, height, txn_matched.len());
19561982
let mut watch_outputs = Vec::new();
19571983
let mut spendable_outputs = Vec::new();
1958-
let mut claimable_outpoints = HashMap::new();
1984+
let mut claim_requests = HashMap::new();
19591985
for tx in txn_matched {
19601986
if tx.input.len() == 1 {
19611987
// Assuming our keys were not leaked (in which case we're screwed no matter what),
@@ -1973,14 +1999,14 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
19731999
};
19742000
if funding_txo.is_none() || (prevout.txid == funding_txo.as_ref().unwrap().0.txid && prevout.vout == funding_txo.as_ref().unwrap().0.index as u32) {
19752001
if (tx.input[0].sequence >> 8*3) as u8 == 0x80 && (tx.lock_time >> 8*3) as u8 == 0x20 {
1976-
let (mut new_outpoints, new_outputs, spendable_output) = self.check_spend_remote_transaction(&tx, height);
2002+
let (mut new_claim_requests, new_outputs, spendable_output) = self.check_spend_remote_transaction(&tx, height);
19772003
if !new_outputs.1.is_empty() {
19782004
watch_outputs.push(new_outputs);
19792005
}
19802006
if let Some(spendable_output) = spendable_output {
19812007
spendable_outputs.push(spendable_output);
19822008
}
1983-
if new_outpoints.is_empty() {
2009+
if new_claim_requests.is_empty() {
19842010
let (local_txn, mut spendable_output, new_outputs) = self.check_spend_local_transaction(&tx, height);
19852011
spendable_outputs.append(&mut spendable_output);
19862012
for tx in local_txn.iter() {
@@ -1991,23 +2017,23 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
19912017
watch_outputs.push(new_outputs);
19922018
}
19932019
}
1994-
for (k, v) in new_outpoints.drain() {
1995-
claimable_outpoints.insert(k, v);
2020+
for (k, v) in new_claim_requests.drain() {
2021+
claim_requests.insert(k, v);
19962022
}
19972023
}
1998-
if !funding_txo.is_none() && claimable_outpoints.is_empty() {
2024+
if !funding_txo.is_none() && claim_requests.is_empty() {
19992025
if let Some(spendable_output) = self.check_spend_closing_transaction(&tx) {
20002026
spendable_outputs.push(spendable_output);
20012027
}
20022028
}
20032029
} else {
20042030
if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) {
2005-
let mut new_outpoints = self.check_spend_remote_htlc(&tx, commitment_number, height);
2006-
for (k, v) in new_outpoints.drain() {
2007-
claimable_outpoints.insert(k, v);
2031+
let mut new_claim_requests = self.check_spend_remote_htlc(&tx, commitment_number, height);
2032+
for (k, v) in new_claim_requests.drain() {
2033+
claim_requests.insert(k, v);
20082034
}
2009-
for (k, v) in new_outpoints.drain() {
2010-
claimable_outpoints.insert(k, v);
2035+
for (k, v) in new_claim_requests.drain() {
2036+
claim_requests.insert(k, v);
20112037
}
20122038
}
20132039
}
@@ -2065,7 +2091,7 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
20652091
}
20662092
}
20672093

2068-
let mut spendable_output = self.onchain_tx_handler.block_connected(txn_matched, claimable_outpoints, height, broadcaster, fee_estimator);
2094+
let mut spendable_output = self.onchain_tx_handler.block_connected(txn_matched, claim_requests, height, broadcaster, fee_estimator);
20692095
spendable_outputs.append(&mut spendable_output);
20702096

20712097
self.last_block_hash = block_hash.clone();

lightning/src/ln/onchaintx.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use secp256k1::Secp256k1;
1414
use secp256k1;
1515

1616
use ln::msgs::DecodeError;
17-
use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial};
17+
use ln::channelmonitor::{ANTI_REORG_DELAY, CLTV_SHARED_CLAIM_BUFFER, InputMaterial, ClaimRequest};
1818
use ln::chan_utils::HTLCType;
1919
use chain::chaininterface::{FeeEstimator, BroadcasterInterface, ConfirmationTarget, MIN_RELAY_FEE_SAT_PER_1000_WEIGHT};
2020
use chain::keysinterface::SpendableOutputDescriptor;
@@ -469,31 +469,31 @@ impl OnchainTxHandler {
469469
Some((new_timer, new_feerate, bumped_tx))
470470
}
471471

472-
pub(crate) fn block_connected<B: Deref>(&mut self, txn_matched: &[&Transaction], claimable_outpoints: HashMap<Sha256dHash, Vec<(u32, bool, BitcoinOutPoint, InputMaterial)>>, height: u32, broadcaster: B, fee_estimator: &FeeEstimator) -> Vec<SpendableOutputDescriptor>
472+
pub(crate) fn block_connected<B: Deref>(&mut self, txn_matched: &[&Transaction], claim_requests: HashMap<Sha256dHash, Vec<ClaimRequest>>, height: u32, broadcaster: B, fee_estimator: &FeeEstimator) -> Vec<SpendableOutputDescriptor>
473473
where B::Target: BroadcasterInterface
474474
{
475475
let mut new_claims = Vec::new();
476476
let mut aggregated_claim = HashMap::new();
477477
let mut aggregated_soonest = ::std::u32::MAX;
478478
let mut new_pending_claim_requests = HashMap::with_capacity(new_claims.len());
479-
let mut new_claimable_outpoints = HashMap::with_capacity(claimable_outpoints.len());
479+
let mut new_claimable_outpoints = HashMap::with_capacity(claim_requests.len());
480480
let mut spendable_outputs = Vec::new();
481481

482482
// Try to aggregate outputs if they're 1) belong to same parent tx, 2) their
483483
// timelock expiration isn't imminent (<= CLTV_SHARED_CLAIM_BUFFER).
484-
for (_, siblings_outpoints) in claimable_outpoints {
485-
for outp in siblings_outpoints {
484+
for (_, siblings_requests) in claim_requests {
485+
for req in siblings_requests {
486486
// Don't claim a outpoint twice that would be bad for privacy and may uselessly lock a CPFP input for a while
487-
if let Some(_) = self.claimable_outpoints.get(&outp.2) { log_trace!(self, "Bouncing off outpoint {}:{}, already registered its claiming request", outp.2.txid, outp.2.vout); } else {
488-
log_trace!(self, "Test if outpoint can be aggregated with expiration {} against {}", outp.0, height + CLTV_SHARED_CLAIM_BUFFER);
489-
if outp.0 <= height + CLTV_SHARED_CLAIM_BUFFER || !outp.1 { // Don't aggregate if outpoint absolute timelock is soon or marked as non-aggregable
487+
if let Some(_) = self.claimable_outpoints.get(&req.outpoint) { log_trace!(self, "Bouncing off outpoint {}:{}, already registered its claiming request", req.outpoint.txid, req.outpoint.vout); } else {
488+
log_trace!(self, "Test if outpoint can be aggregated with expiration {} against {}", req.absolute_timelock, height + CLTV_SHARED_CLAIM_BUFFER);
489+
if req.absolute_timelock <= height + CLTV_SHARED_CLAIM_BUFFER || !req.aggregable { // Don't aggregate if outpoint absolute timelock is soon or marked as non-aggregable
490490
let mut single_input = HashMap::new();
491-
single_input.insert(outp.2, outp.3);
492-
new_claims.push((outp.0, single_input));
491+
single_input.insert(req.outpoint, req.witness_data);
492+
new_claims.push((req.absolute_timelock, single_input));
493493
} else {
494-
aggregated_claim.insert(outp.2, outp.3);
495-
if outp.0 < aggregated_soonest {
496-
aggregated_soonest = outp.0;
494+
aggregated_claim.insert(req.outpoint, req.witness_data);
495+
if req.absolute_timelock < aggregated_soonest {
496+
aggregated_soonest = req.absolute_timelock;
497497
}
498498
}
499499
}

0 commit comments

Comments
 (0)