Skip to content

Commit bb0901a

Browse files
committed
Allow forwarding HTLCs that were constructed for previous config
1 parent 14c8c4c commit bb0901a

File tree

3 files changed

+91
-21
lines changed

3 files changed

+91
-21
lines changed

lightning/src/ln/channelmanager.rs

+46-11
Original file line numberDiff line numberDiff line change
@@ -2096,6 +2096,26 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
20962096
})
20972097
}
20982098

2099+
fn check_htlc_forward(
2100+
&self, htlc: &msgs::UpdateAddHTLC, amt_to_forward: u64, outgoing_cltv_value: u32, config: &ChannelConfig,
2101+
) -> Option<(&'static str, u16)> {
2102+
let fee = amt_to_forward.checked_mul(config.forwarding_fee_proportional_millionths as u64)
2103+
.and_then(|prop_fee| (prop_fee / 1000000).checked_add(config.forwarding_fee_base_msat as u64));
2104+
if fee.is_none() || htlc.amount_msat < fee.unwrap() || (htlc.amount_msat - fee.unwrap()) < amt_to_forward {
2105+
return Some((
2106+
"Prior hop has deviated from specified fees parameters or origin node has obsolete ones",
2107+
0x1000 | 12, // fee_insufficient
2108+
));
2109+
}
2110+
if (htlc.cltv_expiry as u64) < outgoing_cltv_value as u64 + config.cltv_expiry_delta as u64 {
2111+
return Some((
2112+
"Forwarding node has tampered with the intended HTLC values or origin node has an obsolete cltv_expiry_delta",
2113+
0x1000 | 13, // incorrect_cltv_expiry
2114+
));
2115+
}
2116+
None
2117+
}
2118+
20992119
fn decode_update_add_htlc_onion(&self, msg: &msgs::UpdateAddHTLC) -> (PendingHTLCStatus, MutexGuard<ChannelHolder<Signer>>) {
21002120
macro_rules! return_malformed_err {
21012121
($msg: expr, $err_code: expr) => {
@@ -2230,7 +2250,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22302250
},
22312251
Some(id) => Some(id.clone()),
22322252
};
2233-
let (chan_update_opt, forwardee_cltv_expiry_delta) = if let Some(forwarding_id) = forwarding_id_opt {
2253+
let chan_update_opt = if let Some(forwarding_id) = forwarding_id_opt {
22342254
let chan = channel_state.as_mut().unwrap().by_id.get_mut(&forwarding_id).unwrap();
22352255
if !chan.should_announce() && !self.default_configuration.accept_forwards_to_priv_channels {
22362256
// Note that the behavior here should be identical to the above block - we
@@ -2246,6 +2266,14 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22462266
}
22472267
let chan_update_opt = self.get_channel_update_for_onion(*short_channel_id, chan).ok();
22482268

2269+
// We attempt to forward the HTLC with the current policy, or the previous
2270+
// for a short period of time.
2271+
if let Some(prev_config) = chan.get_prev_config() {
2272+
if chan.get_update_time_counter() > prev_config.1 + 1 {
2273+
chan.clear_prev_config();
2274+
}
2275+
}
2276+
22492277
// Note that we could technically not return an error yet here and just hope
22502278
// that the connection is reestablished or monitor updated by the time we get
22512279
// around to doing the actual forward, but better to fail early if we can and
@@ -2257,18 +2285,25 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
22572285
if *amt_to_forward < chan.get_counterparty_htlc_minimum_msat() { // amount_below_minimum
22582286
break Some(("HTLC amount was below the htlc_minimum_msat", 0x1000 | 11, chan_update_opt));
22592287
}
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));
2288+
match self.check_htlc_forward(
2289+
&msg, *amt_to_forward, *outgoing_cltv_value, &chan.get_config(),
2290+
) {
2291+
Some((err, code)) => {
2292+
if let Some(prev_config) = chan.get_prev_config() {
2293+
if let Some((err, code)) = self.check_htlc_forward(
2294+
&msg, *amt_to_forward, *outgoing_cltv_value, &prev_config.0
2295+
) {
2296+
break Some((err, code, chan_update_opt));
2297+
}
2298+
} else {
2299+
break Some((err, code, chan_update_opt));
2300+
}
2301+
},
2302+
None => {},
22652303
}
2266-
(chan_update_opt, chan.get_cltv_expiry_delta())
2267-
} else { (None, MIN_CLTV_EXPIRY_DELTA) };
2304+
chan_update_opt
2305+
} else { None };
22682306

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-
}
22722307
let cur_height = self.best_block.read().unwrap().height() + 1;
22732308
// Theoretically, channel counterparty shouldn't send us a HTLC expiring now,
22742309
// but we want to be robust wrt to counterparty packet sanitization (see

lightning/src/ln/functional_test_utils.rs

+10-3
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.get_prev_config() {
1697+
prev_config.0.forwarding_fee_base_msat
1698+
} else {
1699+
channel.get_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

+35-7
Original file line numberDiff line numberDiff line change
@@ -616,9 +616,25 @@ fn test_onion_failure_stale_channel_update() {
616616
const PAYMENT_AMT: u64 = 40000;
617617
send_payment(&nodes[0], &[&nodes[1], &nodes[2]], PAYMENT_AMT);
618618

619+
// Closure to connect a block with a timestamp that will increase a channel's
620+
// `update_time_counter`.
621+
let connect_block = |timestamp: u32| {
622+
connect_block(&nodes[1], &bitcoin::Block {
623+
header: bitcoin::BlockHeader {
624+
version: 2,
625+
prev_blockhash: nodes[1].best_block_hash(),
626+
merkle_root: Default::default(),
627+
time: timestamp,
628+
bits: 42,
629+
nonce: 42,
630+
},
631+
txdata: vec![],
632+
});
633+
};
634+
619635
// Closure to update and retrieve the latest ChannelUpdate.
620636
let update_and_get_channel_update = |config: &ChannelConfig, expect_new_update: bool,
621-
prev_update: Option<&msgs::ChannelUpdate>| -> Option<msgs::ChannelUpdate> {
637+
prev_update: Option<&msgs::ChannelUpdate>, expire_prev_config: bool| -> Option<msgs::ChannelUpdate> {
622638
nodes[1].node.update_channel_config(
623639
channel_to_update_counterparty, &[channel_to_update.2], config,
624640
).unwrap();
@@ -634,13 +650,16 @@ fn test_onion_failure_stale_channel_update() {
634650
if prev_update.is_some() {
635651
assert!(new_update.contents.timestamp > prev_update.unwrap().contents.timestamp)
636652
}
653+
if expire_prev_config {
654+
connect_block(new_update.contents.timestamp + 1);
655+
}
637656
Some(new_update)
638657
};
639658

640659
// We'll be attempting to route payments using the default ChannelUpdate for channels. This will
641660
// lead to onion failures at the first hop once we update the HTLC relay parameters for the
642661
// second hop.
643-
let (route, payment_hash, _, payment_secret) = get_route_and_payment_hash!(
662+
let (route, payment_hash, payment_preimage, payment_secret) = get_route_and_payment_hash!(
644663
nodes[0], nodes[2], PAYMENT_AMT
645664
);
646665
let expect_onion_failure = |name: &str, error_code: u16, channel_update: &msgs::ChannelUpdate| {
@@ -667,28 +686,37 @@ fn test_onion_failure_stale_channel_update() {
667686
.find(|channel| channel.channel_id == channel_to_update.2).unwrap()
668687
.config.unwrap();
669688
config.forwarding_fee_base_msat = u32::max_value();
670-
let msg = update_and_get_channel_update(&config, true, None).unwrap();
689+
let msg = update_and_get_channel_update(&config, true, None, false).unwrap();
690+
691+
// The old policy should still be in effect until a new block is connected.
692+
send_along_route_with_secret(&nodes[0], route.clone(), &[&[&nodes[1], &nodes[2]]], PAYMENT_AMT,
693+
payment_hash, payment_secret);
694+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], payment_preimage);
695+
696+
// Connect a block, which should expire the previous config, leading to a failure when
697+
// forwarding the HTLC.
698+
connect_block(msg.contents.timestamp + 1);
671699
expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
672700

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

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

680708
// Reset the base fee to the default and increase the proportional fee which should trigger a
681709
// new ChannelUpdate.
682710
config.forwarding_fee_base_msat = default_config.forwarding_fee_base_msat;
683711
config.cltv_expiry_delta = u16::max_value();
684-
let msg = update_and_get_channel_update(&config, true, Some(&msg)).unwrap();
712+
let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
685713
expect_onion_failure("incorrect_cltv_expiry", UPDATE|13, &msg);
686714

687715
// Reset the proportional fee and increase the CLTV expiry delta which should trigger a new
688716
// ChannelUpdate.
689717
config.cltv_expiry_delta = default_config.cltv_expiry_delta;
690718
config.forwarding_fee_proportional_millionths = u32::max_value();
691-
let msg = update_and_get_channel_update(&config, true, Some(&msg)).unwrap();
719+
let msg = update_and_get_channel_update(&config, true, Some(&msg), true).unwrap();
692720
expect_onion_failure("fee_insufficient", UPDATE|12, &msg);
693721

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

0 commit comments

Comments
 (0)