Merge PR #4986: ADR 011 - Generalize Genesis Accounts

This commit is contained in:
Ruaridh 2019-09-05 10:02:48 -04:00 committed by Alexander Bezobchuk
parent 1eb38c4411
commit d7d7a93729
2 changed files with 172 additions and 0 deletions

View File

@ -28,3 +28,4 @@ Please add a entry below in your Pull Request for an ADR.
- [ADR 002: SDK Documentation Structure](./adr-002-docs-structure.md)
- [ADR 006: Secret Store Replacement](./adr-006-secret-store-replacement.md)
- [ADR 010: Modular AnteHandler](./adr-010-modular-antehandler.md)
- [ADR 011: Generalize Genesis Accounts](./adr-011-generalize-genesis-accounts.md)

View File

@ -0,0 +1,171 @@
# 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)
accounts := ak.GetAllAccounts(ctx)
// convert accounts to []GenesisAccounts type
genAccounts := make([]GenesisAccounts, len(accounts))
for i := range accounts {
ga := accounts[i].(GenesisAccount) // will panic if an account doesn't implement GenesisAccount
genAccounts[i] = ga
}
return NewGenesisState(params, accounts)
}
```
### 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