From f8e2d984b2b233d9562bc8c412d2a670e5a614ee Mon Sep 17 00:00:00 2001 From: Matt Kocubinski Date: Mon, 10 Apr 2023 16:39:59 -0500 Subject: [PATCH] feat(x/tx): tx decoder (#15414) Co-authored-by: Aaron Craelius --- tests/integration/aminojson/aminojson_test.go | 262 +--------------- tests/integration/rapidgen/rapidgen.go | 292 ++++++++++++++++++ tests/integration/tx/.gitignore | 1 + tests/integration/tx/decode_test.go | 139 +++++++++ x/tx/CHANGELOG.md | 4 + x/tx/decode/adr027.go | 88 ++++++ x/tx/decode/decode.go | 141 +++++++++ x/tx/decode/decode_test.go | 102 ++++++ x/tx/decode/errors.go | 6 +- 9 files changed, 780 insertions(+), 255 deletions(-) create mode 100644 tests/integration/rapidgen/rapidgen.go create mode 100644 tests/integration/tx/.gitignore create mode 100644 tests/integration/tx/decode_test.go create mode 100644 x/tx/decode/adr027.go create mode 100644 x/tx/decode/decode.go create mode 100644 x/tx/decode/decode_test.go diff --git a/tests/integration/aminojson/aminojson_test.go b/tests/integration/aminojson/aminojson_test.go index bae33a8bdd..25b714b6a7 100644 --- a/tests/integration/aminojson/aminojson_test.go +++ b/tests/integration/aminojson/aminojson_test.go @@ -7,11 +7,8 @@ import ( "testing" "time" - cosmos_proto "github.com/cosmos/cosmos-proto" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -20,42 +17,31 @@ import ( "github.com/cosmos/cosmos-proto/rapidproto" gogoproto "github.com/cosmos/gogoproto/proto" - "cosmossdk.io/api/amino" authapi "cosmossdk.io/api/cosmos/auth/v1beta1" authzapi "cosmossdk.io/api/cosmos/authz/v1beta1" bankapi "cosmossdk.io/api/cosmos/bank/v1beta1" v1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" - consensusapi "cosmossdk.io/api/cosmos/consensus/v1" "cosmossdk.io/api/cosmos/crypto/ed25519" multisigapi "cosmossdk.io/api/cosmos/crypto/multisig" "cosmossdk.io/api/cosmos/crypto/secp256k1" distapi "cosmossdk.io/api/cosmos/distribution/v1beta1" - evidenceapi "cosmossdk.io/api/cosmos/evidence/v1beta1" - feegrantapi "cosmossdk.io/api/cosmos/feegrant/v1beta1" - gov_v1_api "cosmossdk.io/api/cosmos/gov/v1" gov_v1beta1_api "cosmossdk.io/api/cosmos/gov/v1beta1" - groupapi "cosmossdk.io/api/cosmos/group/v1" - mintapi "cosmossdk.io/api/cosmos/mint/v1beta1" - paramsapi "cosmossdk.io/api/cosmos/params/v1beta1" slashingapi "cosmossdk.io/api/cosmos/slashing/v1beta1" stakingapi "cosmossdk.io/api/cosmos/staking/v1beta1" txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1" - upgradeapi "cosmossdk.io/api/cosmos/upgrade/v1beta1" vestingapi "cosmossdk.io/api/cosmos/vesting/v1beta1" "cosmossdk.io/x/evidence" - evidencetypes "cosmossdk.io/x/evidence/types" - feegranttypes "cosmossdk.io/x/feegrant" feegrantmodule "cosmossdk.io/x/feegrant/module" "cosmossdk.io/x/tx/signing/aminojson" signing_testutil "cosmossdk.io/x/tx/signing/testutil" "cosmossdk.io/x/upgrade" - upgradetypes "cosmossdk.io/x/upgrade/types" codectypes "github.com/cosmos/cosmos-sdk/codec/types" ed25519types "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" secp256k1types "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" gogo_testpb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/gogo/testpb" pulsar_testpb "github.com/cosmos/cosmos-sdk/tests/integration/aminojson/internal/pulsar/testpb" + "github.com/cosmos/cosmos-sdk/tests/integration/rapidgen" "github.com/cosmos/cosmos-sdk/testutil/testdata" "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/bech32" @@ -72,251 +58,19 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/consensus" - consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" "github.com/cosmos/cosmos-sdk/x/distribution" disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/gov" - gov_v1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" gov_v1beta1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" - grouptypes "github.com/cosmos/cosmos-sdk/x/group" groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" "github.com/cosmos/cosmos-sdk/x/mint" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/params/types/proposal" "github.com/cosmos/cosmos-sdk/x/slashing" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -type generatedType struct { - pulsar proto.Message - gogo gogoproto.Message - opts rapidproto.GeneratorOptions -} - -func genType(gogo gogoproto.Message, pulsar proto.Message, opts rapidproto.GeneratorOptions) generatedType { - return generatedType{ - pulsar: pulsar, - gogo: gogo, - opts: opts, - } -} - -func withDecisionPolicy(opts rapidproto.GeneratorOptions) rapidproto.GeneratorOptions { - return opts. - WithAnyTypes( - &groupapi.ThresholdDecisionPolicy{}, - &groupapi.PercentageDecisionPolicy{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.ThresholdDecisionPolicy{}). - WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.PercentageDecisionPolicy{}) -} - -func generatorFieldMapper(t *rapid.T, field protoreflect.FieldDescriptor, name string) (protoreflect.Value, bool) { - opts := field.Options() - switch { - case proto.HasExtension(opts, cosmos_proto.E_Scalar): - scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string) - switch scalar { - case "cosmos.Int": - i32 := rapid.Int32().Draw(t, name) - return protoreflect.ValueOfString(fmt.Sprintf("%d", i32)), true - case "cosmos.Dec": - return protoreflect.ValueOfString(""), true - } - case field.Kind() == protoreflect.BytesKind: - if proto.HasExtension(opts, amino.E_Encoding) { - encoding := proto.GetExtension(opts, amino.E_Encoding).(string) - if encoding == "cosmos_dec_bytes" { - return protoreflect.ValueOfBytes([]byte{}), true - } - } - } - - return protoreflect.Value{}, false -} - -var ( - genOpts = rapidproto.GeneratorOptions{ - Resolver: protoregistry.GlobalTypes, - FieldMaps: []rapidproto.FieldMapper{generatorFieldMapper}, - } - genTypes = []generatedType{ - // auth - genType(&authtypes.Params{}, &authapi.Params{}, genOpts), - genType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, genOpts.WithAnyTypes(&ed25519.PubKey{})), - genType(&authtypes.ModuleAccount{}, &authapi.ModuleAccount{}, genOpts.WithAnyTypes(&ed25519.PubKey{})), - genType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, genOpts), - genType(&authtypes.MsgUpdateParams{}, &authapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - - // authz - genType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, genOpts), - genType(&authztypes.Grant{}, &authzapi.Grant{}, - genOpts.WithAnyTypes(&authzapi.GenericAuthorization{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}), - ), - genType(&authztypes.MsgGrant{}, &authzapi.MsgGrant{}, - genOpts.WithAnyTypes(&authzapi.GenericAuthorization{}). - WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}). - WithDisallowNil(), - ), - genType(&authztypes.MsgExec{}, &authzapi.MsgExec{}, - genOpts.WithAnyTypes(&authzapi.MsgGrant{}, &authzapi.GenericAuthorization{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}). - WithInterfaceHint("cosmos.base.v1beta1.Msg", &authzapi.MsgGrant{}), - ), - - // bank - genType(&banktypes.MsgSend{}, &bankapi.MsgSend{}, genOpts.WithDisallowNil()), - genType(&banktypes.MsgMultiSend{}, &bankapi.MsgMultiSend{}, genOpts.WithDisallowNil()), - genType(&banktypes.MsgUpdateParams{}, &bankapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - genType(&banktypes.MsgSetSendEnabled{}, &bankapi.MsgSetSendEnabled{}, genOpts), - genType(&banktypes.SendAuthorization{}, &bankapi.SendAuthorization{}, genOpts), - genType(&banktypes.Params{}, &bankapi.Params{}, genOpts), - - // consensus - genType(&consensustypes.MsgUpdateParams{}, &consensusapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - - // crypto - genType(&multisig.LegacyAminoPubKey{}, &multisigapi.LegacyAminoPubKey{}, - genOpts.WithAnyTypes(&ed25519.PubKey{}, &secp256k1.PubKey{})), - - // distribution - genType(&disttypes.MsgWithdrawDelegatorReward{}, &distapi.MsgWithdrawDelegatorReward{}, genOpts), - genType(&disttypes.MsgWithdrawValidatorCommission{}, &distapi.MsgWithdrawValidatorCommission{}, genOpts), - genType(&disttypes.MsgSetWithdrawAddress{}, &distapi.MsgSetWithdrawAddress{}, genOpts), - genType(&disttypes.MsgFundCommunityPool{}, &distapi.MsgFundCommunityPool{}, genOpts), - genType(&disttypes.MsgUpdateParams{}, &distapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - genType(&disttypes.MsgCommunityPoolSpend{}, &distapi.MsgCommunityPoolSpend{}, genOpts), - genType(&disttypes.MsgDepositValidatorRewardsPool{}, &distapi.MsgDepositValidatorRewardsPool{}, genOpts), - genType(&disttypes.Params{}, &distapi.Params{}, genOpts), - - // evidence - genType(&evidencetypes.Equivocation{}, &evidenceapi.Equivocation{}, genOpts.WithDisallowNil()), - genType(&evidencetypes.MsgSubmitEvidence{}, &evidenceapi.MsgSubmitEvidence{}, - genOpts.WithAnyTypes(&evidenceapi.Equivocation{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.evidence.v1beta1.Evidence", &evidenceapi.Equivocation{})), - - // feegrant - genType(&feegranttypes.MsgGrantAllowance{}, &feegrantapi.MsgGrantAllowance{}, - genOpts.WithDisallowNil(). - WithAnyTypes( - &feegrantapi.BasicAllowance{}, - &feegrantapi.PeriodicAllowance{}). - WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}). - WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}), - ), - genType(&feegranttypes.MsgRevokeAllowance{}, &feegrantapi.MsgRevokeAllowance{}, genOpts), - genType(&feegranttypes.BasicAllowance{}, &feegrantapi.BasicAllowance{}, genOpts.WithDisallowNil()), - genType(&feegranttypes.PeriodicAllowance{}, &feegrantapi.PeriodicAllowance{}, genOpts.WithDisallowNil()), - genType(&feegranttypes.AllowedMsgAllowance{}, &feegrantapi.AllowedMsgAllowance{}, - genOpts.WithDisallowNil(). - WithAnyTypes( - &feegrantapi.BasicAllowance{}, - &feegrantapi.PeriodicAllowance{}). - WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}). - WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}), - ), - - // gov v1beta1 - genType(&gov_v1beta1_types.MsgSubmitProposal{}, &gov_v1beta1_api.MsgSubmitProposal{}, - genOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}), - ), - genType(&gov_v1beta1_types.MsgDeposit{}, &gov_v1beta1_api.MsgDeposit{}, genOpts), - genType(&gov_v1beta1_types.MsgVote{}, &gov_v1beta1_api.MsgVote{}, genOpts), - genType(&gov_v1beta1_types.MsgVoteWeighted{}, &gov_v1beta1_api.MsgVoteWeighted{}, genOpts), - genType(&gov_v1beta1_types.TextProposal{}, &gov_v1beta1_api.TextProposal{}, genOpts), - - // gov v1 - genType(&gov_v1_types.MsgSubmitProposal{}, &gov_v1_api.MsgSubmitProposal{}, - genOpts.WithAnyTypes(&gov_v1_api.MsgVote{}, &gov_v1_api.MsgVoteWeighted{}, &gov_v1_api.MsgDeposit{}, - &gov_v1_api.MsgExecLegacyContent{}, &gov_v1_api.MsgUpdateParams{}). - WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}). - WithDisallowNil(), - ), - genType(&gov_v1_types.MsgDeposit{}, &gov_v1_api.MsgDeposit{}, genOpts), - genType(&gov_v1_types.MsgVote{}, &gov_v1_api.MsgVote{}, genOpts), - genType(&gov_v1_types.MsgVoteWeighted{}, &gov_v1_api.MsgVoteWeighted{}, genOpts), - genType(&gov_v1_types.MsgExecLegacyContent{}, &gov_v1_api.MsgExecLegacyContent{}, - genOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}). - WithDisallowNil(). - WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{})), - genType(&gov_v1_types.MsgUpdateParams{}, &gov_v1_api.MsgUpdateParams{}, genOpts.WithDisallowNil()), - - // group - genType(&grouptypes.MsgCreateGroup{}, &groupapi.MsgCreateGroup{}, genOpts), - genType(&grouptypes.MsgUpdateGroupMembers{}, &groupapi.MsgUpdateGroupMembers{}, genOpts), - genType(&grouptypes.MsgUpdateGroupAdmin{}, &groupapi.MsgUpdateGroupAdmin{}, genOpts), - genType(&grouptypes.MsgUpdateGroupMetadata{}, &groupapi.MsgUpdateGroupMetadata{}, genOpts), - genType(&grouptypes.MsgCreateGroupWithPolicy{}, &groupapi.MsgCreateGroupWithPolicy{}, - withDecisionPolicy(genOpts)), - genType(&grouptypes.MsgCreateGroupPolicy{}, &groupapi.MsgCreateGroupPolicy{}, - withDecisionPolicy(genOpts)), - genType(&grouptypes.MsgUpdateGroupPolicyAdmin{}, &groupapi.MsgUpdateGroupPolicyAdmin{}, genOpts), - genType(&grouptypes.MsgUpdateGroupPolicyDecisionPolicy{}, &groupapi.MsgUpdateGroupPolicyDecisionPolicy{}, - withDecisionPolicy(genOpts)), - genType(&grouptypes.MsgUpdateGroupPolicyMetadata{}, &groupapi.MsgUpdateGroupPolicyMetadata{}, genOpts), - genType(&grouptypes.MsgSubmitProposal{}, &groupapi.MsgSubmitProposal{}, - genOpts.WithDisallowNil(). - WithAnyTypes(&groupapi.MsgCreateGroup{}, &groupapi.MsgUpdateGroupMembers{}). - WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgCreateGroup{}). - WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgUpdateGroupMembers{}), - ), - genType(&grouptypes.MsgVote{}, &groupapi.MsgVote{}, genOpts), - genType(&grouptypes.MsgExec{}, &groupapi.MsgExec{}, genOpts), - genType(&grouptypes.MsgLeaveGroup{}, &groupapi.MsgLeaveGroup{}, genOpts), - - // mint - genType(&minttypes.Params{}, &mintapi.Params{}, genOpts), - genType(&minttypes.MsgUpdateParams{}, &mintapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - - // params - genType(&proposal.ParameterChangeProposal{}, ¶msapi.ParameterChangeProposal{}, genOpts), - - // slashing - genType(&slashingtypes.Params{}, &slashingapi.Params{}, genOpts.WithDisallowNil()), - genType(&slashingtypes.MsgUnjail{}, &slashingapi.MsgUnjail{}, genOpts), - genType(&slashingtypes.MsgUpdateParams{}, &slashingapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - - // staking - genType(&stakingtypes.MsgCreateValidator{}, &stakingapi.MsgCreateValidator{}, - genOpts.WithDisallowNil(). - WithAnyTypes(&ed25519.PubKey{}). - WithInterfaceHint("cosmos.crypto.PubKey", &ed25519.PubKey{}), - ), - genType(&stakingtypes.MsgEditValidator{}, &stakingapi.MsgEditValidator{}, genOpts.WithDisallowNil()), - genType(&stakingtypes.MsgDelegate{}, &stakingapi.MsgDelegate{}, genOpts.WithDisallowNil()), - genType(&stakingtypes.MsgUndelegate{}, &stakingapi.MsgUndelegate{}, genOpts.WithDisallowNil()), - genType(&stakingtypes.MsgBeginRedelegate{}, &stakingapi.MsgBeginRedelegate{}, genOpts.WithDisallowNil()), - genType(&stakingtypes.MsgUpdateParams{}, &stakingapi.MsgUpdateParams{}, genOpts.WithDisallowNil()), - genType(&stakingtypes.StakeAuthorization{}, &stakingapi.StakeAuthorization{}, genOpts), - - // upgrade - genType(&upgradetypes.CancelSoftwareUpgradeProposal{}, &upgradeapi.CancelSoftwareUpgradeProposal{}, genOpts), // nolint:staticcheck // testing legacy code path - genType(&upgradetypes.SoftwareUpgradeProposal{}, &upgradeapi.SoftwareUpgradeProposal{}, genOpts.WithDisallowNil()), // nolint:staticcheck // testing legacy code path - genType(&upgradetypes.Plan{}, &upgradeapi.Plan{}, genOpts.WithDisallowNil()), - genType(&upgradetypes.MsgSoftwareUpgrade{}, &upgradeapi.MsgSoftwareUpgrade{}, genOpts.WithDisallowNil()), - genType(&upgradetypes.MsgCancelUpgrade{}, &upgradeapi.MsgCancelUpgrade{}, genOpts), - - // vesting - genType(&vestingtypes.BaseVestingAccount{}, &vestingapi.BaseVestingAccount{}, genOpts.WithDisallowNil()), - genType(&vestingtypes.ContinuousVestingAccount{}, &vestingapi.ContinuousVestingAccount{}, genOpts.WithDisallowNil()), - genType(&vestingtypes.DelayedVestingAccount{}, &vestingapi.DelayedVestingAccount{}, genOpts.WithDisallowNil()), - genType(&vestingtypes.PeriodicVestingAccount{}, &vestingapi.PeriodicVestingAccount{}, genOpts.WithDisallowNil()), - genType(&vestingtypes.PermanentLockedAccount{}, &vestingapi.PermanentLockedAccount{}, genOpts.WithDisallowNil()), - genType(&vestingtypes.MsgCreateVestingAccount{}, &vestingapi.MsgCreateVestingAccount{}, genOpts), - genType(&vestingtypes.MsgCreatePermanentLockedAccount{}, &vestingapi.MsgCreatePermanentLockedAccount{}, genOpts), - genType(&vestingtypes.MsgCreatePeriodicVestingAccount{}, &vestingapi.MsgCreatePeriodicVestingAccount{}, genOpts), - } -) - // TestAminoJSON_Equivalence tests that x/tx/Encoder encoding is equivalent to the legacy Encoder encoding. // A custom generator is used to generate random messages that are then encoded using both encoders. The custom // generator only supports proto.Message (which implement the protoreflect API) so in order to test legacy gogo types @@ -339,11 +93,11 @@ func TestAminoJSON_Equivalence(t *testing.T) { slashing.AppModuleBasic{}, staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, vesting.AppModuleBasic{}) aj := aminojson.NewAminoJSON() - for _, tt := range genTypes { - name := string(tt.pulsar.ProtoReflect().Descriptor().FullName()) + for _, tt := range rapidgen.DefaultGeneratedTypes { + name := string(tt.Pulsar.ProtoReflect().Descriptor().FullName()) t.Run(name, func(t *testing.T) { - gen := rapidproto.MessageGenerator(tt.pulsar, tt.opts) - fmt.Printf("testing %s\n", tt.pulsar.ProtoReflect().Descriptor().FullName()) + gen := rapidproto.MessageGenerator(tt.Pulsar, tt.Opts) + fmt.Printf("testing %s\n", tt.Pulsar.ProtoReflect().Descriptor().FullName()) rapid.Check(t, func(t *rapid.T) { // uncomment to debug; catch a panic and inspect application state // defer func() { @@ -356,8 +110,8 @@ func TestAminoJSON_Equivalence(t *testing.T) { msg := gen.Draw(t, "msg") postFixPulsarMessage(msg) - gogo := tt.gogo - sanity := tt.pulsar + gogo := tt.Gogo + sanity := tt.Pulsar protoBz, err := proto.Marshal(msg) require.NoError(t, err) @@ -384,7 +138,7 @@ func TestAminoJSON_Equivalence(t *testing.T) { handlerOptions := signing_testutil.HandlerArgumentOptions{ ChainID: "test-chain", Memo: "sometestmemo", - Msg: tt.pulsar, + Msg: tt.Pulsar, AccNum: 1, AccSeq: 2, SignerAddress: "signerAddress", diff --git a/tests/integration/rapidgen/rapidgen.go b/tests/integration/rapidgen/rapidgen.go new file mode 100644 index 0000000000..607ebaed25 --- /dev/null +++ b/tests/integration/rapidgen/rapidgen.go @@ -0,0 +1,292 @@ +package rapidgen + +import ( + "fmt" + + cosmos_proto "github.com/cosmos/cosmos-proto" + "github.com/cosmos/cosmos-proto/rapidproto" + gogoproto "github.com/cosmos/gogoproto/proto" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "pgregory.net/rapid" + + "cosmossdk.io/api/amino" + authapi "cosmossdk.io/api/cosmos/auth/v1beta1" + authzapi "cosmossdk.io/api/cosmos/authz/v1beta1" + bankapi "cosmossdk.io/api/cosmos/bank/v1beta1" + consensusapi "cosmossdk.io/api/cosmos/consensus/v1" + "cosmossdk.io/api/cosmos/crypto/ed25519" + multisigapi "cosmossdk.io/api/cosmos/crypto/multisig" + "cosmossdk.io/api/cosmos/crypto/secp256k1" + distapi "cosmossdk.io/api/cosmos/distribution/v1beta1" + evidenceapi "cosmossdk.io/api/cosmos/evidence/v1beta1" + feegrantapi "cosmossdk.io/api/cosmos/feegrant/v1beta1" + gov_v1_api "cosmossdk.io/api/cosmos/gov/v1" + gov_v1beta1_api "cosmossdk.io/api/cosmos/gov/v1beta1" + groupapi "cosmossdk.io/api/cosmos/group/v1" + mintapi "cosmossdk.io/api/cosmos/mint/v1beta1" + paramsapi "cosmossdk.io/api/cosmos/params/v1beta1" + slashingapi "cosmossdk.io/api/cosmos/slashing/v1beta1" + stakingapi "cosmossdk.io/api/cosmos/staking/v1beta1" + upgradeapi "cosmossdk.io/api/cosmos/upgrade/v1beta1" + vestingapi "cosmossdk.io/api/cosmos/vesting/v1beta1" + evidencetypes "cosmossdk.io/x/evidence/types" + feegranttypes "cosmossdk.io/x/feegrant" + upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + authztypes "github.com/cosmos/cosmos-sdk/x/authz" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + consensustypes "github.com/cosmos/cosmos-sdk/x/consensus/types" + disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + gov_v1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + gov_v1beta1_types "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + grouptypes "github.com/cosmos/cosmos-sdk/x/group" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/params/types/proposal" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +type GeneratedType struct { + Pulsar proto.Message + Gogo gogoproto.Message + Opts rapidproto.GeneratorOptions +} + +func GenType(gogo gogoproto.Message, pulsar proto.Message, opts rapidproto.GeneratorOptions) GeneratedType { + return GeneratedType{ + Pulsar: pulsar, + Gogo: gogo, + Opts: opts, + } +} + +func WithDecisionPolicy(opts rapidproto.GeneratorOptions) rapidproto.GeneratorOptions { + return opts. + WithAnyTypes( + &groupapi.ThresholdDecisionPolicy{}, + &groupapi.PercentageDecisionPolicy{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.ThresholdDecisionPolicy{}). + WithInterfaceHint("cosmos.group.v1.DecisionPolicy", &groupapi.PercentageDecisionPolicy{}) +} + +func GeneratorFieldMapper(t *rapid.T, field protoreflect.FieldDescriptor, name string) (protoreflect.Value, bool) { + opts := field.Options() + switch { + case proto.HasExtension(opts, cosmos_proto.E_Scalar): + scalar := proto.GetExtension(opts, cosmos_proto.E_Scalar).(string) + switch scalar { + case "cosmos.Int": + i32 := rapid.Int32().Draw(t, name) + return protoreflect.ValueOfString(fmt.Sprintf("%d", i32)), true + case "cosmos.Dec": + return protoreflect.ValueOfString(""), true + } + case field.Kind() == protoreflect.BytesKind: + if proto.HasExtension(opts, amino.E_Encoding) { + encoding := proto.GetExtension(opts, amino.E_Encoding).(string) + if encoding == "cosmos_dec_bytes" { + return protoreflect.ValueOfBytes([]byte{}), true + } + } + } + + return protoreflect.Value{}, false +} + +var ( + GenOpts = rapidproto.GeneratorOptions{ + Resolver: protoregistry.GlobalTypes, + FieldMaps: []rapidproto.FieldMapper{GeneratorFieldMapper}, + } + SignableTypes = []GeneratedType{ + // auth + GenType(&authtypes.MsgUpdateParams{}, &authapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // authz + GenType(&authztypes.MsgGrant{}, &authzapi.MsgGrant{}, + GenOpts.WithAnyTypes(&authzapi.GenericAuthorization{}). + WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}). + WithDisallowNil(), + ), + GenType(&authztypes.MsgExec{}, &authzapi.MsgExec{}, + GenOpts.WithAnyTypes(&authzapi.MsgGrant{}, &authzapi.GenericAuthorization{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}). + WithInterfaceHint("cosmos.base.v1beta1.Msg", &authzapi.MsgGrant{}), + ), + + // bank + GenType(&banktypes.MsgSend{}, &bankapi.MsgSend{}, GenOpts.WithDisallowNil()), + GenType(&banktypes.MsgMultiSend{}, &bankapi.MsgMultiSend{}, GenOpts.WithDisallowNil()), + GenType(&banktypes.MsgUpdateParams{}, &bankapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + GenType(&banktypes.MsgSetSendEnabled{}, &bankapi.MsgSetSendEnabled{}, GenOpts), + + // consensus + GenType(&consensustypes.MsgUpdateParams{}, &consensusapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // distribution + GenType(&disttypes.MsgWithdrawDelegatorReward{}, &distapi.MsgWithdrawDelegatorReward{}, GenOpts), + GenType(&disttypes.MsgWithdrawValidatorCommission{}, &distapi.MsgWithdrawValidatorCommission{}, GenOpts), + GenType(&disttypes.MsgSetWithdrawAddress{}, &distapi.MsgSetWithdrawAddress{}, GenOpts), + GenType(&disttypes.MsgFundCommunityPool{}, &distapi.MsgFundCommunityPool{}, GenOpts), + GenType(&disttypes.MsgUpdateParams{}, &distapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + GenType(&disttypes.MsgCommunityPoolSpend{}, &distapi.MsgCommunityPoolSpend{}, GenOpts), + GenType(&disttypes.MsgDepositValidatorRewardsPool{}, &distapi.MsgDepositValidatorRewardsPool{}, GenOpts), + + // evidence + GenType(&evidencetypes.MsgSubmitEvidence{}, &evidenceapi.MsgSubmitEvidence{}, + GenOpts.WithAnyTypes(&evidenceapi.Equivocation{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.evidence.v1beta1.Evidence", &evidenceapi.Equivocation{})), + + // feegrant + GenType(&feegranttypes.MsgGrantAllowance{}, &feegrantapi.MsgGrantAllowance{}, + GenOpts.WithDisallowNil(). + WithAnyTypes( + &feegrantapi.BasicAllowance{}, + &feegrantapi.PeriodicAllowance{}). + WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}). + WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}), + ), + GenType(&feegranttypes.MsgRevokeAllowance{}, &feegrantapi.MsgRevokeAllowance{}, GenOpts), + + // gov v1beta1 + GenType(&gov_v1beta1_types.MsgSubmitProposal{}, &gov_v1beta1_api.MsgSubmitProposal{}, + GenOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}), + ), + GenType(&gov_v1beta1_types.MsgDeposit{}, &gov_v1beta1_api.MsgDeposit{}, GenOpts), + GenType(&gov_v1beta1_types.MsgVote{}, &gov_v1beta1_api.MsgVote{}, GenOpts), + GenType(&gov_v1beta1_types.MsgVoteWeighted{}, &gov_v1beta1_api.MsgVoteWeighted{}, GenOpts), + + // gov v1 + GenType(&gov_v1_types.MsgSubmitProposal{}, &gov_v1_api.MsgSubmitProposal{}, + GenOpts.WithAnyTypes(&gov_v1_api.MsgVote{}, &gov_v1_api.MsgVoteWeighted{}, &gov_v1_api.MsgDeposit{}, + &gov_v1_api.MsgExecLegacyContent{}, &gov_v1_api.MsgUpdateParams{}). + WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{}). + WithDisallowNil(), + ), + GenType(&gov_v1_types.MsgDeposit{}, &gov_v1_api.MsgDeposit{}, GenOpts), + GenType(&gov_v1_types.MsgVote{}, &gov_v1_api.MsgVote{}, GenOpts), + GenType(&gov_v1_types.MsgVoteWeighted{}, &gov_v1_api.MsgVoteWeighted{}, GenOpts), + GenType(&gov_v1_types.MsgExecLegacyContent{}, &gov_v1_api.MsgExecLegacyContent{}, + GenOpts.WithAnyTypes(&gov_v1beta1_api.TextProposal{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.gov.v1beta1.Content", &gov_v1beta1_api.TextProposal{})), + GenType(&gov_v1_types.MsgUpdateParams{}, &gov_v1_api.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // group + GenType(&grouptypes.MsgCreateGroup{}, &groupapi.MsgCreateGroup{}, GenOpts), + GenType(&grouptypes.MsgUpdateGroupMembers{}, &groupapi.MsgUpdateGroupMembers{}, GenOpts), + GenType(&grouptypes.MsgUpdateGroupAdmin{}, &groupapi.MsgUpdateGroupAdmin{}, GenOpts), + GenType(&grouptypes.MsgUpdateGroupMetadata{}, &groupapi.MsgUpdateGroupMetadata{}, GenOpts), + GenType(&grouptypes.MsgCreateGroupWithPolicy{}, &groupapi.MsgCreateGroupWithPolicy{}, + WithDecisionPolicy(GenOpts)), + GenType(&grouptypes.MsgCreateGroupPolicy{}, &groupapi.MsgCreateGroupPolicy{}, + WithDecisionPolicy(GenOpts)), + GenType(&grouptypes.MsgUpdateGroupPolicyAdmin{}, &groupapi.MsgUpdateGroupPolicyAdmin{}, GenOpts), + GenType(&grouptypes.MsgUpdateGroupPolicyDecisionPolicy{}, &groupapi.MsgUpdateGroupPolicyDecisionPolicy{}, + WithDecisionPolicy(GenOpts)), + GenType(&grouptypes.MsgUpdateGroupPolicyMetadata{}, &groupapi.MsgUpdateGroupPolicyMetadata{}, GenOpts), + GenType(&grouptypes.MsgSubmitProposal{}, &groupapi.MsgSubmitProposal{}, + GenOpts.WithDisallowNil(). + WithAnyTypes(&groupapi.MsgCreateGroup{}, &groupapi.MsgUpdateGroupMembers{}). + WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgCreateGroup{}). + WithInterfaceHint("cosmos.base.v1beta1.Msg", &groupapi.MsgUpdateGroupMembers{}), + ), + GenType(&grouptypes.MsgVote{}, &groupapi.MsgVote{}, GenOpts), + GenType(&grouptypes.MsgExec{}, &groupapi.MsgExec{}, GenOpts), + GenType(&grouptypes.MsgLeaveGroup{}, &groupapi.MsgLeaveGroup{}, GenOpts), + + // mint + GenType(&minttypes.MsgUpdateParams{}, &mintapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // slashing + GenType(&slashingtypes.MsgUnjail{}, &slashingapi.MsgUnjail{}, GenOpts), + GenType(&slashingtypes.MsgUpdateParams{}, &slashingapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // staking + GenType(&stakingtypes.MsgCreateValidator{}, &stakingapi.MsgCreateValidator{}, + GenOpts.WithDisallowNil(). + WithAnyTypes(&ed25519.PubKey{}). + WithInterfaceHint("cosmos.crypto.PubKey", &ed25519.PubKey{}), + ), + GenType(&stakingtypes.MsgEditValidator{}, &stakingapi.MsgEditValidator{}, GenOpts.WithDisallowNil()), + GenType(&stakingtypes.MsgDelegate{}, &stakingapi.MsgDelegate{}, GenOpts.WithDisallowNil()), + GenType(&stakingtypes.MsgUndelegate{}, &stakingapi.MsgUndelegate{}, GenOpts.WithDisallowNil()), + GenType(&stakingtypes.MsgBeginRedelegate{}, &stakingapi.MsgBeginRedelegate{}, GenOpts.WithDisallowNil()), + GenType(&stakingtypes.MsgUpdateParams{}, &stakingapi.MsgUpdateParams{}, GenOpts.WithDisallowNil()), + + // upgrade + GenType(&upgradetypes.MsgSoftwareUpgrade{}, &upgradeapi.MsgSoftwareUpgrade{}, GenOpts.WithDisallowNil()), + GenType(&upgradetypes.MsgCancelUpgrade{}, &upgradeapi.MsgCancelUpgrade{}, GenOpts), + + // vesting + GenType(&vestingtypes.MsgCreateVestingAccount{}, &vestingapi.MsgCreateVestingAccount{}, GenOpts), + GenType(&vestingtypes.MsgCreatePermanentLockedAccount{}, &vestingapi.MsgCreatePermanentLockedAccount{}, GenOpts), + GenType(&vestingtypes.MsgCreatePeriodicVestingAccount{}, &vestingapi.MsgCreatePeriodicVestingAccount{}, GenOpts), + } + NonsignableTypes = []GeneratedType{ + GenType(&authtypes.Params{}, &authapi.Params{}, GenOpts), + GenType(&authtypes.BaseAccount{}, &authapi.BaseAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})), + GenType(&authtypes.ModuleAccount{}, &authapi.ModuleAccount{}, GenOpts.WithAnyTypes(&ed25519.PubKey{})), + GenType(&authtypes.ModuleCredential{}, &authapi.ModuleCredential{}, GenOpts), + + GenType(&authztypes.GenericAuthorization{}, &authzapi.GenericAuthorization{}, GenOpts), + GenType(&authztypes.Grant{}, &authzapi.Grant{}, + GenOpts.WithAnyTypes(&authzapi.GenericAuthorization{}). + WithDisallowNil(). + WithInterfaceHint("cosmos.authz.v1beta1.Authorization", &authzapi.GenericAuthorization{}), + ), + + GenType(&banktypes.SendAuthorization{}, &bankapi.SendAuthorization{}, GenOpts), + GenType(&banktypes.Params{}, &bankapi.Params{}, GenOpts), + + // crypto + GenType(&multisig.LegacyAminoPubKey{}, &multisigapi.LegacyAminoPubKey{}, + GenOpts.WithAnyTypes(&ed25519.PubKey{}, &secp256k1.PubKey{})), + + GenType(&disttypes.Params{}, &distapi.Params{}, GenOpts), + + GenType(&evidencetypes.Equivocation{}, &evidenceapi.Equivocation{}, GenOpts.WithDisallowNil()), + + GenType(&feegranttypes.BasicAllowance{}, &feegrantapi.BasicAllowance{}, GenOpts.WithDisallowNil()), + GenType(&feegranttypes.PeriodicAllowance{}, &feegrantapi.PeriodicAllowance{}, GenOpts.WithDisallowNil()), + GenType(&feegranttypes.AllowedMsgAllowance{}, &feegrantapi.AllowedMsgAllowance{}, + GenOpts.WithDisallowNil(). + WithAnyTypes( + &feegrantapi.BasicAllowance{}, + &feegrantapi.PeriodicAllowance{}). + WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.BasicAllowance{}). + WithInterfaceHint("cosmos.feegrant.v1beta1.FeeAllowanceI", &feegrantapi.PeriodicAllowance{}), + ), + + GenType(&gov_v1beta1_types.TextProposal{}, &gov_v1beta1_api.TextProposal{}, GenOpts), + + GenType(&minttypes.Params{}, &mintapi.Params{}, GenOpts), + + // params + GenType(&proposal.ParameterChangeProposal{}, ¶msapi.ParameterChangeProposal{}, GenOpts), + + GenType(&slashingtypes.Params{}, &slashingapi.Params{}, GenOpts.WithDisallowNil()), + + GenType(&stakingtypes.StakeAuthorization{}, &stakingapi.StakeAuthorization{}, GenOpts), + + GenType(&upgradetypes.CancelSoftwareUpgradeProposal{}, &upgradeapi.CancelSoftwareUpgradeProposal{}, GenOpts), // nolint:staticcheck // testing legacy code path + GenType(&upgradetypes.SoftwareUpgradeProposal{}, &upgradeapi.SoftwareUpgradeProposal{}, GenOpts.WithDisallowNil()), // nolint:staticcheck // testing legacy code path + GenType(&upgradetypes.Plan{}, &upgradeapi.Plan{}, GenOpts.WithDisallowNil()), + + GenType(&vestingtypes.BaseVestingAccount{}, &vestingapi.BaseVestingAccount{}, GenOpts.WithDisallowNil()), + GenType(&vestingtypes.ContinuousVestingAccount{}, &vestingapi.ContinuousVestingAccount{}, GenOpts.WithDisallowNil()), + GenType(&vestingtypes.DelayedVestingAccount{}, &vestingapi.DelayedVestingAccount{}, GenOpts.WithDisallowNil()), + GenType(&vestingtypes.PermanentLockedAccount{}, &vestingapi.PermanentLockedAccount{}, GenOpts.WithDisallowNil()), + GenType(&vestingtypes.PeriodicVestingAccount{}, &vestingapi.PeriodicVestingAccount{}, GenOpts.WithDisallowNil()), + } + DefaultGeneratedTypes = append(SignableTypes, NonsignableTypes...) +) diff --git a/tests/integration/tx/.gitignore b/tests/integration/tx/.gitignore new file mode 100644 index 0000000000..9309455bc5 --- /dev/null +++ b/tests/integration/tx/.gitignore @@ -0,0 +1 @@ +testdata/rapid diff --git a/tests/integration/tx/decode_test.go b/tests/integration/tx/decode_test.go new file mode 100644 index 0000000000..b2a71ef8c3 --- /dev/null +++ b/tests/integration/tx/decode_test.go @@ -0,0 +1,139 @@ +package tx + +import ( + "testing" + + "github.com/cosmos/cosmos-proto/rapidproto" + gogoproto "github.com/cosmos/gogoproto/proto" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "pgregory.net/rapid" + + "cosmossdk.io/x/evidence" + feegrantmodule "cosmossdk.io/x/feegrant/module" + "cosmossdk.io/x/tx/decode" + "cosmossdk.io/x/upgrade" + "github.com/cosmos/cosmos-sdk/codec/legacy" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/tests/integration/rapidgen" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module/testutil" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/consensus" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + groupmodule "github.com/cosmos/cosmos-sdk/x/group/module" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" +) + +// TestDecode tests that the tx decoder can decode all the txs in the test suite. +func TestDecode(t *testing.T) { + encCfg := testutil.MakeTestEncodingConfig( + auth.AppModuleBasic{}, authzmodule.AppModuleBasic{}, bank.AppModuleBasic{}, consensus.AppModuleBasic{}, + distribution.AppModuleBasic{}, evidence.AppModuleBasic{}, feegrantmodule.AppModuleBasic{}, + gov.AppModuleBasic{}, groupmodule.AppModuleBasic{}, mint.AppModuleBasic{}, params.AppModuleBasic{}, + slashing.AppModuleBasic{}, staking.AppModuleBasic{}, upgrade.AppModuleBasic{}, vesting.AppModuleBasic{}) + + fee := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))) + gas := uint64(200) + memo := "memo" + accSeq := uint64(2) + + _, pubkey, _ := testdata.KeyTestPubAddr() + anyPk, _ := codectypes.NewAnyWithValue(pubkey) + var signerInfo []*txtypes.SignerInfo + signerInfo = append(signerInfo, &txtypes.SignerInfo{ + PublicKey: anyPk, + ModeInfo: &txtypes.ModeInfo{ + Sum: &txtypes.ModeInfo_Single_{ + Single: &txtypes.ModeInfo_Single{ + Mode: signing.SignMode_SIGN_MODE_DIRECT, + }, + }, + }, + Sequence: accSeq, + }) + + authInfo := &txtypes.AuthInfo{ + Fee: &txtypes.Fee{Amount: fee, GasLimit: gas}, + SignerInfos: signerInfo, + } + + authInfoBytes, err := gogoproto.Marshal(authInfo) + require.NoError(t, err) + + for _, tt := range rapidgen.SignableTypes { + name := string(tt.Pulsar.ProtoReflect().Descriptor().FullName()) + t.Run(name, func(t *testing.T) { + gen := rapidproto.MessageGenerator(tt.Pulsar, tt.Opts) + rapid.Check(t, func(t *rapid.T) { + msg := gen.Draw(t, "msg") + gogo := tt.Gogo + sanity := tt.Pulsar + + protoBz, err := proto.Marshal(msg) + require.NoError(t, err) + + err = proto.Unmarshal(protoBz, sanity) + require.NoError(t, err) + + err = encCfg.Codec.Unmarshal(protoBz, gogo) + require.NoError(t, err) + + txBuilder := encCfg.TxConfig.NewTxBuilder() + + sig := signing.SignatureV2{ + PubKey: pubkey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: legacy.Cdc.MustMarshal(pubkey), + }, + Sequence: accSeq, + } + + gogoMsg, ok := gogo.(sdk.Msg) + require.True(t, ok) + + err = txBuilder.SetMsgs(gogoMsg) + txBuilder.SetFeeAmount(fee) + txBuilder.SetGasLimit(gas) + txBuilder.SetMemo(memo) + err = txBuilder.SetSignatures(sig) + require.NoError(t, err) + + tx := txBuilder.GetTx() + txBytes, err := encCfg.TxConfig.TxEncoder()(tx) + decodeCtx, err := decode.NewDecoder(decode.Options{}) + require.NoError(t, err) + decodedTx, err := decodeCtx.Decode(txBytes) + require.NoError(t, err) + require.NotNil(t, decodedTx) + + require.Equal(t, authInfoBytes, decodedTx.TxRaw.AuthInfoBytes) + + anyGogoMsg, err := codectypes.NewAnyWithValue(gogoMsg) + require.NoError(t, err) + + txBody := &txtypes.TxBody{ + Memo: memo, + Messages: []*codectypes.Any{ + anyGogoMsg, + }, + } + bodyBytes, err := gogoproto.Marshal(txBody) + require.NoError(t, err) + + require.Equal(t, bodyBytes, decodedTx.TxRaw.BodyBytes) + }) + }) + } +} diff --git a/x/tx/CHANGELOG.md b/x/tx/CHANGELOG.md index dc3ac1bd12..dd956831e6 100644 --- a/x/tx/CHANGELOG.md +++ b/x/tx/CHANGELOG.md @@ -31,6 +31,10 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## Unreleased +### Features + +* [#15414](https://github.com/cosmos/cosmos-sdk/pull/15414) Add basic transaction decoding support. + ### API Breaking * [#15581](https://github.com/cosmos/cosmos-sdk/pull/15581) `GetSignersOptions` and `directaux.SignModeHandlerOptions` now diff --git a/x/tx/decode/adr027.go b/x/tx/decode/adr027.go new file mode 100644 index 0000000000..0588acbaa9 --- /dev/null +++ b/x/tx/decode/adr027.go @@ -0,0 +1,88 @@ +package decode + +import ( + "fmt" + + "google.golang.org/protobuf/encoding/protowire" +) + +// rejectNonADR027TxRaw rejects txBytes that do not follow ADR-027. This is NOT +// a generic ADR-027 checker, it only applies decoding TxRaw. Specifically, it +// only checks that: +// +// - Field numbers are in ascending order (1, 2, and potentially multiple 3s) +// - Varints are as short as possible +// +// All other ADR-027 edge cases (e.g. default values) are not applicable with +// TxRaw. +func rejectNonADR027TxRaw(txBytes []byte) error { + // Make sure all fields are ordered in ascending order with this variable. + prevTagNum := protowire.Number(0) + + for len(txBytes) > 0 { + tagNum, wireType, m := protowire.ConsumeTag(txBytes) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + // TxRaw only has bytes fields. + if wireType != protowire.BytesType { + return fmt.Errorf("expected %d wire type, got %d", protowire.BytesType, wireType) + } + // Make sure fields are ordered in ascending order. + if tagNum < prevTagNum { + return fmt.Errorf("txRaw must follow ADR-027, got tagNum %d after tagNum %d", tagNum, prevTagNum) + } + prevTagNum = tagNum + + // All 3 fields of TxRaw have wireType == 2, so their next component + // is a varint, so we can safely call ConsumeVarint here. + // Byte structure: + // Inner fields are verified in `DefaultTxDecoder` + lengthPrefix, m := protowire.ConsumeVarint(txBytes[m:]) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + // We make sure that this varint is as short as possible. + n := varintMinLength(lengthPrefix) + if n != m { + return fmt.Errorf("length prefix varint for tagNum %d is not as short as possible, read %d, only need %d", tagNum, m, n) + } + + // Skip over the bytes that store fieldNumber and wireType bytes. + _, _, m = protowire.ConsumeField(txBytes) + if m < 0 { + return fmt.Errorf("invalid length; %w", protowire.ParseError(m)) + } + txBytes = txBytes[m:] + } + + return nil +} + +// varintMinLength returns the minimum number of bytes necessary to encode an +// uint using varint encoding. +func varintMinLength(n uint64) int { + switch { + // Note: 1<