380 lines
13 KiB
Go
380 lines
13 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
gogotypes "github.com/cosmos/gogoproto/types"
|
|
|
|
"cosmossdk.io/collections"
|
|
"cosmossdk.io/collections/indexes"
|
|
"cosmossdk.io/core/address"
|
|
"cosmossdk.io/core/store"
|
|
errorsmod "cosmossdk.io/errors"
|
|
"cosmossdk.io/log"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
)
|
|
|
|
// AccountKeeperI is the interface contract that x/auth's keeper implements.
|
|
type AccountKeeperI interface {
|
|
// Return a new account with the next account number and the specified address. Does not save the new account to the store.
|
|
NewAccountWithAddress(context.Context, sdk.AccAddress) sdk.AccountI
|
|
|
|
// Return a new account with the next account number. Does not save the new account to the store.
|
|
NewAccount(context.Context, sdk.AccountI) sdk.AccountI
|
|
|
|
// Check if an account exists in the store.
|
|
HasAccount(context.Context, sdk.AccAddress) bool
|
|
|
|
// Retrieve an account from the store.
|
|
GetAccount(context.Context, sdk.AccAddress) sdk.AccountI
|
|
|
|
// Set an account in the store.
|
|
SetAccount(context.Context, sdk.AccountI)
|
|
|
|
// Remove an account from the store.
|
|
RemoveAccount(context.Context, sdk.AccountI)
|
|
|
|
// Iterate over all accounts, calling the provided function. Stop iteration when it returns true.
|
|
IterateAccounts(context.Context, func(sdk.AccountI) bool)
|
|
|
|
// Fetch the public key of an account at a specified address
|
|
GetPubKey(context.Context, sdk.AccAddress) (cryptotypes.PubKey, error)
|
|
|
|
// Fetch the sequence of an account at a specified address.
|
|
GetSequence(context.Context, sdk.AccAddress) (uint64, error)
|
|
|
|
// Fetch the next account number, and increment the internal counter.
|
|
NextAccountNumber(context.Context) uint64
|
|
|
|
// GetModulePermissions fetches per-module account permissions
|
|
GetModulePermissions() map[string]types.PermissionsForAddress
|
|
|
|
// AddressCodec returns the account address codec.
|
|
AddressCodec() address.Codec
|
|
}
|
|
|
|
func NewAccountIndexes(sb *collections.SchemaBuilder) AccountsIndexes {
|
|
return AccountsIndexes{
|
|
Number: indexes.NewUnique(
|
|
sb, types.AccountNumberStoreKeyPrefix, "account_by_number", collections.Uint64Key, sdk.AccAddressKey,
|
|
func(_ sdk.AccAddress, v sdk.AccountI) (uint64, error) {
|
|
return v.GetAccountNumber(), nil
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
type AccountsIndexes struct {
|
|
// Number is a unique index that indexes accounts by their account number.
|
|
Number *indexes.Unique[uint64, sdk.AccAddress, sdk.AccountI]
|
|
}
|
|
|
|
func (a AccountsIndexes) IndexesList() []collections.Index[sdk.AccAddress, sdk.AccountI] {
|
|
return []collections.Index[sdk.AccAddress, sdk.AccountI]{
|
|
a.Number,
|
|
}
|
|
}
|
|
|
|
// AccountKeeper encodes/decodes accounts using the go-amino (binary)
|
|
// encoding/decoding library.
|
|
type AccountKeeper struct {
|
|
addressCodec address.Codec
|
|
|
|
storeService store.KVStoreService
|
|
cdc codec.BinaryCodec
|
|
permAddrs map[string]types.PermissionsForAddress
|
|
bech32Prefix string
|
|
|
|
// enableUnorderedTxs enables unordered transaction support.
|
|
// This boolean helps sigverify ante handlers to determine if they should process unordered transactions.
|
|
enableUnorderedTxs bool
|
|
|
|
// The prototypical AccountI constructor.
|
|
proto func() sdk.AccountI
|
|
|
|
// the address capable of executing a MsgUpdateParams message. Typically, this
|
|
// should be the x/gov module account.
|
|
authority string
|
|
|
|
// State
|
|
Schema collections.Schema
|
|
Params collections.Item[types.Params]
|
|
AccountNumber collections.Sequence
|
|
Accounts *collections.IndexedMap[sdk.AccAddress, sdk.AccountI, AccountsIndexes]
|
|
UnorderedNonces collections.KeySet[collections.Pair[int64, []byte]]
|
|
}
|
|
|
|
type InitOption func(*AccountKeeper)
|
|
|
|
// WithUnorderedTransactions enables unordered transaction support.
|
|
// When true, sigverify ante handlers will validate and process unordered transactions.
|
|
// When false, sigverify ante handlers will reject unordered transactions.
|
|
func WithUnorderedTransactions(enable bool) InitOption {
|
|
return func(ak *AccountKeeper) {
|
|
ak.enableUnorderedTxs = enable
|
|
}
|
|
}
|
|
|
|
var _ AccountKeeperI = &AccountKeeper{}
|
|
|
|
// NewAccountKeeper returns a new AccountKeeperI that uses go-amino to
|
|
// (binary) encode and decode concrete sdk.Accounts.
|
|
// `maccPerms` is a map that takes accounts' addresses as keys, and their respective permissions as values. This map is used to construct
|
|
// types.PermissionsForAddress and is used in keeper.ValidatePermissions. Permissions are plain strings,
|
|
// and don't have to fit into any predefined structure. This auth module does not use account permissions internally, though other modules
|
|
// may use auth.Keeper to access the accounts permissions map.
|
|
func NewAccountKeeper(
|
|
cdc codec.BinaryCodec, storeService store.KVStoreService, proto func() sdk.AccountI,
|
|
maccPerms map[string][]string, ac address.Codec, bech32Prefix, authority string, opts ...InitOption,
|
|
) AccountKeeper {
|
|
permAddrs := make(map[string]types.PermissionsForAddress)
|
|
for name, perms := range maccPerms {
|
|
permAddrs[name] = types.NewPermissionsForAddress(name, perms)
|
|
}
|
|
|
|
sb := collections.NewSchemaBuilder(storeService)
|
|
|
|
ak := AccountKeeper{
|
|
addressCodec: ac,
|
|
bech32Prefix: bech32Prefix,
|
|
storeService: storeService,
|
|
proto: proto,
|
|
cdc: cdc,
|
|
permAddrs: permAddrs,
|
|
authority: authority,
|
|
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
|
|
AccountNumber: collections.NewSequence(sb, types.GlobalAccountNumberKey, "account_number"),
|
|
Accounts: collections.NewIndexedMap(sb, types.AddressStoreKeyPrefix, "accounts", sdk.AccAddressKey, codec.CollInterfaceValue[sdk.AccountI](cdc), NewAccountIndexes(sb)),
|
|
UnorderedNonces: collections.NewKeySet(sb, types.UnorderedNoncesKey, "unordered_nonces", collections.PairKeyCodec(collections.Int64Key, collections.BytesKey)),
|
|
}
|
|
schema, err := sb.Build()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ak.Schema = schema
|
|
|
|
for _, opt := range opts {
|
|
opt(&ak)
|
|
}
|
|
return ak
|
|
}
|
|
|
|
func (ak AccountKeeper) UnorderedTransactionsEnabled() bool {
|
|
return ak.enableUnorderedTxs
|
|
}
|
|
|
|
// GetAuthority returns the x/auth module's authority.
|
|
func (ak AccountKeeper) GetAuthority() string {
|
|
return ak.authority
|
|
}
|
|
|
|
// AddressCodec returns the x/auth account address codec.
|
|
// x/auth is tied to bech32 encoded user accounts
|
|
func (ak AccountKeeper) AddressCodec() address.Codec {
|
|
return ak.addressCodec
|
|
}
|
|
|
|
// Logger returns a module-specific logger.
|
|
func (ak AccountKeeper) Logger(ctx context.Context) log.Logger {
|
|
return sdk.UnwrapSDKContext(ctx).Logger().With("module", "x/"+types.ModuleName)
|
|
}
|
|
|
|
// GetPubKey Returns the PubKey of the account at address
|
|
func (ak AccountKeeper) GetPubKey(ctx context.Context, addr sdk.AccAddress) (cryptotypes.PubKey, error) {
|
|
acc := ak.GetAccount(ctx, addr)
|
|
if acc == nil {
|
|
return nil, errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
|
|
}
|
|
return acc.GetPubKey(), nil
|
|
}
|
|
|
|
// GetSequence Returns the Sequence of the account at address
|
|
func (ak AccountKeeper) GetSequence(ctx context.Context, addr sdk.AccAddress) (uint64, error) {
|
|
acc := ak.GetAccount(ctx, addr)
|
|
if acc == nil {
|
|
return 0, errorsmod.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
|
|
}
|
|
|
|
return acc.GetSequence(), nil
|
|
}
|
|
|
|
func (ak AccountKeeper) getAccountNumberLegacy(ctx context.Context) (uint64, error) {
|
|
store := ak.storeService.OpenKVStore(ctx)
|
|
b, err := store.Get(types.LegacyGlobalAccountNumberKey)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get legacy account number: %w", err)
|
|
}
|
|
v := new(gogotypes.UInt64Value)
|
|
if err := v.Unmarshal(b); err != nil {
|
|
return 0, fmt.Errorf("failed to unmarshal legacy account number: %w", err)
|
|
}
|
|
return v.Value, nil
|
|
}
|
|
|
|
// NextAccountNumber returns and increments the global account number counter.
|
|
// If the global account number is not set, it initializes it with value 0.
|
|
func (ak AccountKeeper) NextAccountNumber(ctx context.Context) uint64 {
|
|
n, err := collections.Item[uint64](ak.AccountNumber).Get(ctx)
|
|
if err != nil && errors.Is(err, collections.ErrNotFound) {
|
|
// this won't happen in the tip of production network,
|
|
// but can happen when query historical states,
|
|
// fallback to old key for backward-compatibility.
|
|
// for more info, see https://github.com/cosmos/cosmos-sdk/issues/23741
|
|
n, err = ak.getAccountNumberLegacy(ctx)
|
|
}
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := ak.AccountNumber.Set(ctx, n+1); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return n
|
|
}
|
|
|
|
// GetModulePermissions fetches per-module account permissions.
|
|
func (ak AccountKeeper) GetModulePermissions() map[string]types.PermissionsForAddress {
|
|
return ak.permAddrs
|
|
}
|
|
|
|
// ValidatePermissions validates that the module account has been granted
|
|
// permissions within its set of allowed permissions.
|
|
func (ak AccountKeeper) ValidatePermissions(macc sdk.ModuleAccountI) error {
|
|
permAddr := ak.permAddrs[macc.GetName()]
|
|
for _, perm := range macc.GetPermissions() {
|
|
if !permAddr.HasPermission(perm) {
|
|
return fmt.Errorf("invalid module permission %s", perm)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetModuleAddress returns an address based on the module name
|
|
func (ak AccountKeeper) GetModuleAddress(moduleName string) sdk.AccAddress {
|
|
permAddr, ok := ak.permAddrs[moduleName]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return permAddr.GetAddress()
|
|
}
|
|
|
|
// GetModuleAddressAndPermissions returns an address and permissions based on the module name
|
|
func (ak AccountKeeper) GetModuleAddressAndPermissions(moduleName string) (addr sdk.AccAddress, permissions []string) {
|
|
permAddr, ok := ak.permAddrs[moduleName]
|
|
if !ok {
|
|
return addr, permissions
|
|
}
|
|
|
|
return permAddr.GetAddress(), permAddr.GetPermissions()
|
|
}
|
|
|
|
// GetModuleAccountAndPermissions gets the module account from the auth account store and its
|
|
// registered permissions
|
|
func (ak AccountKeeper) GetModuleAccountAndPermissions(ctx context.Context, moduleName string) (sdk.ModuleAccountI, []string) {
|
|
addr, perms := ak.GetModuleAddressAndPermissions(moduleName)
|
|
if addr == nil {
|
|
return nil, []string{}
|
|
}
|
|
|
|
acc := ak.GetAccount(ctx, addr)
|
|
if acc != nil {
|
|
macc, ok := acc.(sdk.ModuleAccountI)
|
|
if !ok {
|
|
panic("account is not a module account")
|
|
}
|
|
return macc, perms
|
|
}
|
|
|
|
// create a new module account
|
|
macc := types.NewEmptyModuleAccount(moduleName, perms...)
|
|
maccI := (ak.NewAccount(ctx, macc)).(sdk.ModuleAccountI) // set the account number
|
|
ak.SetModuleAccount(ctx, maccI)
|
|
|
|
return maccI, perms
|
|
}
|
|
|
|
// GetModuleAccount gets the module account from the auth account store, if the account does not
|
|
// exist in the AccountKeeper, then it is created.
|
|
func (ak AccountKeeper) GetModuleAccount(ctx context.Context, moduleName string) sdk.ModuleAccountI {
|
|
acc, _ := ak.GetModuleAccountAndPermissions(ctx, moduleName)
|
|
return acc
|
|
}
|
|
|
|
// SetModuleAccount sets the module account to the auth account store
|
|
func (ak AccountKeeper) SetModuleAccount(ctx context.Context, macc sdk.ModuleAccountI) {
|
|
ak.SetAccount(ctx, macc)
|
|
}
|
|
|
|
// add getter for bech32Prefix
|
|
func (ak AccountKeeper) getBech32Prefix() (string, error) {
|
|
return ak.bech32Prefix, nil
|
|
}
|
|
|
|
// GetParams gets the auth module's parameters.
|
|
func (ak AccountKeeper) GetParams(ctx context.Context) (params types.Params) {
|
|
params, err := ak.Params.Get(ctx)
|
|
if err != nil && !errors.Is(err, collections.ErrNotFound) {
|
|
panic(err)
|
|
}
|
|
return params
|
|
}
|
|
|
|
// -------------------------------------
|
|
// Unordered Nonce management methods
|
|
// -------------------------------------
|
|
|
|
// ContainsUnorderedNonce reports whether the sender has used this timeout already.
|
|
func (ak AccountKeeper) ContainsUnorderedNonce(ctx sdk.Context, sender []byte, timeout time.Time) (bool, error) {
|
|
return ak.UnorderedNonces.Has(ctx, collections.Join(timeout.UnixNano(), sender))
|
|
}
|
|
|
|
// TryAddUnorderedNonce tries to add a new unordered nonce for the sender.
|
|
// If the sender already has an entry with the provided timeout, an error is returned.
|
|
func (ak AccountKeeper) TryAddUnorderedNonce(ctx sdk.Context, sender []byte, timeout time.Time) error {
|
|
alreadyHas, err := ak.ContainsUnorderedNonce(ctx, sender, timeout)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check unordered nonce in storage: %w", err)
|
|
}
|
|
if alreadyHas {
|
|
return fmt.Errorf("sender %s has already used timeout %d", sdk.AccAddress(sender).String(), timeout.UnixNano())
|
|
}
|
|
|
|
return ak.UnorderedNonces.Set(ctx, collections.Join(timeout.UnixNano(), sender))
|
|
}
|
|
|
|
// RemoveExpiredUnorderedNonces removes all unordered nonces that have a timeout value before
|
|
// the current block time.
|
|
func (ak AccountKeeper) RemoveExpiredUnorderedNonces(ctx sdk.Context) error {
|
|
blkTime := ctx.BlockTime().UnixNano()
|
|
it, err := ak.UnorderedNonces.Iterate(ctx, collections.NewPrefixUntilPairRange[int64, []byte](blkTime))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer it.Close()
|
|
|
|
keys, err := it.Keys()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, key := range keys {
|
|
if err := ak.UnorderedNonces.Remove(ctx, key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|