Skip to content

Commit 174f42e

Browse files
committed
Mark counterparty revoked offered HTLCs as Unpinnable
If the counterparty broadcasts a revoked transaction with offered HTLCs, the output is not immediately pinnable as the counterparty cannot claim the HTLC until the CLTV expires and they use an HTLC-Timeout path. Here we properly set these packages as `Unpinnable`, changing some transaction generation during tests.
1 parent 2e80fbd commit 174f42e

File tree

3 files changed

+60
-38
lines changed

3 files changed

+60
-38
lines changed

lightning/src/chain/package.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,8 +699,13 @@ impl PackageSolvingData {
699699
match self {
700700
PackageSolvingData::RevokedOutput(RevokedOutput { .. }) =>
701701
PackageMalleability::Malleable(AggregationCluster::Unpinnable),
702-
PackageSolvingData::RevokedHTLCOutput(..) =>
703-
PackageMalleability::Malleable(AggregationCluster::Pinnable),
702+
PackageSolvingData::RevokedHTLCOutput(RevokedHTLCOutput { htlc, .. }) => {
703+
if htlc.offered {
704+
PackageMalleability::Malleable(AggregationCluster::Unpinnable)
705+
} else {
706+
PackageMalleability::Malleable(AggregationCluster::Pinnable)
707+
}
708+
},
704709
PackageSolvingData::CounterpartyOfferedHTLCOutput(..) =>
705710
PackageMalleability::Malleable(AggregationCluster::Unpinnable),
706711
PackageSolvingData::CounterpartyReceivedHTLCOutput(..) =>

lightning/src/ln/functional_tests.rs

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::chain;
1515
use crate::chain::{ChannelMonitorUpdateStatus, Confirm, Listen, Watch};
1616
use crate::chain::chaininterface::LowerBoundedFeeEstimator;
1717
use crate::chain::channelmonitor;
18-
use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY};
18+
use crate::chain::channelmonitor::{Balance, ChannelMonitorUpdateStep, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS, ANTI_REORG_DELAY, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE};
1919
use crate::chain::transaction::OutPoint;
2020
use crate::sign::{ecdsa::EcdsaChannelSigner, EntropySource, OutputSpender, SignerProvider};
2121
use crate::events::bump_transaction::WalletSource;
@@ -2645,14 +2645,12 @@ fn test_justice_tx_htlc_timeout() {
26452645
mine_transaction(&nodes[1], &revoked_local_txn[0]);
26462646
{
26472647
let mut node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
2648-
// The unpinnable, revoked to_self output, and the pinnable, revoked htlc output will
2649-
// be claimed in separate transactions.
2650-
assert_eq!(node_txn.len(), 2);
2651-
for tx in node_txn.iter() {
2652-
assert_eq!(tx.input.len(), 1);
2653-
check_spends!(tx, revoked_local_txn[0]);
2654-
}
2655-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
2648+
// The revoked HTLC output is not pinnable for another `TEST_FINAL_CLTV` blocks, and is
2649+
// thus claimed in the same transaction with the revoked to_self output.
2650+
assert_eq!(node_txn.len(), 1);
2651+
assert_eq!(node_txn[0].input.len(), 2);
2652+
check_spends!(node_txn[0], revoked_local_txn[0]);
2653+
assert_ne!(node_txn[0].input[0].previous_output, node_txn[0].input[1].previous_output);
26562654
node_txn.clear();
26572655
}
26582656
check_added_monitors!(nodes[1], 1);
@@ -2872,28 +2870,26 @@ fn claim_htlc_outputs() {
28722870
assert!(nodes[1].node.get_and_clear_pending_events().is_empty());
28732871

28742872
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
2875-
assert_eq!(node_txn.len(), 2); // Two penalty transactions:
2876-
assert_eq!(node_txn[0].input.len(), 1); // Claims the unpinnable, revoked output.
2877-
assert_eq!(node_txn[1].input.len(), 2); // Claims both pinnable, revoked HTLC outputs separately.
2878-
check_spends!(node_txn[0], revoked_local_txn[0]);
2879-
check_spends!(node_txn[1], revoked_local_txn[0]);
2880-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
2881-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[1].previous_output);
2882-
assert_ne!(node_txn[1].input[0].previous_output, node_txn[1].input[1].previous_output);
2873+
assert_eq!(node_txn.len(), 2); // ChannelMonitor: penalty txn
2874+
2875+
// The ChannelMonitor should claim the accepted HTLC output separately from the offered
2876+
// HTLC and to_self outputs.
2877+
let accepted_claim = node_txn.iter().filter(|tx| tx.input.len() == 1).next().unwrap();
2878+
let offered_to_self_claim = node_txn.iter().filter(|tx| tx.input.len() == 2).next().unwrap();
2879+
check_spends!(accepted_claim, revoked_local_txn[0]);
2880+
check_spends!(offered_to_self_claim, revoked_local_txn[0]);
2881+
assert_eq!(accepted_claim.input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
28832882

28842883
let mut witness_lens = BTreeSet::new();
2885-
witness_lens.insert(node_txn[0].input[0].witness.last().unwrap().len());
2886-
witness_lens.insert(node_txn[1].input[0].witness.last().unwrap().len());
2887-
witness_lens.insert(node_txn[1].input[1].witness.last().unwrap().len());
2888-
assert_eq!(witness_lens.len(), 3);
2884+
witness_lens.insert(offered_to_self_claim.input[0].witness.last().unwrap().len());
2885+
witness_lens.insert(offered_to_self_claim.input[1].witness.last().unwrap().len());
2886+
assert_eq!(witness_lens.len(), 2);
28892887
assert_eq!(*witness_lens.iter().skip(0).next().unwrap(), 77); // revoked to_local
2890-
assert_eq!(*witness_lens.iter().skip(1).next().unwrap(), OFFERED_HTLC_SCRIPT_WEIGHT); // revoked offered HTLC
2891-
assert_eq!(*witness_lens.iter().skip(2).next().unwrap(), ACCEPTED_HTLC_SCRIPT_WEIGHT); // revoked received HTLC
2888+
assert_eq!(*witness_lens.iter().skip(1).next().unwrap(), OFFERED_HTLC_SCRIPT_WEIGHT);
28922889

2893-
// Finally, mine the penalty transactions and check that we get an HTLC failure after
2890+
// Finally, mine the penalty transaction and check that we get an HTLC failure after
28942891
// ANTI_REORG_DELAY confirmations.
2895-
mine_transaction(&nodes[1], &node_txn[0]);
2896-
mine_transaction(&nodes[1], &node_txn[1]);
2892+
mine_transaction(&nodes[1], accepted_claim);
28972893
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
28982894
expect_payment_failed!(nodes[1], payment_hash_2, false);
28992895
}
@@ -5056,8 +5052,7 @@ fn test_static_spendable_outputs_timeout_tx() {
50565052
check_spends!(spend_txn[2], node_txn[0], commitment_tx[0]); // All outputs
50575053
}
50585054

5059-
#[test]
5060-
fn test_static_spendable_outputs_justice_tx_revoked_commitment_tx() {
5055+
fn do_test_static_spendable_outputs_justice_tx_revoked_commitment_tx(split_tx: bool) {
50615056
let chanmon_cfgs = create_chanmon_cfgs(2);
50625057
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
50635058
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
@@ -5073,20 +5068,28 @@ fn test_static_spendable_outputs_justice_tx_revoked_commitment_tx() {
50735068

50745069
claim_payment(&nodes[0], &vec!(&nodes[1])[..], payment_preimage);
50755070

5071+
if split_tx {
5072+
connect_blocks(&nodes[1], TEST_FINAL_CLTV - COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE + 1);
5073+
}
5074+
50765075
mine_transaction(&nodes[1], &revoked_local_txn[0]);
50775076
check_closed_broadcast!(nodes[1], true);
50785077
check_added_monitors!(nodes[1], 1);
50795078
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 100000);
50805079

5081-
// The unpinnable, revoked to_self output and the pinnable, revoked HTLC output will be claimed
5082-
// in separate transactions.
5080+
// If the HTLC expires in more than COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE blocks, we'll
5081+
// claim both the revoked and HTLC outputs in one transaction, otherwise we'll split them as we
5082+
// consider the HTLC output as pinnable and want to claim pinnable and unpinnable outputs
5083+
// separately.
50835084
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap().clone();
5084-
assert_eq!(node_txn.len(), 2);
5085+
assert_eq!(node_txn.len(), if split_tx { 2 } else { 1 });
50855086
for tx in node_txn.iter() {
5086-
assert_eq!(tx.input.len(), 1);
5087+
assert_eq!(tx.input.len(), if split_tx { 1 } else { 2 });
50875088
check_spends!(tx, revoked_local_txn[0]);
50885089
}
5089-
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
5090+
if split_tx {
5091+
assert_ne!(node_txn[0].input[0].previous_output, node_txn[1].input[0].previous_output);
5092+
}
50905093

50915094
mine_transaction(&nodes[1], &node_txn[0]);
50925095
connect_blocks(&nodes[1], ANTI_REORG_DELAY - 1);
@@ -5096,6 +5099,12 @@ fn test_static_spendable_outputs_justice_tx_revoked_commitment_tx() {
50965099
check_spends!(spend_txn[0], node_txn[0]);
50975100
}
50985101

5102+
#[test]
5103+
fn test_static_spendable_outputs_justice_tx_revoked_commitment_tx() {
5104+
do_test_static_spendable_outputs_justice_tx_revoked_commitment_tx(true);
5105+
do_test_static_spendable_outputs_justice_tx_revoked_commitment_tx(false);
5106+
}
5107+
50995108
#[test]
51005109
fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
51015110
let mut chanmon_cfgs = create_chanmon_cfgs(2);
@@ -5128,6 +5137,10 @@ fn test_static_spendable_outputs_justice_tx_revoked_htlc_timeout_tx() {
51285137
check_spends!(revoked_htlc_txn[0], revoked_local_txn[0]);
51295138
assert_ne!(revoked_htlc_txn[0].lock_time, LockTime::ZERO); // HTLC-Timeout
51305139

5140+
// In order to connect `revoked_htlc_txn[0]` we must first advance the chain by
5141+
// `TEST_FINAL_CLTV` blocks as otherwise the transaction is consensus-invalid due to its
5142+
// locktime.
5143+
connect_blocks(&nodes[1], TEST_FINAL_CLTV);
51315144
// B will generate justice tx from A's revoked commitment/HTLC tx
51325145
connect_block(&nodes[1], &create_dummy_block(nodes[1].best_block_hash(), 42, vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone()]));
51335146
check_closed_broadcast!(nodes[1], true);

lightning/src/ln/monitor_tests.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//! Further functional tests which test blockchain reorganizations.
1111
1212
use crate::sign::{ecdsa::EcdsaChannelSigner, OutputSpender, SpendableOutputDescriptor};
13-
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS,LATENCY_GRACE_PERIOD_BLOCKS, Balance, BalanceSource, ChannelMonitorUpdateStep};
13+
use crate::chain::channelmonitor::{ANTI_REORG_DELAY, ARCHIVAL_DELAY_BLOCKS,LATENCY_GRACE_PERIOD_BLOCKS, COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE, Balance, BalanceSource, ChannelMonitorUpdateStep};
1414
use crate::chain::transaction::OutPoint;
1515
use crate::chain::chaininterface::{ConfirmationTarget, LowerBoundedFeeEstimator, compute_feerate_sat_per_1000_weight};
1616
use crate::events::bump_transaction::{BumpTransactionEvent, WalletSource};
@@ -1734,6 +1734,12 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
17341734
assert_eq!(revoked_htlc_success.lock_time, LockTime::ZERO);
17351735
assert_ne!(revoked_htlc_timeout.lock_time, LockTime::ZERO);
17361736

1737+
// First connect blocks until the HTLC expires with
1738+
// `COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE` blocks, making us consider all the HTLCs
1739+
// pinnable claims, which the remainder of the test assumes.
1740+
connect_blocks(&nodes[0], TEST_FINAL_CLTV - COUNTERPARTY_CLAIMABLE_WITHIN_BLOCKS_PINNABLE);
1741+
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(&nodes[0],
1742+
[HTLCDestination::FailedPayment { payment_hash: failed_payment_hash }]);
17371743
// A will generate justice tx from B's revoked commitment/HTLC tx
17381744
mine_transaction(&nodes[0], &revoked_local_txn[0]);
17391745
check_closed_broadcast!(nodes[0], true);
@@ -1846,8 +1852,6 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
18461852
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
18471853

18481854
connect_blocks(&nodes[0], revoked_htlc_timeout.lock_time.to_consensus_u32() - nodes[0].best_block_info().1);
1849-
expect_pending_htlcs_forwardable_and_htlc_handling_failed_ignore!(&nodes[0],
1850-
[HTLCDestination::FailedPayment { payment_hash: failed_payment_hash }]);
18511855
// As time goes on A may split its revocation claim transaction into multiple.
18521856
let as_fewer_input_rbf = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
18531857
for tx in as_fewer_input_rbf.iter() {

0 commit comments

Comments
 (0)