From d7d7a93729f2ee09453bbe1de6edd7a029b0b3ac Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Thu, 5 Sep 2019 10:02:48 -0400 Subject: [PATCH] Merge PR #4986: ADR 011 - Generalize Genesis Accounts --- docs/architecture/README.MD | 1 + .../adr-011-generalize-genesis-accounts.md | 171 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 docs/architecture/adr-011-generalize-genesis-accounts.md diff --git a/docs/architecture/README.MD b/docs/architecture/README.MD index b6b2b91f6b..165026a7ad 100644 --- a/docs/architecture/README.MD +++ b/docs/architecture/README.MD @@ -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) diff --git a/docs/architecture/adr-011-generalize-genesis-accounts.md b/docs/architecture/adr-011-generalize-genesis-accounts.md new file mode 100644 index 0000000000..09e61c120e --- /dev/null +++ b/docs/architecture/adr-011-generalize-genesis-accounts.md @@ -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