diff --git a/app/src/types/generated/firewall_pb.d.ts b/app/src/types/generated/firewall_pb.d.ts index f2739fb0a..176c6b61a 100644 --- a/app/src/types/generated/firewall_pb.d.ts +++ b/app/src/types/generated/firewall_pb.d.ts @@ -193,6 +193,11 @@ export class Action extends jspb.Message { getSessionId_asB64(): string; setSessionId(value: Uint8Array | string): void; + getMacaroonIdentifier(): Uint8Array | string; + getMacaroonIdentifier_asU8(): Uint8Array; + getMacaroonIdentifier_asB64(): string; + setMacaroonIdentifier(value: Uint8Array | string): void; + serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Action.AsObject; static toObject(includeInstance: boolean, msg: Action): Action.AsObject; @@ -216,6 +221,7 @@ export namespace Action { state: ActionStateMap[keyof ActionStateMap], errorReason: string, sessionId: Uint8Array | string, + macaroonIdentifier: Uint8Array | string, } } diff --git a/app/src/types/generated/firewall_pb.js b/app/src/types/generated/firewall_pb.js index 4c9701674..a1886d620 100644 --- a/app/src/types/generated/firewall_pb.js +++ b/app/src/types/generated/firewall_pb.js @@ -1303,7 +1303,8 @@ proto.litrpc.Action.toObject = function(includeInstance, msg) { timestamp: jspb.Message.getFieldWithDefault(msg, 8, "0"), state: jspb.Message.getFieldWithDefault(msg, 9, 0), errorReason: jspb.Message.getFieldWithDefault(msg, 10, ""), - sessionId: msg.getSessionId_asB64() + sessionId: msg.getSessionId_asB64(), + macaroonIdentifier: msg.getMacaroonIdentifier_asB64() }; if (includeInstance) { @@ -1384,6 +1385,10 @@ proto.litrpc.Action.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {!Uint8Array} */ (reader.readBytes()); msg.setSessionId(value); break; + case 12: + var value = /** @type {!Uint8Array} */ (reader.readBytes()); + msg.setMacaroonIdentifier(value); + break; default: reader.skipField(); break; @@ -1490,6 +1495,13 @@ proto.litrpc.Action.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getMacaroonIdentifier_asU8(); + if (f.length > 0) { + writer.writeBytes( + 12, + f + ); + } }; @@ -1715,6 +1727,48 @@ proto.litrpc.Action.prototype.setSessionId = function(value) { }; +/** + * optional bytes macaroon_identifier = 12; + * @return {!(string|Uint8Array)} + */ +proto.litrpc.Action.prototype.getMacaroonIdentifier = function() { + return /** @type {!(string|Uint8Array)} */ (jspb.Message.getFieldWithDefault(this, 12, "")); +}; + + +/** + * optional bytes macaroon_identifier = 12; + * This is a type-conversion wrapper around `getMacaroonIdentifier()` + * @return {string} + */ +proto.litrpc.Action.prototype.getMacaroonIdentifier_asB64 = function() { + return /** @type {string} */ (jspb.Message.bytesAsB64( + this.getMacaroonIdentifier())); +}; + + +/** + * optional bytes macaroon_identifier = 12; + * Note that Uint8Array is not supported on all browsers. + * @see http://caniuse.com/Uint8Array + * This is a type-conversion wrapper around `getMacaroonIdentifier()` + * @return {!Uint8Array} + */ +proto.litrpc.Action.prototype.getMacaroonIdentifier_asU8 = function() { + return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( + this.getMacaroonIdentifier())); +}; + + +/** + * @param {!(string|Uint8Array)} value + * @return {!proto.litrpc.Action} returns this + */ +proto.litrpc.Action.prototype.setMacaroonIdentifier = function(value) { + return jspb.Message.setProto3BytesField(this, 12, value); +}; + + /** * @enum {number} */ diff --git a/config_dev.go b/config_dev.go index 2dd937b9b..ae7d18977 100644 --- a/config_dev.go +++ b/config_dev.go @@ -108,7 +108,7 @@ func NewStores(cfg *Config, clock clock.Clock) (*stores, error) { acctStore := accounts.NewSQLStore(sqlStore.BaseDB, clock) sessStore := session.NewSQLStore(sqlStore.BaseDB, clock) - firewallStore := firewalldb.NewSQLDB(sqlStore.BaseDB) + firewallStore := firewalldb.NewSQLDB(sqlStore.BaseDB, clock) stores.accounts = acctStore stores.sessions = sessStore @@ -123,7 +123,7 @@ func NewStores(cfg *Config, clock clock.Clock) (*stores, error) { acctStore := accounts.NewSQLStore(sqlStore.BaseDB, clock) sessStore := session.NewSQLStore(sqlStore.BaseDB, clock) - firewallStore := firewalldb.NewSQLDB(sqlStore.BaseDB) + firewallStore := firewalldb.NewSQLDB(sqlStore.BaseDB, clock) stores.accounts = acctStore stores.sessions = sessStore @@ -154,7 +154,7 @@ func NewStores(cfg *Config, clock clock.Clock) (*stores, error) { } firewallBoltDB, err := firewalldb.NewBoltDB( - networkDir, firewalldb.DBFilename, stores.sessions, + networkDir, firewalldb.DBFilename, stores.sessions, clock, ) if err != nil { return stores, fmt.Errorf("error creating firewall BoltDB: %v", diff --git a/config_prod.go b/config_prod.go index d10f0adb8..5ea897fc8 100644 --- a/config_prod.go +++ b/config_prod.go @@ -56,7 +56,7 @@ func NewStores(cfg *Config, clock clock.Clock) (*stores, error) { stores.closeFns["sessions"] = sessStore.Close firewallDB, err := firewalldb.NewBoltDB( - networkDir, firewalldb.DBFilename, sessStore, + networkDir, firewalldb.DBFilename, sessStore, clock, ) if err != nil { return stores, fmt.Errorf("error creating firewall DB: %v", err) diff --git a/firewall/request_logger.go b/firewall/request_logger.go index ad602c48d..3463dff2a 100644 --- a/firewall/request_logger.go +++ b/firewall/request_logger.go @@ -5,7 +5,6 @@ import ( "fmt" "strings" "sync" - "time" "github.com/lightninglabs/lightning-terminal/firewalldb" mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware" @@ -183,21 +182,20 @@ func (r *RequestLogger) addNewAction(ctx context.Context, ri *RequestInfo, withPayloadData bool) error { // If no macaroon is provided, then an empty 4-byte array is used as the - // session ID. Otherwise, the macaroon is used to derive a session ID. - var sessionID [4]byte + // macaroon ID. Otherwise, the last 4 bytes of the macaroon's root key + // ID are used. + var macaroonID [4]byte if ri.Macaroon != nil { var err error - sessionID, err = session.IDFromMacaroon(ri.Macaroon) + macaroonID, err = session.IDFromMacaroon(ri.Macaroon) if err != nil { return fmt.Errorf("could not extract ID from macaroon") } } - action := &firewalldb.Action{ - SessionID: sessionID, - RPCMethod: ri.URI, - AttemptedAt: time.Now(), - State: firewalldb.ActionStateInit, + actionReq := &firewalldb.AddActionReq{ + MacaroonIdentifier: macaroonID, + RPCMethod: ri.URI, } if withPayloadData { @@ -211,19 +209,19 @@ func (r *RequestLogger) addNewAction(ctx context.Context, ri *RequestInfo, return fmt.Errorf("unable to decode response: %v", err) } - action.RPCParamsJson = jsonBytes + actionReq.RPCParamsJson = jsonBytes meta := ri.MetaInfo if meta != nil { - action.ActorName = meta.ActorName - action.FeatureName = meta.Feature - action.Trigger = meta.Trigger - action.Intent = meta.Intent - action.StructuredJsonData = meta.StructuredJsonData + actionReq.ActorName = meta.ActorName + actionReq.FeatureName = meta.Feature + actionReq.Trigger = meta.Trigger + actionReq.Intent = meta.Intent + actionReq.StructuredJsonData = meta.StructuredJsonData } } - locator, err := r.actionsDB.AddAction(ctx, action) + locator, err := r.actionsDB.AddAction(ctx, actionReq) if err != nil { return err } diff --git a/firewalldb/actions.go b/firewalldb/actions.go index 8f81b02c8..129913a19 100644 --- a/firewalldb/actions.go +++ b/firewalldb/actions.go @@ -5,6 +5,7 @@ import ( "time" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/fn" ) // ActionState represents the state of an action. @@ -28,12 +29,21 @@ const ( ActionStateError ActionState = 3 ) -// Action represents an RPC call made through the firewall. -type Action struct { - // SessionID is the ID of the session that this action belongs to. - // Note that this is not serialized on persistence since the action is - // already stored under a bucket identified by the session ID. - SessionID session.ID +// AddActionReq is the request that is used to add a new Action to the database. +// It contains all the information that is needed to create a new Action in the +// ActionStateInit State. +type AddActionReq struct { + // MacaroonIdentifier is a 4 byte identifier created from the last 4 + // bytes of the root key ID of the macaroon used to perform the action. + MacaroonIdentifier [4]byte + + // SessionID holds the optional session ID of the session that this + // action was performed with. + // + // NOTE: for our BoltDB impl, this is not persisted in any way, and we + // populate it by casting the macaroon ID to a session.ID and so is not + // guaranteed to be linked to an existing session. + SessionID fn.Option[session.ID] // ActorName is the name of the entity who performed the Action. ActorName string @@ -59,6 +69,11 @@ type Action struct { // RPCParams is the method parameters of the request in JSON form. RPCParamsJson []byte +} + +// Action represents an RPC call made through the firewall. +type Action struct { + AddActionReq // AttemptedAt is the time at which this action was created. AttemptedAt time.Time @@ -181,7 +196,7 @@ func WithActionState(state ActionState) ListActionOption { // ActionsWriteDB is an abstraction over the Actions DB that will allow a // caller to add new actions as well as change the values of an existing action. type ActionsWriteDB interface { - AddAction(ctx context.Context, action *Action) (ActionLocator, error) + AddAction(ctx context.Context, req *AddActionReq) (ActionLocator, error) SetActionState(ctx context.Context, al ActionLocator, state ActionState, errReason string) error } diff --git a/firewalldb/actions_kvdb.go b/firewalldb/actions_kvdb.go index 5f5615953..f2b20465f 100644 --- a/firewalldb/actions_kvdb.go +++ b/firewalldb/actions_kvdb.go @@ -10,6 +10,7 @@ import ( "time" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/tlv" "go.etcd.io/bbolt" ) @@ -53,8 +54,14 @@ var ( ) // AddAction serialises and adds an Action to the DB under the given sessionID. -func (db *BoltDB) AddAction(_ context.Context, action *Action) (ActionLocator, - error) { +func (db *BoltDB) AddAction(_ context.Context, + req *AddActionReq) (ActionLocator, error) { + + action := &Action{ + AddActionReq: *req, + AttemptedAt: db.clock.Now().UTC(), + State: ActionStateInit, + } var buf bytes.Buffer if err := SerializeAction(&buf, action); err != nil { @@ -74,7 +81,7 @@ func (db *BoltDB) AddAction(_ context.Context, action *Action) (ActionLocator, } sessBucket, err := actionsBucket.CreateBucketIfNotExists( - action.SessionID[:], + action.MacaroonIdentifier[:], ) if err != nil { return err @@ -103,7 +110,7 @@ func (db *BoltDB) AddAction(_ context.Context, action *Action) (ActionLocator, } locator = kvdbActionLocator{ - sessionID: action.SessionID, + sessionID: action.MacaroonIdentifier, actionID: nextActionIndex, } @@ -543,7 +550,8 @@ func DeserializeAction(r io.Reader, sessionID session.ID) (*Action, error) { return nil, err } - action.SessionID = sessionID + action.MacaroonIdentifier = sessionID + action.SessionID = fn.Some(sessionID) action.ActorName = string(actor) action.FeatureName = string(featureName) action.Trigger = string(trigger) diff --git a/firewalldb/actions_test.go b/firewalldb/actions_test.go index 26f33596d..12824ff39 100644 --- a/firewalldb/actions_test.go +++ b/firewalldb/actions_test.go @@ -6,15 +6,33 @@ import ( "testing" "time" + "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/fn" "github.com/stretchr/testify/require" ) var ( - sessionID1 = intToSessionID(1) - sessionID2 = intToSessionID(2) + testTime1 = time.Unix(32100, 0) + testTime2 = time.Unix(12300, 0) +) + +// TestActionStorage tests that the ActionsListDB CRUD logic. +func TestActionStorage(t *testing.T) { + t.Parallel() + + ctx := context.Background() + clock := clock.NewTestClock(testTime1) + + db, err := NewBoltDB(t.TempDir(), "test.db", nil, clock) + require.NoError(t, err) + t.Cleanup(func() { + _ = db.Close() + }) - action1 = &Action{ - SessionID: sessionID1, + sessionID1 := intToSessionID(1) + action1Req := &AddActionReq{ + SessionID: fn.Some(sessionID1), + MacaroonIdentifier: sessionID1, ActorName: "Autopilot", FeatureName: "auto-fees", Trigger: "fee too low", @@ -22,33 +40,31 @@ var ( StructuredJsonData: "{\"something\":\"nothing\"}", RPCMethod: "UpdateChanPolicy", RPCParamsJson: []byte("new fee"), - AttemptedAt: time.Unix(32100, 0), - State: ActionStateDone, } - action2 = &Action{ - SessionID: sessionID2, - ActorName: "Autopilot", - FeatureName: "rebalancer", - Trigger: "channels not balanced", - Intent: "balance", - RPCMethod: "SendToRoute", - RPCParamsJson: []byte("hops, amount"), - AttemptedAt: time.Unix(12300, 0), - State: ActionStateInit, + action1 := &Action{ + AddActionReq: *action1Req, + AttemptedAt: testTime1, + State: ActionStateDone, } -) -// TestActionStorage tests that the ActionsListDB CRUD logic. -func TestActionStorage(t *testing.T) { - tmpDir := t.TempDir() - ctx := context.Background() + sessionID2 := intToSessionID(2) + action2Req := &AddActionReq{ + SessionID: fn.Some(sessionID2), + MacaroonIdentifier: sessionID2, + ActorName: "Autopilot", + FeatureName: "rebalancer", + Trigger: "channels not balanced", + Intent: "balance", + RPCMethod: "SendToRoute", + RPCParamsJson: []byte("hops, amount"), + } - db, err := NewBoltDB(tmpDir, "test.db", nil) - require.NoError(t, err) - t.Cleanup(func() { - _ = db.Close() - }) + action2 := &Action{ + AddActionReq: *action2Req, + AttemptedAt: testTime2, + State: ActionStateInit, + } actions, _, _, err := db.ListActions( ctx, nil, @@ -66,10 +82,14 @@ func TestActionStorage(t *testing.T) { require.NoError(t, err) require.Len(t, actions, 0) - _, err = db.AddAction(ctx, action1) + locator1, err := db.AddAction(ctx, action1Req) require.NoError(t, err) + err = db.SetActionState(ctx, locator1, ActionStateDone, "") + require.NoError(t, err) + + clock.SetTime(testTime2) - locator2, err := db.AddAction(ctx, action2) + locator2, err := db.AddAction(ctx, action2Req) require.NoError(t, err) actions, _, _, err = db.ListActions( @@ -102,7 +122,7 @@ func TestActionStorage(t *testing.T) { action2.State = ActionStateDone assertEqualActions(t, action2, actions[0]) - _, err = db.AddAction(ctx, action1) + _, err = db.AddAction(ctx, action1Req) require.NoError(t, err) // Check that providing no session id and no filter function returns @@ -138,10 +158,12 @@ func TestActionStorage(t *testing.T) { // TestListActions tests some ListAction options. // TODO(elle): cover more test cases here. func TestListActions(t *testing.T) { + t.Parallel() + tmpDir := t.TempDir() ctx := context.Background() - db, err := NewBoltDB(tmpDir, "test.db", nil) + db, err := NewBoltDB(tmpDir, "test.db", nil, clock.NewDefaultClock()) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -153,8 +175,9 @@ func TestListActions(t *testing.T) { actionIds := 0 addAction := func(sessionID [4]byte) { actionIds++ - action := &Action{ - SessionID: sessionID, + + actionReq := &AddActionReq{ + MacaroonIdentifier: sessionID, ActorName: "Autopilot", FeatureName: fmt.Sprintf("%d", actionIds), Trigger: "fee too low", @@ -162,11 +185,9 @@ func TestListActions(t *testing.T) { StructuredJsonData: "{\"something\":\"nothing\"}", RPCMethod: "UpdateChanPolicy", RPCParamsJson: []byte("new fee"), - AttemptedAt: time.Unix(32100, 0), - State: ActionStateDone, } - _, err := db.AddAction(ctx, action) + _, err := db.AddAction(ctx, actionReq) require.NoError(t, err) } @@ -179,7 +200,7 @@ func TestListActions(t *testing.T) { require.Len(t, dbActions, len(al)) for i, a := range al { require.EqualValues( - t, a.sessionID, dbActions[i].SessionID, + t, a.sessionID, dbActions[i].MacaroonIdentifier, ) require.Equal(t, a.actionID, dbActions[i].FeatureName) } @@ -335,15 +356,53 @@ func TestListActions(t *testing.T) { func TestListGroupActions(t *testing.T) { t.Parallel() ctx := context.Background() + clock := clock.NewTestClock(testTime1) group1 := intToSessionID(0) + sessionID1 := intToSessionID(1) + action1Req := &AddActionReq{ + SessionID: fn.Some(sessionID1), + MacaroonIdentifier: sessionID1, + ActorName: "Autopilot", + FeatureName: "auto-fees", + Trigger: "fee too low", + Intent: "increase fee", + StructuredJsonData: "{\"something\":\"nothing\"}", + RPCMethod: "UpdateChanPolicy", + RPCParamsJson: []byte("new fee"), + } + + action1 := &Action{ + AddActionReq: *action1Req, + AttemptedAt: testTime1, + State: ActionStateDone, + } + + sessionID2 := intToSessionID(2) + action2Req := &AddActionReq{ + SessionID: fn.Some(sessionID2), + MacaroonIdentifier: sessionID2, + ActorName: "Autopilot", + FeatureName: "rebalancer", + Trigger: "channels not balanced", + Intent: "balance", + RPCMethod: "SendToRoute", + RPCParamsJson: []byte("hops, amount"), + } + + action2 := &Action{ + AddActionReq: *action2Req, + AttemptedAt: testTime2, + State: ActionStateInit, + } + // Link session 1 and session 2 to group 1. index := NewMockSessionDB() index.AddPair(sessionID1, group1) index.AddPair(sessionID2, group1) - db, err := NewBoltDB(t.TempDir(), "test.db", index) + db, err := NewBoltDB(t.TempDir(), "test.db", index, clock) require.NoError(t, err) t.Cleanup(func() { _ = db.Close() @@ -355,25 +414,29 @@ func TestListGroupActions(t *testing.T) { require.Empty(t, al) // Add an action under session 1. - _, err = db.AddAction(ctx, action1) + locator1, err := db.AddAction(ctx, action1Req) + require.NoError(t, err) + err = db.SetActionState(ctx, locator1, ActionStateDone, "") require.NoError(t, err) // There should now be one action in the group. al, _, _, err = db.ListActions(ctx, nil, WithActionGroupID(group1)) require.NoError(t, err) require.Len(t, al, 1) - require.Equal(t, sessionID1, al[0].SessionID) + assertEqualActions(t, action1, al[0]) + + clock.SetTime(testTime2) // Add an action under session 2. - _, err = db.AddAction(ctx, action2) + _, err = db.AddAction(ctx, action2Req) require.NoError(t, err) // There should now be actions in the group. al, _, _, err = db.ListActions(ctx, nil, WithActionGroupID(group1)) require.NoError(t, err) require.Len(t, al, 2) - require.Equal(t, sessionID1, al[0].SessionID) - require.Equal(t, sessionID2, al[1].SessionID) + assertEqualActions(t, action1, al[0]) + assertEqualActions(t, action2, al[1]) } func assertEqualActions(t *testing.T, expected, got *Action) { diff --git a/firewalldb/interface.go b/firewalldb/interface.go index a024ce8eb..7da9cf5b0 100644 --- a/firewalldb/interface.go +++ b/firewalldb/interface.go @@ -104,8 +104,8 @@ type PrivacyMapper interface { // ActionDB is an interface that abstracts the database operations needed for // the Action persistence and querying. type ActionDB interface { - // AddAction persists the given action to the database. - AddAction(ctx context.Context, action *Action) (ActionLocator, error) + // AddAction persists a new action to the database. + AddAction(ctx context.Context, req *AddActionReq) (ActionLocator, error) // SetActionState finds the action specified by the ActionLocator and // sets its state to the given state. diff --git a/firewalldb/kvdb_store.go b/firewalldb/kvdb_store.go index 99497a27d..edef36a11 100644 --- a/firewalldb/kvdb_store.go +++ b/firewalldb/kvdb_store.go @@ -8,6 +8,7 @@ import ( "path/filepath" "time" + "github.com/lightningnetwork/lnd/clock" "go.etcd.io/bbolt" ) @@ -37,13 +38,15 @@ var ( type BoltDB struct { *bbolt.DB + clock clock.Clock + sessionIDIndex SessionDB } // NewBoltDB creates a new bolt database that can be found at the given // directory. -func NewBoltDB(dir, fileName string, sessionIDIndex SessionDB) (*BoltDB, - error) { +func NewBoltDB(dir, fileName string, sessionIDIndex SessionDB, + clock clock.Clock) (*BoltDB, error) { firstInit := false path := filepath.Join(dir, fileName) @@ -70,6 +73,7 @@ func NewBoltDB(dir, fileName string, sessionIDIndex SessionDB) (*BoltDB, return &BoltDB{ DB: db, sessionIDIndex: sessionIDIndex, + clock: clock, }, nil } diff --git a/firewalldb/kvstores_test.go b/firewalldb/kvstores_test.go index 592188c77..20f6ec0c7 100644 --- a/firewalldb/kvstores_test.go +++ b/firewalldb/kvstores_test.go @@ -19,7 +19,7 @@ func TestKVStoreTxs(t *testing.T) { t.Parallel() ctx := context.Background() - db := NewTestDB(t) + db := NewTestDB(t, clock.NewDefaultClock()) store := db.GetKVStores("AutoFees", [4]byte{1, 1, 1, 1}, "auto-fees") // Test that if an action fails midway through the transaction, then @@ -79,14 +79,15 @@ func TestTempAndPermStores(t *testing.T) { // session level KV stores. func testTempAndPermStores(t *testing.T, featureSpecificStore bool) { ctx := context.Background() + clock := clock.NewDefaultClock() var featureName string if featureSpecificStore { featureName = "auto-fees" } - sessions := session.NewTestDB(t, clock.NewDefaultClock()) - store := NewTestDBWithSessions(t, sessions) + sessions := session.NewTestDB(t, clock) + store := NewTestDBWithSessions(t, sessions, clock) db := NewDB(store) require.NoError(t, db.Start(ctx)) @@ -172,9 +173,10 @@ func testTempAndPermStores(t *testing.T, featureSpecificStore bool) { func TestKVStoreNameSpaces(t *testing.T) { t.Parallel() ctx := context.Background() + clock := clock.NewDefaultClock() - sessions := session.NewTestDB(t, clock.NewDefaultClock()) - db := NewTestDBWithSessions(t, sessions) + sessions := session.NewTestDB(t, clock) + db := NewTestDBWithSessions(t, sessions, clock) // Create 2 sessions that we can reference. sess1, err := sessions.NewSession( @@ -397,9 +399,10 @@ func TestKVStoreNameSpaces(t *testing.T) { func TestKVStoreSessionCoupling(t *testing.T) { t.Parallel() ctx := context.Background() + clock := clock.NewDefaultClock() - sessions := session.NewTestDB(t, clock.NewDefaultClock()) - db := NewTestDBWithSessions(t, sessions) + sessions := session.NewTestDB(t, clock) + db := NewTestDBWithSessions(t, sessions, clock) // Get a kvstore namespaced by a session ID for a session that does // not exist. diff --git a/firewalldb/privacy_mapper_test.go b/firewalldb/privacy_mapper_test.go index fbdf880ff..9ba6a5b16 100644 --- a/firewalldb/privacy_mapper_test.go +++ b/firewalldb/privacy_mapper_test.go @@ -15,9 +15,10 @@ import ( func TestPrivacyMapStorage(t *testing.T) { t.Parallel() ctx := context.Background() + clock := clock.NewDefaultClock() - sessions := session.NewTestDB(t, clock.NewDefaultClock()) - db := NewTestDBWithSessions(t, sessions) + sessions := session.NewTestDB(t, clock) + db := NewTestDBWithSessions(t, sessions, clock) // First up, let's test that the correct error is returned if an // attempt is made to write to a privacy map that is not linked to @@ -221,9 +222,10 @@ func TestPrivacyMapStorage(t *testing.T) { func TestPrivacyMapTxs(t *testing.T) { t.Parallel() ctx := context.Background() + clock := clock.NewDefaultClock() - sessions := session.NewTestDB(t, clock.NewDefaultClock()) - db := NewTestDBWithSessions(t, sessions) + sessions := session.NewTestDB(t, clock) + db := NewTestDBWithSessions(t, sessions, clock) sess, err := sessions.NewSession( ctx, "test", session.TypeAutopilot, time.Unix(1000, 0), "", diff --git a/firewalldb/sql_store.go b/firewalldb/sql_store.go index acca60ce1..369920d63 100644 --- a/firewalldb/sql_store.go +++ b/firewalldb/sql_store.go @@ -5,6 +5,7 @@ import ( "database/sql" "github.com/lightninglabs/lightning-terminal/db" + "github.com/lightningnetwork/lnd/clock" ) // SQLQueries is a subset of the sqlc.Queries interface that can be used to @@ -30,6 +31,8 @@ type SQLDB struct { // BaseDB represents the underlying database connection. *db.BaseDB + + clock clock.Clock } // A compile-time assertion to ensure that SQLDB implements the RulesDB @@ -38,7 +41,7 @@ var _ RulesDB = (*SQLDB)(nil) // NewSQLDB creates a new SQLStore instance given an open SQLQueries // storage backend. -func NewSQLDB(sqlDB *db.BaseDB) *SQLDB { +func NewSQLDB(sqlDB *db.BaseDB, clock clock.Clock) *SQLDB { executor := db.NewTransactionExecutor( sqlDB, func(tx *sql.Tx) SQLQueries { return sqlDB.WithTx(tx) @@ -48,6 +51,7 @@ func NewSQLDB(sqlDB *db.BaseDB) *SQLDB { return &SQLDB{ db: executor, BaseDB: sqlDB, + clock: clock, } } diff --git a/firewalldb/test_kvdb.go b/firewalldb/test_kvdb.go index 91ea130b1..2c0ad66c9 100644 --- a/firewalldb/test_kvdb.go +++ b/firewalldb/test_kvdb.go @@ -6,30 +6,33 @@ import ( "testing" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/clock" "github.com/stretchr/testify/require" ) // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t *testing.T) *BoltDB { - return NewTestDBFromPath(t, t.TempDir()) +func NewTestDB(t *testing.T, clock clock.Clock) *BoltDB { + return NewTestDBFromPath(t, t.TempDir(), clock) } // NewTestDBFromPath is a helper function that creates a new BoltStore with a // connection to an existing BBolt database for testing. -func NewTestDBFromPath(t *testing.T, dbPath string) *BoltDB { - return newDBFromPathWithSessions(t, dbPath, nil) +func NewTestDBFromPath(t *testing.T, dbPath string, clock clock.Clock) *BoltDB { + return newDBFromPathWithSessions(t, dbPath, nil, clock) } // NewTestDBWithSessions creates a new test BoltDB Store with access to an // existing sessions DB. -func NewTestDBWithSessions(t *testing.T, sessStore session.Store) *BoltDB { - return newDBFromPathWithSessions(t, t.TempDir(), sessStore) +func NewTestDBWithSessions(t *testing.T, sessStore session.Store, + clock clock.Clock) *BoltDB { + + return newDBFromPathWithSessions(t, t.TempDir(), sessStore, clock) } func newDBFromPathWithSessions(t *testing.T, dbPath string, - sessStore session.Store) *BoltDB { + sessStore session.Store, clock clock.Clock) *BoltDB { - store, err := NewBoltDB(dbPath, DBFilename, sessStore) + store, err := NewBoltDB(dbPath, DBFilename, sessStore, clock) require.NoError(t, err) t.Cleanup(func() { diff --git a/firewalldb/test_postgres.go b/firewalldb/test_postgres.go index aeb012351..f5777e4cb 100644 --- a/firewalldb/test_postgres.go +++ b/firewalldb/test_postgres.go @@ -6,15 +6,16 @@ import ( "testing" "github.com/lightninglabs/lightning-terminal/db" + "github.com/lightningnetwork/lnd/clock" ) // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t *testing.T) *SQLDB { - return NewSQLDB(db.NewTestPostgresDB(t).BaseDB) +func NewTestDB(t *testing.T, clock clock.Clock) *SQLDB { + return NewSQLDB(db.NewTestPostgresDB(t).BaseDB, clock) } // NewTestDBFromPath is a helper function that creates a new BoltStore with a // connection to an existing BBolt database for testing. -func NewTestDBFromPath(t *testing.T, _ string) *SQLDB { - return NewSQLDB(db.NewTestPostgresDB(t).BaseDB) +func NewTestDBFromPath(t *testing.T, _ string, clock clock.Clock) *SQLDB { + return NewSQLDB(db.NewTestPostgresDB(t).BaseDB, clock) } diff --git a/firewalldb/test_sql.go b/firewalldb/test_sql.go index d256480f9..947ff1491 100644 --- a/firewalldb/test_sql.go +++ b/firewalldb/test_sql.go @@ -6,14 +6,17 @@ import ( "testing" "github.com/lightninglabs/lightning-terminal/session" + "github.com/lightningnetwork/lnd/clock" "github.com/stretchr/testify/require" ) // NewTestDBWithSessions creates a new test SQLDB Store with access to an // existing sessions DB. -func NewTestDBWithSessions(t *testing.T, sessionStore session.Store) *SQLDB { +func NewTestDBWithSessions(t *testing.T, sessionStore session.Store, + clock clock.Clock) *SQLDB { + sessions, ok := sessionStore.(*session.SQLStore) require.True(t, ok) - return NewSQLDB(sessions.BaseDB) + return NewSQLDB(sessions.BaseDB, clock) } diff --git a/firewalldb/test_sqlite.go b/firewalldb/test_sqlite.go index 2497584d9..5496cb205 100644 --- a/firewalldb/test_sqlite.go +++ b/firewalldb/test_sqlite.go @@ -6,15 +6,18 @@ import ( "testing" "github.com/lightninglabs/lightning-terminal/db" + "github.com/lightningnetwork/lnd/clock" ) // NewTestDB is a helper function that creates an BBolt database for testing. -func NewTestDB(t *testing.T) *SQLDB { - return NewSQLDB(db.NewTestSqliteDB(t).BaseDB) +func NewTestDB(t *testing.T, clock clock.Clock) *SQLDB { + return NewSQLDB(db.NewTestSqliteDB(t).BaseDB, clock) } // NewTestDBFromPath is a helper function that creates a new BoltStore with a // connection to an existing BBolt database for testing. -func NewTestDBFromPath(t *testing.T, dbPath string) *SQLDB { - return NewSQLDB(db.NewTestSqliteDbHandleFromPath(t, dbPath).BaseDB) +func NewTestDBFromPath(t *testing.T, dbPath string, clock clock.Clock) *SQLDB { + return NewSQLDB( + db.NewTestSqliteDbHandleFromPath(t, dbPath).BaseDB, clock, + ) } diff --git a/litrpc/firewall.pb.go b/litrpc/firewall.pb.go index 23a991ee9..b79c6c6ae 100644 --- a/litrpc/firewall.pb.go +++ b/litrpc/firewall.pb.go @@ -464,6 +464,9 @@ type Action struct { ErrorReason string `protobuf:"bytes,10,opt,name=error_reason,json=errorReason,proto3" json:"error_reason,omitempty"` // The ID of the session under which the action was performed. SessionId []byte `protobuf:"bytes,11,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // The 4 byte identifier of the macaroon that was used to perform the action. + // This is derived from the last 4 bytes of the macaroon's root key ID. + MacaroonIdentifier []byte `protobuf:"bytes,12,opt,name=macaroon_identifier,json=macaroonIdentifier,proto3" json:"macaroon_identifier,omitempty"` } func (x *Action) Reset() { @@ -575,6 +578,13 @@ func (x *Action) GetSessionId() []byte { return nil } +func (x *Action) GetMacaroonIdentifier() []byte { + if x != nil { + return x.MacaroonIdentifier + } + return nil +} + var File_firewall_proto protoreflect.FileDescriptor var file_firewall_proto_rawDesc = []byte{ @@ -629,7 +639,7 @@ var file_firewall_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x4f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x84, 0x03, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xb5, 0x03, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, @@ -653,28 +663,31 @@ var file_firewall_proto_rawDesc = []byte{ 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x2a, 0x54, 0x0a, - 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, 0x0d, - 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, - 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x4f, 0x4e, 0x45, - 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x10, 0x03, 0x32, 0xb5, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, 0x6c, - 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, - 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, 0x69, - 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x14, 0x50, 0x72, 0x69, 0x76, - 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, - 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, - 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, - 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, - 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, 0x70, - 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x2f, 0x0a, + 0x13, 0x6d, 0x61, 0x63, 0x61, 0x72, 0x6f, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x6d, 0x61, 0x63, 0x61, + 0x72, 0x6f, 0x6f, 0x6e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x2a, 0x54, + 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x11, 0x0a, + 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x11, 0x0a, 0x0d, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, + 0x47, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x4f, 0x4e, + 0x45, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x03, 0x32, 0xb5, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6c, + 0x6c, 0x12, 0x46, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1a, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6c, + 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x14, 0x50, 0x72, 0x69, + 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x72, 0x69, 0x76, 0x61, + 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x6c, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x72, 0x69, 0x76, 0x61, 0x63, 0x79, 0x4d, 0x61, 0x70, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, 0x32, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, + 0x6e, 0x69, 0x6e, 0x67, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, + 0x6e, 0x67, 0x2d, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2f, 0x6c, 0x69, 0x74, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/litrpc/firewall.proto b/litrpc/firewall.proto index 1a4dff43a..c6ff0061c 100644 --- a/litrpc/firewall.proto +++ b/litrpc/firewall.proto @@ -209,6 +209,12 @@ message Action { The ID of the session under which the action was performed. */ bytes session_id = 11; + + /* + The 4 byte identifier of the macaroon that was used to perform the action. + This is derived from the last 4 bytes of the macaroon's root key ID. + */ + bytes macaroon_identifier = 12; } enum ActionState { diff --git a/litrpc/firewall.swagger.json b/litrpc/firewall.swagger.json index d76caa9ba..a62ea042a 100644 --- a/litrpc/firewall.swagger.json +++ b/litrpc/firewall.swagger.json @@ -132,6 +132,11 @@ "type": "string", "format": "byte", "description": "The ID of the session under which the action was performed." + }, + "macaroon_identifier": { + "type": "string", + "format": "byte", + "description": "The 4 byte identifier of the macaroon that was used to perform the action.\nThis is derived from the last 4 bytes of the macaroon's root key ID." } } }, diff --git a/proto/firewall.proto b/proto/firewall.proto index 9f429b78e..a41749b28 100644 --- a/proto/firewall.proto +++ b/proto/firewall.proto @@ -209,6 +209,12 @@ message Action { The ID of the session under which the action was performed. */ bytes session_id = 11; + + /* + The 4 byte identifier of the macaroon that was used to perform the action. + This is derived from the last 4 bytes of the macaroon's root key ID. + */ + bytes macaroon_identifier = 12; } enum ActionState { diff --git a/session_rpcserver.go b/session_rpcserver.go index 40d649d1d..59ebfdb29 100644 --- a/session_rpcserver.go +++ b/session_rpcserver.go @@ -731,8 +731,14 @@ func (s *sessionRpcServer) ListActions(ctx context.Context, return nil, err } + var sessionID session.ID + a.SessionID.WhenSome(func(id session.ID) { + sessionID = id + }) + resp[i] = &litrpc.Action{ - SessionId: a.SessionID[:], + SessionId: sessionID[:], + MacaroonIdentifier: a.MacaroonIdentifier[:], ActorName: a.ActorName, FeatureName: a.FeatureName, Trigger: a.Trigger,