Skip to content

Commit c2c63cc

Browse files
authored
Merge pull request #1146 from ViktorT-11/2025-09-actions-sql-store-persits-mac-rootkey-id
[sql-53] firewalldb: actions migration prep 2 - persist full macaroon root key ID in the SQL actions store
2 parents f900660 + bd59605 commit c2c63cc

File tree

7 files changed

+114
-57
lines changed

7 files changed

+114
-57
lines changed

firewall/request_logger.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
"sync"
88

99
"github.com/lightninglabs/lightning-terminal/firewalldb"
10+
litmac "github.com/lightninglabs/lightning-terminal/macaroons"
1011
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
11-
"github.com/lightninglabs/lightning-terminal/session"
1212
"github.com/lightningnetwork/lnd/fn"
1313
"github.com/lightningnetwork/lnd/lnrpc"
1414
"github.com/lightningnetwork/lnd/macaroons"
@@ -182,22 +182,28 @@ func (r *RequestLogger) Intercept(ctx context.Context,
182182
func (r *RequestLogger) addNewAction(ctx context.Context, ri *RequestInfo,
183183
withPayloadData bool) error {
184184

185-
var macaroonID fn.Option[[4]byte]
185+
var (
186+
rootKeyID fn.Option[uint64]
187+
)
188+
186189
if ri.Macaroon != nil {
187190
var err error
188-
macID, err := session.IDFromMacaroon(ri.Macaroon)
191+
192+
fullRootKeyID, err := litmac.RootKeyIDFromMacaroon(
193+
ri.Macaroon,
194+
)
189195
if err != nil {
190-
return fmt.Errorf("could not extract ID from macaroon")
196+
return fmt.Errorf("could not extract root key ID from "+
197+
"macaroon: %w", err)
191198
}
192-
193-
macaroonID = fn.Some([4]byte(macID))
199+
rootKeyID = fn.Some(fullRootKeyID)
194200
}
195201

196202
actionReq := &firewalldb.AddActionReq{
197-
SessionID: ri.SessionID,
198-
AccountID: ri.AccountID,
199-
MacaroonIdentifier: macaroonID,
200-
RPCMethod: ri.URI,
203+
SessionID: ri.SessionID,
204+
AccountID: ri.AccountID,
205+
MacaroonRootKeyID: rootKeyID,
206+
RPCMethod: ri.URI,
201207
}
202208

203209
if withPayloadData {

firewalldb/actions.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,21 @@ const (
3434
// It contains all the information that is needed to create a new Action in the
3535
// ActionStateInit State.
3636
type AddActionReq struct {
37-
// MacaroonIdentifier is a 4 byte identifier created from the last 4
38-
// bytes of the root key ID of the macaroon used to perform the action.
37+
// MacaroonRootKeyID is the uint64 / full 8 bytes of the root key ID of
38+
// the macaroon used to perform the action.
3939
// If no macaroon was used for the action, then this will not be set.
40-
MacaroonIdentifier fn.Option[[4]byte]
40+
//
41+
// NOTE: for our BoltDB impl, only the lower 32 bits / last 4 bytes of
42+
// this uint64 are stored. When read back, the upper 32 bits / first 4
43+
// bytes are zeroed.
44+
MacaroonRootKeyID fn.Option[uint64]
4145

4246
// SessionID holds the optional session ID of the session that this
4347
// action was performed with.
4448
//
4549
// NOTE: for our BoltDB impl, this is not persisted in any way, and we
46-
// populate it by casting the macaroon ID to a session.ID and so is not
47-
// guaranteed to be linked to an existing session.
50+
// populate it by casting the MacaroonRootKeyID to a session.ID and so
51+
// is not guaranteed to be linked to an existing session.
4852
SessionID fn.Option[session.ID]
4953

5054
// AccountID holds the optional account ID of the account that this
@@ -80,6 +84,18 @@ type AddActionReq struct {
8084
RPCParamsJson []byte
8185
}
8286

87+
// MacaroonId returns the 4 byte macaroon ID that is derived from the
88+
// MacaroonRootKeyID. If the MacaroonRootKeyID is not set, then this will return
89+
// an empty 4 byte array.
90+
func (a *AddActionReq) MacaroonId() [4]byte {
91+
var macID [4]byte
92+
a.MacaroonRootKeyID.WhenSome(func(rootID uint64) {
93+
macID = session.IDFromMacRootKeyID(rootID)
94+
})
95+
96+
return macID
97+
}
98+
8399
// Action represents an RPC call made through the firewall.
84100
type Action struct {
85101
AddActionReq

firewalldb/actions_kvdb.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,9 @@ func (db *BoltDB) AddAction(ctx context.Context,
5959
req *AddActionReq) (ActionLocator, error) {
6060

6161
// If no macaroon is provided, then an empty 4-byte array is used as the
62-
// macaroon ID.
63-
var macaroonID [4]byte
64-
req.MacaroonIdentifier.WhenSome(func(id [4]byte) {
65-
macaroonID = id
66-
})
62+
// macaroon ID. Note that the kvdb implementation only stores the last
63+
// 4 bytes of the macaroon root key ID.
64+
macaroonID := req.MacaroonId()
6765

6866
// If the new action links to a session, the session must exist.
6967
// For the bbolt impl of the store, this is our best effort attempt
@@ -596,7 +594,12 @@ func DeserializeAction(r io.Reader, sessionID session.ID) (*Action, error) {
596594
return nil, err
597595
}
598596

599-
action.MacaroonIdentifier = fn.Some([4]byte(sessionID))
597+
// Since the kvdb only persists 4 bytes for the macaroon root key ID, we
598+
// first cast it to a uint32, and then to a uint64, effectively padding
599+
// the first 4 bytes with zeroes.
600+
rootKeyID := uint64(binary.BigEndian.Uint32(sessionID[:]))
601+
602+
action.MacaroonRootKeyID = fn.Some(rootKeyID)
600603
action.SessionID = fn.Some(sessionID)
601604
action.ActorName = string(actor)
602605
action.FeatureName = string(featureName)

firewalldb/actions_sql.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package firewalldb
33
import (
44
"context"
55
"database/sql"
6+
"encoding/binary"
67
"errors"
78
"fmt"
89
"math"
@@ -140,8 +141,11 @@ func (s *SQLDB) AddAction(ctx context.Context,
140141
}
141142

142143
var macID []byte
143-
req.MacaroonIdentifier.WhenSome(func(id [4]byte) {
144-
macID = id[:]
144+
req.MacaroonRootKeyID.WhenSome(func(rootKeyID uint64) {
145+
rootKeyBytes := make([]byte, 8)
146+
binary.BigEndian.PutUint64(rootKeyBytes[:], rootKeyID)
147+
148+
macID = rootKeyBytes
145149
})
146150

147151
id, err := db.InsertAction(ctx, sqlc.InsertActionParams{
@@ -393,14 +397,19 @@ func unmarshalAction(ctx context.Context, db SQLActionQueries,
393397
legacyAcctID = fn.Some(acctID)
394398
}
395399

396-
var macID fn.Option[[4]byte]
397-
if len(dbAction.MacaroonIdentifier) > 0 {
398-
macID = fn.Some([4]byte(dbAction.MacaroonIdentifier))
400+
// Note that we export the full 8 byte macaroon root key ID in the sql
401+
// actions DB, while the kvdb version persists and exports stored the
402+
// last 4 bytes only.
403+
var macRootKeyID fn.Option[uint64]
404+
if len(dbAction.MacaroonIdentifier) >= 8 {
405+
macRootKeyID = fn.Some(
406+
binary.BigEndian.Uint64(dbAction.MacaroonIdentifier),
407+
)
399408
}
400409

401410
return &Action{
402411
AddActionReq: AddActionReq{
403-
MacaroonIdentifier: macID,
412+
MacaroonRootKeyID: macRootKeyID,
404413
AccountID: legacyAcctID,
405414
SessionID: legacySessID,
406415
ActorName: dbAction.ActorName.String,

firewalldb/actions_test.go

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/lightninglabs/lightning-terminal/accounts"
10+
litmac "github.com/lightninglabs/lightning-terminal/macaroons"
1011
"github.com/lightninglabs/lightning-terminal/session"
1112
"github.com/lightningnetwork/lnd/clock"
1213
"github.com/lightningnetwork/lnd/fn"
@@ -60,10 +61,12 @@ func TestActionStorage(t *testing.T) {
6061
acct1, err := accountsDB.NewAccount(ctx, 0, time.Time{}, "foo")
6162
require.NoError(t, err)
6263

64+
sess1RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess1.ID)
65+
6366
action1Req := &AddActionReq{
6467
SessionID: fn.Some(sess1.ID),
6568
AccountID: fn.Some(acct1.ID),
66-
MacaroonIdentifier: fn.Some([4]byte(sess1.ID)),
69+
MacaroonRootKeyID: fn.Some(sess1RootKeyID),
6770
ActorName: "Autopilot",
6871
FeatureName: "auto-fees",
6972
Trigger: "fee too low",
@@ -79,15 +82,17 @@ func TestActionStorage(t *testing.T) {
7982
State: ActionStateDone,
8083
}
8184

85+
sess2RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess2.ID)
86+
8287
action2Req := &AddActionReq{
83-
SessionID: fn.Some(sess2.ID),
84-
MacaroonIdentifier: fn.Some([4]byte(sess2.ID)),
85-
ActorName: "Autopilot",
86-
FeatureName: "rebalancer",
87-
Trigger: "channels not balanced",
88-
Intent: "balance",
89-
RPCMethod: "SendToRoute",
90-
RPCParamsJson: []byte("hops, amount"),
88+
SessionID: fn.Some(sess2.ID),
89+
MacaroonRootKeyID: fn.Some(sess2RootKeyID),
90+
ActorName: "Autopilot",
91+
FeatureName: "rebalancer",
92+
Trigger: "channels not balanced",
93+
Intent: "balance",
94+
RPCMethod: "SendToRoute",
95+
RPCParamsJson: []byte("hops, amount"),
9196
}
9297

9398
action2 := &Action{
@@ -213,8 +218,10 @@ func TestListActions(t *testing.T) {
213218
addAction := func(sessionID [4]byte) {
214219
actionIds++
215220

221+
sessRootKeyID := litmac.NewSuperMacaroonRootKeyID(sessionID)
222+
216223
actionReq := &AddActionReq{
217-
MacaroonIdentifier: fn.Some(sessionID),
224+
MacaroonRootKeyID: fn.Some(sessRootKeyID),
218225
ActorName: "Autopilot",
219226
FeatureName: fmt.Sprintf("%d", actionIds),
220227
Trigger: "fee too low",
@@ -236,11 +243,9 @@ func TestListActions(t *testing.T) {
236243
assertActions := func(dbActions []*Action, al []*action) {
237244
require.Len(t, dbActions, len(al))
238245
for i, a := range al {
239-
mID, err := dbActions[i].MacaroonIdentifier.UnwrapOrErr(
240-
fmt.Errorf("macaroon identifier is none"),
246+
require.EqualValues(
247+
t, a.sessionID, dbActions[i].MacaroonId(),
241248
)
242-
require.NoError(t, err)
243-
require.EqualValues(t, a.sessionID, mID)
244249
require.Equal(t, a.actionID, dbActions[i].FeatureName)
245250
}
246251
}
@@ -424,9 +429,11 @@ func TestListGroupActions(t *testing.T) {
424429
)
425430
require.NoError(t, err)
426431

432+
sess1RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess1.ID)
433+
427434
action1Req := &AddActionReq{
428435
SessionID: fn.Some(sess1.ID),
429-
MacaroonIdentifier: fn.Some([4]byte(sess1.ID)),
436+
MacaroonRootKeyID: fn.Some(sess1RootKeyID),
430437
ActorName: "Autopilot",
431438
FeatureName: "auto-fees",
432439
Trigger: "fee too low",
@@ -442,15 +449,17 @@ func TestListGroupActions(t *testing.T) {
442449
State: ActionStateDone,
443450
}
444451

452+
sess2RootKeyID := litmac.NewSuperMacaroonRootKeyID(sess2.ID)
453+
445454
action2Req := &AddActionReq{
446-
SessionID: fn.Some(sess2.ID),
447-
MacaroonIdentifier: fn.Some([4]byte(sess2.ID)),
448-
ActorName: "Autopilot",
449-
FeatureName: "rebalancer",
450-
Trigger: "channels not balanced",
451-
Intent: "balance",
452-
RPCMethod: "SendToRoute",
453-
RPCParamsJson: []byte("hops, amount"),
455+
SessionID: fn.Some(sess2.ID),
456+
MacaroonRootKeyID: fn.Some(sess2RootKeyID),
457+
ActorName: "Autopilot",
458+
FeatureName: "rebalancer",
459+
Trigger: "channels not balanced",
460+
Intent: "balance",
461+
RPCMethod: "SendToRoute",
462+
RPCParamsJson: []byte("hops, amount"),
454463
}
455464

456465
action2 := &Action{

firewalldb/test_kvdb.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
package firewalldb
44

55
import (
6+
"encoding/binary"
67
"testing"
78

8-
"github.com/lightninglabs/lightning-terminal/accounts"
99
"github.com/lightninglabs/lightning-terminal/session"
1010
"github.com/lightningnetwork/lnd/clock"
1111
"github.com/lightningnetwork/lnd/fn"
@@ -59,8 +59,25 @@ func newDBFromPathWithSessions(t *testing.T, dbPath string,
5959

6060
func assertEqualActions(t *testing.T, expected, got *Action) {
6161
// Accounts are not explicitly linked in our bbolt DB implementation.
62+
actualAccountID := got.AccountID
6263
got.AccountID = expected.AccountID
63-
require.Equal(t, expected, got)
6464

65-
got.AccountID = fn.None[accounts.AccountID]()
65+
// As the kvdb implementation only stores the last 4 bytes Macaroon Root
66+
// Key ID, we pad it with 4 zero bytes when comparing.
67+
expectedMacRootKey := expected.MacaroonRootKeyID
68+
69+
expectedMacRootKey.WhenSome(func(rootID uint64) {
70+
// Remove the 4 byte prefix of the actual Macaroon Root Key ID.
71+
sessID := session.IDFromMacRootKeyID(rootID)
72+
73+
// Recreate the full 8 byte Macaroon Root Key ID (represented as
74+
// a uint64) by padding the first 4 bytes with zeroes.
75+
expected.MacaroonRootKeyID = fn.Some(
76+
uint64(binary.BigEndian.Uint32(sessID[:])),
77+
)
78+
})
79+
80+
require.Equal(t, expected, got)
81+
got.AccountID = actualAccountID
82+
expected.MacaroonRootKeyID = expectedMacRootKey
6683
}

session_rpcserver.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,10 +817,7 @@ func (s *sessionRpcServer) ListActions(ctx context.Context,
817817
sessionID = id
818818
})
819819

820-
var macID [4]byte
821-
a.MacaroonIdentifier.WhenSome(func(id [4]byte) {
822-
macID = id
823-
})
820+
macID := a.MacaroonId()
824821

825822
resp[i] = &litrpc.Action{
826823
SessionId: sessionID[:],

0 commit comments

Comments
 (0)