feat(x/tx): tx decoder (#15414)

Co-authored-by: Aaron Craelius <aaronc@users.noreply.github.com>
This commit is contained in:
Matt Kocubinski 2023-04-10 16:39:59 -05:00 committed by GitHub
parent d5a618db6a
commit f8e2d984b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 780 additions and 255 deletions

View File

@ -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{}, &paramsapi.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",

View File

@ -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{}, &paramsapi.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...)
)

1
tests/integration/tx/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
testdata/rapid

View File

@ -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)
})
})
}
}

View File

@ -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

88
x/tx/decode/adr027.go Normal file
View File

@ -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: <varint of bytes length><bytes sequence>
// 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<<N == 2**N.
case n < 1<<(7):
return 1
case n < 1<<(7*2):
return 2
case n < 1<<(7*3):
return 3
case n < 1<<(7*4):
return 4
case n < 1<<(7*5):
return 5
case n < 1<<(7*6):
return 6
case n < 1<<(7*7):
return 7
case n < 1<<(7*8):
return 8
case n < 1<<(7*9):
return 9
default:
return 10
}
}

141
x/tx/decode/decode.go Normal file
View File

@ -0,0 +1,141 @@
package decode
import (
"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoregistry"
v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)
// DecodedTx contains the decoded transaction, its signers, and other flags.
type DecodedTx struct {
Messages []proto.Message
Tx *v1beta1.Tx
TxRaw *v1beta1.TxRaw
Signers []string
TxBodyHasUnknownNonCriticals bool
}
// Decoder contains the dependencies required for decoding transactions.
type Decoder struct {
getSignersCtx *signing.GetSignersContext
typeResolver protoregistry.MessageTypeResolver
protoFiles *protoregistry.Files
}
// Options are options for creating a Decoder.
type Options struct {
// ProtoFiles are the protobuf files to use for resolving message descriptors.
// If it is nil, the global protobuf registry will be used.
ProtoFiles *protoregistry.Files
TypeResolver protoregistry.MessageTypeResolver
SigningContext *signing.GetSignersContext
}
// NewDecoder creates a new Decoder for decoding transactions.
func NewDecoder(options Options) (*Decoder, error) {
if options.ProtoFiles == nil {
options.ProtoFiles = protoregistry.GlobalFiles
}
if options.TypeResolver == nil {
options.TypeResolver = protoregistry.GlobalTypes
}
getSignersCtx := options.SigningContext
if getSignersCtx == nil {
var err error
getSignersCtx, err = signing.NewGetSignersContext(signing.GetSignersOptions{
ProtoFiles: options.ProtoFiles,
})
if err != nil {
return nil, err
}
}
return &Decoder{
getSignersCtx: getSignersCtx,
protoFiles: options.ProtoFiles,
typeResolver: options.TypeResolver,
}, nil
}
// Decode decodes raw protobuf encoded transaction bytes into a DecodedTx.
func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
// Make sure txBytes follow ADR-027.
err := rejectNonADR027TxRaw(txBytes)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
var raw v1beta1.TxRaw
// reject all unknown proto fields in the root TxRaw
err = RejectUnknownFieldsStrict(txBytes, raw.ProtoReflect().Descriptor(), d.protoFiles)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(txBytes, &raw)
if err != nil {
return nil, err
}
var body v1beta1.TxBody
// allow non-critical unknown fields in TxBody
txBodyHasUnknownNonCriticals, err := RejectUnknownFields(raw.BodyBytes, body.ProtoReflect().Descriptor(), true, d.protoFiles)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(raw.BodyBytes, &body)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
var authInfo v1beta1.AuthInfo
// reject all unknown proto fields in AuthInfo
err = RejectUnknownFieldsStrict(raw.AuthInfoBytes, authInfo.ProtoReflect().Descriptor(), d.protoFiles)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
err = proto.Unmarshal(raw.AuthInfoBytes, &authInfo)
if err != nil {
return nil, errors.Wrap(ErrTxDecode, err.Error())
}
theTx := &v1beta1.Tx{
Body: &body,
AuthInfo: &authInfo,
Signatures: raw.Signatures,
}
var signers []string
var msgs []proto.Message
for _, anyMsg := range body.Messages {
msg, signerErr := anyutil.Unpack(anyMsg, d.protoFiles, d.typeResolver)
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
}
msgs = append(msgs, msg)
ss, signerErr := d.getSignersCtx.GetSigners(msg)
if signerErr != nil {
return nil, errors.Wrap(ErrTxDecode, signerErr.Error())
}
signers = append(signers, ss...)
}
return &DecodedTx{
Messages: msgs,
Tx: theTx,
TxRaw: &raw,
TxBodyHasUnknownNonCriticals: txBodyHasUnknownNonCriticals,
Signers: signers,
}, nil
}

102
x/tx/decode/decode_test.go Normal file
View File

@ -0,0 +1,102 @@
package decode_test
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-proto/anyutil"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
"cosmossdk.io/api/cosmos/crypto/secp256k1"
signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
txv1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/x/tx/decode"
"cosmossdk.io/x/tx/internal/testpb"
)
func TestDecode(t *testing.T) {
accSeq := uint64(2)
pkAny, err := anyutil.New(&secp256k1.PubKey{Key: []byte("foo")})
require.NoError(t, err)
var signerInfo []*txv1beta1.SignerInfo
signerInfo = append(signerInfo, &txv1beta1.SignerInfo{
PublicKey: pkAny,
ModeInfo: &txv1beta1.ModeInfo{
Sum: &txv1beta1.ModeInfo_Single_{
Single: &txv1beta1.ModeInfo_Single{
Mode: signingv1beta1.SignMode_SIGN_MODE_DIRECT,
},
},
},
Sequence: accSeq,
})
testCases := []struct {
name string
msg proto.Message
error string
}{
{
name: "happy path",
msg: &bankv1beta1.MsgSend{},
},
{
name: "empty signer option",
msg: &testpb.A{},
error: "no cosmos.msg.v1.signer option found for message A: tx parse error",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
_, err := proto.Marshal(tc.msg)
require.NoError(t, err)
anyMsg, err := anyutil.New(tc.msg)
require.NoError(t, err)
tx := &txv1beta1.Tx{
Body: &txv1beta1.TxBody{
Messages: []*anypb.Any{anyMsg},
Memo: "memo",
TimeoutHeight: 0,
},
AuthInfo: &txv1beta1.AuthInfo{
SignerInfos: signerInfo,
Fee: &txv1beta1.Fee{
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
GasLimit: 100,
Payer: "payer",
Granter: "",
},
Tip: &txv1beta1.Tip{
Amount: []*basev1beta1.Coin{{Amount: "100", Denom: "denom"}},
Tipper: "tipper",
},
},
Signatures: nil,
}
txBytes, err := proto.Marshal(tx)
require.NoError(t, err)
decoder, err := decode.NewDecoder(decode.Options{})
require.NoError(t, err)
decodeTx, err := decoder.Decode(txBytes)
if tc.error != "" {
require.EqualError(t, err, tc.error)
return
}
require.NoError(t, err)
require.Equal(t,
fmt.Sprintf("/%s", tc.msg.ProtoReflect().Descriptor().FullName()),
decodeTx.Tx.Body.Messages[0].TypeUrl)
})
}
}

View File

@ -6,4 +6,8 @@ const (
txCodespace = "tx"
)
var ErrUnknownField = errors.Register(txCodespace, 2, "unknown protobuf field")
var (
// ErrTxDecode is returned if we cannot parse a transaction
ErrTxDecode = errors.Register(txCodespace, 1, "tx parse error")
ErrUnknownField = errors.Register(txCodespace, 2, "unknown protobuf field")
)