Skip to content

Commit e6ed6af

Browse files
committed
Include ChannelUpdates for failed channels in PaymentParameters
If we receive channel updates as part of failure onions, we know include them in `PaymentParameters::previously_failed_channels`. In the next step, we can then verify the updates against the network graph and account for them in routing.
1 parent c139ac8 commit e6ed6af

File tree

7 files changed

+75
-43
lines changed

7 files changed

+75
-43
lines changed

lightning/src/events/mod.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,13 @@ pub enum PathFailure {
125125
},
126126
/// A hop on the path failed to forward our payment.
127127
OnPath {
128-
/// If present, this [`NetworkUpdate`] can be applied to a [`NetworkGraph`] so that
129-
/// routing decisions can permanently take the update into account.
128+
/// If present, this [`NetworkUpdate`] will be taken into account when retrying the
129+
/// payment.
130130
///
131-
/// However, note that if applying the update is obversable to the rest of the network
132-
/// (e.g., through public gossip), it can introduce a privacy leak as malicious
133-
/// intermediaries may fail HTLCs and try to infer the payment's origin.
131+
/// Note that while this update can also be applied permanently to the [`NetworkGraph`],
132+
/// doing so in a manner observable to the rest of the network (e.g., through public
133+
/// gossip) can introduce a privacy leak as malicious intermediaries may fail HTLCs and
134+
/// try to infer the payment's origin.
134135
///
135136
/// [`NetworkUpdate`]: crate::routing::gossip::NetworkUpdate
136137
/// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph
@@ -628,10 +629,9 @@ pub enum Event {
628629
/// the payment has failed, not just the route in question. If this is not set, the payment may
629630
/// be retried via a different route.
630631
payment_failed_permanently: bool,
631-
/// Extra error details based on the failure type. May contain an update that can be
632-
/// applied to a [`NetworkGraph`].
632+
/// Extra error details based on the failure type.
633633
///
634-
/// [`NetworkGraph`]: crate::routing::gossip::NetworkGraph
634+
/// May contain an update that will be taken into account on retry.
635635
failure: PathFailure,
636636
/// The payment path that failed.
637637
path: Path,

lightning/src/ln/functional_test_utils.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -2156,7 +2156,8 @@ macro_rules! expect_payment_failed {
21562156
pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>(
21572157
payment_failed_events: Vec<Event>, expected_payment_hash: PaymentHash,
21582158
expected_payment_failed_permanently: bool, conditions: PaymentFailedConditions<'e>
2159-
) {
2159+
) -> Option<msgs::ChannelUpdate> {
2160+
let mut channel_update = None;
21602161
if conditions.expected_mpp_parts_remain { assert_eq!(payment_failed_events.len(), 1); } else { assert_eq!(payment_failed_events.len(), 2); }
21612162
let expected_payment_id = match &payment_failed_events[0] {
21622163
Event::PaymentPathFailed { payment_hash, payment_failed_permanently, payment_id, failure,
@@ -2185,6 +2186,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>(
21852186
}
21862187
const CHAN_DISABLED_FLAG: u8 = 2;
21872188
assert_eq!(msg.contents.flags & CHAN_DISABLED_FLAG, 0);
2189+
channel_update = Some(msg.clone());
21882190
},
21892191
NetworkUpdate::ChannelFailure { short_channel_id, is_permanent } if chan_closed => {
21902192
if let Some(scid) = conditions.expected_blamed_scid {
@@ -2215,6 +2217,7 @@ pub fn expect_payment_failed_conditions_event<'a, 'b, 'c, 'd, 'e>(
22152217
_ => panic!("Unexpected second event"),
22162218
}
22172219
}
2220+
channel_update
22182221
}
22192222

22202223
pub fn expect_payment_failed_conditions<'a, 'b, 'c, 'd, 'e>(

lightning/src/ln/msgs.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1109,7 +1109,7 @@ pub struct ChannelAnnouncement {
11091109
/// The unsigned part of a [`channel_update`] message.
11101110
///
11111111
/// [`channel_update`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
1112-
#[derive(Clone, Debug, PartialEq, Eq)]
1112+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
11131113
pub struct UnsignedChannelUpdate {
11141114
/// The genesis hash of the blockchain where the channel is to be opened
11151115
pub chain_hash: BlockHash,
@@ -1147,7 +1147,7 @@ pub struct UnsignedChannelUpdate {
11471147
/// A [`channel_update`] message to be sent to or received from a peer.
11481148
///
11491149
/// [`channel_update`]: https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#the-channel_update-message
1150-
#[derive(Clone, Debug, PartialEq, Eq)]
1150+
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
11511151
pub struct ChannelUpdate {
11521152
/// A signature of the channel update
11531153
pub signature: Signature,

lightning/src/ln/outbound_payment.rs

+14-6
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ use bitcoin::hashes::Hash;
1313
use bitcoin::hashes::sha256::Hash as Sha256;
1414
use bitcoin::secp256k1::{self, Secp256k1, SecretKey};
1515

16+
use crate::routing::gossip::NetworkUpdate;
1617
use crate::sign::{EntropySource, NodeSigner, Recipient};
1718
use crate::events::{self, PaymentFailureReason};
1819
use crate::ln::{PaymentHash, PaymentPreimage, PaymentSecret};
1920
use crate::ln::channelmanager::{ChannelDetails, EventCompletionAction, HTLCSource, PaymentId};
2021
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
22+
use crate::ln::msgs::ChannelUpdate;
2123
use crate::offers::invoice::Bolt12Invoice;
2224
use crate::routing::router::{InFlightHtlcs, Path, PaymentParameters, Route, RouteParameters, Router};
2325
use crate::util::errors::APIError;
@@ -129,9 +131,9 @@ impl PendingOutboundPayment {
129131
_ => false,
130132
}
131133
}
132-
pub fn insert_previously_failed_scid(&mut self, scid: u64) {
134+
pub fn insert_previously_failed_scid(&mut self, scid: u64, channel_update: Option<ChannelUpdate>) {
133135
if let PendingOutboundPayment::Retryable { payment_params: Some(params), .. } = self {
134-
params.previously_failed_channels.push(scid);
136+
params.previously_failed_channels.push((scid, channel_update));
135137
}
136138
}
137139
fn is_awaiting_invoice(&self) -> bool {
@@ -248,7 +250,7 @@ impl PendingOutboundPayment {
248250
if insert_res {
249251
if let PendingOutboundPayment::Retryable {
250252
ref mut pending_amt_msat, ref mut pending_fee_msat,
251-
ref mut remaining_max_total_routing_fee_msat, ..
253+
ref mut remaining_max_total_routing_fee_msat, ..
252254
} = self {
253255
*pending_amt_msat += path.final_value_msat();
254256
let path_fee_msat = path.fee_msat();
@@ -1142,7 +1144,7 @@ impl OutboundPayments {
11421144
if let APIError::ChannelUnavailable { .. } = e {
11431145
let scid = path.hops[0].short_channel_id;
11441146
failed_scid = Some(scid);
1145-
route_params.payment_params.previously_failed_channels.push(scid);
1147+
route_params.payment_params.previously_failed_channels.push((scid, None));
11461148
}
11471149
events.push_back((events::Event::PaymentPathFailed {
11481150
payment_id: Some(payment_id),
@@ -1616,7 +1618,13 @@ impl OutboundPayments {
16161618
// TODO: If we decided to blame ourselves (or one of our channels) in
16171619
// process_onion_failure we should close that channel as it implies our
16181620
// next-hop is needlessly blaming us!
1619-
payment.get_mut().insert_previously_failed_scid(scid);
1621+
1622+
let channel_update = network_update.as_ref().and_then(|upd| {
1623+
if let NetworkUpdate::ChannelUpdateMessage { msg } = upd {
1624+
Some(msg)
1625+
} else { None }
1626+
});
1627+
payment.get_mut().insert_previously_failed_scid(scid, channel_update.cloned());
16201628
}
16211629

16221630
if payment_is_probe || !is_retryable_now || payment_failed_permanently {
@@ -1953,7 +1961,7 @@ mod tests {
19531961
};
19541962
router.expect_find_route(route_params.clone(), Ok(route.clone()));
19551963
let mut route_params_w_failed_scid = route_params.clone();
1956-
route_params_w_failed_scid.payment_params.previously_failed_channels.push(failed_scid);
1964+
route_params_w_failed_scid.payment_params.previously_failed_channels.push((failed_scid, None));
19571965
let mut route_w_failed_scid = route.clone();
19581966
route_w_failed_scid.route_params = Some(route_params_w_failed_scid.clone());
19591967
router.expect_find_route(route_params_w_failed_scid, Ok(route_w_failed_scid));

lightning/src/ln/payment_tests.rs

+31-19
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,18 @@ fn mpp_retry() {
135135
_ => panic!("Unexpected event")
136136
}
137137
events.remove(1);
138-
expect_payment_failed_conditions_event(events, payment_hash, false, PaymentFailedConditions::new().mpp_parts_remain());
138+
139+
let channel_update = expect_payment_failed_conditions_event(events, payment_hash, false,
140+
PaymentFailedConditions::new().mpp_parts_remain().blamed_chan_closed(false));
139141

140142
// Rebalance the channel so the second half of the payment can succeed.
141143
send_payment(&nodes[3], &vec!(&nodes[2])[..], 1_500_000);
142144

143145
// Retry the second half of the payment and make sure it succeeds.
144146
route.paths.remove(0);
145147
route_params.final_value_msat = 1_000_000;
146-
route_params.payment_params.previously_failed_channels.push(chan_4_update.contents.short_channel_id);
148+
route_params.payment_params.previously_failed_channels.push(
149+
(chan_4_update.contents.short_channel_id, channel_update));
147150
// Check the remaining max total routing fee for the second attempt is 50_000 - 1_000 msat fee
148151
// used by the first path
149152
route_params.max_total_routing_fee_msat = Some(max_total_routing_fee_msat - 1_000);
@@ -242,8 +245,8 @@ fn mpp_retry_overpay() {
242245
_ => panic!("Unexpected event")
243246
}
244247
events.remove(1);
245-
expect_payment_failed_conditions_event(events, payment_hash, false,
246-
PaymentFailedConditions::new().mpp_parts_remain());
248+
let channel_update = expect_payment_failed_conditions_event(events, payment_hash, false,
249+
PaymentFailedConditions::new().mpp_parts_remain().blamed_chan_closed(false));
247250

248251
// Rebalance the channel so the second half of the payment can succeed.
249252
send_payment(&nodes[3], &vec!(&nodes[2])[..], 38_000_000);
@@ -254,7 +257,8 @@ fn mpp_retry_overpay() {
254257

255258
route.paths.remove(0);
256259
route_params.final_value_msat -= first_path_value;
257-
route_params.payment_params.previously_failed_channels.push(chan_4_update.contents.short_channel_id);
260+
route_params.payment_params.previously_failed_channels.push(
261+
(chan_4_update.contents.short_channel_id, channel_update));
258262
// Check the remaining max total routing fee for the second attempt accounts only for 1_000 msat
259263
// base fee, but not for overpaid value of the first try.
260264
route_params.max_total_routing_fee_msat.as_mut().map(|m| *m -= 1000);
@@ -2429,7 +2433,7 @@ fn auto_retry_partial_failure() {
24292433

24302434
// Configure the retry1 paths
24312435
let mut payment_params = route_params.payment_params.clone();
2432-
payment_params.previously_failed_channels.push(chan_2_id);
2436+
payment_params.previously_failed_channels.push((chan_2_id, None));
24332437
let mut retry_1_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 2);
24342438
retry_1_params.max_total_routing_fee_msat = None;
24352439

@@ -2460,7 +2464,7 @@ fn auto_retry_partial_failure() {
24602464

24612465
// Configure the retry2 path
24622466
let mut payment_params = retry_1_params.payment_params.clone();
2463-
payment_params.previously_failed_channels.push(chan_3_id);
2467+
payment_params.previously_failed_channels.push((chan_3_id, None));
24642468
let mut retry_2_params = RouteParameters::from_payment_params_and_value(payment_params, amt_msat / 4);
24652469
retry_2_params.max_total_routing_fee_msat = None;
24662470

@@ -2735,7 +2739,7 @@ fn retry_multi_path_single_failed_payment() {
27352739
route.paths[0].hops[0].fee_msat = 50_000_001;
27362740
route.paths[1].hops[0].fee_msat = 50_000_000;
27372741
let mut pay_params = route.route_params.clone().unwrap().payment_params;
2738-
pay_params.previously_failed_channels.push(chans[1].short_channel_id.unwrap());
2742+
pay_params.previously_failed_channels.push((chans[1].short_channel_id.unwrap(), None));
27392743

27402744
let mut retry_params = RouteParameters::from_payment_params_and_value(pay_params, 100_000_000);
27412745
retry_params.max_total_routing_fee_msat = None;
@@ -2819,7 +2823,7 @@ fn immediate_retry_on_failure() {
28192823
route.paths[0].hops[0].fee_msat = 50_000_000;
28202824
route.paths[1].hops[0].fee_msat = 50_000_001;
28212825
let mut pay_params = route_params.payment_params.clone();
2822-
pay_params.previously_failed_channels.push(chans[0].short_channel_id.unwrap());
2826+
pay_params.previously_failed_channels.push((chans[0].short_channel_id.unwrap(), None));
28232827
let retry_params = RouteParameters::from_payment_params_and_value(pay_params, amt_msat);
28242828
route.route_params = Some(retry_params.clone());
28252829
nodes[0].router.expect_find_route(retry_params, Ok(route.clone()));
@@ -2865,8 +2869,11 @@ fn no_extra_retries_on_back_to_back_fail() {
28652869
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
28662870
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
28672871

2868-
let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id;
2869-
let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id;
2872+
let chan_1_update = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0;
2873+
let chan_2_update = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0;
2874+
2875+
let chan_1_scid = chan_1_update.contents.short_channel_id;
2876+
let chan_2_scid = chan_2_update.contents.short_channel_id;
28702877

28712878
let amt_msat = 200_000_000;
28722879
let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[1], amt_msat);
@@ -2926,7 +2933,8 @@ fn no_extra_retries_on_back_to_back_fail() {
29262933
route.route_params.as_mut().unwrap().max_total_routing_fee_msat = None;
29272934
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
29282935
let mut second_payment_params = route_params.payment_params.clone();
2929-
second_payment_params.previously_failed_channels = vec![chan_2_scid, chan_2_scid];
2936+
second_payment_params.previously_failed_channels = vec![
2937+
(chan_2_scid, Some(chan_2_update.clone())), (chan_2_scid, Some(chan_2_update))];
29302938
// On retry, we'll only return one path
29312939
route.paths.remove(1);
29322940
route.paths[0].hops[1].fee_msat = amt_msat;
@@ -3070,8 +3078,11 @@ fn test_simple_partial_retry() {
30703078
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
30713079
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
30723080

3073-
let chan_1_scid = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0.contents.short_channel_id;
3074-
let chan_2_scid = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0.contents.short_channel_id;
3081+
let chan_1_update = create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 0).0;
3082+
let chan_2_update = create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 10_000_000, 0).0;
3083+
3084+
let chan_1_scid = chan_1_update.contents.short_channel_id;
3085+
let chan_2_scid = chan_2_update.contents.short_channel_id;
30753086

30763087
let amt_msat = 200_000_000;
30773088
let (_, payment_hash, _, payment_secret) = get_route_and_payment_hash!(&nodes[0], nodes[2], amt_msat);
@@ -3132,7 +3143,7 @@ fn test_simple_partial_retry() {
31323143
nodes[0].router.expect_find_route(route_params.clone(), Ok(route.clone()));
31333144

31343145
let mut second_payment_params = route_params.payment_params.clone();
3135-
second_payment_params.previously_failed_channels = vec![chan_2_scid];
3146+
second_payment_params.previously_failed_channels = vec![(chan_2_scid, Some(chan_2_update))];
31363147
// On retry, we'll only be asked for one path (or 100k sats)
31373148
route.paths.remove(0);
31383149
let mut retry_params = RouteParameters::from_payment_params_and_value(second_payment_params, amt_msat / 2);
@@ -3354,7 +3365,7 @@ fn test_threaded_payment_retries() {
33543365
// we should still ultimately fail for the same reason - because we're trying to send too
33553366
// many HTLCs at once.
33563367
let mut new_route_params = route_params.clone();
3357-
previously_failed_channels.push(route.paths[0].hops[1].short_channel_id);
3368+
previously_failed_channels.push((route.paths[0].hops[1].short_channel_id, None));
33583369
new_route_params.payment_params.previously_failed_channels = previously_failed_channels.clone();
33593370
new_route_params.max_total_routing_fee_msat.as_mut().map(|m| *m -= 100_000);
33603371
route.paths[0].hops[1].short_channel_id += 1;
@@ -3765,14 +3776,15 @@ fn test_retry_custom_tlvs() {
37653776
_ => panic!("Unexpected event")
37663777
}
37673778
events.remove(1);
3768-
expect_payment_failed_conditions_event(events, payment_hash, false,
3769-
PaymentFailedConditions::new().mpp_parts_remain());
3779+
let channel_update = expect_payment_failed_conditions_event(events, payment_hash, false,
3780+
PaymentFailedConditions::new().mpp_parts_remain().blamed_chan_closed(false));
37703781

37713782
// Rebalance the channel so the retry of the payment can succeed.
37723783
send_payment(&nodes[2], &vec!(&nodes[1])[..], 1_500_000);
37733784

37743785
// Retry the payment and make sure it succeeds
3775-
route_params.payment_params.previously_failed_channels.push(chan_2_update.contents.short_channel_id);
3786+
route_params.payment_params.previously_failed_channels.push(
3787+
(chan_2_update.contents.short_channel_id, channel_update));
37763788
route.route_params = Some(route_params.clone());
37773789
nodes[0].router.expect_find_route(route_params, Ok(route));
37783790
nodes[0].node.process_pending_htlc_forwards();

lightning/src/routing/router.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::blinded_path::{BlindedHop, BlindedPath};
1717
use crate::ln::PaymentHash;
1818
use crate::ln::channelmanager::{ChannelDetails, PaymentId};
1919
use crate::ln::features::{Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
20-
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
20+
use crate::ln::msgs::{ChannelUpdate, DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
2121
use crate::offers::invoice::{BlindedPayInfo, Bolt12Invoice};
2222
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId, RoutingFees};
2323
use crate::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp};
@@ -583,10 +583,14 @@ pub struct PaymentParameters {
583583
/// Default value: 2
584584
pub max_channel_saturation_power_of_half: u8,
585585

586-
/// A list of SCIDs which this payment was previously attempted over and which caused the
586+
/// A list of failed channels which this payment was previously attempted over and which caused the
587587
/// payment to fail. Future attempts for the same payment shouldn't be relayed through any of
588-
/// these SCIDs.
589-
pub previously_failed_channels: Vec<u64>,
588+
/// the given SCIDs.
589+
///
590+
/// Will include a channel update for the failed channel, if we received one in the
591+
/// the failure onion. This update should be verified against the network graph and may then be
592+
/// considered when retrying the payment.
593+
pub previously_failed_channels: Vec<(u64, Option<ChannelUpdate>)>,
590594
}
591595

592596
impl Writeable for PaymentParameters {
@@ -1792,7 +1796,8 @@ where L::Target: Logger {
17921796
recommended_value_msat >= $next_hops_path_htlc_minimum_msat));
17931797

17941798
let payment_failed_on_this_channel = scid_opt.map_or(false,
1795-
|scid| payment_params.previously_failed_channels.contains(&scid));
1799+
|scid| payment_params.previously_failed_channels
1800+
.iter().any(|(failed_scid, _)| scid == *failed_scid));
17961801

17971802
let should_log_candidate = match $candidate {
17981803
CandidateRouteHop::FirstHop { .. } => true,
@@ -6297,11 +6302,12 @@ mod tests {
62976302
Arc::clone(&logger), &scorer, &Default::default(), &random_seed_bytes)
62986303
{
62996304
for chan in route.paths[0].hops.iter() {
6300-
assert!(!payment_params.previously_failed_channels.contains(&chan.short_channel_id));
6305+
assert!(!payment_params.previously_failed_channels
6306+
.iter().any(|(failed_scid, _)| &chan.short_channel_id == failed_scid));
63016307
}
63026308
let victim = (u64::from_ne_bytes(random_seed_bytes[0..8].try_into().unwrap()) as usize)
63036309
% route.paths[0].hops.len();
6304-
payment_params.previously_failed_channels.push(route.paths[0].hops[victim].short_channel_id);
6310+
payment_params.previously_failed_channels.push((route.paths[0].hops[victim].short_channel_id, None));
63056311
} else { break; }
63066312
}
63076313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Backwards Compatibility
2+
3+
* Outbound payments pending retry that have been serialized with LDK pior to 0.0.118 will lose information about previously failed channels upon upgrade.

0 commit comments

Comments
 (0)