Skip to content

Commit

Permalink
feat(x/genutil)!: bulk add genesis accounts (backport #21372) (#21543)
Browse files Browse the repository at this point in the history
Co-authored-by: Reece Williams <[email protected]>
Co-authored-by: Julien Robert <[email protected]>
  • Loading branch information
3 people authored Sep 4, 2024
1 parent 6bdb69e commit fa8ea23
Show file tree
Hide file tree
Showing 5 changed files with 364 additions and 87 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i
### API Breaking Changes

* (baseapp) [#21413](https://github.com/cosmos/cosmos-sdk/pull/21413) Add `SelectBy` method to `Mempool` interface, which is thread-safe to use.
* (x/genutil) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Remove `AddGenesisAccount` for `AddGenesisAccounts`.

### Deprecated

Expand Down
1 change: 1 addition & 0 deletions x/genutil/client/cli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func CommandsWithCustomMigrationMap(genutilModule genutil.AppModule, genMM genes
CollectGenTxsCmd(genutilModule.GenTxValidator()),
ValidateGenesisCmd(genMM),
AddGenesisAccountCmd(),
AddBulkGenesisAccountCmd(),
ExportCmd(appExport),
)

Expand Down
93 changes: 92 additions & 1 deletion x/genutil/client/cli/genaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cli

import (
"bufio"
"encoding/json"
"fmt"
"os"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -71,7 +73,33 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa
vestingAmtStr, _ := cmd.Flags().GetString(flagVestingAmt)
moduleNameStr, _ := cmd.Flags().GetString(flagModuleName)

return genutil.AddGenesisAccount(clientCtx.Codec, clientCtx.AddressCodec, addr, appendflag, config.GenesisFile(), args[1], vestingAmtStr, vestingStart, vestingEnd, moduleNameStr)
addrStr, err := addressCodec.BytesToString(addr)
if err != nil {
return err
}

coins, err := sdk.ParseCoinsNormalized(args[1])
if err != nil {
return err
}

vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr)
if err != nil {
return err
}

accounts := []genutil.GenesisAccount{
{
Address: addrStr,
Coins: coins,
VestingAmt: vestingAmt,
VestingStart: vestingStart,
VestingEnd: vestingEnd,
ModuleName: moduleNameStr,
},
}

return genutil.AddGenesisAccounts(clientCtx.Codec, clientCtx.AddressCodec, accounts, appendflag, config.GenesisFile())
},
}

Expand All @@ -85,3 +113,66 @@ contain valid denominations. Accounts may optionally be supplied with vesting pa

return cmd
}

// AddBulkGenesisAccountCmd returns bulk-add-genesis-account cobra Command.
// This command is provided as a default, applications are expected to provide their own command if custom genesis accounts are needed.
func AddBulkGenesisAccountCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "bulk-add-genesis-account [/file/path.json]",
Short: "Bulk add genesis accounts to genesis.json",
Example: `bulk-add-genesis-account accounts.json
where accounts.json is:
[
{
"address": "cosmos139f7kncmglres2nf3h4hc4tade85ekfr8sulz5",
"coins": [
{ "denom": "umuon", "amount": "100000000" },
{ "denom": "stake", "amount": "200000000" }
]
},
{
"address": "cosmos1e0jnq2sun3dzjh8p2xq95kk0expwmd7shwjpfg",
"coins": [
{ "denom": "umuon", "amount": "500000000" }
],
"vesting_amt": [
{ "denom": "umuon", "amount": "400000000" }
],
"vesting_start": 1724711478,
"vesting_end": 1914013878
}
]
`,
Long: `Add genesis accounts in bulk to genesis.json. The provided account must specify
the account address and a list of initial coins. The list of initial tokens must
contain valid denominations. Accounts may optionally be supplied with vesting parameters.
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
config := client.GetConfigFromCmd(cmd)

f, err := os.Open(args[0])
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()

var accounts []genutil.GenesisAccount
if err := json.NewDecoder(f).Decode(&accounts); err != nil {
return fmt.Errorf("failed to decode JSON: %w", err)
}

appendflag, _ := cmd.Flags().GetBool(flagAppendMode)

return genutil.AddGenesisAccounts(clientCtx.Codec, clientCtx.AddressCodec, accounts, appendflag, config.GenesisFile())
},
}

cmd.Flags().Bool(flagAppendMode, false, "append the coins to an account already in the genesis.json file")
flags.AddQueryFlagsToCmd(cmd)

return cmd
}
169 changes: 169 additions & 0 deletions x/genutil/client/cli/genaccount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package cli_test

import (
"context"
"encoding/json"
"os"
"path"
"testing"

"github.com/spf13/viper"
"github.com/stretchr/testify/require"

corectx "cosmossdk.io/core/context"
"cosmossdk.io/log"
banktypes "cosmossdk.io/x/bank/types"

"github.com/cosmos/cosmos-sdk/client"
codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil"
Expand All @@ -18,8 +22,10 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)

func TestAddGenesisAccountCmd(t *testing.T) {
Expand Down Expand Up @@ -111,3 +117,166 @@ func TestAddGenesisAccountCmd(t *testing.T) {
})
}
}

func TestBulkAddGenesisAccountCmd(t *testing.T) {
ac := codectestutil.CodecOptions{}.GetAddressCodec()
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
_, _, addr3 := testdata.KeyTestPubAddr()
addr1Str, err := ac.BytesToString(addr1)
require.NoError(t, err)
addr2Str, err := ac.BytesToString(addr2)
require.NoError(t, err)
addr3Str, err := ac.BytesToString(addr3)
require.NoError(t, err)

tests := []struct {
name string
state [][]genutil.GenesisAccount
expected map[string]sdk.Coins
appendFlag bool
expectErr bool
}{
{
name: "invalid address",
state: [][]genutil.GenesisAccount{
{
{
Address: "invalid",
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
},
},
expectErr: true,
},
{
name: "no append flag for multiple account adds",
state: [][]genutil.GenesisAccount{
{
{
Address: addr1Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
},
{
{
Address: addr1Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 2)),
},
},
},
appendFlag: false,
expectErr: true,
},

{
name: "multiple additions with append",
state: [][]genutil.GenesisAccount{
{
{
Address: addr1Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
{
Address: addr2Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
},
{
{
Address: addr1Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 2)),
},
{
Address: addr2Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("stake", 1)),
},
{
Address: addr3Str,
Coins: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
},
},
expected: map[string]sdk.Coins{
addr1Str: sdk.NewCoins(sdk.NewInt64Coin("test", 3)),
addr2Str: sdk.NewCoins(sdk.NewInt64Coin("test", 1), sdk.NewInt64Coin("stake", 1)),
addr3Str: sdk.NewCoins(sdk.NewInt64Coin("test", 1)),
},
appendFlag: true,
expectErr: false,
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
home := t.TempDir()
logger := log.NewNopLogger()
v := viper.New()

encodingConfig := moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{})
appCodec := encodingConfig.Codec
txConfig := encodingConfig.TxConfig
err = genutiltest.ExecInitCmd(testMbm, home, appCodec)
require.NoError(t, err)

err = writeAndTrackDefaultConfig(v, home)
require.NoError(t, err)
clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home).
WithAddressCodec(ac).WithTxConfig(txConfig)

ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
ctx = context.WithValue(ctx, corectx.ViperContextKey, v)
ctx = context.WithValue(ctx, corectx.LoggerContextKey, logger)

// The first iteration (pre-append) may not error.
// Check if any errors after all state transitions to genesis.
doesErr := false

// apply multiple state iterations if applicable (e.g. --append)
for _, state := range tc.state {
bz, err := json.Marshal(state)
require.NoError(t, err)

filePath := path.Join(home, "accounts.json")
err = os.WriteFile(filePath, bz, 0o600)
require.NoError(t, err)

cmd := genutilcli.AddBulkGenesisAccountCmd()
args := []string{filePath}
if tc.appendFlag {
args = append(args, "--append")
}
cmd.SetArgs(args)

err = cmd.ExecuteContext(ctx)
if err != nil {
doesErr = true
}
}
require.Equal(t, tc.expectErr, doesErr)

// an error already occurred, no need to check the state
if doesErr {
return
}

appState, _, err := genutiltypes.GenesisStateFromGenFile(path.Join(home, "config", "genesis.json"))
require.NoError(t, err)

bankState := banktypes.GetGenesisStateFromAppState(encodingConfig.Codec, appState)

require.EqualValues(t, len(tc.expected), len(bankState.Balances))
for _, acc := range bankState.Balances {
require.True(t, tc.expected[acc.Address].Equal(acc.Coins), "expected: %v, got: %v", tc.expected[acc.Address], acc.Coins)
}

expectedSupply := sdk.NewCoins()
for _, coins := range tc.expected {
expectedSupply = expectedSupply.Add(coins...)
}
require.Equal(t, expectedSupply, bankState.Supply)
})
}
}
Loading

0 comments on commit fa8ea23

Please sign in to comment.