Skip to content

Commit f3575c5

Browse files
committed
Persist negotiated splice candidates on reload
Prior to supporting RBF, we would avoid persisting `FundedChannel::pending_splice` when there was a pending funding negotiation that could not be resumed on channel reestablishment. With the addition of RBF support, this would cause our previously negotiated splices (but still pending) to be dropped unintentionally.
1 parent bb597dd commit f3575c5

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)