Skip to content

Commit 771bc31

Browse files
committed
firewalldb: assert strong coupling between kvstores and sessions
If the is writing to a kvstore that is namespaced by a session's ID, we first assert that the session exists in the session store. We add a test for this accordingly. We do this in preparation for adding a sql backend which will error out if we try to link a kvstore to a session that does not yet exist.
1 parent 6cdd564 commit 771bc31

File tree

3 files changed

+141
-18
lines changed

3 files changed

+141
-18
lines changed

firewalldb/kvstores_kvdb.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ func (db *BoltDB) GetKVStores(rule string, groupID session.ID,
6060
db: db.DB,
6161
wrapTx: func(tx *bbolt.Tx) KVStoreTx {
6262
return &kvStoreTx{
63-
boltTx: tx,
63+
boltTx: tx,
64+
sessions: db.sessionIDIndex,
6465
kvStores: &kvStores{
6566
ruleName: rule,
6667
groupID: groupID,
@@ -109,6 +110,7 @@ type getBucketFunc func(tx *bbolt.Tx, create bool) (*bbolt.Bucket, error)
109110
type kvStoreTx struct {
110111
boltTx *bbolt.Tx
111112
getBucket getBucketFunc
113+
sessions session.IDToGroupIndex
112114

113115
*kvStores
114116
}
@@ -120,6 +122,7 @@ func (s *kvStoreTx) Global() KVStore {
120122
return &kvStoreTx{
121123
kvStores: s.kvStores,
122124
boltTx: s.boltTx,
125+
sessions: s.sessions,
123126
getBucket: getGlobalRuleBucket(true, s.ruleName),
124127
}
125128
}
@@ -138,6 +141,7 @@ func (s *kvStoreTx) Local() KVStore {
138141
return &kvStoreTx{
139142
kvStores: s.kvStores,
140143
boltTx: s.boltTx,
144+
sessions: s.sessions,
141145
getBucket: fn,
142146
}
143147
}
@@ -150,6 +154,7 @@ func (s *kvStoreTx) GlobalTemp() KVStore {
150154
return &kvStoreTx{
151155
kvStores: s.kvStores,
152156
boltTx: s.boltTx,
157+
sessions: s.sessions,
153158
getBucket: getGlobalRuleBucket(false, s.ruleName),
154159
}
155160
}
@@ -167,6 +172,7 @@ func (s *kvStoreTx) LocalTemp() KVStore {
167172
return &kvStoreTx{
168173
kvStores: s.kvStores,
169174
boltTx: s.boltTx,
175+
sessions: s.sessions,
170176
getBucket: fn,
171177
}
172178
}
@@ -294,6 +300,19 @@ func (s *kvStoreTx) getSessionRuleBucket(perm bool) getBucketFunc {
294300
}
295301

296302
if create {
303+
// NOTE: for a bbolt backend, the context is in any case
304+
// dropped behind the GetSessionIDs call. So passing in
305+
// a new context here is not a problem.
306+
ctx := context.Background()
307+
308+
// If create is true, we expect this to be an existing
309+
// session. So we check that now and return an error
310+
// accordingly if the session does not exist.
311+
_, err := s.sessions.GetSessionIDs(ctx, s.groupID)
312+
if err != nil {
313+
return nil, err
314+
}
315+
297316
sessBucket, err := ruleBucket.CreateBucketIfNotExists(
298317
sessKVStoreBucketKey,
299318
)

firewalldb/kvstores_test.go

+107-16
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"context"
66
"fmt"
77
"testing"
8+
"time"
89

910
"github.com/lightninglabs/lightning-terminal/session"
11+
"github.com/lightningnetwork/lnd/clock"
1012
"github.com/stretchr/testify/require"
1113
)
1214

@@ -83,15 +85,21 @@ func testTempAndPermStores(t *testing.T, featureSpecificStore bool) {
8385
featureName = "auto-fees"
8486
}
8587

86-
store := NewTestDB(t)
88+
sessions := session.NewTestDB(t, clock.NewDefaultClock())
89+
store := NewTestDBWithSessions(t, sessions)
8790
db := NewDB(store)
8891
require.NoError(t, db.Start(ctx))
8992

90-
kvstores := db.GetKVStores(
91-
"test-rule", [4]byte{1, 1, 1, 1}, featureName,
93+
// Create a session that we can reference.
94+
sess, err := sessions.NewSession(
95+
ctx, "test", session.TypeAutopilot, time.Unix(1000, 0),
96+
"something",
9297
)
98+
require.NoError(t, err)
99+
100+
kvstores := db.GetKVStores("test-rule", sess.GroupID, featureName)
93101

94-
err := kvstores.Update(ctx, func(ctx context.Context,
102+
err = kvstores.Update(ctx, func(ctx context.Context,
95103
tx KVStoreTx) error {
96104

97105
// Set an item in the temp store.
@@ -137,7 +145,7 @@ func testTempAndPermStores(t *testing.T, featureSpecificStore bool) {
137145
require.NoError(t, db.Stop())
138146
})
139147

140-
kvstores = db.GetKVStores("test-rule", [4]byte{1, 1, 1, 1}, featureName)
148+
kvstores = db.GetKVStores("test-rule", sess.GroupID, featureName)
141149

142150
// The temp store should no longer have the stored value but the perm
143151
// store should .
@@ -164,23 +172,31 @@ func testTempAndPermStores(t *testing.T, featureSpecificStore bool) {
164172
func TestKVStoreNameSpaces(t *testing.T) {
165173
t.Parallel()
166174
ctx := context.Background()
167-
db := NewTestDB(t)
168175

169-
var (
170-
groupID1 = intToSessionID(1)
171-
groupID2 = intToSessionID(2)
176+
sessions := session.NewTestDB(t, clock.NewDefaultClock())
177+
db := NewTestDBWithSessions(t, sessions)
178+
179+
// Create 2 sessions that we can reference.
180+
sess1, err := sessions.NewSession(
181+
ctx, "test", session.TypeAutopilot, time.Unix(1000, 0), "",
172182
)
183+
require.NoError(t, err)
184+
185+
sess2, err := sessions.NewSession(
186+
ctx, "test1", session.TypeAutopilot, time.Unix(1000, 0), "",
187+
)
188+
require.NoError(t, err)
173189

174190
// Two DBs for same group but different features.
175-
rulesDB1 := db.GetKVStores("test-rule", groupID1, "auto-fees")
176-
rulesDB2 := db.GetKVStores("test-rule", groupID1, "re-balance")
191+
rulesDB1 := db.GetKVStores("test-rule", sess1.GroupID, "auto-fees")
192+
rulesDB2 := db.GetKVStores("test-rule", sess1.GroupID, "re-balance")
177193

178194
// The third DB is for the same rule but a different group. It is
179195
// for the same feature as db 2.
180-
rulesDB3 := db.GetKVStores("test-rule", groupID2, "re-balance")
196+
rulesDB3 := db.GetKVStores("test-rule", sess2.GroupID, "re-balance")
181197

182198
// Test that the three ruleDBs share the same global space.
183-
err := rulesDB1.Update(ctx, func(ctx context.Context,
199+
err = rulesDB1.Update(ctx, func(ctx context.Context,
184200
tx KVStoreTx) error {
185201

186202
return tx.Global().Set(
@@ -311,9 +327,9 @@ func TestKVStoreNameSpaces(t *testing.T) {
311327
// Test that the group space is shared by the first two dbs but not
312328
// the third. To do this, we re-init the DB's but leave the feature
313329
// names out. This way, we will access the group storage.
314-
rulesDB1 = db.GetKVStores("test-rule", groupID1, "")
315-
rulesDB2 = db.GetKVStores("test-rule", groupID1, "")
316-
rulesDB3 = db.GetKVStores("test-rule", groupID2, "")
330+
rulesDB1 = db.GetKVStores("test-rule", sess1.GroupID, "")
331+
rulesDB2 = db.GetKVStores("test-rule", sess1.GroupID, "")
332+
rulesDB3 = db.GetKVStores("test-rule", sess2.GroupID, "")
317333

318334
err = rulesDB1.Update(ctx, func(ctx context.Context,
319335
tx KVStoreTx) error {
@@ -376,6 +392,81 @@ func TestKVStoreNameSpaces(t *testing.T) {
376392
require.True(t, bytes.Equal(v, []byte("thing 3")))
377393
}
378394

395+
// TestKVStoreSessionCoupling tests if we attempt to write to a kvstore that
396+
// is namespaced by a session that does not exist, then we should get an error.
397+
func TestKVStoreSessionCoupling(t *testing.T) {
398+
t.Parallel()
399+
ctx := context.Background()
400+
401+
sessions := session.NewTestDB(t, clock.NewDefaultClock())
402+
db := NewTestDBWithSessions(t, sessions)
403+
404+
// Get a kvstore namespaced by a session ID for a session that does
405+
// not exist.
406+
store := db.GetKVStores("AutoFees", [4]byte{1, 1, 1, 1}, "auto-fees")
407+
408+
err := store.Update(ctx, func(ctx context.Context,
409+
tx KVStoreTx) error {
410+
411+
// First, show that any call to the global namespace will not
412+
// error since it is not namespaced by a session.
413+
res, err := tx.Global().Get(ctx, "foo")
414+
require.NoError(t, err)
415+
require.Nil(t, res)
416+
417+
err = tx.Global().Set(ctx, "foo", []byte("bar"))
418+
require.NoError(t, err)
419+
420+
res, err = tx.Global().Get(ctx, "foo")
421+
require.NoError(t, err)
422+
require.Equal(t, []byte("bar"), res)
423+
424+
// Now we switch to the local store. We don't expect the Get
425+
// call to error since it should just return a nil value for
426+
// key that has not been set.
427+
_, err = tx.Local().Get(ctx, "foo")
428+
require.NoError(t, err)
429+
430+
// For Set, we expect an error since the session does not exist.
431+
err = tx.Local().Set(ctx, "foo", []byte("bar"))
432+
require.ErrorIs(t, err, session.ErrUnknownGroup)
433+
434+
// We again don't expect the error for delete since we just
435+
// expect it to return nil if the key is not found.
436+
err = tx.Local().Del(ctx, "foo")
437+
require.NoError(t, err)
438+
439+
return nil
440+
})
441+
require.NoError(t, err)
442+
443+
// Now, go and create a sessions in the session DB.
444+
sess, err := sessions.NewSession(
445+
ctx, "test", session.TypeAutopilot, time.Unix(1000, 0),
446+
"something",
447+
)
448+
require.NoError(t, err)
449+
450+
// Get a kvstore namespaced by a session ID for a session that now
451+
// does exist.
452+
store = db.GetKVStores("AutoFees", sess.GroupID, "auto-fees")
453+
454+
// Now, repeat the "Set" call for this session's kvstore to
455+
// show that it no longer errors.
456+
err = store.Update(ctx, func(ctx context.Context, tx KVStoreTx) error {
457+
// For Set, we expect an error since the session does not exist.
458+
err = tx.Local().Set(ctx, "foo", []byte("bar"))
459+
require.NoError(t, err)
460+
461+
res, err := tx.Local().Get(ctx, "foo")
462+
require.NoError(t, err)
463+
require.Equal(t, []byte("bar"), res)
464+
465+
return nil
466+
})
467+
require.NoError(t, err)
468+
}
469+
379470
func intToSessionID(i uint32) session.ID {
380471
var id session.ID
381472
byteOrder.PutUint32(id[:], i)

firewalldb/test_kvdb.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package firewalldb
33
import (
44
"testing"
55

6+
"github.com/lightninglabs/lightning-terminal/session"
67
"github.com/stretchr/testify/require"
78
)
89

@@ -14,7 +15,19 @@ func NewTestDB(t *testing.T) *BoltDB {
1415
// NewTestDBFromPath is a helper function that creates a new BoltStore with a
1516
// connection to an existing BBolt database for testing.
1617
func NewTestDBFromPath(t *testing.T, dbPath string) *BoltDB {
17-
store, err := NewBoltDB(dbPath, DBFilename, nil)
18+
return newDBFromPathWithSessions(t, dbPath, nil)
19+
}
20+
21+
// NewTestDBWithSessions creates a new test BoltDB Store with access to an
22+
// existing sessions DB.
23+
func NewTestDBWithSessions(t *testing.T, sessStore session.Store) *BoltDB {
24+
return newDBFromPathWithSessions(t, t.TempDir(), sessStore)
25+
}
26+
27+
func newDBFromPathWithSessions(t *testing.T, dbPath string,
28+
sessStore session.Store) *BoltDB {
29+
30+
store, err := NewBoltDB(dbPath, DBFilename, sessStore)
1831
require.NoError(t, err)
1932

2033
t.Cleanup(func() {

0 commit comments

Comments
 (0)