From d7fccac4fd3ef53ffcc57021f057318fdd66d1fc Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Tue, 20 Aug 2024 14:24:23 -0500 Subject: [PATCH 1/8] feat(genutil): `AddGenesisAccount` with cache --- x/genutil/client/cli/commands.go | 1 + x/genutil/client/cli/genaccount.go | 68 ++++++++++- x/genutil/genaccounts.go | 182 ++++++++++++++++------------- 3 files changed, 170 insertions(+), 81 deletions(-) diff --git a/x/genutil/client/cli/commands.go b/x/genutil/client/cli/commands.go index 4793db294eb1..36e9fc874766 100644 --- a/x/genutil/client/cli/commands.go +++ b/x/genutil/client/cli/commands.go @@ -41,6 +41,7 @@ func CommandsWithCustomMigrationMap(genutilModule genutil.AppModule, genMM genes CollectGenTxsCmd(genutilModule.GenTxValidator()), ValidateGenesisCmd(genMM), AddGenesisAccountCmd(), + AddBulkGenesisAccountCmd(), ExportCmd(appExport), ) diff --git a/x/genutil/client/cli/genaccount.go b/x/genutil/client/cli/genaccount.go index bafd4f2f9426..d189e87f5853 100644 --- a/x/genutil/client/cli/genaccount.go +++ b/x/genutil/client/cli/genaccount.go @@ -2,7 +2,9 @@ package cli import ( "bufio" + "encoding/json" "fmt" + "os" "github.com/spf13/cobra" @@ -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()) }, } @@ -85,3 +113,41 @@ 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", + 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 +} diff --git a/x/genutil/genaccounts.go b/x/genutil/genaccounts.go index d55fdd3ef903..edc336c26981 100644 --- a/x/genutil/genaccounts.go +++ b/x/genutil/genaccounts.go @@ -15,133 +15,155 @@ import ( genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" ) -// AddGenesisAccount adds a genesis account to the genesis state. +type GenesisAccount struct { + // Base + Address string `json:"address"` + Coins sdk.Coins `json:"coins"` + + // Vesting + VestingAmt sdk.Coins `json:"vesting_amt,omitempty"` + VestingStart int64 `json:"vesting_start,omitempty"` + VestingEnd int64 `json:"vesting_end,omitempty"` + + // Module + ModuleName string `json:"module_name,omitempty"` +} + +// AddGenesisAccounts adds a genesis account to the genesis state. // Where `cdc` is client codec, `genesisFileUrl` is the path/url of current genesis file, // `accAddr` is the address to be added to the genesis state, `amountStr` is the list of initial coins // to be added for the account, `appendAcct` updates the account if already exists. // `vestingStart, vestingEnd and vestingAmtStr` respectively are the schedule start time, end time (unix epoch) // `moduleName` is the module name for which the account is being created // and coins to be appended to the account already in the genesis.json file. -func AddGenesisAccount( +func AddGenesisAccounts( cdc codec.Codec, addressCodec address.Codec, - accAddr sdk.AccAddress, + accounts []GenesisAccount, appendAcct bool, - genesisFileURL, amountStr, vestingAmtStr string, - vestingStart, vestingEnd int64, - moduleName string, + genesisFileURL string, ) error { - addr, err := addressCodec.BytesToString(accAddr) + appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL) if err != nil { - return err + return fmt.Errorf("failed to unmarshal genesis state: %w", err) } - coins, err := sdk.ParseCoinsNormalized(amountStr) - if err != nil { - return fmt.Errorf("failed to parse coins: %w", err) - } + authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState) + bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState) - vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr) + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) if err != nil { - return fmt.Errorf("failed to parse vesting amount: %w", err) + return fmt.Errorf("failed to get accounts from any: %w", err) } - // create concrete account type based on input parameters - var genAccount authtypes.GenesisAccount + newSupplyCoinsCache := sdk.NewCoins() + balanceCache := make(map[string]banktypes.Balance) + for _, acc := range accs { + for _, balance := range bankGenState.GetBalances() { + if balance.Address == acc.GetAddress().String() { + balanceCache[acc.GetAddress().String()] = balance + } + } + } - balances := banktypes.Balance{Address: addr, Coins: coins.Sort()} - baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) + for _, acc := range accounts { + addr := acc.Address + coins := acc.Coins - if !vestingAmt.IsZero() { - baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + accAddr, err := sdk.AccAddressFromBech32(addr) if err != nil { - return fmt.Errorf("failed to create base vesting account: %w", err) + return fmt.Errorf("failed to parse account address %s: %w", addr, err) } - if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || - baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { - return errors.New("vesting amount cannot be greater than total amount") - } + // create concrete account type based on input parameters + var genAccount authtypes.GenesisAccount - switch { - case vestingStart != 0 && vestingEnd != 0: - genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) + balances := banktypes.Balance{Address: addr, Coins: coins.Sort()} + baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) - case vestingEnd != 0: - genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) + // TODO: remove vesting logic here? or require it to be done through accounts + // ref: https://github.com/cosmos/cosmos-sdk/issues/21340#issuecomment-2295828922 + // Maybe a separate PR since this is already refactoring the original logic. + vestingAmt := acc.VestingAmt + if !vestingAmt.IsZero() { + vestingStart := acc.VestingStart + vestingEnd := acc.VestingEnd - default: - return errors.New("invalid vesting parameters; must supply start and end time or end time") - } - } else if moduleName != "" { - genAccount = authtypes.NewEmptyModuleAccount(moduleName, authtypes.Burner, authtypes.Minter) - } else { - genAccount = baseAccount - } + baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + if err != nil { + return fmt.Errorf("failed to create base vesting account: %w", err) + } - if err := genAccount.Validate(); err != nil { - return fmt.Errorf("failed to validate new genesis account: %w", err) - } + if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || + baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } - appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL) - if err != nil { - return fmt.Errorf("failed to unmarshal genesis state: %w", err) - } + switch { + case vestingStart != 0 && vestingEnd != 0: + genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) - authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState) + case vestingEnd != 0: + genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) - accs, err := authtypes.UnpackAccounts(authGenState.Accounts) - if err != nil { - return fmt.Errorf("failed to get accounts from any: %w", err) - } + default: + return errors.New("invalid vesting parameters; must supply start and end time or end time") + } + } else if acc.ModuleName != "" { + genAccount = authtypes.NewEmptyModuleAccount(acc.ModuleName, authtypes.Burner, authtypes.Minter) + } else { + genAccount = baseAccount + } - bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState) - if accs.Contains(accAddr) { - if !appendAcct { - return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr) + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) } - genesisB := banktypes.GetGenesisStateFromAppState(cdc, appState) - for idx, acc := range genesisB.Balances { - if acc.Address != addr { - continue + if _, ok := balanceCache[addr]; ok { + if !appendAcct { + return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr) } - updatedCoins := acc.Coins.Add(coins...) - bankGenState.Balances[idx] = banktypes.Balance{Address: addr, Coins: updatedCoins.Sort()} - break - } - } else { - // Add the new account to the set of genesis accounts and sanitize the accounts afterwards. - accs = append(accs, genAccount) - accs = authtypes.SanitizeGenesisAccounts(accs) + for idx, acc := range bankGenState.Balances { + if acc.Address != addr { + continue + } - genAccs, err := authtypes.PackAccounts(accs) - if err != nil { - return fmt.Errorf("failed to convert accounts into any's: %w", err) + updatedCoins := acc.Coins.Add(coins...) + bankGenState.Balances[idx] = banktypes.Balance{Address: addr, Coins: updatedCoins.Sort()} + break + } + } else { + accs = append(accs, genAccount) + bankGenState.Balances = append(bankGenState.Balances, balances) } - authGenState.Accounts = genAccs - authGenStateBz, err := cdc.MarshalJSON(&authGenState) - if err != nil { - return fmt.Errorf("failed to marshal auth genesis state: %w", err) - } - appState[authtypes.ModuleName] = authGenStateBz + newSupplyCoinsCache = newSupplyCoinsCache.Add(coins...) + } + + accs = authtypes.SanitizeGenesisAccounts(accs) - bankGenState.Balances = append(bankGenState.Balances, balances) + authGenState.Accounts, err = authtypes.PackAccounts(accs) + if err != nil { + return fmt.Errorf("failed to convert accounts into any's: %w", err) + } + + appState[authtypes.ModuleName], err = cdc.MarshalJSON(&authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) } bankGenState.Balances, err = banktypes.SanitizeGenesisBalances(bankGenState.Balances, addressCodec) if err != nil { - return fmt.Errorf("failed to sanitize genesis balance: %w", err) + return fmt.Errorf("failed to sanitize genesis bank Balances: %w", err) } - bankGenState.Supply = bankGenState.Supply.Add(balances.Coins...) - bankGenStateBz, err := cdc.MarshalJSON(bankGenState) + bankGenState.Supply = bankGenState.Supply.Add(newSupplyCoinsCache...) + + appState[banktypes.ModuleName], err = cdc.MarshalJSON(bankGenState) if err != nil { return fmt.Errorf("failed to marshal bank genesis state: %w", err) } - appState[banktypes.ModuleName] = bankGenStateBz appStateJSON, err := json.Marshal(appState) if err != nil { From 670282d9b9b5f3fc5525b0c533e1acb88bd63b95 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 21 Aug 2024 22:25:05 -0500 Subject: [PATCH 2/8] test: bulk accounts including append --- CHANGELOG.md | 9 +- x/genutil/client/cli/genaccount_test.go | 170 ++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db41a1eda64a..b7215eae2f3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (crypto/keyring) [#20212](https://github.com/cosmos/cosmos-sdk/pull/20212) Expose the db keyring used in the keystore. * (client/tx) [#20870](https://github.com/cosmos/cosmos-sdk/pull/20870) Add `timeout-timestamp` field for tx body defines time based timeout.Add `WithTimeoutTimestamp` to tx factory. Increased gas cost for processing newly added timeout timestamp field in tx body. * (client) [#21074](https://github.com/cosmos/cosmos-sdk/pull/21074) Add auto cli for node service +* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Add a `bulk-add-genesis-account` genesis command to add many genesis accounts at once. ### Improvements @@ -140,7 +141,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (client) [#17215](https://github.com/cosmos/cosmos-sdk/pull/17215) `server.StartCmd`,`server.ExportCmd`,`server.NewRollbackCmd`,`pruning.Cmd`,`genutilcli.InitCmd`,`genutilcli.GenTxCmd`,`genutilcli.CollectGenTxsCmd`,`genutilcli.AddGenesisAccountCmd`, do not take a home directory anymore. It is inferred from the root command. * (client) [#17259](https://github.com/cosmos/cosmos-sdk/pull/17259) Remove deprecated `clientCtx.PrintObjectLegacy`. Use `clientCtx.PrintProto` or `clientCtx.PrintRaw` instead. * (types) [#17348](https://github.com/cosmos/cosmos-sdk/pull/17348) Remove the `WrapServiceResult` function. - * The `*sdk.Result` returned by the msg server router will not contain the `.Data` field. + * The `*sdk.Result` returned by the msg server router will not contain the `.Data` field. * (types) [#17426](https://github.com/cosmos/cosmos-sdk/pull/17426) `NewContext` does not take a `cmtproto.Header{}` any longer. * `WithChainID` / `WithBlockHeight` / `WithBlockHeader` must be used to set values on the context * (client/keys) [#17503](https://github.com/cosmos/cosmos-sdk/pull/17503) `clientkeys.NewKeyOutput`, `MkConsKeyOutput`, `MkValKeyOutput`, `MkAccKeyOutput`, `MkAccKeysOutput` now take their corresponding address codec instead of using the global SDK config. @@ -196,7 +197,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (x/crisis) [#20043](https://github.com/cosmos/cosmos-sdk/pull/20043) Changed `NewMsgVerifyInvariant` to accept a string as argument instead of an `AccAddress`. * (x/simulation)[#20056](https://github.com/cosmos/cosmos-sdk/pull/20056) `SimulateFromSeed` now takes an address codec as argument. * (server) [#20140](https://github.com/cosmos/cosmos-sdk/pull/20140) Remove embedded grpc-web proxy in favor of standalone grpc-web proxy. [Envoy Proxy](https://www.envoyproxy.io/docs/envoy/latest/start/start) -* (client) [#20255](https://github.com/cosmos/cosmos-sdk/pull/20255) Use comet proofOp proto type instead of sdk version to avoid needing to translate to later be proven in the merkle proof runtime. +* (client) [#20255](https://github.com/cosmos/cosmos-sdk/pull/20255) Use comet proofOp proto type instead of sdk version to avoid needing to translate to later be proven in the merkle proof runtime. * (types)[#20369](https://github.com/cosmos/cosmos-sdk/pull/20369) The signature of `HasAminoCodec` has changed to accept a `core/legacy.Amino` interface instead of `codec.LegacyAmino`. * (server) [#20422](https://github.com/cosmos/cosmos-sdk/pull/20422) Deprecated `ServerContext`. To get `cmtcfg.Config` from cmd, use `client.GetCometConfigFromCmd(cmd)` instead of `server.GetServerContextFromCmd(cmd).Config` * (x/genutil) [#20740](https://github.com/cosmos/cosmos-sdk/pull/20740) Update `genutilcli.Commands` and `genutilcli.CommandsWithCustomMigrationMap` to take the genesis module and abstract the module manager. @@ -208,7 +209,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Client Breaking Changes -* (runtime) [#19040](https://github.com/cosmos/cosmos-sdk/pull/19040) Simplify app config implementation and deprecate `/cosmos/app/v1alpha1/config` query. +* (runtime) [#19040](https://github.com/cosmos/cosmos-sdk/pull/19040) Simplify app config implementation and deprecate `/cosmos/app/v1alpha1/config` query. ### CLI Breaking Changes @@ -272,7 +273,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (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. diff --git a/x/genutil/client/cli/genaccount_test.go b/x/genutil/client/cli/genaccount_test.go index 9acf60e29ade..f8f0e3bd9f3f 100644 --- a/x/genutil/client/cli/genaccount_test.go +++ b/x/genutil/client/cli/genaccount_test.go @@ -2,6 +2,9 @@ package cli_test import ( "context" + "encoding/json" + "os" + "path" "testing" "github.com/spf13/viper" @@ -11,6 +14,7 @@ import ( "cosmossdk.io/log" "cosmossdk.io/x/auth" + banktypes "cosmossdk.io/x/bank/types" "github.com/cosmos/cosmos-sdk/client" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/crypto/hd" @@ -18,8 +22,10 @@ import ( "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + "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) { @@ -111,3 +117,167 @@ 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, 0644) + 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) + + }) + } +} From e15b4ae1f79f5247f92f36e735361ad44ea381cb Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 21 Aug 2024 22:27:02 -0500 Subject: [PATCH 3/8] remove todo --- x/genutil/genaccounts.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/x/genutil/genaccounts.go b/x/genutil/genaccounts.go index edc336c26981..3cfa060ff941 100644 --- a/x/genutil/genaccounts.go +++ b/x/genutil/genaccounts.go @@ -81,9 +81,6 @@ func AddGenesisAccounts( balances := banktypes.Balance{Address: addr, Coins: coins.Sort()} baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) - // TODO: remove vesting logic here? or require it to be done through accounts - // ref: https://github.com/cosmos/cosmos-sdk/issues/21340#issuecomment-2295828922 - // Maybe a separate PR since this is already refactoring the original logic. vestingAmt := acc.VestingAmt if !vestingAmt.IsZero() { vestingStart := acc.VestingStart From 217e1b64d24b78556df2a4c492e3c285b2359680 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 21 Aug 2024 22:29:14 -0500 Subject: [PATCH 4/8] lint --- x/genutil/client/cli/genaccount_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/x/genutil/client/cli/genaccount_test.go b/x/genutil/client/cli/genaccount_test.go index f8f0e3bd9f3f..3cd7bad147ca 100644 --- a/x/genutil/client/cli/genaccount_test.go +++ b/x/genutil/client/cli/genaccount_test.go @@ -13,8 +13,8 @@ import ( corectx "cosmossdk.io/core/context" "cosmossdk.io/log" "cosmossdk.io/x/auth" - banktypes "cosmossdk.io/x/bank/types" + "github.com/cosmos/cosmos-sdk/client" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/crypto/hd" @@ -240,7 +240,7 @@ func TestBulkAddGenesisAccountCmd(t *testing.T) { require.NoError(t, err) filePath := path.Join(home, "accounts.json") - err = os.WriteFile(filePath, bz, 0644) + err = os.WriteFile(filePath, bz, 0o644) require.NoError(t, err) cmd := genutilcli.AddBulkGenesisAccountCmd() @@ -277,7 +277,6 @@ func TestBulkAddGenesisAccountCmd(t *testing.T) { expectedSupply = expectedSupply.Add(coins...) } require.Equal(t, expectedSupply, bankState.Supply) - }) } } From bd9cfa6db8c5dc6a68d4d953a7791ad75dcd9046 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 21 Aug 2024 22:34:24 -0500 Subject: [PATCH 5/8] `AddGenesisAccounts` comment --- x/genutil/genaccounts.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/x/genutil/genaccounts.go b/x/genutil/genaccounts.go index 3cfa060ff941..2c6549c29ab1 100644 --- a/x/genutil/genaccounts.go +++ b/x/genutil/genaccounts.go @@ -29,13 +29,9 @@ type GenesisAccount struct { ModuleName string `json:"module_name,omitempty"` } -// AddGenesisAccounts adds a genesis account to the genesis state. -// Where `cdc` is client codec, `genesisFileUrl` is the path/url of current genesis file, -// `accAddr` is the address to be added to the genesis state, `amountStr` is the list of initial coins -// to be added for the account, `appendAcct` updates the account if already exists. -// `vestingStart, vestingEnd and vestingAmtStr` respectively are the schedule start time, end time (unix epoch) -// `moduleName` is the module name for which the account is being created -// and coins to be appended to the account already in the genesis.json file. +// AddGenesisAccounts adds genesis accounts to the genesis state. +// Where `cdc` is the client codec, `addressCodec` is the address codec, `accounts` are the genesis accounts to add, +// `appendAcct` updates the account if already exists, and `genesisFileURL` is the path/url of the current genesis file. func AddGenesisAccounts( cdc codec.Codec, addressCodec address.Codec, From a4a0fe5b924128aa3073a4f986485c2fa483aee4 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Wed, 21 Aug 2024 22:36:17 -0500 Subject: [PATCH 6/8] lint: gosec --- x/genutil/client/cli/genaccount_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/genutil/client/cli/genaccount_test.go b/x/genutil/client/cli/genaccount_test.go index 3cd7bad147ca..2fda41b8a3bd 100644 --- a/x/genutil/client/cli/genaccount_test.go +++ b/x/genutil/client/cli/genaccount_test.go @@ -240,7 +240,7 @@ func TestBulkAddGenesisAccountCmd(t *testing.T) { require.NoError(t, err) filePath := path.Join(home, "accounts.json") - err = os.WriteFile(filePath, bz, 0o644) + err = os.WriteFile(filePath, bz, 0o600) require.NoError(t, err) cmd := genutilcli.AddBulkGenesisAccountCmd() From 25d9719d23dd7bac0b33f22f4f4a67f30be1dbcf Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Thu, 22 Aug 2024 09:11:09 -0500 Subject: [PATCH 7/8] chore: move changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7215eae2f3d..bb30bd9f902d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i ### Features * (baseapp) [#20291](https://github.com/cosmos/cosmos-sdk/pull/20291) Simulate nested messages. +* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Add a `bulk-add-genesis-account` genesis command to add many genesis accounts at once. ### Improvements @@ -79,7 +80,6 @@ Every module contains its own CHANGELOG.md. Please refer to the module you are i * (crypto/keyring) [#20212](https://github.com/cosmos/cosmos-sdk/pull/20212) Expose the db keyring used in the keystore. * (client/tx) [#20870](https://github.com/cosmos/cosmos-sdk/pull/20870) Add `timeout-timestamp` field for tx body defines time based timeout.Add `WithTimeoutTimestamp` to tx factory. Increased gas cost for processing newly added timeout timestamp field in tx body. * (client) [#21074](https://github.com/cosmos/cosmos-sdk/pull/21074) Add auto cli for node service -* (cli) [#21372](https://github.com/cosmos/cosmos-sdk/pull/21372) Add a `bulk-add-genesis-account` genesis command to add many genesis accounts at once. ### Improvements From 3cc0ec323b774d5db8ad79babd6f6b8c8faadf33 Mon Sep 17 00:00:00 2001 From: Reece Williams Date: Mon, 26 Aug 2024 17:43:58 -0500 Subject: [PATCH 8/8] fix: address codec & example field --- x/genutil/client/cli/genaccount.go | 25 +++++++++++++++++++++++++ x/genutil/genaccounts.go | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/x/genutil/client/cli/genaccount.go b/x/genutil/client/cli/genaccount.go index 62dd4a1b261f..938e711b3aca 100644 --- a/x/genutil/client/cli/genaccount.go +++ b/x/genutil/client/cli/genaccount.go @@ -120,6 +120,31 @@ 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. diff --git a/x/genutil/genaccounts.go b/x/genutil/genaccounts.go index 2c6549c29ab1..f9c48a6d0529 100644 --- a/x/genutil/genaccounts.go +++ b/x/genutil/genaccounts.go @@ -66,7 +66,7 @@ func AddGenesisAccounts( addr := acc.Address coins := acc.Coins - accAddr, err := sdk.AccAddressFromBech32(addr) + accAddr, err := addressCodec.StringToBytes(addr) if err != nil { return fmt.Errorf("failed to parse account address %s: %w", addr, err) }