diff --git a/protocol/lib/metrics/metric_keys.go b/protocol/lib/metrics/metric_keys.go index c7c42b4c44..9146cf1ff7 100644 --- a/protocol/lib/metrics/metric_keys.go +++ b/protocol/lib/metrics/metric_keys.go @@ -89,4 +89,7 @@ const ( EndBlocker = "end_blocker" EndBlockerLag = "end_blocker_lag" + + // Account plus + MissingRegisteredAuthenticator = "missing_registered_authenticator" ) diff --git a/protocol/x/accountplus/keeper/authenticators.go b/protocol/x/accountplus/keeper/authenticators.go index 4d604206c1..69c7599f57 100644 --- a/protocol/x/accountplus/keeper/authenticators.go +++ b/protocol/x/accountplus/keeper/authenticators.go @@ -6,8 +6,13 @@ import ( "cosmossdk.io/errors" "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" gogotypes "github.com/cosmos/gogoproto/types" + "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/authenticator" "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" ) @@ -106,3 +111,124 @@ func (k Keeper) SetNextAuthenticatorId(ctx sdk.Context, authenticatorId uint64) store := ctx.KVStore(k.storeKey) store.Set([]byte(types.AuthenticatorIdKeyPrefix), b) } + +// GetSelectedAuthenticatorData gets a single authenticator for the account from the store. +func (k Keeper) GetSelectedAuthenticatorData( + ctx sdk.Context, + account sdk.AccAddress, + selectedAuthenticator uint64, +) (*types.AccountAuthenticator, error) { + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.AuthenticatorKeyPrefix), + ) + bz := store.Get(types.KeyAccountId(account, selectedAuthenticator)) + if bz == nil { + return &types.AccountAuthenticator{}, errors.Wrap( + types.ErrAuthenticatorNotFound, + fmt.Sprintf("authenticator %d not found for account %s", selectedAuthenticator, account), + ) + } + authenticatorFromStore, err := k.unmarshalAccountAuthenticator(bz) + if err != nil { + return &types.AccountAuthenticator{}, err + } + + return authenticatorFromStore, nil +} + +// GetInitializedAuthenticatorForAccount returns a single initialized authenticator for the account. +// It fetches the authenticator data from the store, gets the authenticator struct from the manager, +// then calls initialize on the authenticator data +func (k Keeper) GetInitializedAuthenticatorForAccount( + ctx sdk.Context, + account sdk.AccAddress, + selectedAuthenticator uint64, +) (authenticator.InitializedAuthenticator, error) { + // Get the authenticator data from the store + authenticatorFromStore, err := k.GetSelectedAuthenticatorData(ctx, account, selectedAuthenticator) + if err != nil { + return authenticator.InitializedAuthenticator{}, err + } + + uninitializedAuthenticator := k.authenticatorManager.GetAuthenticatorByType(authenticatorFromStore.Type) + if uninitializedAuthenticator == nil { + // This should never happen, but if it does, it means that stored authenticator is not registered + // or somehow the registered authenticator was removed / malformed + telemetry.IncrCounter(1, metrics.MissingRegisteredAuthenticator) + k.Logger(ctx).Error( + "account asscoicated authenticator not registered in manager", + "type", authenticatorFromStore.Type, + "id", selectedAuthenticator, + ) + + return authenticator.InitializedAuthenticator{}, + errors.Wrapf( + sdkerrors.ErrLogic, + "authenticator id %d failed to initialize, authenticator type %s not registered in manager", + selectedAuthenticator, authenticatorFromStore.Type, + ) + } + // Ensure that initialization of each authenticator works as expected + // NOTE: Always return a concrete authenticator not a pointer, do not modify in place + // NOTE: The authenticator manager returns a struct that is reused + initializedAuthenticator, err := uninitializedAuthenticator.Initialize(authenticatorFromStore.Config) + if err != nil { + return authenticator.InitializedAuthenticator{}, + errors.Wrapf( + err, + "authenticator %d with type %s failed to initialize", + selectedAuthenticator, authenticatorFromStore.Type, + ) + } + if initializedAuthenticator == nil { + return authenticator.InitializedAuthenticator{}, + errors.Wrapf( + types.ErrInitializingAuthenticator, + "authenticator.Initialize returned nil for %d with type %s", + selectedAuthenticator, authenticatorFromStore.Type, + ) + } + + finalAuthenticator := authenticator.InitializedAuthenticator{ + Id: authenticatorFromStore.Id, + Authenticator: initializedAuthenticator, + } + + return finalAuthenticator, nil +} + +// GetAuthenticatorDataForAccount gets all authenticators AccAddressFromBech32 with an account +// from the store. +func (k Keeper) GetAuthenticatorDataForAccount( + ctx sdk.Context, + account sdk.AccAddress, +) ([]*types.AccountAuthenticator, error) { + authenticators := make([]*types.AccountAuthenticator, 0) + + store := prefix.NewStore( + ctx.KVStore(k.storeKey), + []byte(types.AuthenticatorKeyPrefix), + ) + iterator := storetypes.KVStorePrefixIterator(store, []byte(account.String())) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + authenticator, err := k.unmarshalAccountAuthenticator(iterator.Value()) + if err != nil { + return nil, err + } + authenticators = append(authenticators, authenticator) + } + + return authenticators, nil +} + +// unmarshalAccountAuthenticator is used to unmarshal the AccountAuthenticator from the store +func (k Keeper) unmarshalAccountAuthenticator(bz []byte) (*types.AccountAuthenticator, error) { + var accountAuthenticator types.AccountAuthenticator + err := k.cdc.Unmarshal(bz, &accountAuthenticator) + if err != nil { + return &types.AccountAuthenticator{}, errors.Wrap(err, "failed to unmarshal account authenticator") + } + return &accountAuthenticator, nil +} diff --git a/protocol/x/accountplus/keeper/keeper_test.go b/protocol/x/accountplus/keeper/keeper_test.go index 89deb898df..19f3991c89 100644 --- a/protocol/x/accountplus/keeper/keeper_test.go +++ b/protocol/x/accountplus/keeper/keeper_test.go @@ -135,3 +135,33 @@ func (s *KeeperTestSuite) TestKeeper_GetAndSetAuthenticatorId() { authenticatorId = s.tApp.App.AccountPlusKeeper.InitializeOrGetNextAuthenticatorId(ctx) s.Require().Equal(authenticatorId, uint64(2), "Initialize/Get authenticator id returned incorrect id") } + +func (s *KeeperTestSuite) TestKeeper_GetAuthenticatorDataForAccount() { + ctx := s.Ctx + + // Set up account + key := "6cf5103c60c939a5f38e383b52239c5296c968579eec1c68a47d70fbf1d19159" + bz, _ := hex.DecodeString(key) + priv := &secp256k1.PrivKey{Key: bz} + accAddress := sdk.AccAddress(priv.PubKey().Address()) + + _, err := s.tApp.App.AccountPlusKeeper.AddAuthenticator( + ctx, + accAddress, + "SignatureVerification", + priv.PubKey().Bytes(), + ) + s.Require().NoError(err, "Should successfully add a SignatureVerification") + + _, err = s.tApp.App.AccountPlusKeeper.AddAuthenticator( + ctx, + accAddress, + "SignatureVerification", + priv.PubKey().Bytes(), + ) + s.Require().NoError(err, "Should successfully add a MessageFilter") + + authenticators, err := s.tApp.App.AccountPlusKeeper.GetAuthenticatorDataForAccount(ctx, accAddress) + s.Require().NoError(err) + s.Require().Equal(len(authenticators), 2, "Getting authenticators returning incorrect data") +} diff --git a/protocol/x/accountplus/keeper/query.go b/protocol/x/accountplus/keeper/query.go new file mode 100644 index 0000000000..0bbd00a257 --- /dev/null +++ b/protocol/x/accountplus/keeper/query.go @@ -0,0 +1,69 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/dydxprotocol/v4-chain/protocol/x/accountplus/types" +) + +var _ types.QueryServer = Keeper{} + +// GetAuthenticators returns all authenticators for an account. +func (k Keeper) GetAuthenticators( + ctx context.Context, + request *types.GetAuthenticatorsRequest, +) (*types.GetAuthenticatorsResponse, error) { + if request == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + acc, err := sdk.AccAddressFromBech32(request.Account) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + authenticators, err := k.GetAuthenticatorDataForAccount(sdkCtx, acc) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.GetAuthenticatorsResponse{AccountAuthenticators: authenticators}, nil +} + +// GetAuthenticator returns a specific authenticator for an account given its authenticator id. +func (k Keeper) GetAuthenticator( + ctx context.Context, + request *types.GetAuthenticatorRequest, +) (*types.GetAuthenticatorResponse, error) { + if request == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + acc, err := sdk.AccAddressFromBech32(request.Account) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + authenticator, err := k.GetSelectedAuthenticatorData(sdkCtx, acc, request.AuthenticatorId) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.GetAuthenticatorResponse{AccountAuthenticator: authenticator}, nil +} + +// GetParams returns the parameters for the accountplus module. +func (k Keeper) Params(goCtx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + ctx := sdk.UnwrapSDKContext(goCtx) + + return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil +} diff --git a/protocol/x/accountplus/types/codec.go b/protocol/x/accountplus/types/codec.go index 994545c1e9..e3e58daf7d 100644 --- a/protocol/x/accountplus/types/codec.go +++ b/protocol/x/accountplus/types/codec.go @@ -5,6 +5,11 @@ import ( cdctypes "github.com/cosmos/cosmos-sdk/codec/types" ) +// AuthenticatorTxOptions +type AuthenticatorTxOptions interface { + GetSelectedAuthenticators() []uint64 +} + func RegisterCodec(cdc *codec.LegacyAmino) {} func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { diff --git a/protocol/x/accountplus/types/errors.go b/protocol/x/accountplus/types/errors.go index 48ad01cbd6..54a23b0e51 100644 --- a/protocol/x/accountplus/types/errors.go +++ b/protocol/x/accountplus/types/errors.go @@ -18,4 +18,9 @@ var ( 3, "Authenticator data exceeds maximum length", ) + ErrInitializingAuthenticator = errorsmod.Register( + ModuleName, + 4, + "Error initializing authenticator", + ) )