From dc1a470f60f8c0fa8c1d2b7cebff83efbaac5fa0 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 19:27:47 +0000 Subject: [PATCH 1/2] feat(x/genutil): bulk add genesis accounts (backport #21372) (#21544) Co-authored-by: Reece Williams <31943163+Reecepbcups@users.noreply.github.com> Co-authored-by: Reece Williams Co-authored-by: Julien Robert --- CHANGELOG.md | 4 + x/auth/helpers/genaccounts.go | 143 +++++++++++++++++++++++ x/genutil/client/cli/core_genesis_cmd.go | 2 +- x/genutil/client/cli/genaccount.go | 67 +++++++++++ 4 files changed, 215 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 386d655f1fda..32234dc05d25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/staking) Fix a possible bypass of delagator slashing: [GHSA-86h5-xcpx-cfqc](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-86h5-xcpx-cfqc) * (store) [#435](https://github.com/crypto-org-chain/cosmos-sdk/pull/435) Fix a nil pointer panic when query historical state where a new store don't exist. +### Features + +* (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. + ## [v0.47.11](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.11) - 2024-04-22 ### Bug Fixes diff --git a/x/auth/helpers/genaccounts.go b/x/auth/helpers/genaccounts.go index 00ee73c18e52..e3827fa05898 100644 --- a/x/auth/helpers/genaccounts.go +++ b/x/auth/helpers/genaccounts.go @@ -135,3 +135,146 @@ func AddGenesisAccount( genDoc.AppState = appStateJSON return genutil.ExportGenesisFile(genDoc, genesisFileUrl) } + +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 genesis accounts to the genesis state. +// Where `cdc` is the client 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, + accounts []GenesisAccount, + appendAcct bool, + genesisFileURL string, +) error { + appState, appGenesis, err := genutiltypes.GenesisStateFromGenFile(genesisFileURL) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := authtypes.GetGenesisStateFromAppState(cdc, appState) + bankGenState := banktypes.GetGenesisStateFromAppState(cdc, appState) + + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + if err != nil { + return fmt.Errorf("failed to get accounts from any: %w", err) + } + + 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 + } + } + } + + for _, acc := range accounts { + addr := acc.Address + coins := acc.Coins + + accAddr, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return fmt.Errorf("failed to parse account address %s: %w", addr, err) + } + + // create concrete account type based on input parameters + var genAccount authtypes.GenesisAccount + + balances := banktypes.Balance{Address: addr, Coins: coins.Sort()} + baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) + + vestingAmt := acc.VestingAmt + if !vestingAmt.IsZero() { + vestingStart := acc.VestingStart + vestingEnd := acc.VestingEnd + + baseVestingAccount := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + if (balances.Coins.IsZero() && !baseVestingAccount.OriginalVesting.IsZero()) || + baseVestingAccount.OriginalVesting.IsAnyGT(balances.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + + switch { + case vestingStart != 0 && vestingEnd != 0: + genAccount = authvesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart) + + case vestingEnd != 0: + genAccount = authvesting.NewDelayedVestingAccountRaw(baseVestingAccount) + + 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 + } + + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) + } + + if _, ok := balanceCache[addr]; ok { + if !appendAcct { + return fmt.Errorf(" Account %s already exists\nUse `append` flag to append account at existing address", accAddr) + } + + for idx, acc := range bankGenState.Balances { + if acc.Address != addr { + continue + } + + 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) + } + + newSupplyCoinsCache = newSupplyCoinsCache.Add(coins...) + } + + accs = authtypes.SanitizeGenesisAccounts(accs) + + 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 = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + 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) + } + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + appGenesis.AppState = appStateJSON + return genutil.ExportGenesisFile(appGenesis, genesisFileURL) +} diff --git a/x/genutil/client/cli/core_genesis_cmd.go b/x/genutil/client/cli/core_genesis_cmd.go index 384ddc9c782c..bd625e5a75ef 100644 --- a/x/genutil/client/cli/core_genesis_cmd.go +++ b/x/genutil/client/cli/core_genesis_cmd.go @@ -21,7 +21,6 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag RunE: client.ValidateCmd, } gentxModule := moduleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) - cmd.AddCommand( GenTxCmd(moduleBasics, txConfig, banktypes.GenesisBalancesIterator{}, defaultNodeHome), @@ -30,6 +29,7 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag gentxModule.GenTxValidator), ValidateGenesisCmd(moduleBasics), AddGenesisAccountCmd(defaultNodeHome), + AddBulkGenesisAccountCmd(defaultNodeHome), ) return cmd diff --git a/x/genutil/client/cli/genaccount.go b/x/genutil/client/cli/genaccount.go index 043428daf5ab..0f6fdf66bd71 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/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -86,3 +88,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) *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 []auth.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 auth.AddGenesisAccounts(clientCtx.Codec, 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 +} From bee77a88ccad4a38ef55ea2be4df4868b832f746 Mon Sep 17 00:00:00 2001 From: mmsqe Date: Tue, 8 Oct 2024 16:35:16 +0800 Subject: [PATCH 2/2] lint --- x/auth/helpers/genaccounts.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/x/auth/helpers/genaccounts.go b/x/auth/helpers/genaccounts.go index e3827fa05898..7e60b57098ed 100644 --- a/x/auth/helpers/genaccounts.go +++ b/x/auth/helpers/genaccounts.go @@ -198,7 +198,8 @@ func AddGenesisAccounts( baseAccount := authtypes.NewBaseAccount(accAddr, nil, 0, 0) vestingAmt := acc.VestingAmt - if !vestingAmt.IsZero() { + switch { + case !vestingAmt.IsZero(): vestingStart := acc.VestingStart vestingEnd := acc.VestingEnd @@ -218,9 +219,9 @@ func AddGenesisAccounts( default: return errors.New("invalid vesting parameters; must supply start and end time or end time") } - } else if acc.ModuleName != "" { + case acc.ModuleName != "": genAccount = authtypes.NewEmptyModuleAccount(acc.ModuleName, authtypes.Burner, authtypes.Minter) - } else { + default: genAccount = baseAccount }