diff --git a/simapp/app.go b/simapp/app.go index 26ff08499443..c5fb30d07f28 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -848,6 +848,7 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino paramsKeeper.Subspace(slashingtypes.ModuleName) paramsKeeper.Subspace(govtypes.ModuleName) paramsKeeper.Subspace(crisistypes.ModuleName) + // TODO: params? return paramsKeeper } diff --git a/simapp/simd/cmd/blscmd/add_gen_bls.go b/simapp/simd/cmd/blscmd/add_gen_bls.go new file mode 100644 index 000000000000..e594e6f9d551 --- /dev/null +++ b/simapp/simd/cmd/blscmd/add_gen_bls.go @@ -0,0 +1,108 @@ +package cmd + +import ( + "encoding/json" + "errors" + "fmt" + + tmos "github.com/cometbft/cometbft/libs/os" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + "cosmossdk.io/x/bls/types" +) + +func AddGenBlsCmd(validator genutiltypes.MessageValidator) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-bls [genesis_bls_file]", + Short: "Add a genesis BLS key to genesis.json", + Long: `Add a genesis BLS key per validator and update the pregenesis file in place to include their +BLS keys in the checkpointing module's genesis state.' +`, + 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) + + // load genesis BLS key + genKeyFilePath := args[0] + if !tmos.FileExists(genKeyFilePath) { + return errors.New("genesis BLS key file does not exist") + } + genKey, err := types.LoadGenesisKeyFromFile(genKeyFilePath) + if err != nil { + return err + } + err = genKey.Validate() + if err != nil { + return err + } + + // load genesis state + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + checkpointingGenState := types.GetGenesisStateFromAppState(clientCtx.Codec, appState) + + // check duplication + gks := checkpointingGenState.GetGenesisKeys() + for _, gk := range gks { + if gk.ValidatorAddress == genKey.ValidatorAddress { + return errors.New("validator address already exists") + } + } + + // check correspondence of genesis transactions + // each genesis BLS key should have a corresponding + // genesis transaction + genTxState := genutiltypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + foundInGenTx := false + for _, genTx := range genTxState.GenTxs { + tx, err := genutiltypes.ValidateAndGetGenTx(genTx, clientCtx.TxConfig.TxJSONDecoder(), validator) + if err != nil { + return err + } + msgs := tx.GetMsgs() + if len(msgs) == 0 { + return errors.New("invalid genesis transaction") + } + msgCreateValidator := msgs[0].(*stakingtypes.MsgCreateValidator) + if msgCreateValidator.ValidatorAddress == genKey.ValidatorAddress { + foundInGenTx = true + } + } + if !foundInGenTx { + return errors.New("corresponding genesis tx is not found, add genesis tx with the same validator address first") + } + + gks = append(gks, genKey) + checkpointingGenState.GenesisKeys = gks + + checkpointingGenStateBz, err := clientCtx.Codec.MarshalJSON(&checkpointingGenState) + if err != nil { + return fmt.Errorf("failed to marshal checkpointing genesis state: %w", err) + } + + appState[types.ModuleName] = checkpointingGenStateBz + + appStateJSON, err := json.Marshal(appState) + if err != nil { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + return cmd +} diff --git a/simapp/simd/cmd/blscmd/add_gen_bls_test.go b/simapp/simd/cmd/blscmd/add_gen_bls_test.go new file mode 100644 index 000000000000..b37ee8779401 --- /dev/null +++ b/simapp/simd/cmd/blscmd/add_gen_bls_test.go @@ -0,0 +1,128 @@ +package cmd_test + +//import ( +// "context" +// "fmt" +// "path/filepath" +// "testing" +// +// cmd "cosmossdk.io/simapp/simd/cmd/blscmd" +// "cosmossdk.io/x/epoching/testepoching/datagen" +// "github.com/cosmos/cosmos-sdk/server/config" +// "github.com/cosmos/cosmos-sdk/testutil/cli" +// "github.com/cosmos/cosmos-sdk/x/genutil" +// +// tmconfig "github.com/cometbft/cometbft/config" +// tmjson "github.com/cometbft/cometbft/libs/json" +// "github.com/cometbft/cometbft/libs/log" +// "github.com/cometbft/cometbft/libs/tempfile" +// "github.com/spf13/viper" +// "github.com/stretchr/testify/require" +// +// "github.com/cosmos/cosmos-sdk/client" +// "github.com/cosmos/cosmos-sdk/client/flags" +// "github.com/cosmos/cosmos-sdk/server" +// "github.com/cosmos/cosmos-sdk/testutil/network" +// genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" +// genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +// +// app "cosmossdk.io/simapp" +// //"github.com/babylonchain/babylon/cmd/babylond/cmd" +// "cosmossdk.io/privval" +// //"github.com/babylonchain/babylon/testutil/cli" +// //"github.com/babylonchain/babylon/testutil/datagen" +// "cosmossdk.io/x/bls/types" +//) +// +//// test adding genesis BLS keys without gentx +//// error is expected +//func Test_AddGenBlsCmdWithoutGentx(t *testing.T) { +// home := t.TempDir() +// logger := log.NewNopLogger() +// tmcfg, err := genutiltest.CreateDefaultTendermintConfig(home) +// require.NoError(t, err) +// +// appCodec := app.GetEncodingConfig().Marshaler +// gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) +// +// err = genutiltest.ExecInitCmd(testMbm, home, appCodec) +// require.NoError(t, err) +// +// serverCtx := server.NewContext(viper.New(), tmcfg, logger) +// clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) +// cfg := serverCtx.Config +// cfg.SetRoot(clientCtx.HomeDir) +// +// ctx := context.Background() +// ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) +// ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) +// +// genKey := datagen.GenerateGenesisKey() +// jsonBytes, err := tmjson.MarshalIndent(genKey, "", " ") +// require.NoError(t, err) +// genKeyFileName := filepath.Join(home, fmt.Sprintf("gen-bls-%s.json", genKey.ValidatorAddress)) +// err = tempfile.WriteFileAtomic(genKeyFileName, jsonBytes, 0600) +// require.NoError(t, err) +// addGenBlsCmd := cmd.AddGenBlsCmd(gentxModule.GenTxValidator) +// addGenBlsCmd.SetArgs( +// []string{genKeyFileName}, +// ) +// err = addGenBlsCmd.ExecuteContext(ctx) +// require.Error(t, err) +//} +// +//// test adding genesis BLS keys with gentx +//// error is expected if adding duplicate +//func Test_AddGenBlsCmdWithGentx(t *testing.T) { +// min := network.MinimumAppConfig() +// cfg, _ := network.DefaultConfigWithAppConfig(min) +// config.SetConfigTemplate(config.DefaultConfigTemplate) +// cfg.NumValidators = 1 +// +// testNetwork, err := network.New(t, t.TempDir(), cfg) +// require.NoError(t, err) +// defer testNetwork.Cleanup() +// +// _, err = testNetwork.WaitForHeight(1) +// require.NoError(t, err) +// gentxModule := app.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) +// +// targetCfg := tmconfig.DefaultConfig() +// targetCfg.SetRoot(filepath.Join(testNetwork.Validators[0].Dir, "simd")) +// targetGenesisFile := targetCfg.GenesisFile() +// targetCtx := testNetwork.Validators[0].ClientCtx +// for i := 0; i < cfg.NumValidators; i++ { +// v := testNetwork.Validators[i] +// // build and create genesis BLS key +// genBlsCmd := cmd.GenBlsCmd() +// nodeCfg := tmconfig.DefaultConfig() +// homeDir := filepath.Join(v.Dir, "simd") +// nodeCfg.SetRoot(homeDir) +// keyPath := nodeCfg.PrivValidatorKeyFile() +// statePath := nodeCfg.PrivValidatorStateFile() +// filePV := privval.GenWrappedFilePV(keyPath, statePath) +// defer filePV.Clean(keyPath, statePath) +// filePV.SetAccAddress(v.Address) +// _, err = cli.ExecTestCLICmd(v.ClientCtx, genBlsCmd, []string{fmt.Sprintf("--%s=%s", flags.FlagHome, homeDir)}) +// require.NoError(t, err) +// genKeyFileName := filepath.Join(filepath.Dir(keyPath), fmt.Sprintf("gen-bls-%s.json", v.ValAddress)) +// genKey, err := types.LoadGenesisKeyFromFile(genKeyFileName) +// require.NoError(t, err) +// require.NotNil(t, genKey) +// +// // add genesis BLS key to the target context +// addBlsCmd := cmd.AddGenBlsCmd(gentxModule.GenTxValidator) +// _, err = cli.ExecTestCLICmd(targetCtx, addBlsCmd, []string{genKeyFileName}) +// require.NoError(t, err) +// appState, _, err := genutiltypes.GenesisStateFromGenFile(targetGenesisFile) +// require.NoError(t, err) +// // test duplicate +// _, err = cli.ExecTestCLICmd(targetCtx, addBlsCmd, []string{genKeyFileName}) +// require.Error(t, err) +// +// checkpointingGenState := types.GetGenesisStateFromAppState(v.ClientCtx.Codec, appState) +// require.NotEmpty(t, checkpointingGenState.GenesisKeys) +// gks := checkpointingGenState.GetGenesisKeys() +// require.Equal(t, genKey, gks[i]) +// } +//} diff --git a/simapp/simd/cmd/blscmd/create_bls_key.go b/simapp/simd/cmd/blscmd/create_bls_key.go new file mode 100644 index 000000000000..708e17893ed6 --- /dev/null +++ b/simapp/simd/cmd/blscmd/create_bls_key.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + + tmconfig "github.com/cometbft/cometbft/config" + tmos "github.com/cometbft/cometbft/libs/os" + "github.com/spf13/cobra" + + app "cosmossdk.io/simapp" + + "cosmossdk.io/privval" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func CreateBlsKeyCmd() *cobra.Command { + bech32PrefixAccAddr := sdk.Bech32PrefixAccAddr + + cmd := &cobra.Command{ + Use: "create-bls-key [account-address]", + Args: cobra.ExactArgs(1), + Short: "Create a pair of BLS keys for a validator", + Long: strings.TrimSpace( + fmt.Sprintf(`create-bls will create a pair of BLS keys that are used to +send BLS signatures for checkpointing. + +BLS keys are stored along with other validator keys in priv_validator_key.json, +which should exist before running the command (via babylond init or babylond testnet). + +Example: +$ babylond create-bls-key %s1f5tnl46mk4dfp4nx3n2vnrvyw2h2ydz6ykhk3r --home ./ +`, + bech32PrefixAccAddr, + ), + ), + + RunE: func(cmd *cobra.Command, args []string) error { + homeDir, _ := cmd.Flags().GetString(flags.FlagHome) + + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + return CreateBlsKey(homeDir, addr) + }, + } + + cmd.Flags().String(flags.FlagHome, app.DefaultNodeHome, "The node home directory") + + return cmd +} + +func CreateBlsKey(home string, addr sdk.AccAddress) error { + nodeCfg := tmconfig.DefaultConfig() + keyPath := filepath.Join(home, nodeCfg.PrivValidatorKeyFile()) + statePath := filepath.Join(home, nodeCfg.PrivValidatorStateFile()) + if !tmos.FileExists(keyPath) { + return errors.New("validator key file does not exist") + } + pv := privval.LoadWrappedFilePV(keyPath, statePath) + wrappedPV := privval.NewWrappedFilePV(pv.GetValPrivKey(), bls12381.GenPrivKey(), keyPath, statePath) + wrappedPV.SetAccAddress(addr) + + return nil +} diff --git a/simapp/simd/cmd/blscmd/custom_babylon_config.go b/simapp/simd/cmd/blscmd/custom_babylon_config.go new file mode 100644 index 000000000000..17d1ec3a623e --- /dev/null +++ b/simapp/simd/cmd/blscmd/custom_babylon_config.go @@ -0,0 +1,55 @@ +package cmd + +import ( + serverconfig "github.com/cosmos/cosmos-sdk/server/config" +) + +const ( + defaultKeyName = "" + defaultGasPrice = "0.01ubbn" + defaultGasAdjustment = 1.5 +) + +func defaultSignerConfig() SignerConfig { + return SignerConfig{ + KeyName: defaultKeyName, + GasPrice: defaultGasPrice, + GasAdjustment: defaultGasAdjustment, + } +} + +type SignerConfig struct { + KeyName string `mapstructure:"key-name"` + GasPrice string `mapstructure:"gas-price"` + GasAdjustment float64 `mapstructure:"gas-adjustment"` +} + +type BabylonAppConfig struct { + serverconfig.Config `mapstructure:",squash"` + + SignerConfig SignerConfig `mapstructure:"signer-config"` +} + +func DefaultBabylonConfig() *BabylonAppConfig { + return &BabylonAppConfig{ + Config: *serverconfig.DefaultConfig(), + SignerConfig: defaultSignerConfig(), + } +} + +func DefaultBabylonTemplate() string { + return serverconfig.DefaultConfigTemplate + ` +############################################################################### +### Babylon BLS configuration ### +############################################################################### + +[signer-config] + +# Configures which key that the BLS signer uses to sign BLS-sig transactions +key-name = "{{ .SignerConfig.KeyName }}" +# Configures the gas-price that the signer would like to pay +gas-price = "{{ .SignerConfig.GasPrice }}" +# Configures the adjustment of the gas cost of estimation +gas-adjustment = "{{ .SignerConfig.GasAdjustment }}" +` +} diff --git a/simapp/simd/cmd/blscmd/genaccounts.go b/simapp/simd/cmd/blscmd/genaccounts.go new file mode 100644 index 000000000000..0f9208df69fc --- /dev/null +++ b/simapp/simd/cmd/blscmd/genaccounts.go @@ -0,0 +1,186 @@ +package cmd + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/server" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + authvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" +) + +const ( + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" +) + +// AddGenesisAccountCmd returns add-genesis-account cobra Command. +func AddGenesisAccountCmd(defaultNodeHome string) *cobra.Command { + cmd := &cobra.Command{ + Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", + Short: "Add a genesis account to genesis.json", + Long: `Add a genesis account to genesis.json. The provided account must specify +the account address or key name and a list of initial coins. If a key name is given, +the address will be looked up in the local Keybase. The list of initial tokens must +contain valid denominations. Accounts may optionally be supplied with vesting parameters. +`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + serverCtx := server.GetServerContextFromCmd(cmd) + config := serverCtx.Config + + config.SetRoot(clientCtx.HomeDir) + + var kr keyring.Keyring + addr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + inBuf := bufio.NewReader(cmd.InOrStdin()) + keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend) + if keyringBackend != "" && clientCtx.Keyring == nil { + var err error + kr, err = keyring.New(sdk.KeyringServiceName(), keyringBackend, clientCtx.HomeDir, inBuf, clientCtx.Codec) + if err != nil { + return err + } + } else { + kr = clientCtx.Keyring + } + + info, err := kr.Key(args[0]) + if err != nil { + return fmt.Errorf("failed to get address from Keyring: %w", err) + } + addr, err = info.GetAddress() + if err != nil { + return err + } + } + + coins, err := sdk.ParseCoinsNormalized(args[1]) + if err != nil { + return fmt.Errorf("failed to parse coins: %w", err) + } + + vestingStart, _ := cmd.Flags().GetInt64(flagVestingStart) + vestingEnd, _ := cmd.Flags().GetInt64(flagVestingEnd) + vestingAmtStr, _ := cmd.Flags().GetString(flagVestingAmt) + + vestingAmt, err := sdk.ParseCoinsNormalized(vestingAmtStr) + if err != nil { + return fmt.Errorf("failed to parse vesting amount: %w", err) + } + + // create concrete account type based on input parameters + var genAccount authtypes.GenesisAccount + + balances := banktypes.Balance{Address: addr.String(), Coins: coins.Sort()} + baseAccount := authtypes.NewBaseAccount(addr, nil, 0, 0) + + if !vestingAmt.IsZero() { + baseVestingAccount, err := authvesting.NewBaseVestingAccount(baseAccount, vestingAmt.Sort(), vestingEnd) + if err != nil { + return fmt.Errorf("failed to init vesting 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") + } + + 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 { + genAccount = baseAccount + } + + if err := genAccount.Validate(); err != nil { + return fmt.Errorf("failed to validate new genesis account: %w", err) + } + + genFile := config.GenesisFile() + appState, genDoc, err := genutiltypes.GenesisStateFromGenFile(genFile) + if err != nil { + return fmt.Errorf("failed to unmarshal genesis state: %w", err) + } + + authGenState := authtypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + + accs, err := authtypes.UnpackAccounts(authGenState.Accounts) + if err != nil { + return fmt.Errorf("failed to get accounts from any: %w", err) + } + + if accs.Contains(addr) { + return fmt.Errorf("cannot add account at existing address %s", addr) + } + + // Add the new account to the set of genesis accounts and sanitize the + // accounts afterwards. + accs = append(accs, genAccount) + accs = authtypes.SanitizeGenesisAccounts(accs) + + genAccs, err := authtypes.PackAccounts(accs) + if err != nil { + return fmt.Errorf("failed to convert accounts into any's: %w", err) + } + authGenState.Accounts = genAccs + + authGenStateBz, err := clientCtx.Codec.MarshalJSON(&authGenState) + if err != nil { + return fmt.Errorf("failed to marshal auth genesis state: %w", err) + } + + appState[authtypes.ModuleName] = authGenStateBz + + bankGenState := banktypes.GetGenesisStateFromAppState(clientCtx.Codec, appState) + bankGenState.Balances = append(bankGenState.Balances, balances) + bankGenState.Balances = banktypes.SanitizeGenesisBalances(bankGenState.Balances) + bankGenState.Supply = bankGenState.Supply.Add(balances.Coins...) + + bankGenStateBz, err := clientCtx.Codec.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 { + return fmt.Errorf("failed to marshal application genesis state: %w", err) + } + + genDoc.AppState = appStateJSON + return genutil.ExportGenesisFile(genDoc, genFile) + }, + } + + cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") + cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Int64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Int64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/simapp/simd/cmd/blscmd/genaccounts_test.go b/simapp/simd/cmd/blscmd/genaccounts_test.go new file mode 100644 index 000000000000..730f41d9d192 --- /dev/null +++ b/simapp/simd/cmd/blscmd/genaccounts_test.go @@ -0,0 +1,111 @@ +package cmd_test + +import ( + "context" + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/cometbft/cometbft/libs/log" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/genutil" + genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" + + bbncmd "github.com/babylonchain/babylon/cmd/babylond/cmd" + + app "cosmossdk.io/simapp" +) + +var testMbm = module.NewBasicManager(genutil.AppModuleBasic{}) + +func TestAddGenesisAccountCmd(t *testing.T) { + _, _, addr1 := testdata.KeyTestPubAddr() + tests := []struct { + name string + addr string + denom string + withKeyring bool + expectErr bool + }{ + { + name: "invalid address", + addr: "", + denom: "1000atom", + withKeyring: false, + expectErr: true, + }, + { + name: "valid address", + addr: addr1.String(), + denom: "1000atom", + withKeyring: false, + expectErr: false, + }, + { + name: "multiple denoms", + addr: addr1.String(), + denom: "1000atom, 2000bbn", + withKeyring: false, + expectErr: false, + }, + { + name: "with keyring", + addr: "ser", + denom: "1000atom", + withKeyring: 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.CreateDefaultTendermintConfig(home) + require.NoError(t, err) + + appCodec := app.GetEncodingConfig().Marshaler + err = genutiltest.ExecInitCmd(testMbm, home, appCodec) + require.NoError(t, err) + + serverCtx := server.NewContext(viper.New(), cfg, logger) + clientCtx := client.Context{}.WithCodec(appCodec).WithHomeDir(home) + + if tc.withKeyring { + path := hd.CreateHDPath(118, 0, 0).String() + kr, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendMemory, home, nil, clientCtx.Codec) + require.NoError(t, err) + _, _, err = kr.NewMnemonic(tc.addr, keyring.English, path, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + require.NoError(t, err) + clientCtx = clientCtx.WithKeyring(kr) + } + + ctx := context.Background() + ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) + ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) + + cmd := bbncmd.AddGenesisAccountCmd(home) + cmd.SetArgs([]string{ + tc.addr, + tc.denom, + fmt.Sprintf("--%s=home", flags.FlagHome)}) + + if tc.expectErr { + require.Error(t, cmd.ExecuteContext(ctx)) + } else { + require.NoError(t, cmd.ExecuteContext(ctx)) + } + }) + } +} diff --git a/simapp/simd/cmd/blscmd/genbls.go b/simapp/simd/cmd/blscmd/genbls.go new file mode 100644 index 000000000000..560edbc27937 --- /dev/null +++ b/simapp/simd/cmd/blscmd/genbls.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "errors" + "path/filepath" + "strings" + + tmconfig "github.com/cometbft/cometbft/config" + tmos "github.com/cometbft/cometbft/libs/os" + "github.com/spf13/cobra" + + app "cosmossdk.io/simapp" + + "cosmossdk.io/privval" + "github.com/cosmos/cosmos-sdk/client/flags" +) + +func GenBlsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-genesis-bls", + Short: "Create genesis BLS key file for the validator", + Long: strings.TrimSpace(`genbls will create a BLS key file that consists of +{address, bls_pub_key, pop, pub_key} where pop is the proof-of-possession that proves +the ownership of bls_pub_key which is bonded with pub_key. + +The pre-conditions of running the generate-genesis-bls-key are the existence of the keyring, +and the existence of priv_validator_key.json which contains the validator private key. + + +Example: +$ babylond genbls --home ./ +`), + + RunE: func(cmd *cobra.Command, args []string) error { + homeDir, _ := cmd.Flags().GetString(flags.FlagHome) + + nodeCfg := tmconfig.DefaultConfig() + keyPath := filepath.Join(homeDir, nodeCfg.PrivValidatorKeyFile()) + statePath := filepath.Join(homeDir, nodeCfg.PrivValidatorStateFile()) + if !tmos.FileExists(keyPath) { + return errors.New("validator key file does not exist") + } + + wrappedPV := privval.LoadWrappedFilePV(keyPath, statePath) + + outputFileName, err := wrappedPV.ExportGenBls(filepath.Dir(keyPath)) + if err != nil { + return err + } + + cmd.PrintErrf("Genesis BLS keys written to %q\n", outputFileName) + return nil + }, + } + + cmd.Flags().String(flags.FlagHome, app.DefaultNodeHome, "The node home directory") + + return cmd +} diff --git a/simapp/simd/cmd/blscmd/genbls_test.go b/simapp/simd/cmd/blscmd/genbls_test.go new file mode 100644 index 000000000000..0933004ece23 --- /dev/null +++ b/simapp/simd/cmd/blscmd/genbls_test.go @@ -0,0 +1,81 @@ +package cmd_test + +//import ( +// "bufio" +// "context" +// "fmt" +// "path/filepath" +// "testing" +// +// tmconfig "github.com/cometbft/cometbft/config" +// "github.com/cometbft/cometbft/libs/log" +// "github.com/spf13/viper" +// "github.com/stretchr/testify/require" +// +// "github.com/cosmos/cosmos-sdk/client" +// "github.com/cosmos/cosmos-sdk/client/flags" +// "github.com/cosmos/cosmos-sdk/crypto/hd" +// "github.com/cosmos/cosmos-sdk/crypto/keyring" +// "github.com/cosmos/cosmos-sdk/server" +// "github.com/cosmos/cosmos-sdk/testutil" +// sdk "github.com/cosmos/cosmos-sdk/types" +// genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" +// +// app "cosmossdk.io/simapp" +// "github.com/babylonchain/babylon/cmd/babylond/cmd" +// "cosmossdk.io/x/bls/types" +// +// "cosmossdk.io/privval" +//) +// +//func Test_GenBlsCmd(t *testing.T) { +// home := t.TempDir() +// encodingConfig := app.GetEncodingConfig() +// logger := log.NewNopLogger() +// cfg, err := genutiltest.CreateDefaultTendermintConfig(home) +// require.NoError(t, err) +// +// err = genutiltest.ExecInitCmd(app.ModuleBasics, home, encodingConfig.Marshaler) +// require.NoError(t, err) +// +// serverCtx := server.NewContext(viper.New(), cfg, logger) +// clientCtx := client.Context{}. +// WithCodec(encodingConfig.Marshaler). +// WithHomeDir(home). +// WithTxConfig(encodingConfig.TxConfig) +// +// ctx := context.Background() +// ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) +// ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) +// genBlsCmd := cmd.GenBlsCmd() +// genBlsCmd.SetArgs([]string{fmt.Sprintf("--%s=%s", flags.FlagHome, home)}) +// +// // create keyring to get the validator address +// kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, home, bufio.NewReader(genBlsCmd.InOrStdin()), clientCtx.Codec) +// require.NoError(t, err) +// keyringAlgos, _ := kb.SupportedAlgorithms() +// algo, err := keyring.NewSigningAlgoFromString(string(hd.Secp256k1Type), keyringAlgos) +// require.NoError(t, err) +// addr, _, err := testutil.GenerateSaveCoinKey(kb, home, "", true, algo) +// require.NoError(t, err) +// +// // create BLS keys +// nodeCfg := tmconfig.DefaultConfig() +// keyPath := filepath.Join(home, nodeCfg.PrivValidatorKeyFile()) +// statePath := filepath.Join(home, nodeCfg.PrivValidatorStateFile()) +// filePV := privval.GenWrappedFilePV(keyPath, statePath) +// defer filePV.Clean(keyPath, statePath) +// filePV.SetAccAddress(addr) +// +// // execute the gen-bls cmd +// err = genBlsCmd.ExecuteContext(ctx) +// require.NoError(t, err) +// outputFilePath := filepath.Join(filepath.Dir(keyPath), fmt.Sprintf("gen-bls-%s.json", sdk.ValAddress(addr).String())) +// require.NoError(t, err) +// genKey, err := types.LoadGenesisKeyFromFile(outputFilePath) +// require.NoError(t, err) +// require.Equal(t, sdk.ValAddress(addr).String(), genKey.ValidatorAddress) +// require.True(t, filePV.Key.BlsPubKey.Equal(*genKey.BlsKey.Pubkey)) +// require.Equal(t, filePV.Key.PubKey.Bytes(), genKey.ValPubkey.Bytes()) +// require.True(t, genKey.BlsKey.Pop.IsValid(*genKey.BlsKey.Pubkey, genKey.ValPubkey)) +//} diff --git a/simapp/simd/cmd/commands.go b/simapp/simd/cmd/commands.go index 6f94fd3379db..4802203c7d10 100644 --- a/simapp/simd/cmd/commands.go +++ b/simapp/simd/cmd/commands.go @@ -16,6 +16,7 @@ import ( "cosmossdk.io/simapp" "cosmossdk.io/simapp/params" + cmd "cosmossdk.io/simapp/simd/cmd/blscmd" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" "github.com/cosmos/cosmos-sdk/client/debug" @@ -129,6 +130,8 @@ func initRootCmd( server.AddCommands(rootCmd, simapp.DefaultNodeHome, newApp, appExport, addModuleInitFlags) + //gentxModule := simapp.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic) + // add keybase, auxiliary RPC, query, genesis, and tx child commands rootCmd.AddCommand( server.StatusCommand(), @@ -136,6 +139,11 @@ func initRootCmd( queryCommand(), txCommand(), keys.Commands(), + + // bls + cmd.CreateBlsKeyCmd(), + cmd.GenBlsCmd(), + //cmd.AddGenBlsCmd(gentxModule.GenTxValidator), ) } diff --git a/x/bls/client/cli/tx.go b/x/bls/client/cli/tx.go index e93c1ae7550a..e06c656c9418 100644 --- a/x/bls/client/cli/tx.go +++ b/x/bls/client/cli/tx.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" + flag "github.com/spf13/pflag" + "cosmossdk.io/core/address" "github.com/spf13/cobra" @@ -86,8 +88,66 @@ func CmdTxAddBlsSig() *cobra.Command { return cmd } +// LEGACY + +func flagSetDescriptionCreate() *flag.FlagSet { + fs := flag.NewFlagSet("", flag.ContinueOnError) + + fs.String(cosmoscli.FlagMoniker, "", "The validator's name") + fs.String(cosmoscli.FlagIdentity, "", "The optional identity signature (ex. UPort or Keybase)") + fs.String(cosmoscli.FlagWebsite, "", "The validator's (optional) website") + fs.String(cosmoscli.FlagSecurityContact, "", "The validator's (optional) security contact email") + fs.String(cosmoscli.FlagDetails, "", "The validator's (optional) details") + + return fs +} + +// NewCreateValidatorCmd returns a CLI command handler for creating a MsgCreateValidator transaction. +func NewCreateValidatorCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-validator", + Short: "create new validator initialized with a self-delegation to it", + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + txf, err := tx.NewFactoryCLI(clientCtx, cmd.Flags()) + if err != nil { + return err + } + + txf, msg, err := newBuildCreateValidatorMsg(clientCtx, txf, cmd.Flags()) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxWithFactory(clientCtx, txf, msg) + }, + } + + cmd.Flags().AddFlagSet(cosmoscli.FlagSetPublicKey()) + cmd.Flags().AddFlagSet(cosmoscli.FlagSetAmount()) + cmd.Flags().AddFlagSet(flagSetDescriptionCreate()) + cmd.Flags().AddFlagSet(cosmoscli.FlagSetCommissionCreate()) + cmd.Flags().AddFlagSet(cosmoscli.FlagSetMinSelfDelegation()) + + cmd.Flags().String(cosmoscli.FlagIP, "", fmt.Sprintf("The node's public IP. It takes effect only when used in combination with --%s", flags.FlagGenerateOnly)) + cmd.Flags().String(cosmoscli.FlagNodeID, "", "The node's ID") + flags.AddTxFlagsToCmd(cmd) + + _ = cmd.MarkFlagRequired(flags.FlagFrom) + _ = cmd.MarkFlagRequired(cosmoscli.FlagAmount) + _ = cmd.MarkFlagRequired(cosmoscli.FlagPubKey) + _ = cmd.MarkFlagRequired(cosmoscli.FlagMoniker) + + return cmd +} + func CmdWrappedCreateValidator(ac address.Codec) *cobra.Command { - cmd := cosmoscli.NewCreateValidatorCmd(ac) + //cmd := cosmoscli.NewCreateValidatorCmd(ac) + cmd := NewCreateValidatorCmd() cmd.Long = strings.TrimSpace( string(`create-validator will create a new validator initialized with a self-delegation to it using the BLS key generated for the validator (e.g., via babylond create-bls-key). @@ -125,6 +185,7 @@ before running the command (e.g., via babylond create-bls-key).`)) panic(err) } + // TODO: fix defaultNodeHome := filepath.Join(userHomeDir, ".babylond") cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The node home directory") diff --git a/x/bls/keeper/msg_server.go b/x/bls/keeper/msg_server.go index 0f0f613a235e..cef083d0034f 100644 --- a/x/bls/keeper/msg_server.go +++ b/x/bls/keeper/msg_server.go @@ -37,12 +37,42 @@ func (m msgServer) AddBlsSig(goCtx context.Context, msg *types.MsgAddBlsSig) (*t return &types.MsgAddBlsSigResponse{}, nil } +// TODO: add become bls validator, pop msg + // WrappedCreateValidator registers validator's BLS public key // and forwards corresponding MsgCreateValidator message to // the epoching module func (m msgServer) WrappedCreateValidator(goCtx context.Context, msg *types.MsgWrappedCreateValidator) (*types.MsgWrappedCreateValidatorResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) + // stateless checks on the inside `MsgCreateValidator` msg + //if err := m.k.epochingKeeper.CheckMsgCreateValidator(ctx, msg.MsgCreateValidator); err != nil { + // return nil, err + //} + + if msg.VerifyPoP() != true { + return nil, fmt.Errorf("the proof-of-possession is not valid") + } + valAddr, err := sdk.ValAddressFromBech32(msg.MsgCreateValidator.ValidatorAddress) + if err != nil { + return nil, err + } + + // store BLS public key + err = m.k.CreateRegistration(ctx, *msg.Key.Pubkey, valAddr) + if err != nil { + return nil, err + } + + return &types.MsgWrappedCreateValidatorResponse{}, err +} + +// WrappedCreateValidatorOrigin registers validator's BLS public key +// and forwards corresponding MsgCreateValidator message to +// the epoching module +func (m msgServer) WrappedCreateValidatorOrigin(goCtx context.Context, msg *types.MsgWrappedCreateValidator) (*types.MsgWrappedCreateValidatorResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + // stateless checks on the inside `MsgCreateValidator` msg if err := m.k.epochingKeeper.CheckMsgCreateValidator(ctx, msg.MsgCreateValidator); err != nil { return nil, err diff --git a/x/bls/module.go b/x/bls/module.go index 019ae2ea94bf..8636058a3077 100644 --- a/x/bls/module.go +++ b/x/bls/module.go @@ -6,6 +6,7 @@ import ( "fmt" "cosmossdk.io/core/address" + "cosmossdk.io/core/appmodule" "github.com/gorilla/mux" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" @@ -26,6 +27,13 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} + //_ module.AppModuleSimulation = AppModule{} + _ module.HasServices = AppModule{} + _ module.HasInvariants = AppModule{} + _ module.HasABCIGenesis = AppModule{} + //_ module.HasABCIEndBlock = AppModule{} + _ appmodule.AppModule = AppModule{} + //_ appmodule.HasBeginBlocker = AppModule{} ) // ---------------------------------------------------------------------------- @@ -47,12 +55,13 @@ func (AppModuleBasic) Name() string { return types.ModuleName } -func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) -} +//// Deprecated +//func (AppModuleBasic) RegisterCodec(cdc *codec.LegacyAmino) { +// types.RegisterCodec(cdc) +//} func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { - types.RegisterCodec(cdc) + types.RegisterLegacyAminoCodec(cdc) } // RegisterInterfaces registers the module's interface types diff --git a/x/bls/types/codec.go b/x/bls/types/codec.go index 4bcf69594c67..841b5e173012 100644 --- a/x/bls/types/codec.go +++ b/x/bls/types/codec.go @@ -2,12 +2,15 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/msgservice" ) -func RegisterCodec(cdc *codec.LegacyAmino) { +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + legacy.RegisterAminoMsg(cdc, &MsgAddBlsSig{}, "cosmos-sdk/MsgAddBlsSig") + legacy.RegisterAminoMsg(cdc, &MsgWrappedCreateValidator{}, "cosmos-sdk/MsgWrappedCreateValidator") } // TODO: interface wiring @@ -20,6 +23,8 @@ func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) } +// TODO: RegisterLegacyAminoCodec + var ( Amino = codec.NewLegacyAmino() ModuleCdc = codec.NewProtoCodec(cdctypes.NewInterfaceRegistry()) diff --git a/x/bls/types/msgs.go b/x/bls/types/msgs.go index f12a3df9d88d..29ce8a2a7efe 100644 --- a/x/bls/types/msgs.go +++ b/x/bls/types/msgs.go @@ -6,7 +6,6 @@ import ( codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/bls12381" ed255192 "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -69,8 +68,14 @@ func (m *MsgAddBlsSig) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{signer} } -func (m *MsgWrappedCreateValidator) VerifyPoP(valPubkey cryptotypes.PubKey) bool { - return m.Key.Pop.IsValid(*m.Key.Pubkey, valPubkey) +// TODO: fixed +func (m *MsgWrappedCreateValidator) VerifyPoP() bool { + var pubKey ed255192.PubKey + err := pubKey.Unmarshal(m.MsgCreateValidator.Pubkey.GetValue()) + if err != nil { + return false + } + return m.Key.Pop.IsValid(*m.Key.Pubkey, &pubKey) } // ValidateBasic validates statelesss message elements @@ -89,7 +94,7 @@ func (m *MsgWrappedCreateValidator) ValidateBasic() error { if err != nil { return err } - ok := m.VerifyPoP(&pubKey) + ok := m.VerifyPoP() if !ok { return errors.New("the proof-of-possession is not valid") }