Skip to content

Commit d2f149c

Browse files
committed
Add anchors coverage to test_revoked_counterparty_htlc_tx_balances
1 parent bae076b commit d2f149c

File tree

2 files changed

+125
-34
lines changed

2 files changed

+125
-34
lines changed

lightning/src/ln/functional_test_utils.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -2795,7 +2795,8 @@ pub fn create_network<'a, 'b: 'a, 'c: 'b>(node_count: usize, cfgs: &'b Vec<NodeC
27952795
}
27962796

27972797
// Note that the following only works for CLTV values up to 128
2798-
pub const ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 137; //Here we have a diff due to HTLC CLTV expiry being < 2^15 in test
2798+
pub const ACCEPTED_HTLC_SCRIPT_WEIGHT: usize = 137; // Here we have a diff due to HTLC CLTV expiry being < 2^15 in test
2799+
pub const ACCEPTED_HTLC_SCRIPT_WEIGHT_ANCHORS: usize = 140; // Here we have a diff due to HTLC CLTV expiry being < 2^15 in test
27992800

28002801
#[derive(PartialEq)]
28012802
pub enum HTLCType { NONE, TIMEOUT, SUCCESS }

lightning/src/ln/monitor_tests.rs

+123-33
Original file line numberDiff line numberDiff line change
@@ -1365,15 +1365,39 @@ fn test_revoked_counterparty_commitment_balances() {
13651365
do_test_revoked_counterparty_commitment_balances(true, false);
13661366
}
13671367

1368-
#[test]
1369-
fn test_revoked_counterparty_htlc_tx_balances() {
1368+
fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
13701369
// Tests `get_claimable_balances` for revocation spends of HTLC transactions.
13711370
let mut chanmon_cfgs = create_chanmon_cfgs(2);
13721371
chanmon_cfgs[1].keys_manager.disable_revocation_policy_check = true;
13731372
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1374-
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1373+
let mut user_config = test_default_channel_config();
1374+
if anchors {
1375+
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
1376+
user_config.manually_accept_inbound_channels = true;
1377+
}
1378+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[Some(user_config), Some(user_config)]);
13751379
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
13761380

1381+
let coinbase_tx = Transaction {
1382+
version: 2,
1383+
lock_time: PackedLockTime::ZERO,
1384+
input: vec![TxIn { ..Default::default() }],
1385+
output: vec![
1386+
TxOut {
1387+
value: Amount::ONE_BTC.to_sat(),
1388+
script_pubkey: nodes[0].wallet_source.get_change_script().unwrap(),
1389+
},
1390+
TxOut {
1391+
value: Amount::ONE_BTC.to_sat(),
1392+
script_pubkey: nodes[1].wallet_source.get_change_script().unwrap(),
1393+
},
1394+
],
1395+
};
1396+
if anchors {
1397+
nodes[0].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 0 }, coinbase_tx.output[0].value);
1398+
nodes[1].wallet_source.add_utxo(bitcoin::OutPoint { txid: coinbase_tx.txid(), vout: 1 }, coinbase_tx.output[1].value);
1399+
}
1400+
13771401
// Create some initial channels
13781402
let (_, _, chan_id, funding_tx) =
13791403
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 11_000_000);
@@ -1385,9 +1409,15 @@ fn test_revoked_counterparty_htlc_tx_balances() {
13851409
let revoked_local_txn = get_local_commitment_txn!(nodes[1], chan_id);
13861410
assert_eq!(revoked_local_txn[0].input.len(), 1);
13871411
assert_eq!(revoked_local_txn[0].input[0].previous_output.txid, funding_tx.txid());
1412+
if anchors {
1413+
assert_eq!(revoked_local_txn[0].output[4].value, 10000); // to_self output
1414+
} else {
1415+
assert_eq!(revoked_local_txn[0].output[2].value, 10000); // to_self output
1416+
}
13881417

1389-
// The to-be-revoked commitment tx should have two HTLCs and an output for both sides
1390-
assert_eq!(revoked_local_txn[0].output.len(), 4);
1418+
// The to-be-revoked commitment tx should have two HTLCs, an output for each side, and an
1419+
// anchor output for each side if enabled.
1420+
assert_eq!(revoked_local_txn[0].output.len(), if anchors { 6 } else { 4 });
13911421

13921422
claim_payment(&nodes[0], &[&nodes[1]], payment_preimage);
13931423

@@ -1399,16 +1429,25 @@ fn test_revoked_counterparty_htlc_tx_balances() {
13991429
check_closed_broadcast!(nodes[1], true);
14001430
check_added_monitors!(nodes[1], 1);
14011431
check_closed_event!(nodes[1], 1, ClosureReason::CommitmentTxConfirmed, [nodes[0].node.get_our_node_id()], 1000000);
1432+
if anchors {
1433+
handle_bump_htlc_event(&nodes[1], 1);
1434+
}
14021435
let revoked_htlc_success = {
14031436
let mut txn = nodes[1].tx_broadcaster.txn_broadcast();
14041437
assert_eq!(txn.len(), 1);
1405-
assert_eq!(txn[0].input.len(), 1);
1406-
assert_eq!(txn[0].input[0].witness.last().unwrap().len(), ACCEPTED_HTLC_SCRIPT_WEIGHT);
1407-
check_spends!(txn[0], revoked_local_txn[0]);
1438+
assert_eq!(txn[0].input.len(), if anchors { 2 } else { 1 });
1439+
assert_eq!(txn[0].input[0].previous_output.vout, if anchors { 3 } else { 1 });
1440+
assert_eq!(txn[0].input[0].witness.last().unwrap().len(),
1441+
if anchors { ACCEPTED_HTLC_SCRIPT_WEIGHT_ANCHORS } else { ACCEPTED_HTLC_SCRIPT_WEIGHT });
1442+
check_spends!(txn[0], revoked_local_txn[0], coinbase_tx);
14081443
txn.pop().unwrap()
14091444
};
1445+
let revoked_htlc_success_fee = chan_feerate * revoked_htlc_success.weight() as u64 / 1000;
14101446

14111447
connect_blocks(&nodes[1], TEST_FINAL_CLTV);
1448+
if anchors {
1449+
handle_bump_htlc_event(&nodes[1], 2);
1450+
}
14121451
let revoked_htlc_timeout = {
14131452
let mut txn = nodes[1].tx_broadcaster.unique_txn_broadcast();
14141453
assert_eq!(txn.len(), 2);
@@ -1418,7 +1457,7 @@ fn test_revoked_counterparty_htlc_tx_balances() {
14181457
txn.remove(0)
14191458
}
14201459
};
1421-
check_spends!(revoked_htlc_timeout, revoked_local_txn[0]);
1460+
check_spends!(revoked_htlc_timeout, revoked_local_txn[0], coinbase_tx);
14221461
assert_ne!(revoked_htlc_success.input[0].previous_output, revoked_htlc_timeout.input[0].previous_output);
14231462
assert_eq!(revoked_htlc_success.lock_time.0, 0);
14241463
assert_ne!(revoked_htlc_timeout.lock_time.0, 0);
@@ -1430,17 +1469,37 @@ fn test_revoked_counterparty_htlc_tx_balances() {
14301469
check_closed_event!(nodes[0], 1, ClosureReason::CommitmentTxConfirmed, [nodes[1].node.get_our_node_id()], 1000000);
14311470
let to_remote_conf_height = nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1;
14321471

1433-
let as_commitment_claim_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
1434-
assert_eq!(as_commitment_claim_txn.len(), 1);
1435-
check_spends!(as_commitment_claim_txn[0], revoked_local_txn[0]);
1472+
let revoked_to_self_claim = {
1473+
let mut as_commitment_claim_txn = nodes[0].tx_broadcaster.txn_broadcast();
1474+
assert_eq!(as_commitment_claim_txn.len(), if anchors { 2 } else { 1 });
1475+
if anchors {
1476+
assert_eq!(as_commitment_claim_txn[0].input.len(), 1);
1477+
assert_eq!(as_commitment_claim_txn[0].input[0].previous_output.vout, 4); // Separate to_remote claim
1478+
check_spends!(as_commitment_claim_txn[0], revoked_local_txn[0]);
1479+
assert_eq!(as_commitment_claim_txn[1].input.len(), 2);
1480+
assert_eq!(as_commitment_claim_txn[1].input[0].previous_output.vout, 2);
1481+
assert_eq!(as_commitment_claim_txn[1].input[1].previous_output.vout, 3);
1482+
check_spends!(as_commitment_claim_txn[1], revoked_local_txn[0]);
1483+
Some(as_commitment_claim_txn.remove(0))
1484+
} else {
1485+
assert_eq!(as_commitment_claim_txn[0].input.len(), 3);
1486+
assert_eq!(as_commitment_claim_txn[0].input[0].previous_output.vout, 2);
1487+
assert_eq!(as_commitment_claim_txn[0].input[1].previous_output.vout, 0);
1488+
assert_eq!(as_commitment_claim_txn[0].input[2].previous_output.vout, 1);
1489+
check_spends!(as_commitment_claim_txn[0], revoked_local_txn[0]);
1490+
None
1491+
}
1492+
};
14361493

14371494
// The next two checks have the same balance set for A - even though we confirm a revoked HTLC
14381495
// transaction our balance tracking doesn't use the on-chain value so the
14391496
// `CounterpartyRevokedOutputClaimable` entry doesn't change.
1497+
let commitment_tx_fee = chan_feerate *
1498+
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000;
1499+
let anchor_outputs_value = if anchors { channel::ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 };
14401500
let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
14411501
// to_remote output in B's revoked commitment
1442-
amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
1443-
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
1502+
amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
14441503
confirmation_height: to_remote_conf_height,
14451504
}, Balance::CounterpartyRevokedOutputClaimable {
14461505
// to_self output in B's revoked commitment
@@ -1456,22 +1515,36 @@ fn test_revoked_counterparty_htlc_tx_balances() {
14561515
mine_transaction(&nodes[0], &revoked_htlc_success);
14571516
let as_htlc_claim_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
14581517
assert_eq!(as_htlc_claim_tx.len(), 2);
1518+
assert_eq!(as_htlc_claim_tx[0].input.len(), 1);
14591519
check_spends!(as_htlc_claim_tx[0], revoked_htlc_success);
1460-
check_spends!(as_htlc_claim_tx[1], revoked_local_txn[0]); // A has to generate a new claim for the remaining revoked
1461-
// outputs (which no longer includes the spent HTLC output)
1520+
// A has to generate a new claim for the remaining revoked outputs (which no longer includes the
1521+
// spent HTLC output)
1522+
assert_eq!(as_htlc_claim_tx[1].input.len(), if anchors { 1 } else { 2 });
1523+
assert_eq!(as_htlc_claim_tx[1].input[0].previous_output.vout, 2);
1524+
if !anchors {
1525+
assert_eq!(as_htlc_claim_tx[1].input[1].previous_output.vout, 0);
1526+
}
1527+
check_spends!(as_htlc_claim_tx[1], revoked_local_txn[0]);
14621528

14631529
assert_eq!(as_balances,
14641530
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
14651531

14661532
assert_eq!(as_htlc_claim_tx[0].output.len(), 1);
1467-
fuzzy_assert_eq(as_htlc_claim_tx[0].output[0].value,
1468-
3_000 - chan_feerate * (revoked_htlc_success.weight() + as_htlc_claim_tx[0].weight()) as u64 / 1000);
1533+
let as_revoked_htlc_success_claim_fee = chan_feerate * as_htlc_claim_tx[0].weight() as u64 / 1000;
1534+
if anchors {
1535+
// With anchors, B can pay for revoked_htlc_success's fee with additional inputs, rather
1536+
// than with the HTLC itself.
1537+
fuzzy_assert_eq(as_htlc_claim_tx[0].output[0].value,
1538+
3_000 - as_revoked_htlc_success_claim_fee);
1539+
} else {
1540+
fuzzy_assert_eq(as_htlc_claim_tx[0].output[0].value,
1541+
3_000 - revoked_htlc_success_fee - as_revoked_htlc_success_claim_fee);
1542+
}
14691543

14701544
mine_transaction(&nodes[0], &as_htlc_claim_tx[0]);
14711545
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
14721546
// to_remote output in B's revoked commitment
1473-
amount_satoshis: 1_000_000 - 11_000 - 3_000 - chan_feerate *
1474-
(channel::commitment_tx_base_weight(&channel_type_features) + 2 * channel::COMMITMENT_TX_WEIGHT_PER_HTLC) / 1000,
1547+
amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
14751548
confirmation_height: to_remote_conf_height,
14761549
}, Balance::CounterpartyRevokedOutputClaimable {
14771550
// to_self output in B's revoked commitment
@@ -1525,11 +1598,24 @@ fn test_revoked_counterparty_htlc_tx_balances() {
15251598
}
15261599

15271600
mine_transaction(&nodes[0], &revoked_htlc_timeout);
1528-
let as_second_htlc_claim_tx = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap().split_off(0);
1529-
assert_eq!(as_second_htlc_claim_tx.len(), 2);
1530-
1531-
check_spends!(as_second_htlc_claim_tx[0], revoked_htlc_timeout);
1532-
check_spends!(as_second_htlc_claim_tx[1], revoked_local_txn[0]);
1601+
let (revoked_htlc_timeout_claim, revoked_to_self_claim) = {
1602+
let mut as_second_htlc_claim_tx = nodes[0].tx_broadcaster.txn_broadcast();
1603+
assert_eq!(as_second_htlc_claim_tx.len(), if anchors { 1 } else { 2 });
1604+
if anchors {
1605+
assert_eq!(as_second_htlc_claim_tx[0].input.len(), 1);
1606+
assert_eq!(as_second_htlc_claim_tx[0].input[0].previous_output.vout, 0);
1607+
check_spends!(as_second_htlc_claim_tx[0], revoked_htlc_timeout);
1608+
(as_second_htlc_claim_tx.remove(0), revoked_to_self_claim.unwrap())
1609+
} else {
1610+
assert_eq!(as_second_htlc_claim_tx[0].input.len(), 1);
1611+
assert_eq!(as_second_htlc_claim_tx[0].input[0].previous_output.vout, 0);
1612+
check_spends!(as_second_htlc_claim_tx[0], revoked_htlc_timeout);
1613+
assert_eq!(as_second_htlc_claim_tx[1].input.len(), 1);
1614+
assert_eq!(as_second_htlc_claim_tx[1].input[0].previous_output.vout, 2);
1615+
check_spends!(as_second_htlc_claim_tx[1], revoked_local_txn[0]);
1616+
(as_second_htlc_claim_tx.remove(0), as_second_htlc_claim_tx.remove(0))
1617+
}
1618+
};
15331619

15341620
// Connect blocks to finalize the HTLC resolution with the HTLC-Timeout transaction. In a
15351621
// previous iteration of the revoked balance handling this would result in us "forgetting" that
@@ -1543,31 +1629,31 @@ fn test_revoked_counterparty_htlc_tx_balances() {
15431629
}]),
15441630
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
15451631

1546-
mine_transaction(&nodes[0], &as_second_htlc_claim_tx[0]);
1632+
mine_transaction(&nodes[0], &revoked_htlc_timeout_claim);
15471633
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
15481634
// to_self output in B's revoked commitment
15491635
amount_satoshis: 10_000,
15501636
}, Balance::ClaimableAwaitingConfirmations {
1551-
amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
1637+
amount_satoshis: revoked_htlc_timeout_claim.output[0].value,
15521638
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
15531639
}]),
15541640
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
15551641

1556-
mine_transaction(&nodes[0], &as_second_htlc_claim_tx[1]);
1642+
mine_transaction(&nodes[0], &revoked_to_self_claim);
15571643
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
15581644
// to_self output in B's revoked commitment
1559-
amount_satoshis: as_second_htlc_claim_tx[1].output[0].value,
1645+
amount_satoshis: revoked_to_self_claim.output[0].value,
15601646
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,
15611647
}, Balance::ClaimableAwaitingConfirmations {
1562-
amount_satoshis: as_second_htlc_claim_tx[0].output[0].value,
1648+
amount_satoshis: revoked_htlc_timeout_claim.output[0].value,
15631649
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 2,
15641650
}]),
15651651
sorted_vec(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances()));
15661652

15671653
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 2);
1568-
test_spendable_output(&nodes[0], &as_second_htlc_claim_tx[0], false);
1654+
test_spendable_output(&nodes[0], &revoked_htlc_timeout_claim, false);
15691655
connect_blocks(&nodes[0], 1);
1570-
test_spendable_output(&nodes[0], &as_second_htlc_claim_tx[1], false);
1656+
test_spendable_output(&nodes[0], &revoked_to_self_claim, false);
15711657

15721658
assert_eq!(nodes[0].chain_monitor.chain_monitor.get_monitor(funding_outpoint).unwrap().get_claimable_balances(), Vec::new());
15731659

@@ -1581,7 +1667,11 @@ fn test_revoked_counterparty_htlc_tx_balances() {
15811667
}
15821668

15831669
#[test]
1584-
fn test_revoked_counterparty_aggregated_claims() {
1670+
fn test_revoked_counterparty_htlc_tx_balances() {
1671+
do_test_revoked_counterparty_htlc_tx_balances(false);
1672+
do_test_revoked_counterparty_htlc_tx_balances(true);
1673+
}
1674+
15851675
// Tests `get_claimable_balances` for revoked counterparty commitment transactions when
15861676
// claiming with an aggregated claim transaction.
15871677
let mut chanmon_cfgs = create_chanmon_cfgs(2);

0 commit comments

Comments
 (0)