Skip to content

Commit

Permalink
feat(accounts): add genesis account initialization (#20642)
Browse files Browse the repository at this point in the history
Co-authored-by: unknown unknown <unknown@unknown>
  • Loading branch information
testinginprod and unknown unknown authored Jun 12, 2024
1 parent d3d6448 commit 40492cd
Show file tree
Hide file tree
Showing 9 changed files with 362 additions and 150 deletions.
235 changes: 166 additions & 69 deletions api/cosmos/accounts/v1/genesis.pulsar.go

Large diffs are not rendered by default.

47 changes: 46 additions & 1 deletion x/accounts/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,48 @@
# x/accounts

The x/accounts module provides module and facilities for writing smart cosmos-sdk accounts.
The x/accounts module provides module and facilities for writing smart cosmos-sdk accounts.

# Genesis

## Creating accounts on genesis

In order to create accounts at genesis, the `x/accounts` module allows developers to provide
a list of genesis `MsgInit` messages that will be executed in the `x/accounts` genesis flow.

The init messages are generated offline. You can also use the following CLI command to generate the
json messages: `simd accounts tx init [account type] [msg] --from me --genesis`. This will generate
a jsonified init message wrapped in an x/accounts `MsgInit`.

This follows the same initialization flow and rules that would happen if the chain is running.
The only concrete difference is that this is happening at the genesis block.

For example, given the following `genesis.json` file:

```json
{
"app_state": {
"accounts": {
"init_account_msgs": [
{
"sender": "account_creator_address",
"account_type": "lockup",
"message": {
"@type": "cosmos.accounts.defaults.lockup.MsgInitLockupAccount",
"owner": "some_owner",
"end_time": "..",
"start_time": ".."
},
"funds": [
{
"denom": "stake",
"amount": "1000"
}
]
}
]
}
}
}
```

The accounts module will run the lockup account initialization message.
11 changes: 11 additions & 0 deletions x/accounts/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,20 @@ func GetTxInitCmd() *cobra.Command {
Message: msgBytes,
}

isGenesis, err := cmd.Flags().GetBool("genesis")
if err != nil {
return err
}

// in case the genesis flag is provided then the init message is printed.
if isGenesis {
return clientCtx.WithOutputFormat(flags.OutputFormatJSON).PrintProto(&msg)
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg)
},
}
cmd.Flags().Bool("genesis", false, "if true will print the json init message for genesis")
flags.AddTxFlagsToCmd(cmd)
return cmd
}
Expand Down
40 changes: 22 additions & 18 deletions x/accounts/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,7 @@ import (
func (k Keeper) ExportState(ctx context.Context) (*v1.GenesisState, error) {
genState := &v1.GenesisState{}

// get account number
accountNumber, err := k.AccountNumber.Peek(ctx)
if err != nil {
return nil, err
}

genState.AccountNumber = accountNumber

err = k.AccountsByType.Walk(ctx, nil, func(accAddr []byte, accType string) (stop bool, err error) {
err := k.AccountsByType.Walk(ctx, nil, func(accAddr []byte, accType string) (stop bool, err error) {
accNum, err := k.AccountByNumber.Get(ctx, accAddr)
if err != nil {
return true, err
Expand Down Expand Up @@ -64,7 +56,7 @@ func (k Keeper) exportAccount(ctx context.Context, addr []byte, accType string,
}

func (k Keeper) ImportState(ctx context.Context, genState *v1.GenesisState) error {
var largestNum *uint64
lastAccountNumber := uint64(0)
var err error
// import accounts
for _, acc := range genState.Accounts {
Expand All @@ -73,19 +65,31 @@ func (k Keeper) ImportState(ctx context.Context, genState *v1.GenesisState) erro
return fmt.Errorf("%w: %s", err, acc.Address)
}

accNum := acc.AccountNumber

if largestNum == nil || *largestNum < accNum {
largestNum = &accNum
// update lastAccountNumber if the current account being processed
// has a bigger account number.
if lastAccountNumber < acc.AccountNumber {
lastAccountNumber = acc.AccountNumber
}
}

if largestNum != nil {
// set the account number to the highest account number to avoid duplicate account number
err = k.AccountNumber.Set(ctx, *largestNum)
// we set the latest account number only if there were any genesis accounts, otherwise
// we leave it unset.
if genState.Accounts != nil {
// due to sequence semantics, we store the next account number.
err = k.AccountNumber.Set(ctx, lastAccountNumber+1)
if err != nil {
return err
}
}

return err
// after this execute account creation msgs.
for index, msgInit := range genState.InitAccountMsgs {
_, _, err = k.initFromMsg(ctx, msgInit)
if err != nil {
return fmt.Errorf("invalid genesis account msg init at index %d, msg %s: %w", index, msgInit, err)
}
}
return nil
}

func (k Keeper) importAccount(ctx context.Context, acc *v1.GenesisAccount) error {
Expand Down
20 changes: 18 additions & 2 deletions x/accounts/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ func TestGenesis(t *testing.T) {
acc, err := NewTestAccount(deps)
return testAccountType, acc, err
})
// add to state a genesis account init msg.
initMsg, err := implementation.PackAny(&types.Empty{})
require.NoError(t, err)
state.InitAccountMsgs = append(state.InitAccountMsgs, &v1.MsgInit{
Sender: "sender-2",
AccountType: testAccountType,
Message: initMsg,
Funds: nil,
})
err = k.ImportState(ctx, state)
require.NoError(t, err)

Expand All @@ -52,6 +61,12 @@ func TestGenesis(t *testing.T) {
require.NoError(t, err)
require.Equal(t, &types.UInt64Value{Value: 20}, resp)

// check initted on genesis account
addr3, err := k.makeAddress(2)
require.NoError(t, err)
resp, err = k.Query(ctx, addr3, &types.DoubleValue{})
require.NoError(t, err)
require.Equal(t, &types.UInt64Value{Value: 0}, resp)
// reset state
k, ctx = newKeeper(t, func(deps implementation.Dependencies) (string, implementation.Account, error) {
acc, err := NewTestAccount(deps)
Expand All @@ -66,8 +81,9 @@ func TestGenesis(t *testing.T) {

currentAccNum, err := k.AccountNumber.Peek(ctx)
require.NoError(t, err)
// AccountNumber should be set to the highest account number in the genesis state
require.Equal(t, uint64(99), currentAccNum)
// AccountNumber should be set to the highest account number in the genesis state + 2
// (one is the sequence offset, the other is the genesis account being added through init msg)
require.Equal(t, state.Accounts[0].AccountNumber+2, currentAccNum)
}

func TestImportAccountError(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions x/accounts/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"cosmossdk.io/core/appmodule"
"cosmossdk.io/x/accounts/accountstd"
"cosmossdk.io/x/accounts/internal/implementation"
v1 "cosmossdk.io/x/accounts/v1"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -156,6 +157,23 @@ func (k Keeper) Init(
return initResp, accountAddr, nil
}

// initFromMsg is a helper which inits an account given a v1.MsgInit.
func (k Keeper) initFromMsg(ctx context.Context, initMsg *v1.MsgInit) (implementation.ProtoMsg, []byte, error) {
creator, err := k.addressCodec.StringToBytes(initMsg.Sender)
if err != nil {
return nil, nil, err
}

// decode message bytes into the concrete boxed message type
msg, err := implementation.UnpackAnyRaw(initMsg.Message)
if err != nil {
return nil, nil, err
}

// run account creation logic
return k.Init(ctx, initMsg.AccountType, creator, msg, initMsg.Funds)
}

// init initializes the account, given the type, the creator the newly created account number, its address and the
// initialization message.
func (k Keeper) init(
Expand Down
17 changes: 3 additions & 14 deletions x/accounts/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package accounts

import (
"context"
"fmt"

"cosmossdk.io/core/event"
"cosmossdk.io/x/accounts/internal/implementation"
Expand All @@ -19,21 +20,9 @@ type msgServer struct {
}

func (m msgServer) Init(ctx context.Context, request *v1.MsgInit) (*v1.MsgInitResponse, error) {
creator, err := m.k.addressCodec.StringToBytes(request.Sender)
resp, accAddr, err := m.k.initFromMsg(ctx, request)
if err != nil {
return nil, err
}

// decode message bytes into the concrete boxed message type
msg, err := implementation.UnpackAnyRaw(request.Message)
if err != nil {
return nil, err
}

// run account creation logic
resp, accAddr, err := m.k.Init(ctx, request.AccountType, creator, msg, request.Funds)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to initialize account: %w", err)
}

// encode the address
Expand Down
8 changes: 5 additions & 3 deletions x/accounts/proto/cosmos/accounts/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ package cosmos.accounts.v1;

option go_package = "cosmossdk.io/x/accounts/v1";

import "cosmos/accounts/v1/tx.proto";

// GenesisState defines the accounts' module's genesis state.
message GenesisState {
// account_number is the latest account number.
uint64 account_number = 1;
// accounts are the genesis accounts.
repeated GenesisAccount accounts = 2;
repeated GenesisAccount accounts = 1;
// init_accounts_msgs defines the genesis messages that will be executed to init the account.
repeated cosmos.accounts.v1.MsgInit init_account_msgs = 2;
}

// GenesisAccount defines an account to be initialized in the genesis state.
Expand Down
Loading

0 comments on commit 40492cd

Please sign in to comment.