What changed, and why it matters
This commit is a straightforward internal code reorganization. It moves existing channel-state update logic out of the OpenChannel object and into a new 'lifecycle store' interface implemented by ChannelStateDB. The actual database reads, writes, and locking behavior appear unchanged. There is no indication this fixes a security bug or introduces a new vulnerability.
No security action required. Review as normal code-quality refactor if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors lifecycle methods (Refresh, MarkConfirmationHeight, MarkCloseConfirmationHeight, MarkAsOpen, MarkRealScid, MarkScidAliasNegotiated) from OpenChannel receivers to ChannelStateDB methods satisfying a new OpenChannelLifecycleStore interface in chanstate. It also converts fullSync from an OpenChannel method to a package helper fullSyncOpenChannel. The kvdb transaction wrapping, bucket fetches, fetchOpenChannel/putOpenChannel calls, and in-memory updates remain functionally identical. No new external inputs, no authorization changes, no cryptographic changes, and no data-format changes are introduced.
Changed components
channeldb/channel.gochanstate/interface.goInspect captured patch +170 / −77
diff --git a/channeldb/channel.go b/channeldb/channel.go
index 00f94e6..103307b 100644
--- a/channeldb/channel.go
+++ b/channeldb/channel.go
@@ -1127,9 +1127,16 @@ func (c *OpenChannel) Refresh() error {
c.Lock()
defer c.Unlock()
- err := kvdb.View(c.Db.backend, func(tx kvdb.RTx) error {
+ return c.Db.RefreshChannel(c)
+}
+
+// RefreshChannel updates the in-memory channel state using the latest state
+// observed on disk.
+func (c *ChannelStateDB) RefreshChannel(channel *OpenChannel) error {
+ return kvdb.View(c.backend, func(tx kvdb.RTx) error {
chanBucket, err := fetchChanBucket(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
@@ -1137,30 +1144,27 @@ func (c *OpenChannel) Refresh() error {
// We'll re-populating the in-memory channel with the info
// fetched from disk.
- if err := fetchChanInfo(chanBucket, c); err != nil {
+ if err := fetchChanInfo(chanBucket, channel); err != nil {
return fmt.Errorf("unable to fetch chan info: %w", err)
}
// Also populate the channel's commitment states for both sides
// of the channel.
- if err := fetchChanCommitments(chanBucket, c); err != nil {
+ err = fetchChanCommitments(chanBucket, channel)
+ if err != nil {
return fmt.Errorf("unable to fetch chan commitments: "+
"%v", err)
}
// Also retrieve the current revocation state.
- if err := fetchChanRevocationState(chanBucket, c); err != nil {
+ err = fetchChanRevocationState(chanBucket, channel)
+ if err != nil {
return fmt.Errorf("unable to fetch chan revocations: "+
"%v", err)
}
return nil
}, func() {})
- if err != nil {
- return err
- }
-
- return nil
}
// fetchChanBucket is a helper function that returns the bucket where a
@@ -1301,9 +1305,9 @@ func fetchFinalHtlcsBucketRw(tx kvdb.RwTx,
return chanBucket, nil
}
-// fullSync syncs the contents of an OpenChannel while re-using an existing
-// database transaction.
-func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
+// fullSyncOpenChannel syncs the contents of an OpenChannel while re-using an
+// existing database transaction.
+func fullSyncOpenChannel(tx kvdb.RwTx, c *OpenChannel) error {
// Fetch the outpoint bucket and check if the outpoint already exists.
opBucket := tx.ReadWriteBucket(outpointBucket)
if opBucket == nil {
@@ -1399,29 +1403,40 @@ func (c *OpenChannel) MarkConfirmationHeight(height uint32) error {
c.Lock()
defer c.Unlock()
- if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
+ if err := c.Db.MarkChannelConfirmationHeight(c, height); err != nil {
+ return err
+ }
+
+ c.ConfirmationHeight = height
+
+ return nil
+}
+
+// MarkChannelConfirmationHeight updates the channel's confirmation height once
+// the channel opening transaction receives one confirmation.
+func (c *ChannelStateDB) MarkChannelConfirmationHeight(channel *OpenChannel,
+ height uint32) error {
+
+ return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
}
- channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
+ diskChannel, err := fetchOpenChannel(
+ chanBucket, &channel.FundingOutpoint,
+ )
if err != nil {
return err
}
- channel.ConfirmationHeight = height
-
- return putOpenChannel(chanBucket, channel)
- }, func() {}); err != nil {
- return err
- }
-
- c.ConfirmationHeight = height
+ diskChannel.ConfirmationHeight = height
- return nil
+ return putOpenChannel(chanBucket, diskChannel)
+ }, func() {})
}
// ResetCloseConfirmationHeight clears the channel's close confirmation height
@@ -1438,63 +1453,86 @@ func (c *OpenChannel) MarkCloseConfirmationHeight(
c.Lock()
defer c.Unlock()
- if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
+ err := c.Db.MarkChannelCloseConfirmationHeight(c, height)
+ if err != nil {
+ return err
+ }
+
+ c.CloseConfirmationHeight = height
+
+ return nil
+}
+
+// MarkChannelCloseConfirmationHeight updates the channel's close confirmation
+// height when the closing transaction is first detected in a block.
+func (c *ChannelStateDB) MarkChannelCloseConfirmationHeight(
+ channel *OpenChannel, height fn.Option[uint32]) error {
+
+ return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
}
- channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
+ diskChannel, err := fetchOpenChannel(
+ chanBucket, &channel.FundingOutpoint,
+ )
if err != nil {
return err
}
- channel.CloseConfirmationHeight = height
+ diskChannel.CloseConfirmationHeight = height
- return putOpenChannel(chanBucket, channel)
- }, func() {}); err != nil {
+ return putOpenChannel(chanBucket, diskChannel)
+ }, func() {})
+}
+
+// MarkAsOpen marks a channel as fully open given a locator that uniquely
+// describes its location within the chain.
+func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
+ c.Lock()
+ defer c.Unlock()
+
+ if err := c.Db.MarkChannelOpen(c, openLoc); err != nil {
return err
}
- c.CloseConfirmationHeight = height
+ c.IsPending = false
+ c.ShortChannelID = openLoc
+ c.Packager = NewChannelPackager(openLoc)
return nil
}
-// MarkAsOpen marks a channel as fully open given a locator that uniquely
+// MarkChannelOpen marks a channel as fully open given a locator that uniquely
// describes its location within the chain.
-func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
- c.Lock()
- defer c.Unlock()
+func (c *ChannelStateDB) MarkChannelOpen(channel *OpenChannel,
+ openLoc lnwire.ShortChannelID) error {
- if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
+ return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
}
- channel, err := fetchOpenChannel(chanBucket, &c.FundingOutpoint)
+ diskChannel, err := fetchOpenChannel(
+ chanBucket, &channel.FundingOutpoint,
+ )
if err != nil {
return err
}
- channel.IsPending = false
- channel.ShortChannelID = openLoc
-
- return putOpenChannel(chanBucket, channel)
- }, func() {}); err != nil {
- return err
- }
+ diskChannel.IsPending = false
+ diskChannel.ShortChannelID = openLoc
- c.IsPending = false
- c.ShortChannelID = openLoc
- c.Packager = NewChannelPackager(openLoc)
-
- return nil
+ return putOpenChannel(chanBucket, diskChannel)
+ }, func() {})
}
// MarkRealScid marks the zero-conf channel's confirmed ShortChannelID. This
@@ -1503,31 +1541,39 @@ func (c *OpenChannel) MarkRealScid(realScid lnwire.ShortChannelID) error {
c.Lock()
defer c.Unlock()
- if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
+ if err := c.Db.MarkChannelRealScid(c, realScid); err != nil {
+ return err
+ }
+
+ c.confirmedScid = realScid
+
+ return nil
+}
+
+// MarkChannelRealScid marks the zero-conf channel's confirmed ShortChannelID.
+func (c *ChannelStateDB) MarkChannelRealScid(channel *OpenChannel,
+ realScid lnwire.ShortChannelID) error {
+
+ return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
}
- channel, err := fetchOpenChannel(
- chanBucket, &c.FundingOutpoint,
+ diskChannel, err := fetchOpenChannel(
+ chanBucket, &channel.FundingOutpoint,
)
if err != nil {
return err
}
- channel.confirmedScid = realScid
-
- return putOpenChannel(chanBucket, channel)
- }, func() {}); err != nil {
- return err
- }
-
- c.confirmedScid = realScid
+ diskChannel.confirmedScid = realScid
- return nil
+ return putOpenChannel(chanBucket, diskChannel)
+ }, func() {})
}
// MarkScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in-memory and
@@ -1536,30 +1582,40 @@ func (c *OpenChannel) MarkScidAliasNegotiated() error {
c.Lock()
defer c.Unlock()
- if err := kvdb.Update(c.Db.backend, func(tx kvdb.RwTx) error {
+ if err := c.Db.MarkChannelScidAliasNegotiated(c); err != nil {
+ return err
+ }
+
+ c.ChanType |= ScidAliasFeatureBit
+
+ return nil
+}
+
+// MarkChannelScidAliasNegotiated adds ScidAliasFeatureBit to ChanType in the
+// database.
+func (c *ChannelStateDB) MarkChannelScidAliasNegotiated(
+ channel *OpenChannel) error {
+
+ return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
chanBucket, err := fetchChanBucketRw(
- tx, c.IdentityPub, &c.FundingOutpoint, c.ChainHash,
+ tx, channel.IdentityPub, &channel.FundingOutpoint,
+ channel.ChainHash,
)
if err != nil {
return err
}
- channel, err := fetchOpenChannel(
- chanBucket, &c.FundingOutpoint,
+ diskChannel, err := fetchOpenChannel(
+ chanBucket, &channel.FundingOutpoint,
)
if err != nil {
return err
}
- channel.ChanType |= ScidAliasFeatureBit
- return putOpenChannel(chanBucket, channel)
- }, func() {}); err != nil {
- return err
- }
+ diskChannel.ChanType |= ScidAliasFeatureBit
- c.ChanType |= ScidAliasFeatureBit
-
- return nil
+ return putOpenChannel(chanBucket, diskChannel)
+ }, func() {})
}
// MarkDataLoss marks sets the channel status to LocalDataLoss and stores the
@@ -2215,7 +2271,7 @@ func (c *OpenChannel) SyncPending(addr net.Addr, pendingHeight uint32) error {
// LinkNode (if needed) for the channel peer.
func syncNewChannel(tx kvdb.RwTx, c *OpenChannel, addrs []net.Addr) error {
// First, sync all the persistent channel state to disk.
- if err := c.fullSync(tx); err != nil {
+ if err := fullSyncOpenChannel(tx, c); err != nil {
return err
}
diff --git a/chanstate/interface.go b/chanstate/interface.go
index 0db90f7..46914f6 100644
--- a/chanstate/interface.go
+++ b/chanstate/interface.go
@@ -3,6 +3,7 @@ package chanstate
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/wire/v2"
+ "github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
"github.com/lightningnetwork/lnd/lnwire"
)
@@ -23,6 +24,10 @@ type Store[Channel any] interface {
// HistoricalChannelStore owns the post-close historical channel view.
HistoricalChannelStore[Channel]
+ // OpenChannelLifecycleStore owns persisted lifecycle state for open
+ // channel records.
+ OpenChannelLifecycleStore[Channel]
+
// ClosedChannelStore owns closed-channel summaries and lifecycle
// mutations.
ClosedChannelStore[Channel]
@@ -103,6 +108,38 @@ type HistoricalChannelStore[Channel any] interface {
FetchHistoricalChannel(outPoint *wire.OutPoint) (Channel, error)
}
+// OpenChannelLifecycleStore owns persisted lifecycle state for open channel
+// records.
+type OpenChannelLifecycleStore[Channel any] interface {
+ // RefreshChannel updates the in-memory channel state using the latest
+ // state observed on disk.
+ RefreshChannel(channel Channel) error
+
+ // MarkChannelConfirmationHeight updates the channel's confirmation
+ // height once the channel opening transaction receives one
+ // confirmation.
+ MarkChannelConfirmationHeight(channel Channel, height uint32) error
+
+ // MarkChannelCloseConfirmationHeight updates the channel's close
+ // confirmation height when the closing transaction is first detected
+ // in a block.
+ MarkChannelCloseConfirmationHeight(channel Channel,
+ height fn.Option[uint32]) error
+
+ // MarkChannelOpen marks a channel as fully open given a locator that
+ // uniquely describes its location within the chain.
+ MarkChannelOpen(channel Channel, openLoc lnwire.ShortChannelID) error
+
+ // MarkChannelRealScid marks the zero-conf channel's confirmed
+ // ShortChannelID.
+ MarkChannelRealScid(channel Channel,
+ realScid lnwire.ShortChannelID) error
+
+ // MarkChannelScidAliasNegotiated marks that the scid-alias feature
+ // bit was negotiated during the lifetime of the channel.
+ MarkChannelScidAliasNegotiated(channel Channel) error
+}
+
// ClosedChannelStore owns closed-channel summaries and lifecycle mutations.
type ClosedChannelStore[Channel any] interface {
// FetchClosedChannels attempts to fetch all closed channels from the
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.