Skip to content

Commit d1f819d

Browse files
Merge pull request #4227 from valentinewallace/2025-10-reconstruct-mgr-fwd-htlcs
Reconstruct `ChannelManager` forwarded HTLCs maps from `Channel`s
2 parents fd85279 + a24dcff commit d1f819d

File tree

7 files changed

+575
-77
lines changed

7 files changed

+575
-77
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ lightning-dns-resolver/target
1616
ext-functional-test-demo/target
1717
no-std-check/target
1818
msrv-no-dev-deps-check/target
19+
lightning-tests/target

lightning-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ lightning-types = { path = "../lightning-types", features = ["_test_utils"] }
1515
lightning-invoice = { path = "../lightning-invoice", default-features = false }
1616
lightning-macros = { path = "../lightning-macros" }
1717
lightning = { path = "../lightning", features = ["_test_utils"] }
18+
lightning_0_2 = { package = "lightning", version = "0.2.0", features = ["_test_utils"] }
1819
lightning_0_1 = { package = "lightning", version = "0.1.7", features = ["_test_utils"] }
1920
lightning_0_0_125 = { package = "lightning", version = "0.0.125", features = ["_test_utils"] }
2021

lightning-tests/src/upgrade_downgrade_tests.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@
1010
//! Tests which test upgrading from previous versions of LDK or downgrading to previous versions of
1111
//! LDK.
1212
13+
use lightning_0_2::commitment_signed_dance as commitment_signed_dance_0_2;
14+
use lightning_0_2::events::Event as Event_0_2;
15+
use lightning_0_2::get_monitor as get_monitor_0_2;
16+
use lightning_0_2::ln::channelmanager::PaymentId as PaymentId_0_2;
17+
use lightning_0_2::ln::channelmanager::RecipientOnionFields as RecipientOnionFields_0_2;
18+
use lightning_0_2::ln::functional_test_utils as lightning_0_2_utils;
19+
use lightning_0_2::ln::msgs::ChannelMessageHandler as _;
20+
use lightning_0_2::routing::router as router_0_2;
21+
use lightning_0_2::util::ser::Writeable as _;
22+
1323
use lightning_0_1::commitment_signed_dance as commitment_signed_dance_0_1;
1424
use lightning_0_1::events::ClosureReason as ClosureReason_0_1;
1525
use lightning_0_1::expect_pending_htlcs_forwardable_ignore as expect_pending_htlcs_forwardable_ignore_0_1;
@@ -498,3 +508,194 @@ fn test_0_1_htlc_forward_after_splice() {
498508
do_test_0_1_htlc_forward_after_splice(true);
499509
do_test_0_1_htlc_forward_after_splice(false);
500510
}
511+
512+
#[derive(PartialEq, Eq)]
513+
enum MidHtlcForwardCase {
514+
// Restart the upgraded node after locking an HTLC forward into the inbound edge, but before
515+
// decoding the onion.
516+
PreOnionDecode,
517+
// Restart the upgraded node after locking an HTLC forward into the inbound edge + decoding the
518+
// onion.
519+
PostOnionDecode,
520+
// Restart the upgraded node after the HTLC has been decoded and placed in the pending intercepted
521+
// HTLCs map.
522+
Intercept,
523+
}
524+
525+
#[test]
526+
fn upgrade_pre_htlc_forward_onion_decode() {
527+
do_upgrade_mid_htlc_forward(MidHtlcForwardCase::PreOnionDecode);
528+
}
529+
530+
#[test]
531+
fn upgrade_mid_htlc_forward() {
532+
do_upgrade_mid_htlc_forward(MidHtlcForwardCase::PostOnionDecode);
533+
}
534+
535+
#[test]
536+
fn upgrade_mid_htlc_intercept_forward() {
537+
do_upgrade_mid_htlc_forward(MidHtlcForwardCase::Intercept);
538+
}
539+
540+
fn do_upgrade_mid_htlc_forward(test: MidHtlcForwardCase) {
541+
// In 0.3, we started reconstructing the `ChannelManager`'s HTLC forwards maps from the HTLCs
542+
// contained in `Channel`s, as part of removing the requirement to regularly persist the
543+
// `ChannelManager`. However, HTLC forwards can only be reconstructed this way if they were
544+
// received on 0.3 or higher. Test that HTLC forwards that were serialized on <=0.2 will still
545+
// succeed when read on 0.3+.
546+
let (node_a_ser, node_b_ser, node_c_ser, mon_a_1_ser, mon_b_1_ser, mon_b_2_ser, mon_c_1_ser);
547+
let (node_a_id, node_b_id, node_c_id);
548+
let (payment_secret_bytes, payment_hash_bytes, payment_preimage_bytes);
549+
let chan_id_bytes_b_c;
550+
551+
{
552+
let chanmon_cfgs = lightning_0_2_utils::create_chanmon_cfgs(3);
553+
let node_cfgs = lightning_0_2_utils::create_node_cfgs(3, &chanmon_cfgs);
554+
555+
let mut intercept_cfg = lightning_0_2_utils::test_default_channel_config();
556+
intercept_cfg.accept_intercept_htlcs = true;
557+
let cfgs = &[None, Some(intercept_cfg), None];
558+
let node_chanmgrs = lightning_0_2_utils::create_node_chanmgrs(3, &node_cfgs, cfgs);
559+
let nodes = lightning_0_2_utils::create_network(3, &node_cfgs, &node_chanmgrs);
560+
561+
node_a_id = nodes[0].node.get_our_node_id();
562+
node_b_id = nodes[1].node.get_our_node_id();
563+
node_c_id = nodes[2].node.get_our_node_id();
564+
let chan_id_a = lightning_0_2_utils::create_announced_chan_between_nodes_with_value(
565+
&nodes, 0, 1, 10_000_000, 0,
566+
)
567+
.2;
568+
569+
let chan_id_b = lightning_0_2_utils::create_announced_chan_between_nodes_with_value(
570+
&nodes, 1, 2, 50_000, 0,
571+
)
572+
.2;
573+
chan_id_bytes_b_c = chan_id_b.0;
574+
575+
// Ensure all nodes are at the same initial height.
576+
let node_max_height = nodes.iter().map(|node| node.best_block_info().1).max().unwrap();
577+
for node in &nodes {
578+
let blocks_to_mine = node_max_height - node.best_block_info().1;
579+
if blocks_to_mine > 0 {
580+
lightning_0_2_utils::connect_blocks(node, blocks_to_mine);
581+
}
582+
}
583+
584+
// Initiate an HTLC to be sent over node_a -> node_b -> node_c
585+
let (preimage, hash, secret) =
586+
lightning_0_2_utils::get_payment_preimage_hash(&nodes[2], Some(1_000_000), None);
587+
payment_preimage_bytes = preimage.0;
588+
payment_hash_bytes = hash.0;
589+
payment_secret_bytes = secret.0;
590+
591+
let pay_params = router_0_2::PaymentParameters::from_node_id(
592+
node_c_id,
593+
lightning_0_2_utils::TEST_FINAL_CLTV,
594+
)
595+
.with_bolt11_features(nodes[2].node.bolt11_invoice_features())
596+
.unwrap();
597+
598+
let route_params =
599+
router_0_2::RouteParameters::from_payment_params_and_value(pay_params, 1_000_000);
600+
let mut route = lightning_0_2_utils::get_route(&nodes[0], &route_params).unwrap();
601+
602+
if test == MidHtlcForwardCase::Intercept {
603+
route.paths[0].hops[1].short_channel_id = nodes[1].node.get_intercept_scid();
604+
}
605+
606+
let onion = RecipientOnionFields_0_2::secret_only(secret);
607+
let id = PaymentId_0_2(hash.0);
608+
nodes[0].node.send_payment_with_route(route, hash, onion, id).unwrap();
609+
610+
lightning_0_2_utils::check_added_monitors(&nodes[0], 1);
611+
let send_event = lightning_0_2_utils::SendEvent::from_node(&nodes[0]);
612+
613+
// Lock in the HTLC on the inbound edge of node_b without initiating the outbound edge.
614+
nodes[1].node.handle_update_add_htlc(node_a_id, &send_event.msgs[0]);
615+
commitment_signed_dance_0_2!(nodes[1], nodes[0], send_event.commitment_msg, false);
616+
if test != MidHtlcForwardCase::PreOnionDecode {
617+
nodes[1].node.test_process_pending_update_add_htlcs();
618+
}
619+
let events = nodes[1].node.get_and_clear_pending_events();
620+
if test == MidHtlcForwardCase::Intercept {
621+
assert_eq!(events.len(), 1);
622+
assert!(matches!(events[0], Event_0_2::HTLCIntercepted { .. }));
623+
} else {
624+
assert!(events.is_empty());
625+
}
626+
627+
node_a_ser = nodes[0].node.encode();
628+
node_b_ser = nodes[1].node.encode();
629+
node_c_ser = nodes[2].node.encode();
630+
mon_a_1_ser = get_monitor_0_2!(nodes[0], chan_id_a).encode();
631+
mon_b_1_ser = get_monitor_0_2!(nodes[1], chan_id_a).encode();
632+
mon_b_2_ser = get_monitor_0_2!(nodes[1], chan_id_b).encode();
633+
mon_c_1_ser = get_monitor_0_2!(nodes[2], chan_id_b).encode();
634+
}
635+
636+
// Create a dummy node to reload over with the 0.2 state
637+
let mut chanmon_cfgs = create_chanmon_cfgs(3);
638+
639+
// Our TestChannelSigner will fail as we're jumping ahead, so disable its state-based checks
640+
chanmon_cfgs[0].keys_manager.disable_all_state_policy_checks = true;
641+
chanmon_cfgs[1].keys_manager.disable_all_state_policy_checks = true;
642+
chanmon_cfgs[2].keys_manager.disable_all_state_policy_checks = true;
643+
644+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
645+
let (persister_a, persister_b, persister_c, chain_mon_a, chain_mon_b, chain_mon_c);
646+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
647+
let (node_a, node_b, node_c);
648+
let mut nodes = create_network(3, &node_cfgs, &node_chanmgrs);
649+
650+
let config = test_default_channel_config();
651+
let a_mons = &[&mon_a_1_ser[..]];
652+
reload_node!(nodes[0], config.clone(), &node_a_ser, a_mons, persister_a, chain_mon_a, node_a);
653+
let b_mons = &[&mon_b_1_ser[..], &mon_b_2_ser[..]];
654+
reload_node!(nodes[1], config.clone(), &node_b_ser, b_mons, persister_b, chain_mon_b, node_b);
655+
let c_mons = &[&mon_c_1_ser[..]];
656+
reload_node!(nodes[2], config, &node_c_ser, c_mons, persister_c, chain_mon_c, node_c);
657+
658+
reconnect_nodes(ReconnectArgs::new(&nodes[0], &nodes[1]));
659+
let mut reconnect_b_c_args = ReconnectArgs::new(&nodes[1], &nodes[2]);
660+
reconnect_b_c_args.send_channel_ready = (true, true);
661+
reconnect_b_c_args.send_announcement_sigs = (true, true);
662+
reconnect_nodes(reconnect_b_c_args);
663+
664+
// Now release the HTLC from node_b to node_c, to be claimed back to node_a
665+
nodes[1].node.process_pending_htlc_forwards();
666+
667+
if test == MidHtlcForwardCase::Intercept {
668+
let events = nodes[1].node.get_and_clear_pending_events();
669+
assert_eq!(events.len(), 1);
670+
let (intercept_id, expected_outbound_amt_msat) = match events[0] {
671+
Event::HTLCIntercepted { intercept_id, expected_outbound_amount_msat, .. } => {
672+
(intercept_id, expected_outbound_amount_msat)
673+
},
674+
_ => panic!(),
675+
};
676+
nodes[1]
677+
.node
678+
.forward_intercepted_htlc(
679+
intercept_id,
680+
&ChannelId(chan_id_bytes_b_c),
681+
nodes[2].node.get_our_node_id(),
682+
expected_outbound_amt_msat,
683+
)
684+
.unwrap();
685+
nodes[1].node.process_pending_htlc_forwards();
686+
}
687+
688+
let pay_secret = PaymentSecret(payment_secret_bytes);
689+
let pay_hash = PaymentHash(payment_hash_bytes);
690+
let pay_preimage = PaymentPreimage(payment_preimage_bytes);
691+
692+
check_added_monitors(&nodes[1], 1);
693+
let forward_event = SendEvent::from_node(&nodes[1]);
694+
nodes[2].node.handle_update_add_htlc(node_b_id, &forward_event.msgs[0]);
695+
let commitment = &forward_event.commitment_msg;
696+
do_commitment_signed_dance(&nodes[2], &nodes[1], commitment, false, false);
697+
698+
expect_and_process_pending_htlcs(&nodes[2], false);
699+
expect_payment_claimable!(nodes[2], pay_hash, pay_secret, 1_000_000);
700+
claim_payment(&nodes[0], &[&nodes[1], &nodes[2]], pay_preimage);
701+
}

0 commit comments

Comments
 (0)