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

Merged
merged 11 commits into from
Sep 4, 2024
9 changes: 5 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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.
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 @@ -41,6 +41,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,
},
}
Comment on lines +91 to +100
Copy link
Contributor

Choose a reason for hiding this comment

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

Ensure proper validation for GenesisAccount fields.

The GenesisAccount struct is well-defined. However, consider adding validation methods to ensure that fields like Address and Coins are valid.

Implement validation methods within the GenesisAccount struct to ensure data integrity.


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
Copy link
Member

Choose a reason for hiding this comment

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

Can we add the Example field from cobra and show how the file should look like?

Copy link
Member Author

Choose a reason for hiding this comment

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

3cc0ec3

verification:

image

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,6 +2,9 @@ package cli_test

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

"github.com/spf13/viper"
Expand All @@ -10,6 +13,7 @@ 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"
Expand All @@ -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) {
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