171 lines
5.4 KiB
Markdown
171 lines
5.4 KiB
Markdown
# ADR 011: Generalize Genesis Accounts
|
||
|
||
## Changelog
|
||
|
||
- 2019-08-30: initial draft
|
||
|
||
## Context
|
||
|
||
Currently, the SDK allows for custom account types; the `auth` keeper stores any type fulfilling its `Account` interface. However `auth` does not handle exporting or loading accounts to/from a genesis file, this is done by `genaccounts`, which only handles one of 4 concrete account types (`BaseAccount`, `ContinuousVestingAccount`, `DelayedVestingAccount` and `ModuleAccount`).
|
||
|
||
Projects desiring to use custom accounts (say custom vesting accounts) need to fork and modify `genaccounts`.
|
||
|
||
## Decision
|
||
|
||
In summary, we will (un)marshal all accounts (interface types) directly using amino, rather than converting to `genaccounts`’s `GenesisAccount` type. Since doing this removes the majority of `genaccounts`'s code, we will merge `genaccounts` into `auth`. Marshalled accounts will be stored in `auth`'s genesis state.
|
||
|
||
Detailed changes:
|
||
|
||
### 1) (Un)Marshal accounts directly using amino
|
||
|
||
The `auth` module's `GenesisState` gains a new field `Accounts`. Note these aren't of type `exported.Account` for reasons outlined in section 3.
|
||
|
||
```go
|
||
// GenesisState - all auth state that must be provided at genesis
|
||
type GenesisState struct {
|
||
Params Params `json:"params" yaml:"params"`
|
||
Accounts []GenesisAccount `json:"accounts" yaml:"accounts"`
|
||
}
|
||
```
|
||
|
||
Now `auth`'s `InitGenesis` and `ExportGenesis` (un)marshal accounts as well as the defined params.
|
||
|
||
```go
|
||
// InitGenesis - Init store state from genesis data
|
||
func InitGenesis(ctx sdk.Context, ak AccountKeeper, data GenesisState) {
|
||
ak.SetParams(ctx, data.Params)
|
||
// load the accounts
|
||
for _, a := range data.Accounts {
|
||
acc := ak.NewAccount(ctx, a) // set account number
|
||
ak.SetAccount(ctx, acc)
|
||
}
|
||
}
|
||
|
||
// ExportGenesis returns a GenesisState for a given context and keeper
|
||
func ExportGenesis(ctx sdk.Context, ak AccountKeeper) GenesisState {
|
||
params := ak.GetParams(ctx)
|
||
|
||
var genAccounts []exported.GenesisAccount
|
||
ak.IterateAccounts(ctx, func(account exported.Account) bool {
|
||
genAccount := account.(exported.GenesisAccount)
|
||
genAccounts = append(genAccounts, genAccount)
|
||
return false
|
||
})
|
||
|
||
return NewGenesisState(params, genAccounts)
|
||
}
|
||
```
|
||
|
||
### 2) Register custom account types on the `auth` codec
|
||
|
||
The `auth` codec must have all custom account types registered to marshal them. We will follow the pattern established in `gov` for proposals.
|
||
|
||
An example custom account definition:
|
||
|
||
```go
|
||
import authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||
|
||
// Register the module account type with the auth module codec so it can decode module accounts stored in a genesis file
|
||
func init() {
|
||
authtypes.RegisterAccountTypeCodec(ModuleAccount{}, "cosmos-sdk/ModuleAccount")
|
||
}
|
||
|
||
type ModuleAccount struct {
|
||
...
|
||
```
|
||
|
||
The `auth` codec definition:
|
||
|
||
```go
|
||
var ModuleCdc *codec.Codec
|
||
|
||
func init() {
|
||
ModuleCdc = codec.New()
|
||
// register module msg's and Account interface
|
||
...
|
||
// leave the codec unsealed
|
||
}
|
||
|
||
// RegisterAccountTypeCodec registers an external account type defined in another module for the internal ModuleCdc.
|
||
func RegisterAccountTypeCodec(o interface{}, name string) {
|
||
ModuleCdc.RegisterConcrete(o, name, nil)
|
||
}
|
||
```
|
||
|
||
### 3) Genesis validation for custom account types
|
||
|
||
Modules implement a `ValidateGenesis` method. As `auth` does not know of account implementations, accounts will need to validate themselves.
|
||
|
||
We will unmarshal accounts into a `GenesisAccount` interface that includes a `Validate` method.
|
||
|
||
```go
|
||
type GenesisAccount interface {
|
||
exported.Account
|
||
Validate() error
|
||
}
|
||
```
|
||
|
||
Then the `auth` `ValidateGenesis` function becomes:
|
||
|
||
```go
|
||
// ValidateGenesis performs basic validation of auth genesis data returning an
|
||
// error for any failed validation criteria.
|
||
func ValidateGenesis(data GenesisState) error {
|
||
// Validate params
|
||
...
|
||
|
||
// Validate accounts
|
||
addrMap := make(map[string]bool, len(data.Accounts))
|
||
for _, acc := range data.Accounts {
|
||
|
||
// check for duplicated accounts
|
||
addrStr := acc.GetAddress().String()
|
||
if _, ok := addrMap[addrStr]; ok {
|
||
return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr)
|
||
}
|
||
addrMap[addrStr] = true
|
||
|
||
// check account specific validation
|
||
if err := acc.Validate(); err != nil {
|
||
return fmt.Errorf("invalid account found in genesis state; address: %s, error: %s", addrStr, err.Error())
|
||
}
|
||
|
||
}
|
||
return nil
|
||
}
|
||
```
|
||
|
||
### 4) Move add-genesis-account cli to `auth`
|
||
|
||
The `genaccounts` module contains a cli command to add base or vesting accounts to a genesis file.
|
||
|
||
This will be moved to `auth`. We will leave it to projects to write their own commands to add custom accounts. An extensible cli handler, similar to `gov`, could be created but it is not worth the complexity for this minor use case.
|
||
|
||
### 5) Update module and vesting accounts
|
||
|
||
Under the new scheme, module and vesting account types need some minor updates:
|
||
|
||
- Type registration on `auth`'s codec (shown above)
|
||
- A `Validate` method for each `Account` concrete type
|
||
|
||
## Status
|
||
|
||
Proposed
|
||
|
||
## Consequences
|
||
|
||
### Positive
|
||
|
||
- custom accounts can be used without needing to fork `genaccounts`
|
||
- reduction in lines of code
|
||
|
||
### Negative
|
||
|
||
### Neutral
|
||
|
||
- `genaccounts` module no longer exists
|
||
- accounts in genesis files are stored under `accounts` in `auth` rather than in the `genaccounts` module.
|
||
-`add-genesis-account` cli command now in `auth`
|
||
|
||
## References
|