Skip to content

Commit feb0a9f

Browse files
authored
Merge pull request #546 from TheBlueMatt/2020-03-519-nits
Watch revoked HTLC-Success/Timeout outputs
2 parents c8236b2 + 2d2f658 commit feb0a9f

File tree

4 files changed

+38
-17
lines changed

4 files changed

+38
-17
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,22 +1668,22 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16681668
}
16691669

16701670
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
1671-
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> Vec<ClaimRequest> {
1672-
//TODO: send back new outputs to guarantee pending_claim_request consistency
1671+
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32) -> (Vec<ClaimRequest>, Option<(Sha256dHash, Vec<TxOut>)>) {
1672+
let htlc_txid = tx.txid();
16731673
if tx.input.len() != 1 || tx.output.len() != 1 || tx.input[0].witness.len() != 5 {
1674-
return Vec::new()
1674+
return (Vec::new(), None)
16751675
}
16761676

16771677
macro_rules! ignore_error {
16781678
( $thing : expr ) => {
16791679
match $thing {
16801680
Ok(a) => a,
1681-
Err(_) => return Vec::new()
1681+
Err(_) => return (Vec::new(), None)
16821682
}
16831683
};
16841684
}
16851685

1686-
let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return Vec::new(); };
1686+
let secret = if let Some(secret) = self.get_secret(commitment_number) { secret } else { return (Vec::new(), None); };
16871687
let per_commitment_key = ignore_error!(SecretKey::from_slice(&secret));
16881688
let per_commitment_point = PublicKey::from_secret_key(&self.secp_ctx, &per_commitment_key);
16891689
let (revocation_pubkey, revocation_key) = match self.key_storage {
@@ -1694,16 +1694,15 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
16941694
Storage::Watchtower { .. } => { unimplemented!() }
16951695
};
16961696
let delayed_key = match self.their_delayed_payment_base_key {
1697-
None => return Vec::new(),
1697+
None => return (Vec::new(), None),
16981698
Some(their_delayed_payment_base_key) => ignore_error!(chan_utils::derive_public_key(&self.secp_ctx, &per_commitment_point, &their_delayed_payment_base_key)),
16991699
};
17001700
let redeemscript = chan_utils::get_revokeable_redeemscript(&revocation_pubkey, self.our_to_self_delay, &delayed_key);
1701-
let htlc_txid = tx.txid(); //TODO: This is gonna be a performance bottleneck for watchtowers!
17021701

17031702
log_trace!(self, "Remote HTLC broadcast {}:{}", htlc_txid, 0);
17041703
let witness_data = InputMaterial::Revoked { witness_script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: false, amount: tx.output[0].value };
17051704
let claimable_outpoints = vec!(ClaimRequest { absolute_timelock: height + self.our_to_self_delay as u32, aggregable: true, outpoint: BitcoinOutPoint { txid: htlc_txid, vout: 0}, witness_data });
1706-
claimable_outpoints
1705+
(claimable_outpoints, Some((htlc_txid, tx.output.clone())))
17071706
}
17081707

17091708
fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, delayed_payment_base_key: &SecretKey) -> (Vec<Transaction>, Vec<SpendableOutputDescriptor>, Vec<TxOut>) {
@@ -2019,8 +2018,11 @@ impl<ChanSigner: ChannelKeys> ChannelMonitor<ChanSigner> {
20192018
}
20202019
} else {
20212020
if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) {
2022-
let mut new_outpoints = self.check_spend_remote_htlc(&tx, commitment_number, height);
2021+
let (mut new_outpoints, new_outputs_option) = self.check_spend_remote_htlc(&tx, commitment_number, height);
20232022
claimable_outpoints.append(&mut new_outpoints);
2023+
if let Some(new_outputs) = new_outputs_option {
2024+
watch_outputs.push(new_outputs);
2025+
}
20242026
}
20252027
}
20262028
}

lightning/src/ln/functional_test_utils.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,9 @@ pub fn test_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, chan: &(msgs::Cha
10981098
/// HTLC transaction.
10991099
pub fn test_revoked_htlc_claim_txn_broadcast<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, revoked_tx: Transaction, commitment_revoked_tx: Transaction) {
11001100
let mut node_txn = node.tx_broadcaster.txn_broadcasted.lock().unwrap();
1101-
// We should issue a 2nd transaction if one htlc is dropped from initial claiming tx
1102-
// but sometimes not as feerate is too-low
1103-
if node_txn.len() != 1 && node_txn.len() != 2 { assert!(false); }
1101+
// We may issue multiple claiming transaction on revoked outputs due to block rescan
1102+
// for revoked htlc outputs
1103+
if node_txn.len() != 1 && node_txn.len() != 2 && node_txn.len() != 3 { assert!(false); }
11041104
node_txn.retain(|tx| {
11051105
if tx.input.len() == 1 && tx.input[0].previous_output.txid == revoked_tx.txid() {
11061106
check_spends!(tx, revoked_tx);

lightning/src/ln/functional_tests.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,8 +2127,10 @@ fn test_justice_tx() {
21272127
test_txn_broadcast(&nodes[1], &chan_5, None, HTLCType::NONE);
21282128

21292129
nodes[0].block_notifier.block_connected(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 1);
2130+
// Verify broadcast of revoked HTLC-timeout
21302131
let node_txn = test_txn_broadcast(&nodes[0], &chan_5, Some(revoked_local_txn[0].clone()), HTLCType::TIMEOUT);
21312132
header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
2133+
// Broadcast revoked HTLC-timeout on node 1
21322134
nodes[1].block_notifier.block_connected(&Block { header, txdata: vec![node_txn[1].clone()] }, 1);
21332135
test_revoked_htlc_claim_txn_broadcast(&nodes[1], node_txn[1].clone(), revoked_local_txn[0].clone());
21342136
}
@@ -4182,9 +4184,14 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
41824184
check_closed_broadcast!(nodes[1], false);
41834185

41844186
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
4185-
assert_eq!(node_txn.len(), 4 ); // ChannelMonitor: justice tx on revoked commitment, justice tx on revoked HTLC-timeout, adjusted justice tx, ChannelManager: local commitment tx
4187+
assert_eq!(node_txn.len(), 4); // ChannelMonitor: justice tx on revoked commitment, justice tx on revoked HTLC-timeout, adjusted justice tx, ChannelManager: local commitment tx
4188+
assert_eq!(node_txn[0].input.len(), 2);
4189+
check_spends!(node_txn[0], revoked_local_txn[0]);
4190+
check_spends!(node_txn[1], chan_1.3);
41864191
assert_eq!(node_txn[2].input.len(), 1);
41874192
check_spends!(node_txn[2], revoked_htlc_txn[0]);
4193+
assert_eq!(node_txn[3].input.len(), 1);
4194+
check_spends!(node_txn[3], revoked_local_txn[0]);
41884195

41894196
// Check B's ChannelMonitor was able to generate the right spendable output descriptor
41904197
let spend_txn = check_spendable_outputs!(nodes[1], 1);
@@ -4233,7 +4240,7 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_success_tx() {
42334240

42344241
// Check A's ChannelMonitor was able to generate the right spendable output descriptor
42354242
let spend_txn = check_spendable_outputs!(nodes[0], 1);
4236-
assert_eq!(spend_txn.len(), 4);
4243+
assert_eq!(spend_txn.len(), 5); // Duplicated SpendableOutput due to block rescan after revoked htlc output tracking
42374244
assert_eq!(spend_txn[0], spend_txn[2]);
42384245
check_spends!(spend_txn[0], revoked_local_txn[0]); // spending to_remote output from revoked local tx
42394246
check_spends!(spend_txn[1], node_txn[0]); // spending justice tx output from revoked local tx htlc received output
@@ -6993,7 +7000,7 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
69937000

69947001
assert_eq!(node_txn[0].input.len(), 2);
69957002
check_spends!(node_txn[0], revoked_htlc_txn[0], revoked_htlc_txn[1]);
6996-
//// Verify bumped tx is different and 25% bump heuristic
7003+
// Verify bumped tx is different and 25% bump heuristic
69977004
assert_ne!(first, node_txn[0].txid());
69987005
let fee_2 = revoked_htlc_txn[0].output[0].value + revoked_htlc_txn[1].output[0].value - node_txn[0].output[0].value;
69997006
let feerate_2 = fee_2 * 1000 / node_txn[0].get_weight() as u64;
@@ -7008,7 +7015,13 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
70087015
connect_blocks(&nodes[0].block_notifier, 20, 145, true, header_145.bitcoin_hash());
70097016
{
70107017
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
7011-
assert_eq!(node_txn.len(), 1); //TODO: fix check_spend_remote_htlc lack of watch output
7018+
// We verify than no new transaction has been broadcast because previously
7019+
// we were buggy on this exact behavior by not tracking for monitoring remote HTLC outputs (see #411)
7020+
// which means we wouldn't see a spend of them by a justice tx and bumped justice tx
7021+
// were generated forever instead of safe cleaning after confirmation and ANTI_REORG_SAFE_DELAY blocks.
7022+
// Enforce spending of revoked htlc output by claiming transaction remove request as expected and dry
7023+
// up bumped justice generation.
7024+
assert_eq!(node_txn.len(), 0);
70127025
node_txn.clear();
70137026
}
70147027
check_closed_broadcast!(nodes[0], false);

lightning/src/ln/onchaintx.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ impl OnchainTxHandler {
482482
where B::Target: BroadcasterInterface,
483483
F::Target: FeeEstimator
484484
{
485+
log_trace!(self, "Block at height {} connected with {} claim requests", height, claimable_outpoints.len());
485486
let mut new_claims = Vec::new();
486487
let mut aggregated_claim = HashMap::new();
487488
let mut aggregated_soonest = ::std::u32::MAX;
@@ -573,17 +574,21 @@ impl OnchainTxHandler {
573574
if set_equality {
574575
clean_claim_request_after_safety_delay!();
575576
} else { // If false, generate new claim request with update outpoint set
577+
let mut at_least_one_drop = false;
576578
for input in tx.input.iter() {
577579
if let Some(input_material) = claim_material.per_input_material.remove(&input.previous_output) {
578580
claimed_outputs_material.push((input.previous_output, input_material));
581+
at_least_one_drop = true;
579582
}
580583
// If there are no outpoints left to claim in this request, drop it entirely after ANTI_REORG_DELAY.
581584
if claim_material.per_input_material.is_empty() {
582585
clean_claim_request_after_safety_delay!();
583586
}
584587
}
585588
//TODO: recompute soonest_timelock to avoid wasting a bit on fees
586-
bump_candidates.insert(first_claim_txid_height.0.clone());
589+
if at_least_one_drop {
590+
bump_candidates.insert(first_claim_txid_height.0.clone());
591+
}
587592
}
588593
break; //No need to iterate further, either tx is our or their
589594
} else {
@@ -634,6 +639,7 @@ impl OnchainTxHandler {
634639
}
635640

636641
// Build, bump and rebroadcast tx accordingly
642+
log_trace!(self, "Bumping {} candidates", bump_candidates.len());
637643
for first_claim_txid in bump_candidates.iter() {
638644
if let Some((new_timer, new_feerate)) = {
639645
if let Some(claim_material) = self.pending_claim_requests.get(first_claim_txid) {

0 commit comments

Comments
 (0)