Skip to content

Commit 9284a2b

Browse files
author
Antoine Riard
committed
Add test_set_outpoints_partial_claiming
1 parent f8957c7 commit 9284a2b

File tree

2 files changed

+135
-42
lines changed

2 files changed

+135
-42
lines changed

lightning/src/ln/channelmonitor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,6 +1954,7 @@ impl ChannelMonitor {
19541954

19551955
/// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key
19561956
fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32, fee_estimator: &FeeEstimator) -> (Option<Transaction>, Option<SpendableOutputDescriptor>) {
1957+
//TODO: send back new outputs to guarantee pending_claim_request consistency
19571958
if tx.input.len() != 1 || tx.output.len() != 1 {
19581959
return (None, None)
19591960
}

lightning/src/ln/functional_tests.rs

Lines changed: 134 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6454,6 +6454,8 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
64546454
nodes[1].block_notifier.block_connected(&Block { header, txdata: vec![revoked_local_txn[0].clone()] }, 1);
64556455
check_closed_broadcast!(nodes[1]);
64566456

6457+
let mut received = std::usize::MAX;
6458+
let mut offered = std::usize::MAX;
64576459
let revoked_htlc_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
64586460
assert_eq!(revoked_htlc_txn.len(), 6);
64596461
if revoked_htlc_txn[0].input[0].witness.last().unwrap().len() == ACCEPTED_HTLC_SCRIPT_WEIGHT {
@@ -6462,18 +6464,22 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
64626464
assert_eq!(revoked_htlc_txn[1].input.len(), 1);
64636465
assert_eq!(revoked_htlc_txn[1].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
64646466
check_spends!(revoked_htlc_txn[1], revoked_local_txn[0].clone());
6465-
} else {
6467+
received = 0;
6468+
offered = 1;
6469+
} else if revoked_htlc_txn[1].input[0].witness.last().unwrap().len() == ACCEPTED_HTLC_SCRIPT_WEIGHT {
64666470
assert_eq!(revoked_htlc_txn[1].input.len(), 1);
64676471
check_spends!(revoked_htlc_txn[1], revoked_local_txn[0].clone());
64686472
assert_eq!(revoked_htlc_txn[0].input.len(), 1);
64696473
assert_eq!(revoked_htlc_txn[0].input[0].witness.last().unwrap().len(), OFFERED_HTLC_SCRIPT_WEIGHT);
64706474
check_spends!(revoked_htlc_txn[0], revoked_local_txn[0].clone());
6475+
received = 1;
6476+
offered = 0;
64716477
}
64726478

64736479
// Broadcast set of revoked txn on A
6474-
let header_129 = connect_blocks(&nodes[0].block_notifier, 128, 1, true, header.bitcoin_hash());
6475-
let header_130 = BlockHeader { version: 0x20000000, prev_blockhash: header_129, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6476-
nodes[0].block_notifier.block_connected(&Block { header: header_130, txdata: vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone(), revoked_htlc_txn[1].clone()] }, 130);
6480+
let header_128 = connect_blocks(&nodes[0].block_notifier, 128, 0, true, header.bitcoin_hash());
6481+
let header_129 = BlockHeader { version: 0x20000000, prev_blockhash: header_128, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6482+
nodes[0].block_notifier.block_connected(&Block { header: header_129, txdata: vec![revoked_local_txn[0].clone(), revoked_htlc_txn[0].clone(), revoked_htlc_txn[1].clone()] }, 129);
64776483
let first;
64786484
let second;
64796485
let feerate_1;
@@ -6499,59 +6505,60 @@ fn test_bump_penalty_txn_on_revoked_htlcs() {
64996505
}
65006506

65016507
// Connect three more block to see if bumped penalty are issued for HTLC txn
6502-
let header_133 = connect_blocks(&nodes[0].block_notifier, 3, 130, true, header_130.bitcoin_hash());
6508+
let header_132 = connect_blocks(&nodes[0].block_notifier, 3, 129, true, header_129.bitcoin_hash());
65036509
let node_txn = {
65046510
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6505-
assert_eq!(node_txn.len(), 2); //2 last tx : bumped claiming txn on Revoked HTLCs Txn, there is no bumped commitment tx as it's empty of claiming outpoints
6506-
assert_eq!(node_txn[0].input.len(), 1);
6507-
assert_eq!(node_txn[0].output.len(), 1);
6508-
assert_eq!(node_txn[1].input.len(), 1);
6509-
assert_eq!(node_txn[1].output.len(), 1);
6510-
// Verify bumped tx is different and 25% bump heuristic
6511-
if node_txn[0].input[0].previous_output.txid == revoked_htlc_txn[0].txid() {
6512-
check_spends!(node_txn[0], revoked_htlc_txn[0].clone());
6513-
assert_ne!(first, node_txn[0].txid());
6514-
let fee = revoked_htlc_txn[0].output[0].value - node_txn[0].output[0].value;
6515-
let new_feerate = fee * 1000 / node_txn[0].get_weight() as u64;
6516-
assert!(new_feerate * 100 > feerate_1 * 125);
6517-
6518-
check_spends!(node_txn[1], revoked_htlc_txn[1].clone());
6519-
assert_ne!(second, node_txn[1].txid());
6520-
let fee = revoked_htlc_txn[1].output[0].value - node_txn[1].output[0].value;
6521-
let new_feerate = fee * 1000 / node_txn[1].get_weight() as u64;
6522-
assert!(new_feerate * 100 > feerate_2 * 125);
6523-
} else if node_txn[0].input[0].previous_output.txid == revoked_htlc_txn[1].txid() {
6524-
check_spends!(node_txn[0], revoked_htlc_txn[1].clone());
6525-
assert_ne!(second, node_txn[0].txid());
6526-
let fee = revoked_htlc_txn[1].output[0].value - node_txn[0].output[0].value;
6527-
let new_feerate = fee * 1000 / node_txn[0].get_weight() as u64;
6528-
assert!(new_feerate * 100 > feerate_2 * 125);
6511+
assert_eq!(node_txn.len(), 3); //2 last tx : bumped claiming txn on Revoked HTLCs Txn, there is no bumped commitment tx as it's empty of claiming outpoints
6512+
6513+
let mut penalty_local = std::usize::MAX;
6514+
let mut penalty_offered = std::usize::MAX;
6515+
let mut penalty_received = std::usize::MAX;
6516+
6517+
for (i, tx) in node_txn.iter().enumerate() {
6518+
if tx.input[0].previous_output.txid == revoked_local_txn[0].txid() {
6519+
penalty_local = i;
6520+
} else if tx.input[0].previous_output.txid == revoked_htlc_txn[offered].txid() {
6521+
penalty_offered = i;
6522+
} else if tx.input[0].previous_output.txid == revoked_htlc_txn[received].txid() {
6523+
penalty_received = i;
6524+
}
6525+
}
6526+
check_spends!(node_txn[penalty_local], revoked_local_txn[0].clone());
65296527

6530-
check_spends!(node_txn[1], revoked_htlc_txn[0].clone());
6531-
assert_ne!(first, node_txn[1].txid());
6532-
let fee = revoked_htlc_txn[0].output[0].value - node_txn[1].output[0].value;
6533-
let new_feerate = fee * 1000 / node_txn[1].get_weight() as u64;
6534-
assert!(new_feerate * 100 > feerate_1 * 125);
6535-
} else { assert!(false) }
6536-
let txn = vec![node_txn[0].clone(), node_txn[1].clone()];
6528+
assert_eq!(node_txn[penalty_received].input.len(), 1);
6529+
assert_eq!(node_txn[penalty_received].output.len(), 1);
6530+
assert_eq!(node_txn[penalty_offered].input.len(), 1);
6531+
assert_eq!(node_txn[penalty_offered].output.len(), 1);
6532+
// Verify bumped tx is different and 25% bump heuristic
6533+
check_spends!(node_txn[penalty_offered], revoked_htlc_txn[offered].clone());
6534+
assert_ne!(first, node_txn[penalty_offered].txid());
6535+
let fee = revoked_htlc_txn[offered].output[0].value - node_txn[penalty_offered].output[0].value;
6536+
let new_feerate = fee * 1000 / node_txn[penalty_offered].get_weight() as u64;
6537+
assert!(new_feerate * 100 > feerate_1 * 125);
6538+
6539+
check_spends!(node_txn[penalty_received], revoked_htlc_txn[received].clone());
6540+
assert_ne!(second, node_txn[penalty_received].txid());
6541+
let fee = revoked_htlc_txn[received].output[0].value - node_txn[penalty_received].output[0].value;
6542+
let new_feerate = fee * 1000 / node_txn[penalty_received].get_weight() as u64;
6543+
assert!(new_feerate * 100 > feerate_2 * 125);
6544+
let txn = vec![node_txn[0].clone(), node_txn[1].clone(), node_txn[2].clone()];
65376545
node_txn.clear();
65386546
txn
65396547
};
65406548
// Broadcast claim txn and confirm blocks to avoid further bumps on this outputs
6541-
let header_134 = BlockHeader { version: 0x20000000, prev_blockhash: header_133, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6542-
nodes[0].block_notifier.block_connected(&Block { header: header_134, txdata: node_txn }, 134);
6543-
connect_blocks(&nodes[0].block_notifier, 6, 134, true, header_134.bitcoin_hash());
6549+
let header_133 = BlockHeader { version: 0x20000000, prev_blockhash: header_132, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6550+
nodes[0].block_notifier.block_connected(&Block { header: header_133, txdata: node_txn }, 133);
6551+
let header_140 = connect_blocks(&nodes[0].block_notifier, 6, 134, true, header_133.bitcoin_hash());
65446552
{
65456553
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
65466554
node_txn.clear();
65476555
}
65486556

65496557
// Connect few more blocks and check only penalty transaction for to_local output have been issued
6550-
connect_blocks(&nodes[0].block_notifier, 5, 140, true, header_134.bitcoin_hash());
6558+
connect_blocks(&nodes[0].block_notifier, 7, 140, true, header_140);
65516559
{
65526560
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6553-
assert_eq!(node_txn.len(), 1);
6554-
check_spends!(node_txn[0], revoked_local_txn[0].clone());
6561+
assert_eq!(node_txn.len(), 2); //TODO: should be zero when we fix check_spend_remote_htlc
65556562
node_txn.clear();
65566563
}
65576564
check_closed_broadcast!(nodes[0]);
@@ -6664,3 +6671,88 @@ fn test_bump_penalty_txn_on_remote_commitment() {
66646671
nodes[1].node.get_and_clear_pending_events();
66656672
nodes[1].node.get_and_clear_pending_msg_events();
66666673
}
6674+
6675+
#[test]
6676+
fn test_set_outpoints_partial_claiming() {
6677+
// - remote party claim tx, new bump tx
6678+
// - disconnect remote claiming tx, new bump
6679+
// - disconnect tx, see no tx anymore
6680+
let nodes = create_network(2, &[None, None]);
6681+
6682+
let chan = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1000000, 59000000, LocalFeatures::new(), LocalFeatures::new());
6683+
let payment_preimage_1 = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3_000_000).0;
6684+
let payment_preimage_2 = route_payment(&nodes[1], &vec!(&nodes[0])[..], 3_000_000).0;
6685+
6686+
// Remote commitment txn with 4 outputs: to_local, to_remote, 2 outgoing HTLC
6687+
let remote_txn = nodes[1].node.channel_state.lock().unwrap().by_id.get(&chan.2).unwrap().last_local_commitment_txn.clone();
6688+
assert_eq!(remote_txn[0].output.len(), 4);
6689+
assert_eq!(remote_txn[0].input.len(), 1);
6690+
assert_eq!(remote_txn[0].input[0].previous_output.txid, chan.3.txid());
6691+
6692+
// Connect blocks on node A to advance height towards TEST_FINAL_CLTV
6693+
let prev_header_100 = connect_blocks(&nodes[1].block_notifier, 100, 0, false, Default::default());
6694+
// Provide node A with both preimage
6695+
nodes[0].node.claim_funds(payment_preimage_1, 3_000_000);
6696+
nodes[0].node.claim_funds(payment_preimage_2, 3_000_000);
6697+
check_added_monitors!(nodes[0], 2);
6698+
nodes[0].node.get_and_clear_pending_events();
6699+
nodes[0].node.get_and_clear_pending_msg_events();
6700+
6701+
// Connect blocks on node A commitment transaction
6702+
let header = BlockHeader { version: 0x20000000, prev_blockhash: prev_header_100, merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6703+
nodes[0].block_notifier.block_connected(&Block { header, txdata: vec![remote_txn[0].clone()] }, 101);
6704+
// Verify node A broadcast tx claiming both HTLCs
6705+
{
6706+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6707+
assert_eq!(node_txn.len(), 3);
6708+
check_spends!(node_txn[0], remote_txn[0].clone());
6709+
assert_eq!(node_txn[0].input.len(), 2);
6710+
node_txn.clear();
6711+
}
6712+
nodes[0].node.get_and_clear_pending_msg_events();
6713+
6714+
// Connect blocks on node B
6715+
connect_blocks(&nodes[1].block_notifier, 135, 0, false, Default::default());
6716+
// Verify node B broadcast 2 HTLC-timeout txn
6717+
let partial_claim_tx = {
6718+
let node_txn = nodes[1].tx_broadcaster.txn_broadcasted.lock().unwrap();
6719+
assert_eq!(node_txn.len(), 3);
6720+
check_spends!(node_txn[1], node_txn[0].clone());
6721+
check_spends!(node_txn[2], node_txn[0].clone());
6722+
assert_eq!(node_txn[1].input.len(), 1);
6723+
assert_eq!(node_txn[2].input.len(), 1);
6724+
node_txn[1].clone()
6725+
};
6726+
nodes[1].node.get_and_clear_pending_msg_events();
6727+
6728+
// Broadcast partial claim on node A, should regenerate a claiming tx with HTLC dropped
6729+
let header = BlockHeader { version: 0x20000000, prev_blockhash: header.bitcoin_hash(), merkle_root: Default::default(), time: 42, bits: 42, nonce: 42 };
6730+
nodes[0].block_notifier.block_connected(&Block { header, txdata: vec![partial_claim_tx.clone()] }, 102);
6731+
{
6732+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6733+
assert_eq!(node_txn.len(), 1);
6734+
check_spends!(node_txn[0], remote_txn[0].clone());
6735+
assert_eq!(node_txn[0].input.len(), 1); //dropped HTLC
6736+
node_txn.clear();
6737+
}
6738+
nodes[0].node.get_and_clear_pending_msg_events();
6739+
6740+
// Disconnect last block on node A, should regenerate a claiming tx with HTLC dropped
6741+
nodes[0].block_notifier.block_disconnected(&header, 102);
6742+
{
6743+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6744+
assert_eq!(node_txn.len(), 1);
6745+
check_spends!(node_txn[0], remote_txn[0].clone());
6746+
assert_eq!(node_txn[0].input.len(), 2); //resurrected HTLC
6747+
node_txn.clear();
6748+
}
6749+
6750+
//// Disconnect one more block and then reconnect multiple no transaction should be generated
6751+
nodes[0].block_notifier.block_disconnected(&header, 101);
6752+
connect_blocks(&nodes[1].block_notifier, 15, 101, false, prev_header_100);
6753+
{
6754+
let mut node_txn = nodes[0].tx_broadcaster.txn_broadcasted.lock().unwrap();
6755+
assert_eq!(node_txn.len(), 0);
6756+
node_txn.clear();
6757+
}
6758+
}

0 commit comments

Comments
 (0)