Skip to content

Commit be0d771

Browse files
authored
Merge pull request #4653 from wpaulino/pending-splice-persist
Persist negotiated splice candidates on reload
2 parents e01ecce + f3575c5 commit be0d771

2 files changed

Lines changed: 112 additions & 13 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2989,15 +2989,6 @@ struct PendingFunding {
29892989
contributions: Vec<FundingContribution>,
29902990
}
29912991

2992-
impl_ser_tlv_based!(PendingFunding, {
2993-
(1, funding_negotiation, upgradable_option),
2994-
(3, negotiated_candidates, required_vec),
2995-
(5, sent_funding_txid, option),
2996-
(7, received_funding_txid, option),
2997-
(8, last_funding_feerate_sat_per_1000_weight, option),
2998-
(10, contributions, optional_vec),
2999-
});
3000-
30012992
#[derive(Debug)]
30022993
enum FundingNegotiation {
30032994
AwaitingAck {
@@ -3037,6 +3028,57 @@ impl_writeable_tlv_based_enum_upgradable!(FundingNegotiation,
30373028
unread_variants: AwaitingAck, ConstructingTransaction
30383029
);
30393030

3031+
struct PendingFundingWriteable<'a> {
3032+
pending_funding: &'a PendingFunding,
3033+
reset_funding_negotiation: bool,
3034+
}
3035+
3036+
impl Writeable for PendingFundingWriteable<'_> {
3037+
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
3038+
let funding_negotiation = if self.reset_funding_negotiation {
3039+
None
3040+
} else {
3041+
self.pending_funding.funding_negotiation.as_ref()
3042+
};
3043+
debug_assert!(
3044+
funding_negotiation.is_none()
3045+
|| matches!(
3046+
funding_negotiation,
3047+
Some(FundingNegotiation::AwaitingSignatures { .. })
3048+
)
3049+
);
3050+
let contributions_len = if self.reset_funding_negotiation
3051+
&& self.pending_funding.funding_negotiation.is_some()
3052+
{
3053+
self.pending_funding.contributions.len().saturating_sub(1)
3054+
} else {
3055+
self.pending_funding.contributions.len()
3056+
};
3057+
write_tlv_fields!(writer, {
3058+
(1, funding_negotiation, upgradable_option),
3059+
(3, self.pending_funding.negotiated_candidates, required_vec),
3060+
(5, self.pending_funding.sent_funding_txid, option),
3061+
(7, self.pending_funding.received_funding_txid, option),
3062+
(8, self.pending_funding.last_funding_feerate_sat_per_1000_weight, option),
3063+
(10, self.pending_funding.contributions[..contributions_len], optional_vec),
3064+
});
3065+
Ok(())
3066+
}
3067+
}
3068+
3069+
impl Readable for PendingFunding {
3070+
fn read<R: io::Read>(reader: &mut R) -> Result<Self, DecodeError> {
3071+
Ok(_decode_and_build!(reader, Self, {
3072+
(1, funding_negotiation, upgradable_option),
3073+
(3, negotiated_candidates, required_vec),
3074+
(5, sent_funding_txid, option),
3075+
(7, received_funding_txid, option),
3076+
(8, last_funding_feerate_sat_per_1000_weight, option),
3077+
(10, contributions, optional_vec),
3078+
}))
3079+
}
3080+
}
3081+
30403082
impl FundingNegotiation {
30413083
fn as_funding(&self) -> Option<&FundingScope> {
30423084
match self {
@@ -16305,10 +16347,18 @@ impl<SP: SignerProvider> Writeable for FundedChannel<SP> {
1630516347
let holder_commitment_point_next = self.holder_commitment_point.next_point();
1630616348
let holder_commitment_point_pending_next = self.holder_commitment_point.pending_next_point;
1630716349

16308-
// We don't have to worry about resetting the pending `FundingNegotiation` because we
16309-
// can only read `FundingNegotiation::AwaitingSignatures` variants anyway.
16310-
let pending_splice =
16311-
self.pending_splice.as_ref().filter(|_| !self.should_reset_pending_splice_state(true));
16350+
// Avoid writing any negotiations that are not at the signing stage yet, as they cannot be
16351+
// resumed on reestablishment, but keep any already-negotiated candidates.
16352+
let reset_funding_negotiation = self.should_reset_pending_splice_state(true);
16353+
let should_persist_pending_splice =
16354+
!reset_funding_negotiation || !self.pending_funding().is_empty();
16355+
let pending_splice = should_persist_pending_splice
16356+
.then(|| ())
16357+
.and_then(|_| self.pending_splice.as_ref())
16358+
.map(|pending_funding| PendingFundingWriteable {
16359+
pending_funding,
16360+
reset_funding_negotiation,
16361+
});
1631216362

1631316363
let monitor_pending_tx_signatures =
1631416364
self.context.monitor_pending_tx_signatures.then_some(());

lightning/src/ln/splicing_tests.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,55 @@ fn do_test_splice_state_reset_on_disconnect(reload: bool) {
11521152
lock_splice_after_blocks(&nodes[0], &nodes[1], ANTI_REORG_DELAY - 1);
11531153
}
11541154

1155+
#[test]
1156+
fn test_reload_resets_splice_negotiation_without_dropping_candidates() {
1157+
// A reload should abort an in-flight RBF negotiation, but it must not drop the previously
1158+
// negotiated splice candidate that the monitor is still tracking.
1159+
let chanmon_cfgs = create_chanmon_cfgs(2);
1160+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1161+
let (persister_0, chain_monitor_0);
1162+
let node_0;
1163+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1164+
let mut nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1165+
1166+
let node_id_1 = nodes[1].node.get_our_node_id();
1167+
let initial_channel_value_sat = 100_000;
1168+
let (_, _, channel_id, _) =
1169+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, initial_channel_value_sat, 0);
1170+
1171+
let added_value = Amount::from_sat(50_000);
1172+
provide_utxo_reserves(&nodes, 2, added_value * 2);
1173+
1174+
let funding_contribution = do_initiate_splice_in(&nodes[0], &nodes[1], channel_id, added_value);
1175+
let (_splice_tx, _) =
1176+
splice_channel(&nodes[0], &nodes[1], channel_id, funding_contribution.clone());
1177+
1178+
let rbf_feerate = FeeRate::from_sat_per_kwu(FEERATE_FLOOR_SATS_PER_KW as u64 + 25);
1179+
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
1180+
assert_eq!(funding_template.min_rbf_feerate(), Some(rbf_feerate));
1181+
assert!(funding_template.prior_contribution().is_some());
1182+
1183+
let rbf_contribution =
1184+
funding_template.with_prior_contribution(rbf_feerate, FeeRate::MAX).build().unwrap();
1185+
nodes[0].node.funding_contributed(&channel_id, &node_id_1, rbf_contribution, None).unwrap();
1186+
complete_rbf_handshake(&nodes[0], &nodes[1]);
1187+
1188+
let encoded_monitor_0 = get_monitor!(nodes[0], channel_id).encode();
1189+
reload_node!(
1190+
nodes[0],
1191+
nodes[0].node.encode(),
1192+
&[&encoded_monitor_0],
1193+
persister_0,
1194+
chain_monitor_0,
1195+
node_0
1196+
);
1197+
let _ = get_event!(&nodes[0], Event::SpliceNegotiationFailed);
1198+
1199+
let funding_template = nodes[0].node.splice_channel(&channel_id, &node_id_1).unwrap();
1200+
assert_eq!(funding_template.min_rbf_feerate(), Some(rbf_feerate));
1201+
assert_eq!(funding_template.prior_contribution().unwrap(), &funding_contribution);
1202+
}
1203+
11551204
#[test]
11561205
fn test_config_reject_inbound_splices() {
11571206
// Tests that nodes with `reject_inbound_splices` properly reject inbound splices but still

0 commit comments

Comments
 (0)