Skip to content

Commit

Permalink
feat(gentx): allow custom tx validation (cosmos#12956)
Browse files Browse the repository at this point in the history
## Description

Closes: cosmos#12833



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
robert-zaremba authored and Wryhder committed Oct 26, 2022
1 parent fdaee67 commit 3dcb8f9
Show file tree
Hide file tree
Showing 12 changed files with 50 additions and 33 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (appModule) Remove `Route`, `QuerierRoute` and `LegacyQuerierHandler` from AppModule Interface.
* (x/modules) Remove all LegacyQueries and related code from modules
* (store) [#11825](https://github.com/cosmos/cosmos-sdk/pull/11825) Make extension snapshotter interface safer to use, renamed the util function `WriteExtensionItem` to `WriteExtensionPayload`.
* (x/genutil)[#12956](https://github.com/cosmos/cosmos-sdk/pull/12956) `genutil.AppModuleBasic` has a new attribute: genesis transaction validation function. The existing validation logic is implemented in `genutiltypes.DefaultMessageValidator`. Use `genutil.NewAppModuleBasic` to create a new genutil Module Basic.
* (codec) [#12964](https://github.com/cosmos/cosmos-sdk/pull/12964) `ProtoCodec.MarshalInterface` now returns an error when serializing unregistered types and a subsequent `ProtoCodec.UnmarshalInterface` would fail.
* (x/staking) [#12973](https://github.com/cosmos/cosmos-sdk/pull/12973) Removed `stakingkeeper.RandomValidator`. Use `testutil.RandSliceElem(r, sk.GetAllValidators(ctx))` instead.

Expand Down
2 changes: 1 addition & 1 deletion simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ var (
// and genesis verification.
ModuleBasics = module.NewBasicManager(
auth.AppModuleBasic{},
genutil.AppModuleBasic{},
genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator),
bank.AppModuleBasic{},
capability.AppModuleBasic{},
staking.AppModuleBasic{},
Expand Down
2 changes: 1 addition & 1 deletion simapp/app_legacy.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ var (
// and genesis verification.
ModuleBasics = module.NewBasicManager(
auth.AppModuleBasic{},
genutil.AppModuleBasic{},
genutil.NewAppModuleBasic(genutiltypes.DefaultMessageValidator),
bank.AppModuleBasic{},
capability.AppModuleBasic{},
staking.AppModuleBasic{},
Expand Down
9 changes: 7 additions & 2 deletions simapp/simd/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/crisis"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
)

// NewRootCmd creates a new root command for simd. It is called once in the
Expand Down Expand Up @@ -165,12 +167,15 @@ lru_size = 0`
func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
cfg := sdk.GetConfig()
cfg.Seal()
gentxModule := simapp.ModuleBasics[genutiltypes.ModuleName].(genutil.AppModuleBasic)

rootCmd.AddCommand(
genutilcli.InitCmd(simapp.ModuleBasics, simapp.DefaultNodeHome),
genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome),
genutilcli.CollectGenTxsCmd(banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome,
gentxModule.GenTxValidator),
genutilcli.MigrateGenesisCmd(),
genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig, banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome),
genutilcli.GenTxCmd(simapp.ModuleBasics, encodingConfig.TxConfig,
banktypes.GenesisBalancesIterator{}, simapp.DefaultNodeHome),
genutilcli.ValidateGenesisCmd(simapp.ModuleBasics),
AddGenesisAccountCmd(simapp.DefaultNodeHome),
tmcli.NewCompletionCmd(rootCmd, true),
Expand Down
2 changes: 1 addition & 1 deletion simapp/simd/cmd/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ func collectGenFiles(
return err
}

nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.Codec, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, genBalIterator)
nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.Codec, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, genBalIterator, genutiltypes.DefaultMessageValidator)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion testutil/network/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error {
}

appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig,
tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{})
tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{}, genutiltypes.DefaultMessageValidator)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions x/genutil/client/cli/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
const flagGenTxDir = "gentx-dir"

// CollectGenTxsCmd - return the cobra command to collect genesis transactions
func CollectGenTxsCmd(genBalIterator types.GenesisBalancesIterator, defaultNodeHome string) *cobra.Command {
func CollectGenTxsCmd(genBalIterator types.GenesisBalancesIterator, defaultNodeHome string, validator types.MessageValidator) *cobra.Command {
cmd := &cobra.Command{
Use: "collect-gentxs",
Short: "Collect genesis txs and output a genesis.json file",
Expand Down Expand Up @@ -52,7 +52,7 @@ func CollectGenTxsCmd(genBalIterator types.GenesisBalancesIterator, defaultNodeH

appMessage, err := genutil.GenAppStateFromConfig(cdc,
clientCtx.TxConfig,
config, initCfg, *genDoc, genBalIterator)
config, initCfg, *genDoc, genBalIterator, validator)
if err != nil {
return errors.Wrap(err, "failed to get genesis app state from config")
}
Expand Down
7 changes: 4 additions & 3 deletions x/genutil/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import (
// GenAppStateFromConfig gets the genesis app state from the config
func GenAppStateFromConfig(cdc codec.JSONCodec, txEncodingConfig client.TxEncodingConfig,
config *cfg.Config, initCfg types.InitConfig, genDoc tmtypes.GenesisDoc, genBalIterator types.GenesisBalancesIterator,
validator types.MessageValidator,
) (appState json.RawMessage, err error) {
// process genesis transactions, else create default genesis.json
appGenTxs, persistentPeers, err := CollectTxs(
cdc, txEncodingConfig.TxJSONDecoder(), config.Moniker, initCfg.GenTxsDir, genDoc, genBalIterator,
)
cdc, txEncodingConfig.TxJSONDecoder(), config.Moniker, initCfg.GenTxsDir, genDoc, genBalIterator, validator)
if err != nil {
return appState, err
}
Expand Down Expand Up @@ -69,6 +69,7 @@ func GenAppStateFromConfig(cdc codec.JSONCodec, txEncodingConfig client.TxEncodi
// the list of appGenTxs, and persistent peers required to generate genesis.json.
func CollectTxs(cdc codec.JSONCodec, txJSONDecoder sdk.TxDecoder, moniker, genTxsDir string,
genDoc tmtypes.GenesisDoc, genBalIterator types.GenesisBalancesIterator,
validator types.MessageValidator,
) (appGenTxs []sdk.Tx, persistentPeers string, err error) {
// prepare a map of all balances in genesis state to then validate
// against the validators addresses
Expand Down Expand Up @@ -110,7 +111,7 @@ func CollectTxs(cdc codec.JSONCodec, txJSONDecoder sdk.TxDecoder, moniker, genTx
return appGenTxs, persistentPeers, err
}

genTx, err := types.ValidateAndGetGenTx(jsonRawTx, txJSONDecoder)
genTx, err := types.ValidateAndGetGenTx(jsonRawTx, txJSONDecoder, validator)
if err != nil {
return appGenTxs, persistentPeers, err
}
Expand Down
2 changes: 1 addition & 1 deletion x/genutil/collect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestCollectTxsHandlesDirectories(t *testing.T) {
balItr := new(doNothingIterator)

dnc := &doNothingUnmarshalJSON{cdc}
if _, _, err := genutil.CollectTxs(dnc, txDecoder, "foo", testDir, gdoc, balItr); err != nil {
if _, _, err := genutil.CollectTxs(dnc, txDecoder, "foo", testDir, gdoc, balItr, gtypes.DefaultMessageValidator); err != nil {
t.Fatal(err)
}
}
12 changes: 10 additions & 2 deletions x/genutil/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ var (
)

// AppModuleBasic defines the basic application module used by the genutil module.
type AppModuleBasic struct{}
type AppModuleBasic struct {
GenTxValidator types.MessageValidator
}

// NewAppModuleBasic creates AppModuleBasic, validator is a function used to validate genesis
// transactions.
func NewAppModuleBasic(validator types.MessageValidator) AppModuleBasic {
return AppModuleBasic{validator}
}

// Name returns the genutil module's name.
func (AppModuleBasic) Name() string {
Expand All @@ -53,7 +61,7 @@ func (b AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, txEncodingConfig cl
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}

return types.ValidateGenesis(&data, txEncodingConfig.TxJSONDecoder())
return types.ValidateGenesis(&data, txEncodingConfig.TxJSONDecoder(), b.GenTxValidator)
}

// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the genutil module.
Expand Down
36 changes: 19 additions & 17 deletions x/genutil/types/genesis_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,37 +93,39 @@ func GenesisStateFromGenFile(genFile string) (genesisState map[string]json.RawMe
}

// ValidateGenesis validates GenTx transactions
func ValidateGenesis(genesisState *GenesisState, txJSONDecoder sdk.TxDecoder) error {
func ValidateGenesis(genesisState *GenesisState, txJSONDecoder sdk.TxDecoder, validator MessageValidator) error {
for _, genTx := range genesisState.GenTxs {
_, err := ValidateAndGetGenTx(genTx, txJSONDecoder)
_, err := ValidateAndGetGenTx(genTx, txJSONDecoder, validator)
if err != nil {
return err
}
}
return nil
}

// ValidateAndGetGenTx validates the genesis transaction and returns GenTx if valid
// it cannot verify the signature as it is stateless validation
func ValidateAndGetGenTx(genTx json.RawMessage, txJSONDecoder sdk.TxDecoder) (sdk.Tx, error) {
tx, err := txJSONDecoder(genTx)
if err != nil {
return tx, fmt.Errorf("failed to decode gentx: %s, error: %s", genTx, err)
}
type MessageValidator func([]sdk.Msg) error

msgs := tx.GetMsgs()
func DefaultMessageValidator(msgs []sdk.Msg) error {
if len(msgs) != 1 {
return tx, fmt.Errorf("unexpected number of GenTx messages; got: %d, expected: 1", len(msgs))
return fmt.Errorf("unexpected number of GenTx messages; got: %d, expected: 1", len(msgs))
}

// TODO: abstract back to staking
if _, ok := msgs[0].(*stakingtypes.MsgCreateValidator); !ok {
return tx, fmt.Errorf("unexpected GenTx message type; expected: MsgCreateValidator, got: %T", msgs[0])
return fmt.Errorf("unexpected GenTx message type; expected: MsgCreateValidator, got: %T", msgs[0])
}

if err := msgs[0].ValidateBasic(); err != nil {
return tx, fmt.Errorf("invalid GenTx '%s': %s", msgs[0], err)
return fmt.Errorf("invalid GenTx '%s': %w", msgs[0], err)
}

return nil
}

// ValidateAndGetGenTx validates the genesis transaction and returns GenTx if valid
// it cannot verify the signature as it is stateless validation
func ValidateAndGetGenTx(genTx json.RawMessage, txJSONDecoder sdk.TxDecoder, validator MessageValidator) (sdk.Tx, error) {
tx, err := txJSONDecoder(genTx)
if err != nil {
return tx, fmt.Errorf("failed to decode gentx: %s, error: %s", genTx, err)
}

return tx, nil
return tx, validator(tx.GetMsgs())
}
4 changes: 2 additions & 2 deletions x/genutil/types/genesis_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestValidateGenesisMultipleMessages(t *testing.T) {
tx := txBuilder.GetTx()
genesisState := types.NewGenesisStateFromTx(txConfig.TxJSONEncoder(), []sdk.Tx{tx})

err = types.ValidateGenesis(genesisState, txConfig.TxJSONDecoder())
err = types.ValidateGenesis(genesisState, txConfig.TxJSONDecoder(), types.DefaultMessageValidator)
require.Error(t, err)
}

Expand All @@ -72,7 +72,7 @@ func TestValidateGenesisBadMessage(t *testing.T) {
tx := txBuilder.GetTx()
genesisState := types.NewGenesisStateFromTx(txConfig.TxJSONEncoder(), []sdk.Tx{tx})

err = types.ValidateGenesis(genesisState, txConfig.TxJSONDecoder())
err = types.ValidateGenesis(genesisState, txConfig.TxJSONDecoder(), types.DefaultMessageValidator)
require.Error(t, err)
}

Expand Down

0 comments on commit 3dcb8f9

Please sign in to comment.