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) (#21544) #755

Merged
merged 1 commit into from
Sep 16, 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#269](https://github.com/crypto-org-chain/cosmos-sdk/pull/269) Add `StreamingManager` to baseapp to extend the abci listeners.
* (crypto/keyring) [#20212](https://github.com/cosmos/cosmos-sdk/pull/20212) Expose the db keyring used in the keystore.
* (baseapp) [#565](https://github.com/crypto-org-chain/cosmos-sdk/pull/565) Support incarnation cache when executed in block-stm.
* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Added a `bulk-add-genesis-account` genesis command to add many genesis accounts at once.

### Improvements

Expand Down Expand Up @@ -76,7 +77,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (types) [#19759](https://github.com/cosmos/cosmos-sdk/pull/19759) Align SignerExtractionAdapter in PriorityNonceMempool Remove.
* (client) [#19870](https://github.com/cosmos/cosmos-sdk/pull/19870) Add new query command `wait-tx`. Alias `event-query-tx-for` to `wait-tx` for backward compatibility.

### Improvements
### Improvements

* (telemetry) [#19903](https://github.com/cosmos/cosmos-sdk/pull/19903) Conditionally emit metrics based on enablement.
* **Introduction of `Now` Function**: Added a new function called `Now` to the telemetry package. It returns the current system time if telemetry is enabled, or a zero time if telemetry is not enabled.
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(txConfig client.TxConfig, moduleBasics modul
CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, defaultNodeHome, gentxModule.GenTxValidator, txConfig.SigningContext().ValidatorAddressCodec()),
ValidateGenesisCmd(moduleBasics),
AddGenesisAccountCmd(defaultNodeHome, txConfig.SigningContext().AddressCodec()),
AddBulkGenesisAccountCmd(defaultNodeHome, txConfig.SigningContext().AddressCodec()),
)

return cmd
Expand Down
67 changes: 67 additions & 0 deletions 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 @@ -91,3 +93,68 @@ 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(defaultNodeHome string, addressCodec address.Codec) *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)
serverCtx := server.GetServerContextFromCmd(cmd)
config := serverCtx.Config

config.SetRoot(clientCtx.HomeDir)

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, addressCodec, accounts, appendflag, config.GenesisFile())
},
}

cmd.Flags().Bool(flagAppendMode, false, "append the coins to an account already in the genesis.json file")
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
flags.AddQueryFlagsToCmd(cmd)

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

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

"github.com/spf13/viper"
Expand All @@ -20,8 +23,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/auth"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"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 @@ -106,3 +112,158 @@ func TestAddGenesisAccountCmd(t *testing.T) {
})
}
}

func TestBulkAddGenesisAccountCmd(t *testing.T) {
_, _, addr1 := testdata.KeyTestPubAddr()
_, _, addr2 := testdata.KeyTestPubAddr()
_, _, addr3 := testdata.KeyTestPubAddr()
addr1Str := addr1.String()
addr2Str := addr2.String()
addr3Str := addr3.String()

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()
cfg, err := genutiltest.CreateDefaultCometConfig(home)
require.NoError(t, err)

appCodec := moduletestutil.MakeTestEncodingConfig(auth.AppModuleBasic{}).Codec
err = genutiltest.ExecInitCmd(testMbm, home, appCodec)
require.NoError(t, err)

serverCtx := server.NewContext(viper.New(), cfg, logger)
clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home)

ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx)
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx)

// 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(home, addresscodec.NewBech32Codec("cosmos"))
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(appCodec, 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