What changed, and why it matters
This commit is a straightforward internal code cleanup: it changes the type of one field in LND's channel database from a concrete database pointer to a more generic 'Store' interface. The goal is to make future refactoring easier, not to fix a bug or close a security hole. No security relevance is visible in the change itself or in the commit message.
No security action required. Treat as normal refactoring/technical-debt work.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors channeldb.OpenChannel.Db from ChannelStateDB to cstate.Store[OpenChannel]. It updates test helpers to explicitly extract the concrete ChannelStateDB when they need raw KV access, rather than reaching through OpenChannel.Db directly. This is an abstraction-layer change intended to decouple call sites from the concrete backend before OpenChannel is eventually moved out of channeldb. There are no runtime behavior changes, no new validation, no cryptographic changes, and no privilege or trust-boundary changes.
Changed components
channeldb.OpenChannel.Db field typechanneldb test helperscontractcourt test helpershtlcswitch test helpersInspect captured patch +79 / −30
diff --git a/channeldb/channel.go b/channeldb/channel.go
index 15bc802..c47d7e3 100644
--- a/channeldb/channel.go
+++ b/channeldb/channel.go
@@ -801,8 +801,11 @@ type OpenChannel struct {
// immutable.
CustomBlob fn.Option[tlv.Blob]
- // TODO(roasbeef): eww
- Db *ChannelStateDB
+ // Db persists channel state through the chanstate Store contract. This
+ // field intentionally keeps the existing name while the code moves from
+ // channeldb toward chanstate so call sites can become backend
+ // independent before the OpenChannel type itself is moved.
+ Db cstate.Store[*OpenChannel]
// TODO(roasbeef): just need to store local and remote HTLC's?
diff --git a/channeldb/close_channel_test.go b/channeldb/close_channel_test.go
index cd9c796..e2dcc63 100644
--- a/channeldb/close_channel_test.go
+++ b/channeldb/close_channel_test.go
@@ -15,10 +15,12 @@ import (
// revocationLogBucket of the given channel. The helper navigates the raw KV
// tree so the test does not depend on the higher-level commit-chain
// machinery.
-func writeTestRevlogEntries(t *testing.T, ch *OpenChannel, n int) {
+func writeTestRevlogEntries(t *testing.T, cdb *ChannelStateDB,
+ ch *OpenChannel, n int) {
+
t.Helper()
- err := kvdb.Update(ch.Db.backend, func(tx kvdb.RwTx) error {
+ err := kvdb.Update(cdb.backend, func(tx kvdb.RwTx) error {
openChanBkt := tx.ReadWriteBucket(openChannelBucket)
require.NotNil(t, openChanBkt, "openChannelBucket missing")
@@ -56,11 +58,13 @@ func writeTestRevlogEntries(t *testing.T, ch *OpenChannel, n int) {
// writeTestForwardingPackages writes n empty forwarding packages for the
// given channel using distinct remote commitment heights.
-func writeTestForwardingPackages(t *testing.T, ch *OpenChannel, n int) {
+func writeTestForwardingPackages(t *testing.T, cdb *ChannelStateDB,
+ ch *OpenChannel, n int) {
+
t.Helper()
packager := NewChannelPackager(ch.ShortChanID())
- err := kvdb.Update(ch.Db.backend, func(tx kvdb.RwTx) error {
+ err := kvdb.Update(cdb.backend, func(tx kvdb.RwTx) error {
for i := range n {
pkg := NewFwdPkg(
ch.ShortChanID(), uint64(i), nil, nil,
@@ -78,11 +82,13 @@ func writeTestForwardingPackages(t *testing.T, ch *OpenChannel, n int) {
// countRevlogEntries returns the number of entries in the revocationLogBucket
// for the given channel, or -1 if the channel bucket no longer exists in
// openChannelBucket.
-func countRevlogEntries(t *testing.T, ch *OpenChannel) int {
+func countRevlogEntries(t *testing.T, cdb *ChannelStateDB,
+ ch *OpenChannel) int {
+
t.Helper()
count := -1
- err := kvdb.View(ch.Db.backend, func(tx kvdb.RTx) error {
+ err := kvdb.View(cdb.backend, func(tx kvdb.RTx) error {
openChanBkt := tx.ReadBucket(openChannelBucket)
if openChanBkt == nil {
return nil
@@ -202,8 +208,8 @@ func TestCloseChannelTombstoneWritePath(t *testing.T) {
const numRevlogEntries = 5
const numFwdPkgs = 3
- writeTestRevlogEntries(t, ch, numRevlogEntries)
- writeTestForwardingPackages(t, ch, numFwdPkgs)
+ writeTestRevlogEntries(t, cdb, ch, numRevlogEntries)
+ writeTestForwardingPackages(t, cdb, ch, numFwdPkgs)
closeChannelForTest(t, cdb, ch)
@@ -224,7 +230,7 @@ func TestCloseChannelTombstoneWritePath(t *testing.T) {
require.Equal(t, ch.FundingOutpoint, closeSummary.ChanPoint)
// Bulk state preserved on disk — tombstoning's whole point.
- require.Equal(t, numRevlogEntries, countRevlogEntries(t, ch))
+ require.Equal(t, numRevlogEntries, countRevlogEntries(t, cdb, ch))
packager := NewChannelPackager(ch.ShortChanID())
var fwdPkgs []*FwdPkg
@@ -281,7 +287,7 @@ func TestCloseChannelTombstoneRemovesFromOpenScans(t *testing.T) {
ch2 := createTestChannel(t, cdb, openChannelOption())
const numRevlogEntries = 5
- writeTestRevlogEntries(t, ch1, numRevlogEntries)
+ writeTestRevlogEntries(t, cdb, ch1, numRevlogEntries)
openChans, err := cdb.FetchAllChannels()
require.NoError(t, err)
@@ -313,7 +319,7 @@ func TestCloseChannelTombstoneRemovesFromOpenScans(t *testing.T) {
// The bulk historical state stays put — that is the whole point of
// the tombstone path on these backends.
- require.Equal(t, numRevlogEntries, countRevlogEntries(t, ch1))
+ require.Equal(t, numRevlogEntries, countRevlogEntries(t, cdb, ch1))
// The outpoint index for ch1 must flip to closed; ch2's stays open.
require.Equal(t, outpointClosed, readOutpointStatus(
@@ -380,14 +386,14 @@ func TestCloseChannelSync(t *testing.T) {
ch := createTestChannel(t, cdb, openChannelOption())
const numRevlogEntries = 4
- writeTestRevlogEntries(t, ch, numRevlogEntries)
- writeTestForwardingPackages(t, ch, 3)
+ writeTestRevlogEntries(t, cdb, ch, numRevlogEntries)
+ writeTestForwardingPackages(t, cdb, ch, 3)
closeChannelForTest(t, cdb, ch)
// The synchronous path wipes the chanBucket inline, so
// countRevlogEntries must report -1 (bucket is gone, not just empty).
- require.Equal(t, -1, countRevlogEntries(t, ch),
+ require.Equal(t, -1, countRevlogEntries(t, cdb, ch),
"channel bucket must be deleted after sync close")
// Forwarding packages are wiped inline.
diff --git a/contractcourt/breach_arbitrator_test.go b/contractcourt/breach_arbitrator_test.go
index 37cf079..a000a6c 100644
--- a/contractcourt/breach_arbitrator_test.go
+++ b/contractcourt/breach_arbitrator_test.go
@@ -952,7 +952,8 @@ func initBreachedState(t *testing.T) (*BreachArbitrator,
contractBreaches := make(chan *ContractBreachEvent)
brar, err := createTestArbiter(
- t, contractBreaches, alice.State().Db.GetParentDB(),
+ t, contractBreaches,
+ testChannelStateDB(t, alice.State()).GetParentDB(),
)
require.NoError(t, err, "unable to initialize test breach arbiter")
@@ -1118,7 +1119,8 @@ func TestBreachHandoffFail(t *testing.T) {
assertNotPendingClosed(t, alice)
brar, err := createTestArbiter(
- t, contractBreaches, alice.State().Db.GetParentDB(),
+ t, contractBreaches,
+ testChannelStateDB(t, alice.State()).GetParentDB(),
)
require.NoError(t, err, "unable to initialize test breach arbiter")
@@ -1763,7 +1765,9 @@ func testBreachSpends(t *testing.T, test breachTest) {
}
// Assert that the channel is fully resolved.
- assertBrarCleanup(t, brar, &chanPoint, alice.State().Db)
+ assertBrarCleanup(
+ t, brar, &chanPoint, testChannelStateDB(t, alice.State()),
+ )
}
// TestBreachDelayedJusticeConfirmation tests that the breach arbiter will
@@ -1968,7 +1972,9 @@ func TestBreachDelayedJusticeConfirmation(t *testing.T) {
}
// Assert that the channel is fully resolved.
- assertBrarCleanup(t, brar, &chanPoint, alice.State().Db)
+ assertBrarCleanup(
+ t, brar, &chanPoint, testChannelStateDB(t, alice.State()),
+ )
}
// findInputIndex returns the index of the input that spends from the given
@@ -2080,7 +2086,9 @@ func assertBrarCleanup(t *testing.T, brar *BreachArbitrator,
func assertPendingClosed(t *testing.T, c *lnwallet.LightningChannel) {
t.Helper()
- closedChans, err := c.State().Db.FetchClosedChannels(true)
+ closedChans, err := testChannelStateDB(
+ t, c.State(),
+ ).FetchClosedChannels(true)
require.NoError(t, err, "unable to load pending closed channels")
for _, chanSummary := range closedChans {
@@ -2097,7 +2105,9 @@ func assertPendingClosed(t *testing.T, c *lnwallet.LightningChannel) {
func assertNotPendingClosed(t *testing.T, c *lnwallet.LightningChannel) {
t.Helper()
- closedChans, err := c.State().Db.FetchClosedChannels(true)
+ closedChans, err := testChannelStateDB(
+ t, c.State(),
+ ).FetchClosedChannels(true)
require.NoError(t, err, "unable to load pending closed channels")
for _, chanSummary := range closedChans {
diff --git a/contractcourt/utils_test.go b/contractcourt/utils_test.go
index 994bc57..27b65b9 100644
--- a/contractcourt/utils_test.go
+++ b/contractcourt/utils_test.go
@@ -12,6 +12,20 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
)
+// testChannelStateDB extracts the ChannelStateDB from the test channel state.
+func testChannelStateDB(t testing.TB,
+ state *channeldb.OpenChannel) *channeldb.ChannelStateDB {
+
+ t.Helper()
+
+ cdb, ok := state.Db.(*channeldb.ChannelStateDB)
+ if !ok {
+ t.Fatalf("expected ChannelStateDB, got %T", state.Db)
+ }
+
+ return cdb
+}
+
// timeout implements a test level timeout.
func timeout() func() {
done := make(chan struct{})
@@ -56,7 +70,9 @@ func copyChannelState(t *testing.T, state *channeldb.OpenChannel) (
*channeldb.OpenChannel, error) {
// Make a copy of the DB.
- dbFile := filepath.Join(state.Db.GetParentDB().Path(), "channel.db")
+ dbFile := filepath.Join(
+ testChannelStateDB(t, state).GetParentDB().Path(), "channel.db",
+ )
tempDbPath := t.TempDir()
tempDbFile := filepath.Join(tempDbPath, "channel.db")
diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go
index fdc455f..573c0bb 100644
--- a/htlcswitch/link_test.go
+++ b/htlcswitch/link_test.go
@@ -2173,7 +2173,7 @@ func newSingleLinkTestHarness(t *testing.T, chanAmt,
pCache := newMockPreimageCache()
- aliceDb := aliceLc.channel.State().Db.GetParentDB()
+ aliceDb := testChannelStateDB(t, aliceLc.channel).GetParentDB()
aliceSwitch, err := initSwitchWithDB(testStartingHeight, aliceDb)
if err != nil {
return singleLinkTestHarness{}, err
@@ -4853,7 +4853,7 @@ func (h *persistentLinkHarness) restartLink(
pCache = newMockPreimageCache()
)
- aliceDb := aliceChannel.State().Db.GetParentDB()
+ aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB()
if restartSwitch {
var err error
h.hSwitch, err = initSwitchWithDB(testStartingHeight, aliceDb)
diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go
index 10f7bdb..110d97b 100644
--- a/htlcswitch/test_utils.go
+++ b/htlcswitch/test_utils.go
@@ -43,6 +43,20 @@ import (
"github.com/stretchr/testify/require"
)
+// testChannelStateDB extracts the ChannelStateDB from the test channel.
+func testChannelStateDB(t testing.TB,
+ channel *lnwallet.LightningChannel) *channeldb.ChannelStateDB {
+
+ t.Helper()
+
+ cdb, ok := channel.State().Db.(*channeldb.ChannelStateDB)
+ if !ok {
+ t.Fatalf("expected ChannelStateDB, got %T", channel.State().Db)
+ }
+
+ return cdb
+}
+
// maxInflightHtlcs specifies the max number of inflight HTLCs. This number is
// chosen to be smaller than the default 483 so the test can run faster.
const maxInflightHtlcs = 50
@@ -954,9 +968,9 @@ func newThreeHopNetwork(t testing.TB, aliceChannel, firstBobChannel,
secondBobChannel, carolChannel *lnwallet.LightningChannel,
startingHeight uint32, opts ...serverOption) *threeHopNetwork {
- aliceDb := aliceChannel.State().Db.GetParentDB()
- bobDb := firstBobChannel.State().Db.GetParentDB()
- carolDb := carolChannel.State().Db.GetParentDB()
+ aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB()
+ bobDb := testChannelStateDB(t, firstBobChannel).GetParentDB()
+ carolDb := testChannelStateDB(t, carolChannel).GetParentDB()
hopNetwork := newHopNetwork()
@@ -1233,8 +1247,8 @@ func newTwoHopNetwork(t testing.TB,
aliceChannel, bobChannel *lnwallet.LightningChannel,
startingHeight uint32) *twoHopNetwork {
- aliceDb := aliceChannel.State().Db.GetParentDB()
- bobDb := bobChannel.State().Db.GetParentDB()
+ aliceDb := testChannelStateDB(t, aliceChannel).GetParentDB()
+ bobDb := testChannelStateDB(t, bobChannel).GetParentDB()
hopNetwork := newHopNetwork()
Why this scored 15/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.