Skip to content

Commit

Permalink
feat(x/genutil): bulk add genesis accounts (backport cosmos#21372) (c…
Browse files Browse the repository at this point in the history
…osmos#21544) (#840)

* feat(x/genutil): bulk add genesis accounts (backport cosmos#21372) (cosmos#21544)

Co-authored-by: Reece Williams <[email protected]>
Co-authored-by: Reece Williams <[email protected]>
Co-authored-by: Julien Robert <[email protected]>

* lint

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Reece Williams <[email protected]>
Co-authored-by: Reece Williams <[email protected]>
Co-authored-by: Julien Robert <[email protected]>
  • Loading branch information
5 people authored Oct 8, 2024
1 parent 5ba20d9 commit e671aed
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
144 changes: 144 additions & 0 deletions x/auth/helpers/genaccounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,147 @@ 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
switch {
case !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")
}
case acc.ModuleName != "":
genAccount = authtypes.NewEmptyModuleAccount(acc.ModuleName, authtypes.Burner, authtypes.Minter)
default:
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)
}
2 changes: 1 addition & 1 deletion x/genutil/client/cli/core_genesis_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -30,6 +29,7 @@ func GenesisCoreCommand(txConfig client.TxConfig, moduleBasics module.BasicManag
gentxModule.GenTxValidator),
ValidateGenesisCmd(moduleBasics),
AddGenesisAccountCmd(defaultNodeHome),
AddBulkGenesisAccountCmd(defaultNodeHome),
)

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/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
Expand Down Expand Up @@ -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
}

0 comments on commit e671aed

Please sign in to comment.