Skip to content

chanstate: use open channel from consumers#10809

Open
ziggie1984 wants to merge 55 commits into
lightningnetwork:masterfrom
ziggie1984:chanstate-openchannel-consumers
Open

chanstate: use open channel from consumers#10809
ziggie1984 wants to merge 55 commits into
lightningnetwork:masterfrom
ziggie1984:chanstate-openchannel-consumers

Conversation

@ziggie1984
Copy link
Copy Markdown
Collaborator

This PR continues the channel-state refactor by updating consumers to depend on chanstate.OpenChannel and related channel-state types directly.\n\nThe intent is to make package boundaries reflect that open-channel state is owned by chanstate, while keeping the temporary channeldb aliases in place for the later code move. The remaining backend-specific implementation can then move in follow-up PRs without mixing that larger movement with consumer updates.\n\nMigration code is intentionally left unchanged.\n\nVerification:\n- make lint-native\n- go test . ./channeldb ./funding ./lnwallet ./contractcourt ./peer ./input ./lnrpc/walletrpc\n- go test -tags=dev ./htlcswitch

ziggie1984 added 30 commits May 14, 2026 16:50
Move the small value types referenced by chanstate.Store out of
channeldb. This includes ChannelConfig, ChannelStatus,
ChannelCloseSummary, ChannelShell, ChanCount, and FinalHtlcInfo. Leave
aliases in channeldb so existing callers keep compiling while the
backend still lives there.

Parameterize the Store facets over the channel type and instantiate
current callers with *channeldb.OpenChannel. This removes the chanstate
-> channeldb import edge without moving OpenChannel yet, keeping the
first step reviewable and backend-neutral.
Move ChannelType and its flag helpers into chanstate while leaving
compatibility aliases in channeldb. This is a backend-neutral value
type and does not require moving any KV serialization logic.

Keep the full type documentation with the moved chanstate definition.
The channeldb aliases preserve the existing public surface while later
commits continue moving OpenChannel state out of the KV package.
Move the OpenChannel error definitions into chanstate and leave
channeldb aliases for existing callers. These errors describe channel
state behavior rather than a concrete KV bucket layout.

Keeping the aliases preserves the public channeldb API while later
commits move more OpenChannel state and receiver logic toward
chanstate.
Move the ShutdownInfo state type, constructor, and closer helper into
chanstate. The type describes channel shutdown state and is not tied to
the concrete KV backend.

Keep the TLV encode and decode helpers in channeldb for now, since
those functions describe the current persisted format. The channeldb
constructor remains as a compatibility wrapper.
Add a lifecycle facet to the chanstate Store contract for refresh,
confirmation, open-state, and SCID mutations. Implement the facet on
ChannelStateDB using the existing KV persistence code.

Update the matching OpenChannel receivers to call through the store
methods instead of reaching into the ChannelStateDB backend directly.
Also convert fullSync into a channeldb helper so that KV-specific code
is no longer an OpenChannel receiver.
Add a status facet to the chanstate Store contract for status bit
updates and data-loss commit point handling. Implement the facet on
ChannelStateDB using the existing persistence code.

Update the matching OpenChannel receivers to call through the store
methods. The broadcast path still uses a private channeldb helper until
its closing-transaction facet is introduced in a later commit.
Add shutdown and close-transaction facets to the chanstate Store
contract. These cover persisted shutdown info plus stored unilateral
and cooperative closing transactions.

Implement the facets on ChannelStateDB with the existing KV code and
update OpenChannel receivers to call through the store methods. The
backend-specific key selection remains private to channeldb.
Add pending-channel setup to the chanstate lifecycle store facet. This
covers the path that writes a new pending channel and records the
funding broadcast height.

Move the OpenChannel receiver to call through ChannelStateDB and pass
the backend explicitly into the channeldb sync helper. This keeps the
link-node persistence detail in channeldb while removing another direct
backend reference from OpenChannel.
Move ChannelCommitment and HTLC into chanstate so upcoming store facets
can name commitment state without importing channeldb.

Leave the KV serialization helpers in channeldb and keep aliases for
existing call sites. This preserves the current disk format and keeps
backend-specific persistence code out of chanstate for now.
Move LogUpdate into chanstate so commitment store interfaces can refer
to pending update state without importing channeldb.

Keep the log-update serialization helpers in channeldb. Those helpers
remain part of the existing KV disk format and can move with the KV
backend implementation later.
Add a commitment-focused store facet for updating local channel
commitment state. This lets OpenChannel call through the chanstate
store contract instead of reaching directly into the KV backend.

Keep the existing KV transaction body on ChannelStateDB for now. The
receiver still owns locking and in-memory state updates while the store
method owns persistence.
Move CommitDiff and its forwarding reference types into chanstate. This
lets the next commitment store facet name pending remote commitment
state without importing channeldb.

Keep forwarding package persistence and commit-diff serialization in
channeldb for now. The aliases preserve existing call sites while the
KV backend code remains in place.
Add the remote commitment-chain append method to the chanstate
commitment store facet.

Move the existing KV transaction body onto ChannelStateDB and have the
OpenChannel receiver call through the store. This removes another
direct backend dependency from OpenChannel while keeping KV persistence
code in channeldb.
Add read-side commitment lookup methods to the chanstate commitment
store facet.

Move the existing OpenChannel KV view transaction bodies onto
ChannelStateDB. Leave the OpenChannel receivers as store-call wrappers.
This removes three more direct backend references from the receiver
code without changing the persisted data format.
Add the next-revocation persistence method to the chanstate commitment
store facet.

Move the existing OpenChannel KV update body onto ChannelStateDB. The
OpenChannel receiver keeps the external locking behavior and delegates
persistence through the store interface.
Move FwdState, PkgFilter, and FwdPkg into chanstate with their existing
comments and helper methods.

Leave channeldb aliases for the moved value types and constructors so
current callers keep compiling. The KV forwarding package persistence
code stays in channeldb.
Add the commitment-tail advancement method to the chanstate commitment
store facet.

Move the existing AdvanceCommitChainTail KV transaction body onto
ChannelStateDB. The OpenChannel receiver now keeps locking and restored
channel checks before delegating persistence through the store.
Add a forwarding-package store facet to chanstate.Store.

Move the existing OpenChannel forwarding-package KV transaction bodies
onto ChannelStateDB. The OpenChannel receivers keep their locking
behavior and delegate package loading, acking, filtering, and removal
through the store.
Add commitment-height, latest-commitment, and remote revocation store
lookups to the chanstate commitment store facet.

Move the existing OpenChannel KV view transaction bodies onto
ChannelStateDB. This leaves the receivers as store-call wrappers while
keeping the persisted format and read behavior unchanged.
Move the remaining OpenChannel revocation-log KV reads onto
ChannelStateDB.

This keeps FindPreviousState and the unit-test tail-height helper as
OpenChannel wrappers. It removes direct backend access from the
receiver methods while leaving RevocationLog in channeldb for now.
Move the revocation-log value types and TLV serialization helpers into
chanstate.

Leave channeldb aliases and wrapper functions for the existing KV
persistence code and tests. Bucket keys, errors, and transaction
helpers stay in channeldb, so this commit only moves backend-neutral
state data.
Add FindPreviousState to the chanstate commitment store facet now that
RevocationLog is a chanstate value type.

This extends the store contract without changing runtime behavior. The
existing ChannelStateDB method already satisfies the new method.
Keep the revocation-log tail-height helper on ChannelStateDB instead of
the OpenChannel receiver.

The helper is only used by channeldb tests, so it should not become
part of the backend-independent chanstate store contract. The tests now
call the concrete helper directly.
Change OpenChannel.Db to the composed chanstate Store interface while
keeping the existing field name.

Tests that need raw channeldb access now assert the concrete test
backend explicitly instead of reaching through OpenChannel.Db. This
keeps backend setup out of the store contract.
Convert the KV-only OpenChannel helpers for TLV aux data and
borked-state lookup into package-level channeldb helpers.

This keeps serialization and bucket inspection code tied to the KV
backend while leaving the OpenChannel receiver set closer to the future
chanstate type.
Add transitional OpenChannel accessors for the channel status and
confirmed SCID fields used by KV store code.

These helpers keep the fields private while allowing channeldb backend
code to continue hydrating and serializing channel state after
OpenChannel moves to chanstate.
Remove the KV forwarding packager from OpenChannel and derive a
ChannelPackager inside the channeldb store methods that need one.

This keeps the backend-specific kvdb transaction helper in channeldb,
so the OpenChannel type no longer carries that dependency toward
chanstate.
Move the backend-neutral ChannelSnapshot value type into chanstate and
leave channeldb with a compatibility alias.

This keeps the future OpenChannel Snapshot receiver close to its return
type without changing existing channeldb callers.
Move the backend-neutral taproot shachain and verification nonce
helpers into chanstate with the thaw-height threshold they support.

Leave channeldb aliases for existing callers while OpenChannel and its
receiver methods are moved across the package boundary.
Add a transitional non-locking status predicate for channeldb store
code and use it from KV serialization helpers.

This avoids calling an unexported OpenChannel helper from channeldb
after the type moves into chanstate.
ziggie1984 added 25 commits May 15, 2026 20:58
Move OpenChannel and its backend-neutral receiver methods into the
chanstate package.

channeldb now keeps a compatibility alias while retaining the KV store
implementation and serialization helpers. Tests that used private
channel status fields now use store-facing accessors.
Drop the temporary channel type parameter from the channel-state store
interfaces now that OpenChannel lives in chanstate.

The domain store facets now refer to *OpenChannel directly while
retaining the same backend-independent shape. Update callers and
compatibility aliases to use the concrete Store and ChannelShell types.
Copy all HTLC fields when cloning channel commitment state.

The old copy method only copied a subset of scalar fields and copied
into nil slices for Signature and ExtraData. Allocate those slices and
deep-copy custom record values so snapshots and channel copies retain
complete HTLC metadata.
Move channelnotifier and invoice hop-hint code to the chanstate channel
types.

These consumers already depend on the chanstate store interfaces, so
they no longer need to refer to the channeldb compatibility aliases for
OpenChannel and ChannelCloseSummary.
Move small interface-only consumers to chanstate.OpenChannel.

These packages only expose open-channel values through callback or
store interfaces, so they can depend on the channel-state package
without pulling in channeldb compatibility aliases.
Move channel event payloads in chanfitness to chanstate types.

The event store still uses channeldb for flap-count persistence and
related errors, but open-channel and close-summary values now come from
the channel-state package.
Move the witness subscription HTLC parameter to chanstate.HTLC.

The beacon still depends on channeldb for witness-cache errors, but it
no longer needs the channel DB compatibility alias for HTLC payloads.
Move the channel status manager working set to chanstate.OpenChannel.

The DB interface already returns channelstate channel values, so this
removes another channeldb compatibility alias from the consumer path
while keeping the graph and announcement behavior unchanged.
Move lnpeer.NewChannel to embed chanstate.OpenChannel.

This keeps the peer-facing channel event type independent of the
channeldb compatibility alias while preserving the existing embedded
OpenChannel field shape for callers.
Move static channel backup construction to chanstate channel types.

The package still imports channeldb where it uses real database
concerns, including address sourcing and duplicate-channel recovery.
The backup payload and live-channel source boundaries no longer depend
on the channeldb compatibility aliases.
Move the root backup notifier adapter to chanstate.OpenChannel.

The adapter still depends on channeldb for address sourcing and close
type handling, but the new-channel payload it forwards into chanbackup
now matches the chanstate-owned backup interfaces.
Move the waiting-close channel helper to chanstate.OpenChannel.

The helper consumes channel state returned by the store interface, so
it should not spell the channeldb compatibility alias. Other database
errors and APIs in the wallet RPC server remain on channeldb.
Move the htlcswitch channel fetch callbacks to chanstate types.

The switch still depends on channeldb for its KV circuit storage and
forwarding package access. This commit only moves channel-state
payloads at the switch and circuit-map boundaries.
Move the local channel manager fetch boundary to chanstate types.

The manager still imports channeldb for the concrete not-found error,
but channel state values and config constraints now use the chanstate
package directly.
Move the gossiper channel lookup callback to chanstate.OpenChannel.

Discovery still depends on channeldb for waiting-proof persistence.
This commit only removes the channeldb compatibility alias from the
channel state lookup boundary.
Move watchtower blob and client channel-type boundaries to chanstate.

The wtclient manager still imports channeldb for closed-channel lookup
errors, but the channel type and close-summary payloads now use the
channel state package directly.
Move htlcswitch link-facing channel state boundaries to chanstate.

The link still uses channeldb for forwarding-package persistence, but
channel update callbacks, tower registration, and dust helper channel
types now use the channel state package directly.
Update the funding manager callback and helper signatures to depend on
the chanstate OpenChannel type instead of the channeldb alias.

The funding manager already receives channel persistence through the
chanstate Store interface, so this keeps its open-channel boundary
aligned with the backend-independent package.
Update server callback wiring to use chanstate.OpenChannel at the
funding manager boundary.

This follows the funding package change and removes another
consumer-facing dependency on the channeldb OpenChannel alias.
Update RPC helpers that format open channel data to accept
chanstate.OpenChannel directly.

These helpers only inspect channel state and do not need to name the
channeldb OpenChannel alias.
Build restored channel shells with chanstate.OpenChannel instead of the
channeldb alias.

The restored shell is channel state data, so this keeps the constructor
aligned with the package that now owns the type.
Update contractcourt channel and resolver state boundaries to use
chanstate.OpenChannel instead of the channeldb alias.

This keeps the contract resolution package depending on channel state
data through the package that now owns the type, while leaving
channeldb references for store and error types that still belong there.
Update lnwallet channel, reservation, wallet, and test helpers to use
chanstate.OpenChannel directly.

The wallet package still imports channeldb for database APIs and other
channel-state aliases, but the OpenChannel type boundary now points at
the package that owns the type.
Update peer channel loading, validation, and test helpers to use
chanstate.OpenChannel directly.

The peer package still depends on channeldb for store-level errors and
helpers, but no longer needs the OpenChannel alias in its public
channel-state boundary.
Update htlcswitch test utilities to construct and pass
chanstate.OpenChannel values directly.

This removes another test-only dependency on the channeldb OpenChannel
alias while leaving the test database helpers unchanged.
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request continues the architectural refactor of the channel state management system. By decoupling core channel state definitions from the database-specific implementation in channeldb and moving them into a new chanstate package, the project achieves a cleaner separation of concerns. This change updates numerous consumers throughout the repository to utilize the new chanstate types, establishing a more robust foundation for upcoming backend-specific refactors.

Highlights

  • Refactoring Channel State: Migrated core channel state types, including OpenChannel, ChannelConfig, and ChannelCommitment, from the channeldb package to a new dedicated chanstate package to improve modularity.
  • Consumer Updates: Updated various subsystems across the codebase, including funding, lnwallet, contractcourt, and the htlcswitch, to depend on chanstate.OpenChannel instead of the previous channeldb alias.
  • Temporary Compatibility: Retained temporary type aliases in channeldb to ensure backward compatibility during the transition and to facilitate future backend-specific refactoring.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ziggie1984 ziggie1984 self-assigned this May 16, 2026
@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label May 16, 2026
@github-actions
Copy link
Copy Markdown

🔴 PR Severity: CRITICAL

Highest severity file wins | 67 non-test files | ~7,900 lines changed (excl. tests)

🔴 Critical (46 files)
  • channeldb/channel.go — massive refactor: 510 additions, 2053 deletions; core channel state persistence
  • channeldb/chanstate_assertions.go — new file; channel state consistency assertions
  • channeldb/db.go — channel database core
  • channeldb/forwarding_package.go — forwarding package state (296 lines removed)
  • channeldb/revocation_log.go — revocation log management (507 lines removed)
  • chanstate/open_channel.go — new file, 1246 lines; open channel types extracted from channeldb
  • chanstate/revocation_log.go — new file, 558 lines; revocation log types in new package
  • chanstate/forwarding.go — new file, 308 lines; forwarding state types
  • chanstate/commitment.go — new file, 304 lines; commitment transaction types
  • chanstate/interface.go — channel state interface definitions (270 lines changed)
  • chanstate/channel_type.go — new file, 157 lines; channel type definitions
  • chanstate/close_summary.go — new file, 126 lines; channel close summary types
  • chanstate/channel_status.go — new file, 110 lines; channel status types
  • chanstate/config.go — new file, 108 lines; channel state config
  • chanstate/taproot.go — new file, 79 lines; taproot-specific channel state
  • chanstate/shutdown.go — new file; channel shutdown types
  • chanstate/snapshot.go — new file; channel snapshot types
  • chanstate/channel.go — new file; channel state base types
  • chanstate/open_channel_types.go — new file; open channel auxiliary types
  • chanstate/errors.go — new file; channel state error definitions
  • contractcourt/chain_arbitrator.go — on-chain dispute coordination
  • contractcourt/channel_arbitrator.go — channel-level arbitration
  • contractcourt/anchor_resolver.go — anchor output resolution
  • contractcourt/breach_resolver.go — breach remedy resolution
  • contractcourt/chain_watcher.go — chain event monitoring
  • contractcourt/commit_sweep_resolver.go — commitment sweep resolution
  • contractcourt/contract_resolver.go — contract resolution base
  • contractcourt/htlc_lease_resolver.go — HTLC lease resolution
  • contractcourt/htlc_success_resolver.go — HTLC success resolution
  • contractcourt/htlc_timeout_resolver.go — HTLC timeout resolution
  • htlcswitch/circuit_map.go — HTLC circuit persistence
  • htlcswitch/interfaces.go — HTLC switch interfaces
  • htlcswitch/link.go — channel link state machine
  • htlcswitch/mock.go — switch mock (not excluded: not mock_*.go or *_mock.go pattern)
  • htlcswitch/switch.go — HTLC forwarding switch
  • htlcswitch/test_utils.go — switch test utilities
  • lnwallet/channel.go — wallet channel operations
  • lnwallet/commitment.go — commitment transaction construction
  • lnwallet/reservation.go — channel funding reservation
  • lnwallet/wallet.go — wallet core
  • lnwallet/aux_leaf_store.go — auxiliary leaf storage
  • lnwallet/test_utils.go — wallet test utilities
  • funding/manager.go — channel funding workflow
  • peer/brontide.go — encrypted peer connection management
  • peer/test_utils.go — peer test utilities
  • rpcserver.go / server.go — core server coordination
🟠 High (9 files)
  • discovery/ban.go — gossip ban management
  • discovery/gossiper.go — gossip protocol
  • lnrpc/invoicesrpc/addinvoice.go — invoice RPC
  • lnrpc/walletrpc/walletkit_server.go — wallet RPC
  • routing/blindedpath/blinded_path.go — blinded path routing
  • routing/localchans/manager.go — local channel route management
  • watchtower/blob/type.go — watchtower blob types
  • watchtower/wtclient/client.go — watchtower client
  • watchtower/wtclient/manager.go — watchtower client manager
🟡 Medium (11 files)
  • chanbackup/backup.go, chanbackup/pubsub.go, chanbackup/single.go — channel backup
  • chanfitness/chaneventstore.go — channel fitness tracking
  • channel_notifier.go, channelnotifier/channelnotifier.go — channel notifications
  • chanrestore.go — channel restore
  • lnpeer/peer.go — peer interface
  • netann/chan_status_manager.go, netann/interface.go — network announcements
  • witness_beacon.go — witness beacon

Analysis

This PR is a large-scale architectural refactor that extracts channel state types from channeldb into a new chanstate package. The scope is sweeping:

  • New chanstate package (14 new files, ~3,900 lines): Houses OpenChannel, ChannelCommitment, RevocationLog, forwarding state, and related types previously embedded in channeldb. This is a structural reorganization of the channel state data model.
  • channeldb/channel.go loses ~2,053 lines (types migrated to chanstate), making this the heaviest individual file change.
  • The refactor cascades into every critical subsystem: contractcourt, htlcswitch, lnwallet, funding, peer, rpcserver, server, and more — all updated to reference the new package.

Reviewer concerns:

  1. Correctness of type migration — any semantic drift between the old channeldb types and the new chanstate equivalents could introduce subtle bugs in commitment or revocation logic.
  2. Interface boundaries — chanstate/interface.go changes define what the rest of the system sees; these deserve careful scrutiny.
  3. The breadth of the cascade (67 non-test files, 6+ critical packages) makes this a high-risk integration change requiring expert review across all affected subsystems.

To override, add a severity-override-{critical,high,medium,low} label.
<!-- pr-severity-bot -->

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the channel state persistence layer by migrating core types and logic from channeldb into a new chanstate package, with corresponding updates across the codebase to use these new definitions. The review identifies a critical issue in the OpenChannel.Copy method where the Db field is not initialized in the cloned object, which would cause nil pointer dereferences during subsequent method calls. Furthermore, the reviewer suggests improving the robustness of HTLC matching in the ActiveHtlcs method by using unique identifiers like HtlcIndex and the Incoming flag instead of relying on OnionBlob hashes.

Comment thread chanstate/open_channel.go
Comment on lines +1095 to +1126
clone := &OpenChannel{
ChanType: c.ChanType,
ChainHash: c.ChainHash,
FundingOutpoint: c.FundingOutpoint,
ShortChannelID: c.ShortChannelID,
IsPending: c.IsPending,
IsInitiator: c.IsInitiator,
chanStatus: c.chanStatus,
FundingBroadcastHeight: c.FundingBroadcastHeight,
ConfirmationHeight: c.ConfirmationHeight,
NumConfsRequired: c.NumConfsRequired,
ChannelFlags: c.ChannelFlags,
IdentityPub: c.IdentityPub,
Capacity: c.Capacity,
TotalMSatSent: c.TotalMSatSent,
TotalMSatReceived: c.TotalMSatReceived,
InitialLocalBalance: c.InitialLocalBalance,
InitialRemoteBalance: c.InitialRemoteBalance,
LocalChanCfg: c.LocalChanCfg,
RemoteChanCfg: c.RemoteChanCfg,
LocalCommitment: c.LocalCommitment.Copy(),
RemoteCommitment: c.RemoteCommitment.Copy(),
RemoteCurrentRevocation: c.RemoteCurrentRevocation,
RemoteNextRevocation: c.RemoteNextRevocation,
RevocationProducer: c.RevocationProducer,
RevocationStore: c.RevocationStore,
ThawHeight: c.ThawHeight,
LastWasRevoke: c.LastWasRevoke,
RevocationKeyLocator: c.RevocationKeyLocator,
confirmedScid: c.confirmedScid,
TapscriptRoot: c.TapscriptRoot,
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The Copy() method does not assign the Db field to the cloned OpenChannel instance. Since many methods on OpenChannel (e.g., Refresh, ApplyChanStatus, CloseChannel) rely on the Db field being populated, calling these methods on a cloned instance will result in a nil pointer dereference panic.

	clone := &OpenChannel{
		ChanType:                c.ChanType,
		ChainHash:               c.ChainHash,
		FundingOutpoint:         c.FundingOutpoint,
		ShortChannelID:          c.ShortChannelID,
		IsPending:               c.IsPending,
		IsInitiator:             c.IsInitiator,
		chanStatus:              c.chanStatus,
		FundingBroadcastHeight:  c.FundingBroadcastHeight,
		ConfirmationHeight:      c.ConfirmationHeight,
		NumConfsRequired:        c.NumConfsRequired,
		ChannelFlags:            c.ChannelFlags,
		IdentityPub:             c.IdentityPub,
		Capacity:                c.Capacity,
		TotalMSatSent:           c.TotalMSatSent,
		TotalMSatReceived:       c.TotalMSatReceived,
		InitialLocalBalance:     c.InitialLocalBalance,
		InitialRemoteBalance:    c.InitialRemoteBalance,
		LocalChanCfg:            c.LocalChanCfg,
		RemoteChanCfg:           c.RemoteChanCfg,
		LocalCommitment:         c.LocalCommitment.Copy(),
		RemoteCommitment:        c.RemoteCommitment.Copy(),
		RemoteCurrentRevocation: c.RemoteCurrentRevocation,
		RemoteNextRevocation:    c.RemoteNextRevocation,
		RevocationProducer:      c.RevocationProducer,
		RevocationStore:         c.RevocationStore,
		ThawHeight:              c.ThawHeight,
		LastWasRevoke:           c.LastWasRevoke,
		RevocationKeyLocator:    c.RevocationKeyLocator,
		confirmedScid:           c.confirmedScid,
		TapscriptRoot:           c.TapscriptRoot,
		Db:                      c.Db,
	}

Comment thread chanstate/open_channel.go
Comment on lines +809 to +841
// We'll only return HTLC's that are locked into *both* commitment
// transactions. So we'll iterate through their set of HTLC's to note
// which ones are present on their commitment.
remoteHtlcs := make(map[[32]byte]struct{})
for _, htlc := range c.RemoteCommitment.Htlcs {
log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+
"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
htlc.Incoming)

onionHash := sha256.Sum256(htlc.OnionBlob[:])
remoteHtlcs[onionHash] = struct{}{}
}

// Now that we know which HTLC's they have, we'll only mark the HTLC's
// as active if *we* know them as well.
activeHtlcs := make([]HTLC, 0, len(remoteHtlcs))
for _, htlc := range c.LocalCommitment.Htlcs {
log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+
"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
htlc.Incoming)

onionHash := sha256.Sum256(htlc.OnionBlob[:])
if _, ok := remoteHtlcs[onionHash]; !ok {
log.Tracef("Skipped htlc due to onion mismatched: "+
"id=%v, update=%v incoming=%v",
htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming)

continue
}

activeHtlcs = append(activeHtlcs, htlc)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for matching HTLCs between local and remote commitments using the hash of the OnionBlob is not robust. Identical HTLCs (same amount, payment hash, expiry, and path) will have the same OnionBlob if the ephemeral key is reused (which can happen with buggy or malicious senders). This can lead to an incorrect count of active HTLCs if they are not present in the same quantity on both commitments. It is recommended to use the HtlcIndex and the Incoming flag, which uniquely and consistently identify an HTLC across both commitment transactions.

	// We'll only return HTLC's that are locked into *both* commitment
	// transactions. So we'll iterate through their set of HTLC's to note
	// which ones are present on their commitment.
	type htlcKey struct {
		id       uint64
		incoming bool
	}
	remoteHtlcs := make(map[htlcKey]struct{})
	for _, htlc := range c.RemoteCommitment.Htlcs {
		log.Tracef("RemoteCommitment has htlc: id=%v, update=%v "+
			"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
			htlc.Incoming)

		remoteHtlcs[htlcKey{htlc.HtlcIndex, htlc.Incoming}] = struct{}{}
	}

	// Now that we know which HTLC's they have, we'll only mark the HTLC's
	// as active if *we* know them as well.
	activeHtlcs := make([]HTLC, 0, len(remoteHtlcs))
	for _, htlc := range c.LocalCommitment.Htlcs {
		log.Tracef("LocalCommitment has htlc: id=%v, update=%v "+
			"incoming=%v", htlc.HtlcIndex, htlc.LogIndex,
			htlc.Incoming)

		// An HTLC is active if it's present on both commitments. On our
		// commitment, an HTLC we received is marked as incoming. On
		// their commitment, that same HTLC is marked as outgoing
		// (incoming=false). Thus we flip the incoming flag when
		// performing the lookup.
		if _, ok := remoteHtlcs[htlcKey{htlc.HtlcIndex, !htlc.Incoming}]; !ok {
			log.Tracef("Skipped htlc due to index mismatched: "+
				"id=%v, update=%v incoming=%v",
				htlc.HtlcIndex, htlc.LogIndex, htlc.Incoming)

			continue
		}

		activeHtlcs = append(activeHtlcs, htlc)
	}

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

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant