Skip to content

Enforce ChannelTransactionParameters completeness#4464

Draft
jkczyz wants to merge 4 commits intolightningdevkit:mainfrom
jkczyz:2026-03-channel-tx-param-refactor
Draft

Enforce ChannelTransactionParameters completeness#4464
jkczyz wants to merge 4 commits intolightningdevkit:mainfrom
jkczyz:2026-03-channel-tx-param-refactor

Conversation

@jkczyz
Copy link
Contributor

@jkczyz jkczyz commented Mar 5, 2026

Introduces intermediate channel phases (PendingV1, PendingV2) between unfunded and funded states, then leverages them to enforce ChannelTransactionParameters completeness rather than relying on runtime unwrap()/expect() calls.

  • Add PendingV1 phase between UnfundedOutboundV1 and Funded, making the V1 state machine explicit: UnfundedOutboundV1 → PendingV1 → Funded
  • Add PendingV2 phase between UnfundedV2 and Funded for V2 channels that have constructed a funding transaction but not yet received commitment_signed
  • Split ChannelTransactionParameters into PartialChannelTransactionParameters (counterparty parameters and funding outpoint are Option) and ChannelTransactionParameters (all fields required). Previously a single struct used Option fields with is_populated() runtime guards scattered across signer code.
  • Make FundingScope generic over the parameters type (FundingScope<P>) so unfunded phases use PartialChannelTransactionParameters and funded phases use ChannelTransactionParameters, letting the compiler enforce which phases have complete data.
  • Concretize methods that only apply to funded channels to take FundingScope<ChannelTransactionParameters> directly, replacing trait-based Option-returning accessors with direct field access and eliminating the InitialRemoteCommitmentReceiver trait.
  • Flatten DirectedChannelTransactionParameters to store resolved fields directly since the Option-based fields it previously delegated to are now split across two types
  • Replace unset_funding_info with Channel::unfund(), which transitions a funded channel back to its unfunded phase since FundedChannel uses FundingScope<ChannelTransactionParameters> where the funding outpoint and counterparty parameters are non-Option and can't be cleared

jkczyz and others added 4 commits March 5, 2026 14:41
Introduce PendingV1Channel to represent the intermediate state where
funding_created has been generated but funding_signed has not yet been
received. This makes the channel state machine more explicit:

  UnfundedOutboundV1 -> PendingV1 -> Funded

Move get_funding_created_msg, funding_signed, signer_maybe_unblocked
(funding path), unset_funding_info, and the InitialRemoteCommitmentReceiver
impl from OutboundV1Channel to PendingV1Channel.

Change OutboundV1Channel::get_funding_created to consume self and return
(PendingV1Channel, Option<FundingCreated>).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mechanical rename to free the PendingV2Channel name for a new struct
that will hold V2 channels with complete funding parameters, following
the PendingV1 pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
ChannelTransactionParameters used Option fields for counterparty
parameters and funding outpoint, relying on runtime unwrap/expect calls
scattered throughout the codebase. This was fragile since it was easy to
call code that assumed populated parameters on a not-yet-funded channel.

Split into PartialChannelTransactionParameters (used during negotiation)
and ChannelTransactionParameters (fully populated, used once funded).
Make FundingScope generic over the parameters type so the compiler
enforces which channel phases have complete data, eliminating the need
for runtime is_populated() guards in signers and elsewhere.

With the type-level distinction in place, also:
- Remove the InitialRemoteCommitmentReceiver trait, replacing it with a
  ChannelContext method that takes complete parameters directly.
- Replace unset_funding_info with Channel::unfund(), which properly
  transitions a funded channel back to unfunded state rather than
  leaving it in an inconsistent "funded without funding info" state.
- Flatten DirectedChannelTransactionParameters to store resolved fields
  directly instead of computing them on each access.
- Concretize methods that only work with complete parameters to take
  FundingScope<ChannelTransactionParameters> directly, using field
  access instead of trait-based Option-returning accessors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ldk-reviews-bot
Copy link

👋 Hi! I see this is a draft PR.
I'll wait to assign reviewers until you mark it as ready for review.
Just convert it out of draft status when you're ready for review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants