Skip to content

Commit 5801821

Browse files
committed
Consider anchor outputs value on inbound HTLCs
This could lead us to accept HTLCs that would put the sender below their reserve, which must never happen.
1 parent 6aaab50 commit 5801821

File tree

3 files changed

+100
-15
lines changed

3 files changed

+100
-15
lines changed

lightning/src/ln/channel.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2780,6 +2780,13 @@ impl<SP: Deref> Channel<SP> where
27802780
if inbound_stats.pending_htlcs_value_msat + msg.amount_msat > self.context.holder_max_htlc_value_in_flight_msat {
27812781
return Err(ChannelError::Close(format!("Remote HTLC add would put them over our max HTLC value ({})", self.context.holder_max_htlc_value_in_flight_msat)));
27822782
}
2783+
2784+
let anchor_outputs_value_msat = if self.context.get_channel_type().supports_anchors_zero_fee_htlc_tx() {
2785+
ANCHOR_OUTPUT_VALUE_SATOSHI * 2 * 1000
2786+
} else {
2787+
0
2788+
};
2789+
27832790
// Check holder_selected_channel_reserve_satoshis (we're getting paid, so they have to at least meet
27842791
// the reserve_satoshis we told them to always have as direct payment so that they lose
27852792
// something if we punish them for broadcasting an old state).
@@ -2843,11 +2850,11 @@ impl<SP: Deref> Channel<SP> where
28432850
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
28442851
self.context.next_remote_commit_tx_fee_msat(htlc_candidate, None) // Don't include the extra fee spike buffer HTLC in calculations
28452852
};
2846-
if pending_remote_value_msat - msg.amount_msat < remote_commit_tx_fee_msat {
2853+
if pending_remote_value_msat - msg.amount_msat - anchor_outputs_value_msat < remote_commit_tx_fee_msat {
28472854
return Err(ChannelError::Close("Remote HTLC add would not leave enough to pay for fees".to_owned()));
28482855
};
28492856

2850-
if pending_remote_value_msat - msg.amount_msat - remote_commit_tx_fee_msat < self.context.holder_selected_channel_reserve_satoshis * 1000 {
2857+
if pending_remote_value_msat - msg.amount_msat - remote_commit_tx_fee_msat - anchor_outputs_value_msat < self.context.holder_selected_channel_reserve_satoshis * 1000 {
28512858
return Err(ChannelError::Close("Remote HTLC add would put them under remote reserve value".to_owned()));
28522859
}
28532860

@@ -2862,7 +2869,7 @@ impl<SP: Deref> Channel<SP> where
28622869
// sensitive to fee spikes.
28632870
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
28642871
let remote_fee_cost_incl_stuck_buffer_msat = 2 * self.context.next_remote_commit_tx_fee_msat(htlc_candidate, Some(()));
2865-
if pending_remote_value_msat - msg.amount_msat - self.context.holder_selected_channel_reserve_satoshis * 1000 < remote_fee_cost_incl_stuck_buffer_msat {
2872+
if pending_remote_value_msat - msg.amount_msat - self.context.holder_selected_channel_reserve_satoshis * 1000 - anchor_outputs_value_msat < remote_fee_cost_incl_stuck_buffer_msat {
28662873
// Note that if the pending_forward_status is not updated here, then it's because we're already failing
28672874
// the HTLC, i.e. its status is already set to failing.
28682875
log_info!(logger, "Attempting to fail HTLC due to fee spike buffer violation in channel {}. Rebalancing is required.", &self.context.channel_id());
@@ -2872,7 +2879,7 @@ impl<SP: Deref> Channel<SP> where
28722879
// Check that they won't violate our local required channel reserve by adding this HTLC.
28732880
let htlc_candidate = HTLCCandidate::new(msg.amount_msat, HTLCInitiator::RemoteOffered);
28742881
let local_commit_tx_fee_msat = self.context.next_local_commit_tx_fee_msat(htlc_candidate, None);
2875-
if self.context.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat {
2882+
if self.context.value_to_self_msat < self.context.counterparty_selected_channel_reserve_satoshis.unwrap() * 1000 + local_commit_tx_fee_msat + anchor_outputs_value_msat {
28762883
return Err(ChannelError::Close("Cannot accept HTLC that would put our balance under counterparty-announced channel reserve value".to_owned()));
28772884
}
28782885
}

lightning/src/ln/monitor_tests.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,7 +1400,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
14001400

14011401
// Create some initial channels
14021402
let (_, _, chan_id, funding_tx) =
1403-
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 11_000_000);
1403+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 12_000_000);
14041404
let funding_outpoint = OutPoint { txid: funding_tx.txid(), index: 0 };
14051405
assert_eq!(funding_outpoint.to_channel_id(), chan_id);
14061406

@@ -1410,9 +1410,9 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
14101410
assert_eq!(revoked_local_txn[0].input.len(), 1);
14111411
assert_eq!(revoked_local_txn[0].input[0].previous_output.txid, funding_tx.txid());
14121412
if anchors {
1413-
assert_eq!(revoked_local_txn[0].output[4].value, 10000); // to_self output
1413+
assert_eq!(revoked_local_txn[0].output[4].value, 11000); // to_self output
14141414
} else {
1415-
assert_eq!(revoked_local_txn[0].output[2].value, 10000); // to_self output
1415+
assert_eq!(revoked_local_txn[0].output[2].value, 11000); // to_self output
14161416
}
14171417

14181418
// The to-be-revoked commitment tx should have two HTLCs, an output for each side, and an
@@ -1499,11 +1499,11 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
14991499
let anchor_outputs_value = if anchors { channel::ANCHOR_OUTPUT_VALUE_SATOSHI * 2 } else { 0 };
15001500
let as_balances = sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
15011501
// to_remote output in B's revoked commitment
1502-
amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
1502+
amount_satoshis: 1_000_000 - 12_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
15031503
confirmation_height: to_remote_conf_height,
15041504
}, Balance::CounterpartyRevokedOutputClaimable {
15051505
// to_self output in B's revoked commitment
1506-
amount_satoshis: 10_000,
1506+
amount_satoshis: 11_000,
15071507
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 1
15081508
amount_satoshis: 3_000,
15091509
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
@@ -1544,11 +1544,11 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
15441544
mine_transaction(&nodes[0], &as_htlc_claim_tx[0]);
15451545
assert_eq!(sorted_vec(vec![Balance::ClaimableAwaitingConfirmations {
15461546
// to_remote output in B's revoked commitment
1547-
amount_satoshis: 1_000_000 - 11_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
1547+
amount_satoshis: 1_000_000 - 12_000 - 3_000 - commitment_tx_fee - anchor_outputs_value,
15481548
confirmation_height: to_remote_conf_height,
15491549
}, Balance::CounterpartyRevokedOutputClaimable {
15501550
// to_self output in B's revoked commitment
1551-
amount_satoshis: 10_000,
1551+
amount_satoshis: 11_000,
15521552
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
15531553
amount_satoshis: 1_000,
15541554
}, Balance::ClaimableAwaitingConfirmations {
@@ -1561,7 +1561,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
15611561
test_spendable_output(&nodes[0], &revoked_local_txn[0], false);
15621562
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
15631563
// to_self output to B
1564-
amount_satoshis: 10_000,
1564+
amount_satoshis: 11_000,
15651565
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
15661566
amount_satoshis: 1_000,
15671567
}, Balance::ClaimableAwaitingConfirmations {
@@ -1574,7 +1574,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
15741574
test_spendable_output(&nodes[0], &as_htlc_claim_tx[0], false);
15751575
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
15761576
// to_self output in B's revoked commitment
1577-
amount_satoshis: 10_000,
1577+
amount_satoshis: 11_000,
15781578
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
15791579
amount_satoshis: 1_000,
15801580
}]),
@@ -1623,7 +1623,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
16231623
connect_blocks(&nodes[0], ANTI_REORG_DELAY - 1);
16241624
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
16251625
// to_self output in B's revoked commitment
1626-
amount_satoshis: 10_000,
1626+
amount_satoshis: 11_000,
16271627
}, Balance::CounterpartyRevokedOutputClaimable { // HTLC 2
16281628
amount_satoshis: 1_000,
16291629
}]),
@@ -1632,7 +1632,7 @@ fn do_test_revoked_counterparty_htlc_tx_balances(anchors: bool) {
16321632
mine_transaction(&nodes[0], &revoked_htlc_timeout_claim);
16331633
assert_eq!(sorted_vec(vec![Balance::CounterpartyRevokedOutputClaimable {
16341634
// to_self output in B's revoked commitment
1635-
amount_satoshis: 10_000,
1635+
amount_satoshis: 11_000,
16361636
}, Balance::ClaimableAwaitingConfirmations {
16371637
amount_satoshis: revoked_htlc_timeout_claim.output[0].value,
16381638
confirmation_height: nodes[0].best_block_info().1 + ANTI_REORG_DELAY - 1,

lightning/src/ln/payment_tests.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,3 +4093,81 @@ fn test_payment_metadata_consistency() {
40934093
do_test_payment_metadata_consistency(false, true);
40944094
do_test_payment_metadata_consistency(false, false);
40954095
}
4096+
4097+
#[test]
4098+
fn test_htlc_forward_considers_anchor_outputs_value() {
4099+
// Tests that:
4100+
//
4101+
// 1) Forwarding nodes don't forward HTLCs that would cause their balance to dip below the
4102+
// reserve when considering the value of anchor outputs.
4103+
//
4104+
// 2) Recipients of `update_add_htlc` properly reject HTLCs that would cause the initiator's
4105+
// balance to dip below the reserve when considering the value of anchor outputs.
4106+
let mut anchors_config = test_default_channel_config();
4107+
anchors_config.channel_handshake_limits.max_funding_satoshis = 100_000_000;
4108+
anchors_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true;
4109+
anchors_config.manually_accept_inbound_channels = true;
4110+
4111+
// Set up a test network of three nodes that replicates a production failure leading to the
4112+
// discovery of this bug.
4113+
let chanmon_cfgs = create_chanmon_cfgs(3);
4114+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
4115+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[Some(anchors_config), Some(anchors_config), Some(anchors_config)]);
4116+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
4117+
4118+
*nodes[0].fee_estimator.sat_per_kw.lock().unwrap() = 293;
4119+
*nodes[1].fee_estimator.sat_per_kw.lock().unwrap() = 293;
4120+
*nodes[2].fee_estimator.sat_per_kw.lock().unwrap() = 293;
4121+
4122+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 20_000_000, 0);
4123+
let (_, _, chan_id_2, _) = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 20_000_000, 19_706_357_000);
4124+
4125+
// Send out an HTLC that would cause the forwarding node to dip below its reserve when
4126+
// considering the value of anchor outputs.
4127+
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(nodes[0], nodes[2], 92_761_092);
4128+
nodes[0].node.send_payment_with_route(
4129+
&route, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_hash.0)
4130+
).unwrap();
4131+
check_added_monitors!(nodes[0], 1);
4132+
4133+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
4134+
assert_eq!(events.len(), 1);
4135+
let mut update_add_htlc = if let MessageSendEvent::UpdateHTLCs { updates, .. } = events.pop().unwrap() {
4136+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &updates.update_add_htlcs[0]);
4137+
check_added_monitors(&nodes[1], 0);
4138+
commitment_signed_dance!(nodes[1], nodes[0], &updates.commitment_signed, false);
4139+
updates.update_add_htlcs[0].clone()
4140+
} else {
4141+
panic!("Unexpected event");
4142+
};
4143+
4144+
// The forwarding node should reject forwarding it as expected.
4145+
expect_pending_htlcs_forwardable!(nodes[1]);
4146+
expect_pending_htlcs_forwardable_and_htlc_handling_failed!(&nodes[1], vec![HTLCDestination::NextHopChannel {
4147+
node_id: Some(nodes[2].node.get_our_node_id()),
4148+
channel_id: chan_id_2
4149+
}]);
4150+
check_added_monitors(&nodes[1], 1);
4151+
4152+
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
4153+
assert_eq!(events.len(), 1);
4154+
if let MessageSendEvent::UpdateHTLCs { updates, .. } = events.pop().unwrap() {
4155+
nodes[0].node.handle_update_fail_htlc(&nodes[1].node.get_our_node_id(), &updates.update_fail_htlcs[0]);
4156+
check_added_monitors(&nodes[0], 0);
4157+
commitment_signed_dance!(nodes[0], nodes[1], &updates.commitment_signed, false);
4158+
} else {
4159+
panic!("Unexpected event");
4160+
}
4161+
4162+
expect_payment_failed!(nodes[0], payment_hash, false);
4163+
4164+
// Assume that the forwarding node did forward it, and make sure the recipient rejects it as an
4165+
// invalid update and closes the channel.
4166+
update_add_htlc.channel_id = chan_id_2;
4167+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &update_add_htlc);
4168+
check_closed_event(&nodes[2], 1, ClosureReason::ProcessingError {
4169+
err: "Remote HTLC add would put them under remote reserve value".to_owned()
4170+
}, false, &[nodes[1].node.get_our_node_id()], 20_000_000);
4171+
check_closed_broadcast(&nodes[2], 1, true);
4172+
check_added_monitors(&nodes[2], 1);
4173+
}

0 commit comments

Comments
 (0)