Skip to content

Commit 025b7dc

Browse files
committed
Allow forwarding HTLCs that were constructed for previous config
This is mostly motivated by the fact that payments may happen while the latest `ChannelUpdate` indicating our new `ChannelConfig` is still propagating throughout the network. By temporarily allowing the previous config, we can help reduce payment failures across the network.
1 parent 66c5cf2 commit 025b7dc

File tree

4 files changed

+77
-21
lines changed

4 files changed

+77
-21
lines changed

lightning/src/ln/channel.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4551,6 +4551,41 @@ impl<Signer: Sign> Channel<Signer> {
45514551
did_channel_update
45524552
}
45534553

4554+
fn internal_htlc_satisfies_config(
4555+
&self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, config: &ChannelConfig,
4556+
) -> Result<(), (&'static str, u16)> {
4557+
let fee = amt_to_forward.checked_mul(config.forwarding_fee_proportional_millionths as u64)
4558+
.and_then(|prop_fee| (prop_fee / 1000000).checked_add(config.forwarding_fee_base_msat as u64));
4559+
if fee.is_none() || htlc.amount_msat < fee.unwrap() ||
4560+
(htlc.amount_msat - fee.unwrap()) < amt_to_forward {
4561+
return Err((
4562+
"Prior hop has deviated from specified fees parameters or origin node has obsolete ones",
4563+
0x1000 | 12, // fee_insufficient
4564+
));
4565+
}
4566+
if (htlc.cltv_expiry as u64) < outgoing_cltv_value as u64 + config.cltv_expiry_delta as u64 {
4567+
return Err((
4568+
"Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
4569+
0x1000 | 13, // incorrect_cltv_expiry
4570+
));
4571+
}
4572+
Ok(())
4573+
}
4574+
4575+
/// Determines whether the parameters of an incoming HTLC to be forwarded satisfy the channel's
4576+
/// [`ChannelConfig`]. This first looks at the channel's previous [`ChannelConfig`], if any, and
4577+
/// if unsuccessful, falls back to the current one.
4578+
pub fn htlc_satisfies_config(
4579+
&self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32,
4580+
) -> Result<(), (&'static str, u16)> {
4581+
if let Some(prev_config) = self.prev_config() {
4582+
if self.internal_htlc_satisfies_config(htlc, amt_to_forward, outgoing_cltv_value, &prev_config).is_ok() {
4583+
return Ok(());
4584+
}
4585+
}
4586+
self.internal_htlc_satisfies_config(&htlc, amt_to_forward, outgoing_cltv_value, &self.config())
4587+
}
4588+
45544589
pub fn get_feerate(&self) -> u32 {
45554590
self.feerate_per_kw
45564591
}

lightning/src/ln/channelmanager.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,7 +2230,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22302230
},
22312231
Some(id) => Some(id.clone()),
22322232
};
2233-
let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
2233+
let chan_update_opt = if let Some(forwarding_id) = forwarding_id_opt {
22342234
let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
22352235
if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
22362236
// Note that the behavior here should be identical to the above block - we
@@ -2257,18 +2257,12 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22572257
if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
22582258
break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
22592259
}
2260-
let fee = amt_to_forward.checked_mul(chan.get_fee_proportional_millionths() as u64)
2261-
.and_then(|prop_fee| { (prop_fee / 1000000)
2262-
.checked_add(chan.get_outbound_forwarding_fee_base_msat() as u64) });
2263-
if fee.is_none() || msg.amount_msat < fee.unwrap() || (msg.amount_msat - fee.unwrap()) < *amt_to_forward { // fee_insufficient
2264-
break Some(("Prior hop has deviated from specified fees parameters or origin node has obsolete ones", 0x1000 | 12, chan_update_opt));
2260+
if let Err((err, code)) = chan.htlc_satisfies_config(&msg, *amt_to_forward, *outgoing_cltv_value) {
2261+
break Some((err, code, chan_update_opt));
22652262
}
2266-
(chan_update_opt, chan.get_cltv_expiry_delta())
2267-
} else { (None, MIN_CLTV_EXPIRY_DELTA) };
2263+
chan_update_opt
2264+
} else { None };
22682265

2269-
if (msg.cltv_expiry as u64) < (*outgoing_cltv_value) as u64 + forwardee_cltv_expiry_delta as u64 { // incorrect_cltv_expiry
2270-
break Some(("Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta", 0x1000 | 13, chan_update_opt));
2271-
}
22722266
let cur_height = self.best_block.read().unwrap().height() + 1;
22732267
// Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
22742268
// but we want to be robust wrt to counterparty packet sanitization (see

lightning/src/ln/functional_test_utils.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,9 +1689,16 @@ pub fn do_claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>,
16891689
($node: expr, $prev_node: expr, $next_node: expr, $new_msgs: expr) => {
16901690
{
16911691
$node.node.handle_update_fulfill_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
1692-
let fee = $node.node.channel_state.lock().unwrap()
1693-
.by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap()
1694-
.config.options.forwarding_fee_base_msat;
1692+
let fee = {
1693+
let channel_state = $node.node.channel_state.lock().unwrap();
1694+
let channel = channel_state
1695+
.by_id.get(&next_msgs.as_ref().unwrap().0.channel_id).unwrap();
1696+
if let Some(prev_config) = channel.prev_config() {
1697+
prev_config.forwarding_fee_base_msat
1698+
} else {
1699+
channel.config().forwarding_fee_base_msat
1700+
}
1701+
};
16951702
expect_payment_forwarded!($node, $next_node, $prev_node, Some(fee as u64), false, false);
16961703
expected_total_fee_msat += fee as u64;
16971704
check_added_monitors!($node, 1);

lightning/src/ln/onion_route_tests.rs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use chain::channelmonitor::{ChannelMonitor, CLTV_CLAIM_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
1515
use chain::keysinterface::{KeysInterface, Recipient};
1616
use ln::{PaymentHash, PaymentSecret};
17+
use ln::channel::EXPIRE_PREV_CONFIG_TICKS;
1718
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, HTLCForwardInfo, CLTV_FAR_FAR_AWAY, MIN_CLTV_EXPIRY_DELTA, PendingHTLCInfo, PendingHTLCRouting};
1819
use ln::onion_utils;
1920
use routing::gossip::{NetworkUpdate, RoutingFees, NodeId};
@@ -616,9 +617,16 @@ fn test_onion_failure_stale_channel_update() {
616617
const PAYMENT_AMT: u64 = 40000;
617618
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], PAYMENT_AMT);
618619

620+
// Closure to force expiry of a channel's previous config.
621+
let expire_prev_config = || {
622+
for _ in 0..EXPIRE_PREV_CONFIG_TICKS {
623+
nodes[1].node.timer_tick_occurred();
624+
}
625+
};
626+
619627
// Closure to update and retrieve the latest ChannelUpdate.
620628
let update_and_get_channel_update = |config: &ChannelConfig, expect_new_update: bool,
621-
prev_update: Option<&msgs::ChannelUpdate>| -> Option<msgs::ChannelUpdate> {
629+
prev_update: Option<&msgs::ChannelUpdate>, should_expire_prev_config: bool| -> Option<msgs::ChannelUpdate> {
622630
nodes[1].node.update_channel_config(
623631
channel_to_update_counterparty, &[channel_to_update.2], config,
624632
).unwrap();
@@ -634,13 +642,16 @@ fn test_onion_failure_stale_channel_update() {
634642
if prev_update.is_some() {
635643
assert!(new_update.contents.timestamp > prev_update.unwrap().contents.timestamp)
636644
}
645+
if should_expire_prev_config {
646+
expire_prev_config();
647+
}
637648
Some(new_update)
638649
};
639650

640651
// We'll be attempting to route payments using the default ChannelUpdate for channels. This will
641652
// lead to onion failures at the first hop once we update the HTLC relay parameters for the
642653
// second hop.
643-
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(
654+
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(
644655
nodes[0], nodes[2], PAYMENT_AMT
645656
);
646657
let expect_onion_failure = |name: &str, error_code: u16, channel_update: &msgs::ChannelUpdate| {
@@ -667,28 +678,37 @@ fn test_onion_failure_stale_channel_update() {
667678
.find(|channel| channel.channel_id == channel_to_update.2).unwrap()
668679
.config.unwrap();
669680
config.forwarding_fee_base_msat = u32::max_value();
670-
let msg = update_and_get_channel_update(&config, true, None).unwrap();
681+
let msg = update_and_get_channel_update(&config, true, None, false).unwrap();
682+
683+
// The old policy should still be in effect until a new block is connected.
684+
send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
685+
payment_hash, payment_secret);
686+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
687+
688+
// Connect a block, which should expire the previous config, leading to a failure when
689+
// forwarding the HTLC.
690+
expire_prev_config();
671691
expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
672692

673693
// Redundant updates should not trigger a new ChannelUpdate.
674-
assert!(update_and_get_channel_update(&config, false, None).is_none());
694+
assert!(update_and_get_channel_update(&config, false, None, false).is_none());
675695

676696
// Similarly, updates that do not have an affect on ChannelUpdate should not trigger a new one.
677697
config.force_close_avoidance_max_fee_satoshis *= 2;
678-
assert!(update_and_get_channel_update(&config, false, None).is_none());
698+
assert!(update_and_get_channel_update(&config, false, None, false).is_none());
679699

680700
// Reset the base fee to the default and increase the proportional fee which should trigger a
681701
// new ChannelUpdate.
682702
config.forwarding_fee_base_msat = default_config.forwarding_fee_base_msat;
683703
config.cltv_expiry_delta = u16::max_value();
684-
let msg = update_and_get_channel_update(&config, true, Some(&msg)).unwrap();
704+
let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
685705
expect_onion_failure("incorrect_cltv_expiry", UPDATE|13, &msg);
686706

687707
// Reset the proportional fee and increase the CLTV expiry delta which should trigger a new
688708
// ChannelUpdate.
689709
config.cltv_expiry_delta = default_config.cltv_expiry_delta;
690710
config.forwarding_fee_proportional_millionths = u32::max_value();
691-
let msg = update_and_get_channel_update(&config, true, Some(&msg)).unwrap();
711+
let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
692712
expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
693713

694714
// To test persistence of the updated config, we'll re-initialize the ChannelManager.

0 commit comments

Comments
 (0)