Skip to content

Commit 831f124

Browse files
authored
Merge pull request #1053 from valentinewallace/2021-08-dedup-payment-sent
Deduplicate PaymentSent events for MPP payments
2 parents 088daf7 + c828ff4 commit 831f124

9 files changed

+336
-136
lines changed

lightning/src/ln/channel.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -5509,7 +5509,7 @@ mod tests {
55095509
use bitcoin::hashes::hex::FromHex;
55105510
use hex;
55115511
use ln::{PaymentPreimage, PaymentHash};
5512-
use ln::channelmanager::HTLCSource;
5512+
use ln::channelmanager::{HTLCSource, MppId};
55135513
use ln::channel::{Channel,InboundHTLCOutput,OutboundHTLCOutput,InboundHTLCState,OutboundHTLCState,HTLCOutputInCommitment,HTLCCandidate,HTLCInitiator,TxCreationKeys};
55145514
use ln::channel::MAX_FUNDING_SATOSHIS;
55155515
use ln::features::InitFeatures;
@@ -5683,6 +5683,7 @@ mod tests {
56835683
path: Vec::new(),
56845684
session_priv: SecretKey::from_slice(&hex::decode("0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap()[..]).unwrap(),
56855685
first_hop_htlc_msat: 548,
5686+
mpp_id: MppId([42; 32]),
56865687
}
56875688
});
56885689

lightning/src/ln/channelmanager.rs

+168-67
Large diffs are not rendered by default.

lightning/src/ln/functional_test_utils.rs

+81-59
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,7 @@ macro_rules! expect_payment_failed_with_update {
10411041
let events = $node.node.get_and_clear_pending_events();
10421042
assert_eq!(events.len(), 1);
10431043
match events[0] {
1044-
Event::PaymentFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
1044+
Event::PaymentFailed { ref payment_hash, rejected_by_dest, ref network_update, ref error_code, ref error_data, .. } => {
10451045
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
10461046
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
10471047
assert!(error_code.is_some(), "expected error_code.is_some() = true");
@@ -1070,7 +1070,7 @@ macro_rules! expect_payment_failed {
10701070
let events = $node.node.get_and_clear_pending_events();
10711071
assert_eq!(events.len(), 1);
10721072
match events[0] {
1073-
Event::PaymentFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data } => {
1073+
Event::PaymentFailed { ref payment_hash, rejected_by_dest, network_update: _, ref error_code, ref error_data, .. } => {
10741074
assert_eq!(*payment_hash, $expected_payment_hash, "unexpected payment_hash");
10751075
assert_eq!(rejected_by_dest, $rejected_by_dest, "unexpected rejected_by_dest value");
10761076
assert!(error_code.is_some(), "expected error_code.is_some() = true");
@@ -1242,9 +1242,11 @@ pub fn claim_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, exp
12421242

12431243
if !skip_last {
12441244
last_update_fulfill_dance!(origin_node, expected_route.first().unwrap());
1245-
expect_payment_sent!(origin_node, our_payment_preimage);
12461245
}
12471246
}
1247+
if !skip_last {
1248+
expect_payment_sent!(origin_node, our_payment_preimage);
1249+
}
12481250
}
12491251

12501252
pub fn claim_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_preimage: PaymentPreimage) {
@@ -1287,77 +1289,97 @@ pub fn send_payment<'a, 'b, 'c>(origin: &Node<'a, 'b, 'c>, expected_route: &[&No
12871289
claim_payment(&origin, expected_route, our_payment_preimage);
12881290
}
12891291

1290-
pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], skip_last: bool, our_payment_hash: PaymentHash) {
1291-
assert!(expected_route.last().unwrap().node.fail_htlc_backwards(&our_payment_hash));
1292-
expect_pending_htlcs_forwardable!(expected_route.last().unwrap());
1293-
check_added_monitors!(expected_route.last().unwrap(), 1);
1292+
pub fn fail_payment_along_route<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_paths_slice: &[&[&Node<'a, 'b, 'c>]], skip_last: bool, our_payment_hash: PaymentHash) {
1293+
let mut expected_paths: Vec<_> = expected_paths_slice.iter().collect();
1294+
for path in expected_paths.iter() {
1295+
assert_eq!(path.last().unwrap().node.get_our_node_id(), expected_paths[0].last().unwrap().node.get_our_node_id());
1296+
}
1297+
assert!(expected_paths[0].last().unwrap().node.fail_htlc_backwards(&our_payment_hash));
1298+
expect_pending_htlcs_forwardable!(expected_paths[0].last().unwrap());
1299+
check_added_monitors!(expected_paths[0].last().unwrap(), expected_paths.len());
12941300

1295-
let mut next_msgs: Option<(msgs::UpdateFailHTLC, msgs::CommitmentSigned)> = None;
1296-
macro_rules! update_fail_dance {
1297-
($node: expr, $prev_node: expr, $last_node: expr) => {
1298-
{
1299-
$node.node.handle_update_fail_htlc(&$prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
1300-
commitment_signed_dance!($node, $prev_node, next_msgs.as_ref().unwrap().1, !$last_node);
1301-
if skip_last && $last_node {
1302-
expect_pending_htlcs_forwardable!($node);
1301+
let mut per_path_msgs: Vec<((msgs::UpdateFailHTLC, msgs::CommitmentSigned), PublicKey)> = Vec::with_capacity(expected_paths.len());
1302+
let events = expected_paths[0].last().unwrap().node.get_and_clear_pending_msg_events();
1303+
assert_eq!(events.len(), expected_paths.len());
1304+
for ev in events.iter() {
1305+
let (update_fail, commitment_signed, node_id) = match ev {
1306+
&MessageSendEvent::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
1307+
assert!(update_add_htlcs.is_empty());
1308+
assert!(update_fulfill_htlcs.is_empty());
1309+
assert_eq!(update_fail_htlcs.len(), 1);
1310+
assert!(update_fail_malformed_htlcs.is_empty());
1311+
assert!(update_fee.is_none());
1312+
(update_fail_htlcs[0].clone(), commitment_signed.clone(), node_id.clone())
1313+
},
1314+
_ => panic!("Unexpected event"),
1315+
};
1316+
per_path_msgs.push(((update_fail, commitment_signed), node_id));
1317+
}
1318+
per_path_msgs.sort_unstable_by(|(_, node_id_a), (_, node_id_b)| node_id_a.cmp(node_id_b));
1319+
expected_paths.sort_unstable_by(|path_a, path_b| path_a[path_a.len() - 2].node.get_our_node_id().cmp(&path_b[path_b.len() - 2].node.get_our_node_id()));
1320+
1321+
for (i, (expected_route, (path_msgs, next_hop))) in expected_paths.iter().zip(per_path_msgs.drain(..)).enumerate() {
1322+
let mut next_msgs = Some(path_msgs);
1323+
let mut expected_next_node = next_hop;
1324+
let mut prev_node = expected_route.last().unwrap();
1325+
1326+
for (idx, node) in expected_route.iter().rev().enumerate().skip(1) {
1327+
assert_eq!(expected_next_node, node.node.get_our_node_id());
1328+
let update_next_node = !skip_last || idx != expected_route.len() - 1;
1329+
if next_msgs.is_some() {
1330+
node.node.handle_update_fail_htlc(&prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
1331+
commitment_signed_dance!(node, prev_node, next_msgs.as_ref().unwrap().1, update_next_node);
1332+
if !update_next_node {
1333+
expect_pending_htlcs_forwardable!(node);
13031334
}
13041335
}
1305-
}
1306-
}
1336+
let events = node.node.get_and_clear_pending_msg_events();
1337+
if update_next_node {
1338+
assert_eq!(events.len(), 1);
1339+
match events[0] {
1340+
MessageSendEvent::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
1341+
assert!(update_add_htlcs.is_empty());
1342+
assert!(update_fulfill_htlcs.is_empty());
1343+
assert_eq!(update_fail_htlcs.len(), 1);
1344+
assert!(update_fail_malformed_htlcs.is_empty());
1345+
assert!(update_fee.is_none());
1346+
expected_next_node = node_id.clone();
1347+
next_msgs = Some((update_fail_htlcs[0].clone(), commitment_signed.clone()));
1348+
},
1349+
_ => panic!("Unexpected event"),
1350+
}
1351+
} else {
1352+
assert!(events.is_empty());
1353+
}
1354+
if !skip_last && idx == expected_route.len() - 1 {
1355+
assert_eq!(expected_next_node, origin_node.node.get_our_node_id());
1356+
}
13071357

1308-
let mut expected_next_node = expected_route.last().unwrap().node.get_our_node_id();
1309-
let mut prev_node = expected_route.last().unwrap();
1310-
for (idx, node) in expected_route.iter().rev().enumerate() {
1311-
assert_eq!(expected_next_node, node.node.get_our_node_id());
1312-
if next_msgs.is_some() {
1313-
// We may be the "last node" for the purpose of the commitment dance if we're
1314-
// skipping the last node (implying it is disconnected) and we're the
1315-
// second-to-last node!
1316-
update_fail_dance!(node, prev_node, skip_last && idx == expected_route.len() - 1);
1358+
prev_node = node;
13171359
}
13181360

1319-
let events = node.node.get_and_clear_pending_msg_events();
1320-
if !skip_last || idx != expected_route.len() - 1 {
1361+
if !skip_last {
1362+
let prev_node = expected_route.first().unwrap();
1363+
origin_node.node.handle_update_fail_htlc(&prev_node.node.get_our_node_id(), &next_msgs.as_ref().unwrap().0);
1364+
check_added_monitors!(origin_node, 0);
1365+
assert!(origin_node.node.get_and_clear_pending_msg_events().is_empty());
1366+
commitment_signed_dance!(origin_node, prev_node, next_msgs.as_ref().unwrap().1, false);
1367+
let events = origin_node.node.get_and_clear_pending_events();
13211368
assert_eq!(events.len(), 1);
13221369
match events[0] {
1323-
MessageSendEvent::UpdateHTLCs { ref node_id, updates: msgs::CommitmentUpdate { ref update_add_htlcs, ref update_fulfill_htlcs, ref update_fail_htlcs, ref update_fail_malformed_htlcs, ref update_fee, ref commitment_signed } } => {
1324-
assert!(update_add_htlcs.is_empty());
1325-
assert!(update_fulfill_htlcs.is_empty());
1326-
assert_eq!(update_fail_htlcs.len(), 1);
1327-
assert!(update_fail_malformed_htlcs.is_empty());
1328-
assert!(update_fee.is_none());
1329-
expected_next_node = node_id.clone();
1330-
next_msgs = Some((update_fail_htlcs[0].clone(), commitment_signed.clone()));
1370+
Event::PaymentFailed { payment_hash, rejected_by_dest, all_paths_failed, .. } => {
1371+
assert_eq!(payment_hash, our_payment_hash);
1372+
assert!(rejected_by_dest);
1373+
assert_eq!(all_paths_failed, i == expected_paths.len() - 1);
13311374
},
13321375
_ => panic!("Unexpected event"),
13331376
}
1334-
} else {
1335-
assert!(events.is_empty());
1336-
}
1337-
if !skip_last && idx == expected_route.len() - 1 {
1338-
assert_eq!(expected_next_node, origin_node.node.get_our_node_id());
1339-
}
1340-
1341-
prev_node = node;
1342-
}
1343-
1344-
if !skip_last {
1345-
update_fail_dance!(origin_node, expected_route.first().unwrap(), true);
1346-
1347-
let events = origin_node.node.get_and_clear_pending_events();
1348-
assert_eq!(events.len(), 1);
1349-
match events[0] {
1350-
Event::PaymentFailed { payment_hash, rejected_by_dest, .. } => {
1351-
assert_eq!(payment_hash, our_payment_hash);
1352-
assert!(rejected_by_dest);
1353-
},
1354-
_ => panic!("Unexpected event"),
13551377
}
13561378
}
13571379
}
13581380

1359-
pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_route: &[&Node<'a, 'b, 'c>], our_payment_hash: PaymentHash) {
1360-
fail_payment_along_route(origin_node, expected_route, false, our_payment_hash);
1381+
pub fn fail_payment<'a, 'b, 'c>(origin_node: &Node<'a, 'b, 'c>, expected_path: &[&Node<'a, 'b, 'c>], our_payment_hash: PaymentHash) {
1382+
fail_payment_along_route(origin_node, &[&expected_path[..]], false, our_payment_hash);
13611383
}
13621384

13631385
pub fn create_chanmon_cfgs(node_count: usize) -> Vec<TestChanMonCfg> {

lightning/src/ln/functional_tests.rs

+36-5
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use chain::transaction::OutPoint;
1919
use chain::keysinterface::BaseSign;
2020
use ln::{PaymentPreimage, PaymentSecret, PaymentHash};
2121
use ln::channel::{COMMITMENT_TX_BASE_WEIGHT, COMMITMENT_TX_WEIGHT_PER_HTLC};
22-
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, PaymentSendFailure, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA};
22+
use ln::channelmanager::{ChannelManager, ChannelManagerReadArgs, MppId, RAACommitmentOrder, PaymentSendFailure, BREAKDOWN_TIMEOUT, MIN_CLTV_EXPIRY_DELTA};
2323
use ln::channel::{Channel, ChannelError};
2424
use ln::{chan_utils, onion_utils};
2525
use ln::chan_utils::HTLC_SUCCESS_TX_WEIGHT;
@@ -3308,7 +3308,7 @@ fn test_simple_peer_disconnect() {
33083308
nodes[1].node.peer_disconnected(&nodes[0].node.get_our_node_id(), false);
33093309

33103310
claim_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], true, payment_preimage_3);
3311-
fail_payment_along_route(&nodes[0], &[&nodes[1], &nodes[2]], true, payment_hash_5);
3311+
fail_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[2]]], true, payment_hash_5);
33123312

33133313
reconnect_nodes(&nodes[0], &nodes[1], (false, false), (0, 0), (0, 0), (0, 0), (1, 0), (1, 0), (false, false));
33143314
{
@@ -3886,7 +3886,8 @@ fn do_test_htlc_timeout(send_partial_mpp: bool) {
38863886
// Use the utility function send_payment_along_path to send the payment with MPP data which
38873887
// indicates there are more HTLCs coming.
38883888
let cur_height = CHAN_CONFIRM_DEPTH + 1; // route_payment calls send_payment, which adds 1 to the current height. So we do the same here to match.
3889-
nodes[0].node.send_payment_along_path(&route.paths[0], &our_payment_hash, &Some(payment_secret), 200000, cur_height, &None).unwrap();
3889+
let mpp_id = MppId([42; 32]);
3890+
nodes[0].node.send_payment_along_path(&route.paths[0], &our_payment_hash, &Some(payment_secret), 200000, cur_height, mpp_id, &None).unwrap();
38903891
check_added_monitors!(nodes[0], 1);
38913892
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
38923893
assert_eq!(events.len(), 1);
@@ -4083,6 +4084,34 @@ fn test_no_txn_manager_serialize_deserialize() {
40834084
send_payment(&nodes[0], &[&nodes[1]], 1000000);
40844085
}
40854086

4087+
#[test]
4088+
fn mpp_failure() {
4089+
let chanmon_cfgs = create_chanmon_cfgs(4);
4090+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
4091+
let node_chanmgrs = create_node_chanmgrs(4, &node_cfgs, &[None, None, None, None]);
4092+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
4093+
4094+
let chan_1_id = create_announced_chan_between_nodes(&nodes, 0, 1, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
4095+
let chan_2_id = create_announced_chan_between_nodes(&nodes, 0, 2, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
4096+
let chan_3_id = create_announced_chan_between_nodes(&nodes, 1, 3, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
4097+
let chan_4_id = create_announced_chan_between_nodes(&nodes, 2, 3, InitFeatures::known(), InitFeatures::known()).0.contents.short_channel_id;
4098+
let logger = test_utils::TestLogger::new();
4099+
4100+
let (_, payment_hash, payment_secret) = get_payment_preimage_hash!(&nodes[3]);
4101+
let net_graph_msg_handler = &nodes[0].net_graph_msg_handler;
4102+
let mut route = get_route(&nodes[0].node.get_our_node_id(), &net_graph_msg_handler.network_graph, &nodes[3].node.get_our_node_id(), Some(InvoiceFeatures::known()), None, &[], 100000, TEST_FINAL_CLTV, &logger).unwrap();
4103+
let path = route.paths[0].clone();
4104+
route.paths.push(path);
4105+
route.paths[0][0].pubkey = nodes[1].node.get_our_node_id();
4106+
route.paths[0][0].short_channel_id = chan_1_id;
4107+
route.paths[0][1].short_channel_id = chan_3_id;
4108+
route.paths[1][0].pubkey = nodes[2].node.get_our_node_id();
4109+
route.paths[1][0].short_channel_id = chan_2_id;
4110+
route.paths[1][1].short_channel_id = chan_4_id;
4111+
send_along_route_with_secret(&nodes[0], route, &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], 200_000, payment_hash, payment_secret);
4112+
fail_payment_along_route(&nodes[0], &[&[&nodes[1], &nodes[3]], &[&nodes[2], &nodes[3]]], false, payment_hash);
4113+
}
4114+
40864115
#[test]
40874116
fn test_dup_htlc_onchain_fails_on_reload() {
40884117
// When a Channel is closed, any outbound HTLCs which were relayed through it are simply
@@ -5913,9 +5942,10 @@ fn test_fail_holding_cell_htlc_upon_free() {
59135942
let events = nodes[0].node.get_and_clear_pending_events();
59145943
assert_eq!(events.len(), 1);
59155944
match &events[0] {
5916-
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
5945+
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed } => {
59175946
assert_eq!(our_payment_hash.clone(), *payment_hash);
59185947
assert_eq!(*rejected_by_dest, false);
5948+
assert_eq!(*all_paths_failed, true);
59195949
assert_eq!(*network_update, None);
59205950
assert_eq!(*error_code, None);
59215951
assert_eq!(*error_data, None);
@@ -5999,9 +6029,10 @@ fn test_free_and_fail_holding_cell_htlcs() {
59996029
let events = nodes[0].node.get_and_clear_pending_events();
60006030
assert_eq!(events.len(), 1);
60016031
match &events[0] {
6002-
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data } => {
6032+
&Event::PaymentFailed { ref payment_hash, ref rejected_by_dest, ref network_update, ref error_code, ref error_data, ref all_paths_failed } => {
60036033
assert_eq!(payment_hash_2.clone(), *payment_hash);
60046034
assert_eq!(*rejected_by_dest, false);
6035+
assert_eq!(*all_paths_failed, true);
60056036
assert_eq!(*network_update, None);
60066037
assert_eq!(*error_code, None);
60076038
assert_eq!(*error_data, None);

lightning/src/ln/onion_route_tests.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ fn run_onion_failure_test_with_fail_intercept<F1,F2,F3>(_name: &str, test_case:
163163

164164
let events = nodes[0].node.get_and_clear_pending_events();
165165
assert_eq!(events.len(), 1);
166-
if let &Event::PaymentFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _ } = &events[0] {
166+
if let &Event::PaymentFailed { payment_hash:_, ref rejected_by_dest, ref network_update, ref error_code, error_data: _, ref all_paths_failed } = &events[0] {
167167
assert_eq!(*rejected_by_dest, !expected_retryable);
168+
assert_eq!(*all_paths_failed, true);
168169
assert_eq!(*error_code, expected_error_code);
169170
if expected_channel_update.is_some() {
170171
match network_update {

lightning/src/ln/onion_utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ pub(super) fn build_first_hop_failure_packet(shared_secret: &[u8], failure_type:
332332
/// Returns update, a boolean indicating that the payment itself failed, and the error code.
333333
#[inline]
334334
pub(super) fn process_onion_failure<T: secp256k1::Signing, L: Deref>(secp_ctx: &Secp256k1<T>, logger: &L, htlc_source: &HTLCSource, mut packet_decrypted: Vec<u8>) -> (Option<NetworkUpdate>, bool, Option<u16>, Option<Vec<u8>>) where L::Target: Logger {
335-
if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat } = htlc_source {
335+
if let &HTLCSource::OutboundRoute { ref path, ref session_priv, ref first_hop_htlc_msat, .. } = htlc_source {
336336
let mut res = None;
337337
let mut htlc_msat = *first_hop_htlc_msat;
338338
let mut error_code_ret = None;

lightning/src/routing/network_graph.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1728,6 +1728,7 @@ mod tests {
17281728
net_graph_msg_handler.handle_event(&Event::PaymentFailed {
17291729
payment_hash: PaymentHash([0; 32]),
17301730
rejected_by_dest: false,
1731+
all_paths_failed: true,
17311732
network_update: Some(NetworkUpdate::ChannelUpdateMessage {
17321733
msg: valid_channel_update,
17331734
}),
@@ -1750,6 +1751,7 @@ mod tests {
17501751
net_graph_msg_handler.handle_event(&Event::PaymentFailed {
17511752
payment_hash: PaymentHash([0; 32]),
17521753
rejected_by_dest: false,
1754+
all_paths_failed: true,
17531755
network_update: Some(NetworkUpdate::ChannelClosed {
17541756
short_channel_id,
17551757
is_permanent: false,
@@ -1771,6 +1773,7 @@ mod tests {
17711773
net_graph_msg_handler.handle_event(&Event::PaymentFailed {
17721774
payment_hash: PaymentHash([0; 32]),
17731775
rejected_by_dest: false,
1776+
all_paths_failed: true,
17741777
network_update: Some(NetworkUpdate::ChannelClosed {
17751778
short_channel_id,
17761779
is_permanent: true,

0 commit comments

Comments
 (0)