-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge PR #4986: ADR 011 - Generalize Genesis Accounts
- Loading branch information
1 parent
1eb38c4
commit d7d7a93
Showing
2 changed files
with
172 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
171 changes: 171 additions & 0 deletions
171
docs/architecture/adr-011-generalize-genesis-accounts.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |