Skip to content

Commit c6c28f7

Browse files
jkczyzclaude
andcommitted
Add PendingV2 channel phase between UnfundedV2 and Funded
Introduce a PendingV2Channel struct and ChannelPhase::PendingV2 variant to represent V2 channels that have completed funding transaction construction but have not yet received commitment_signed. The funding_tx_constructed method now performs the UnfundedV2 → PendingV2 phase transition using core::mem::replace, and funding_transaction_signed and commitment_signed now match on PendingV2 instead of UnfundedV2. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 717c854 commit c6c28f7

1 file changed

Lines changed: 84 additions & 32 deletions

File tree

lightning/src/ln/channel.rs

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,7 @@ enum ChannelPhase<SP: SignerProvider> {
14911491
PendingV1(PendingV1Channel<SP>),
14921492
UnfundedInboundV1(InboundV1Channel<SP>),
14931493
UnfundedV2(UnfundedV2Channel<SP>),
1494+
PendingV2(PendingV2Channel<SP>),
14941495
Funded(FundedChannel<SP>),
14951496
}
14961497

@@ -1506,6 +1507,7 @@ where
15061507
ChannelPhase::PendingV1(chan) => &chan.context,
15071508
ChannelPhase::UnfundedInboundV1(chan) => &chan.context,
15081509
ChannelPhase::UnfundedV2(chan) => &chan.context,
1510+
ChannelPhase::PendingV2(chan) => &chan.context,
15091511
}
15101512
}
15111513

@@ -1517,6 +1519,7 @@ where
15171519
ChannelPhase::PendingV1(chan) => &mut chan.context,
15181520
ChannelPhase::UnfundedInboundV1(chan) => &mut chan.context,
15191521
ChannelPhase::UnfundedV2(chan) => &mut chan.context,
1522+
ChannelPhase::PendingV2(chan) => &mut chan.context,
15201523
}
15211524
}
15221525

@@ -1528,6 +1531,7 @@ where
15281531
ChannelPhase::PendingV1(chan) => &chan.funding,
15291532
ChannelPhase::UnfundedInboundV1(chan) => &chan.funding,
15301533
ChannelPhase::UnfundedV2(chan) => &chan.funding,
1534+
ChannelPhase::PendingV2(chan) => &chan.funding,
15311535
}
15321536
}
15331537

@@ -1540,6 +1544,7 @@ where
15401544
ChannelPhase::PendingV1(chan) => &mut chan.funding,
15411545
ChannelPhase::UnfundedInboundV1(chan) => &mut chan.funding,
15421546
ChannelPhase::UnfundedV2(chan) => &mut chan.funding,
1547+
ChannelPhase::PendingV2(chan) => &mut chan.funding,
15431548
}
15441549
}
15451550

@@ -1551,6 +1556,7 @@ where
15511556
ChannelPhase::PendingV1(chan) => (&chan.funding, &mut chan.context),
15521557
ChannelPhase::UnfundedInboundV1(chan) => (&chan.funding, &mut chan.context),
15531558
ChannelPhase::UnfundedV2(chan) => (&chan.funding, &mut chan.context),
1559+
ChannelPhase::PendingV2(chan) => (&chan.funding, &mut chan.context),
15541560
}
15551561
}
15561562

@@ -1565,6 +1571,7 @@ where
15651571
ChannelPhase::PendingV1(chan) => Some(&mut chan.unfunded_context),
15661572
ChannelPhase::UnfundedInboundV1(chan) => Some(&mut chan.unfunded_context),
15671573
ChannelPhase::UnfundedV2(chan) => Some(&mut chan.unfunded_context),
1574+
ChannelPhase::PendingV2(chan) => Some(&mut chan.unfunded_context),
15681575
}
15691576
}
15701577

@@ -1707,7 +1714,7 @@ where
17071714
shutdown_result: None,
17081715
}))
17091716
},
1710-
ChannelPhase::UnfundedV2(_) => Ok(None),
1717+
ChannelPhase::UnfundedV2(_) | ChannelPhase::PendingV2(_) => Ok(None),
17111718
}
17121719
}
17131720

@@ -1730,6 +1737,7 @@ where
17301737
ChannelPhase::PendingV1(_) => false,
17311738
ChannelPhase::UnfundedInboundV1(_) => false,
17321739
ChannelPhase::UnfundedV2(_) => false,
1740+
ChannelPhase::PendingV2(_) => false,
17331741
};
17341742

17351743
let splice_funding_failed = if let ChannelPhase::Funded(chan) = &mut self.phase {
@@ -1777,15 +1785,8 @@ where
17771785
.map(|msg| ReconnectionMsg::Open(OpenChannelMessage::V1(msg)))
17781786
.unwrap_or(ReconnectionMsg::None)
17791787
},
1780-
ChannelPhase::PendingV1(_) => {
1781-
// PendingV1 channels are not resumable, so this shouldn't be reached.
1782-
debug_assert!(false);
1783-
ReconnectionMsg::None
1784-
},
1785-
ChannelPhase::UnfundedInboundV1(_) => {
1786-
// Since unfunded inbound channel maps are cleared upon disconnecting a peer,
1787-
// they are not persisted and won't be recovered after a crash.
1788-
// Therefore, they shouldn't exist at this point.
1788+
ChannelPhase::PendingV1(_) | ChannelPhase::PendingV2(_) => {
1789+
// Pending channels are not resumable, so this shouldn't be reached.
17891790
debug_assert!(false);
17901791
ReconnectionMsg::None
17911792
},
@@ -1802,6 +1803,13 @@ where
18021803
ReconnectionMsg::None
18031804
}
18041805
},
1806+
ChannelPhase::UnfundedInboundV1(_) => {
1807+
// Since unfunded inbound channel maps are cleared upon disconnecting a peer,
1808+
// they are not persisted and won't be recovered after a crash.
1809+
// Therefore, they shouldn't exist at this point.
1810+
debug_assert!(false);
1811+
ReconnectionMsg::None
1812+
},
18051813
}
18061814
}
18071815

@@ -1820,7 +1828,7 @@ where
18201828
)
18211829
.map(|msg| Some(OpenChannelMessage::V1(msg)))
18221830
},
1823-
ChannelPhase::PendingV1(_) => Ok(None),
1831+
ChannelPhase::PendingV1(_) | ChannelPhase::PendingV2(_) => Ok(None),
18241832
ChannelPhase::UnfundedInboundV1(_) => Ok(None),
18251833
ChannelPhase::UnfundedV2(chan) => {
18261834
if chan.funding.is_outbound() {
@@ -1853,6 +1861,7 @@ where
18531861
ChannelPhase::Undefined => unreachable!(),
18541862
ChannelPhase::UnfundedOutboundV1(_)
18551863
| ChannelPhase::PendingV1(_)
1864+
| ChannelPhase::PendingV2(_)
18561865
| ChannelPhase::UnfundedInboundV1(_) => (None, false),
18571866
ChannelPhase::UnfundedV2(pending_v2_channel) => {
18581867
pending_v2_channel.interactive_tx_constructor.take();
@@ -2030,6 +2039,10 @@ where
20302039
let err = "Got an unexpected tx_abort message: This is an unfunded channel created with V1 channel establishment";
20312040
return Err(ChannelError::Warn(err.into()));
20322041
},
2042+
ChannelPhase::PendingV2(chan) => {
2043+
let had_session = chan.context.interactive_tx_signing_session.take().is_some();
2044+
(had_session, None, false)
2045+
},
20332046
ChannelPhase::UnfundedV2(pending_v2_channel) => {
20342047
let had_constructor =
20352048
pending_v2_channel.interactive_tx_constructor.take().is_some();
@@ -2114,8 +2127,9 @@ where
21142127
}
21152128

21162129
fn funding_tx_constructed(&mut self, funding_outpoint: OutPoint) -> Result<(), AbortReason> {
2117-
let interactive_tx_constructor = match &mut self.phase {
2118-
ChannelPhase::UnfundedV2(chan) => {
2130+
let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined);
2131+
let result = match phase {
2132+
ChannelPhase::UnfundedV2(mut chan) => {
21192133
debug_assert_eq!(
21202134
chan.context.channel_state,
21212135
ChannelState::NegotiatingFunding(
@@ -2133,12 +2147,23 @@ where
21332147
chan.funding.channel_transaction_parameters.funding_outpoint =
21342148
Some(funding_outpoint);
21352149

2136-
chan.interactive_tx_constructor
2150+
let interactive_tx_constructor = chan
2151+
.interactive_tx_constructor
21372152
.take()
2138-
.expect("UnfundedV2Channel::interactive_tx_constructor should be set")
2153+
.expect("UnfundedV2Channel::interactive_tx_constructor should be set");
2154+
2155+
let signing_session = interactive_tx_constructor.into_signing_session();
2156+
chan.context.interactive_tx_signing_session = Some(signing_session);
2157+
2158+
self.phase = ChannelPhase::PendingV2(PendingV2Channel {
2159+
funding: chan.funding,
2160+
context: chan.context,
2161+
unfunded_context: chan.unfunded_context,
2162+
});
2163+
Ok(())
21392164
},
2140-
ChannelPhase::Funded(chan) => {
2141-
if let Some(pending_splice) = chan.pending_splice.as_mut() {
2165+
ChannelPhase::Funded(mut chan) => {
2166+
let result = if let Some(pending_splice) = chan.pending_splice.as_mut() {
21422167
let funding_negotiation = pending_splice.funding_negotiation.take();
21432168
if let Some(FundingNegotiation::ConstructingTransaction {
21442169
mut funding,
@@ -2154,31 +2179,32 @@ where
21542179
funding,
21552180
initial_commitment_signed_from_counterparty: None,
21562181
});
2157-
interactive_tx_constructor
2182+
2183+
let signing_session = interactive_tx_constructor.into_signing_session();
2184+
chan.context.interactive_tx_signing_session = Some(signing_session);
2185+
Ok(())
21582186
} else {
21592187
// Replace the taken state for later error handling
21602188
pending_splice.funding_negotiation = funding_negotiation;
2161-
return Err(AbortReason::InternalError(
2189+
Err(AbortReason::InternalError(
21622190
"Got a tx_complete message in an invalid state",
2163-
));
2191+
))
21642192
}
21652193
} else {
2166-
return Err(AbortReason::InternalError(
2167-
"Got a tx_complete message in an invalid state",
2168-
));
2169-
}
2194+
Err(AbortReason::InternalError("Got a tx_complete message in an invalid state"))
2195+
};
2196+
self.phase = ChannelPhase::Funded(chan);
2197+
result
21702198
},
21712199
_ => {
2200+
self.phase = phase;
21722201
debug_assert!(false);
2173-
return Err(AbortReason::InternalError(
2174-
"Got a tx_complete message in an invalid phase",
2175-
));
2202+
Err(AbortReason::InternalError("Got a tx_complete message in an invalid phase"))
21762203
},
21772204
};
21782205

2179-
let signing_session = interactive_tx_constructor.into_signing_session();
2180-
self.context_mut().interactive_tx_signing_session = Some(signing_session);
2181-
Ok(())
2206+
debug_assert!(!matches!(self.phase, ChannelPhase::Undefined));
2207+
result
21822208
}
21832209

21842210
pub fn funding_transaction_signed<F: FeeEstimator, L: Logger>(
@@ -2187,7 +2213,7 @@ where
21872213
) -> Result<FundingTxSigned, APIError> {
21882214
let (context, funding, pending_splice) = match &mut self.phase {
21892215
ChannelPhase::Undefined => unreachable!(),
2190-
ChannelPhase::UnfundedV2(channel) => (&mut channel.context, &channel.funding, None),
2216+
ChannelPhase::PendingV2(channel) => (&mut channel.context, &channel.funding, None),
21912217
ChannelPhase::Funded(channel) => {
21922218
(&mut channel.context, &channel.funding, channel.pending_splice.as_ref())
21932219
},
@@ -2372,7 +2398,7 @@ where
23722398
) -> Result<(Option<ChannelMonitor<SP::EcdsaSigner>>, Option<ChannelMonitorUpdate>), ChannelError> {
23732399
let phase = core::mem::replace(&mut self.phase, ChannelPhase::Undefined);
23742400
match phase {
2375-
ChannelPhase::UnfundedV2(chan) => {
2401+
ChannelPhase::PendingV2(chan) => {
23762402
let holder_commitment_point = match chan.unfunded_context.holder_commitment_point {
23772403
Some(point) => point,
23782404
None => {
@@ -2483,6 +2509,9 @@ where
24832509
ChannelPhase::UnfundedV2(chan) => {
24842510
chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator)
24852511
},
2512+
ChannelPhase::PendingV2(chan) => {
2513+
chan.context.get_available_balances_for_scope(&chan.funding, fee_estimator)
2514+
},
24862515
}
24872516
}
24882517

@@ -2527,6 +2556,15 @@ where
25272556
}
25282557
}
25292558

2559+
impl<SP: SignerProvider> From<PendingV2Channel<SP>> for Channel<SP>
2560+
where
2561+
SP::EcdsaSigner: ChannelSigner,
2562+
{
2563+
fn from(channel: PendingV2Channel<SP>) -> Self {
2564+
Channel { phase: ChannelPhase::PendingV2(channel) }
2565+
}
2566+
}
2567+
25302568
impl<SP: SignerProvider> From<FundedChannel<SP>> for Channel<SP>
25312569
where
25322570
SP::EcdsaSigner: ChannelSigner,
@@ -14283,6 +14321,20 @@ impl<SP: SignerProvider> UnfundedV2Channel<SP> {
1428314321
}
1428414322
}
1428514323

14324+
/// A V2 channel that has completed funding transaction construction but has not yet
14325+
/// received `commitment_signed`.
14326+
pub(super) struct PendingV2Channel<SP: SignerProvider> {
14327+
pub funding: FundingScope,
14328+
pub context: ChannelContext<SP>,
14329+
pub unfunded_context: UnfundedChannelContext,
14330+
}
14331+
14332+
impl<SP: SignerProvider> PendingV2Channel<SP> {
14333+
pub fn abandon_unfunded_chan(&mut self, closure_reason: ClosureReason) -> ShutdownResult {
14334+
self.context.force_shutdown(&self.funding, closure_reason)
14335+
}
14336+
}
14337+
1428614338
// Unfunded channel utilities
1428714339

1428814340
pub(super) fn get_initial_channel_type(

0 commit comments

Comments
 (0)