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(x/genutil)!: bulk add genesis accounts (backport #21372) #21543

Merged
merged 3 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading