Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(accounts): add genesis account initialization #20642

Merged
merged 6 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 166 additions & 69 deletions api/cosmos/accounts/v1/genesis.pulsar.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion api/cosmos/circuit/v1/tx_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 42 additions & 1 deletion x/accounts/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
# 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure there is only one top-level heading in the document to comply with best practices in Markdown formatting.

Tools
Markdownlint

5-5: null (MD025, single-title, single-h1)
Multiple top-level headings in the same document

# 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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the trailing space at the end of line 12.

Tools
Markdownlint

12-12: Expected: 0 or 2; Actual: 1 (MD009, no-trailing-spaces)
Trailing spaces

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": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this done manually?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh can we add a excerpt that offline generating messages is the way to do this if that is the intended

"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"
}
]
}
]
}
}
}
```

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the file ends with a single newline character.

Tools
Markdownlint

44-44: null (MD047, single-trailing-newline)
Files should end with a single newline character

The accounts module will run the lockup account initialization message.
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
Loading