refactor(x/**): rewrite ante handlers as tx validators (#20488)

Co-authored-by: Likhita Polavarapu <78951027+likhita-809@users.noreply.github.com>
This commit is contained in:
Julien Robert 2024-06-03 18:47:58 +02:00 committed by GitHub
parent d1aab15790
commit 2a5ff384fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 365 additions and 102 deletions

View File

@ -23,6 +23,11 @@ COPY x/bank/go.mod x/bank/go.sum /work/x/bank/
COPY x/mint/go.mod x/mint/go.sum /work/x/mint/
COPY x/consensus/go.mod x/consensus/go.sum /work/x/consensus/
COPY x/accounts/go.mod x/accounts/go.sum /work/x/accounts/
COPY runtime/v2/go.mod runtime/v2/go.sum /work/runtime/v2/
COPY server/v2/appmanager/go.mod server/v2/appmanager/go.sum /work/server/v2/appmanager/
COPY server/v2/core/go.mod server/v2/core/go.sum /work/server/v2/core/
COPY server/v2/stf/go.mod server/v2/stf/go.sum /work/server/v2/stf/
RUN go mod download
COPY ./ /work

1
go.mod
View File

@ -194,6 +194,7 @@ replace (
cosmossdk.io/x/bank => ./x/bank
cosmossdk.io/x/consensus => ./x/consensus
cosmossdk.io/x/staking => ./x/staking
cosmossdk.io/x/tx => ./x/tx
)
// Below are the long-lived replace of the Cosmos SDK

2
go.sum
View File

@ -10,8 +10,6 @@ cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5 h1:eb0kcGyaYHSS0do7+MIWg7UKlskSH01biRNENbm/zDA=
cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5/go.mod h1:drzY4oVisyWvSgpsM7ccQ7IX3efMuVIvd9Eij1Gm/6o=
cosmossdk.io/x/tx v0.13.3 h1:Ha4mNaHmxBc6RMun9aKuqul8yHiL78EKJQ8g23Zf73g=
cosmossdk.io/x/tx v0.13.3/go.mod h1:I8xaHv0rhUdIvIdptKIqzYy27+n2+zBVaxO6fscFhys=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=

View File

@ -7,6 +7,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/auth/signing"
@ -95,6 +96,22 @@ func NewTx(key, value string, accAddress sdk.AccAddress) *KVStoreTx {
}
}
func (msg *KVStoreTx) Hash() [32]byte {
return [32]byte{}
}
func (msg *KVStoreTx) GetGasLimit() (uint64, error) {
return 0, nil
}
func (msg *KVStoreTx) GetMessages() ([]transaction.Msg, error) {
return nil, nil
}
func (msg *KVStoreTx) GetSenders() ([][]byte, error) {
return nil, nil
}
func (msg *KVStoreTx) Type() string {
return "kvstore_tx"
}

View File

@ -23,6 +23,9 @@ func (t Tx) Hash() [32]byte {
}
func (t Tx) GetMessages() ([]transaction.Msg, error) {
if t.Msg == nil {
return nil, errors.New("messages not available or are nil")
}
return []transaction.Msg{t.Msg}, nil
}

View File

@ -38,7 +38,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(options.Environment),
ante.NewTxTimeoutHeightDecorator(),
ante.NewTxTimeoutHeightDecorator(options.Environment),
ante.NewUnorderedTxDecorator(unorderedtx.DefaultMaxUnOrderedTTL, options.TxManager, options.Environment),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),

View File

@ -264,6 +264,7 @@ replace (
cosmossdk.io/x/protocolpool => ../x/protocolpool
cosmossdk.io/x/slashing => ../x/slashing
cosmossdk.io/x/staking => ../x/staking
cosmossdk.io/x/tx => ../x/tx
cosmossdk.io/x/upgrade => ../x/upgrade
)

View File

@ -194,8 +194,6 @@ cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/x/tx v0.13.3 h1:Ha4mNaHmxBc6RMun9aKuqul8yHiL78EKJQ8g23Zf73g=
cosmossdk.io/x/tx v0.13.3/go.mod h1:I8xaHv0rhUdIvIdptKIqzYy27+n2+zBVaxO6fscFhys=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=

View File

@ -257,6 +257,7 @@ replace (
cosmossdk.io/x/protocolpool => ../x/protocolpool
cosmossdk.io/x/slashing => ../x/slashing
cosmossdk.io/x/staking => ../x/staking
cosmossdk.io/x/tx => ../x/tx
cosmossdk.io/x/upgrade => ../x/upgrade
)

View File

@ -196,8 +196,6 @@ cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc h1:R9O9d75e0qZYUsVV0zzi+D7cNLnX2JrUOQNoIPaF0Bg=
cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc/go.mod h1:amTTatOUV3u1PsKmNb87z6/galCxrRbz9kRdJkL0DyU=
cosmossdk.io/x/tx v0.13.3 h1:Ha4mNaHmxBc6RMun9aKuqul8yHiL78EKJQ8g23Zf73g=
cosmossdk.io/x/tx v0.13.3/go.mod h1:I8xaHv0rhUdIvIdptKIqzYy27+n2+zBVaxO6fscFhys=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=

View File

@ -12,6 +12,7 @@ import (
_ "cosmossdk.io/api/cosmos/counter/v1"
_ "cosmossdk.io/api/cosmos/crypto/secp256k1"
"cosmossdk.io/core/log"
"cosmossdk.io/core/transaction"
"cosmossdk.io/x/auth/signing"
codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil"
@ -75,6 +76,26 @@ var (
_ cryptotypes.PubKey = (*testPubKey)(nil)
)
func (tx testTx) Bytes() []byte {
return []byte{}
}
func (tx testTx) Hash() [32]byte {
return [32]byte{}
}
func (tx testTx) GetGasLimit() (uint64, error) {
return 0, nil
}
func (tx testTx) GetMessages() ([]transaction.Msg, error) {
return nil, nil
}
func (tx testTx) GetSenders() ([][]byte, error) {
return nil, nil
}
func (tx testTx) GetMsgs() []sdk.Msg { return nil }
func (tx testTx) GetReflectMessages() ([]protoreflect.Message, error) { return nil, nil }
@ -89,6 +110,26 @@ type sigErrTx struct {
getSigs func() ([]txsigning.SignatureV2, error)
}
func (sigErrTx) Bytes() []byte {
return []byte{}
}
func (sigErrTx) Hash() [32]byte {
return [32]byte{}
}
func (sigErrTx) GetGasLimit() (uint64, error) {
return 0, nil
}
func (sigErrTx) GetMessages() ([]transaction.Msg, error) {
return nil, nil
}
func (sigErrTx) GetSenders() ([][]byte, error) {
return nil, nil
}
func (sigErrTx) Size() int64 { return 0 }
func (sigErrTx) GetMsgs() []sdk.Msg { return nil }

View File

@ -8,6 +8,8 @@ import (
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/reflect/protoreflect"
"cosmossdk.io/core/transaction"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/mempool"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
@ -24,6 +26,26 @@ func (n nonVerifiableTx) GetReflectMessages() ([]protoreflect.Message, error) {
panic("not implemented")
}
func (n nonVerifiableTx) Bytes() []byte {
return []byte{}
}
func (n nonVerifiableTx) Hash() [32]byte {
return [32]byte{}
}
func (n nonVerifiableTx) GetGasLimit() (uint64, error) {
return 0, nil
}
func (n nonVerifiableTx) GetMessages() ([]transaction.Msg, error) {
return nil, nil
}
func (n nonVerifiableTx) GetSenders() ([][]byte, error) {
return nil, nil
}
func TestDefaultSignerExtractor(t *testing.T) {
accounts := simtypes.RandomAccounts(rand.New(rand.NewSource(0)), 1)
sa := accounts[0].Address

View File

@ -7,7 +7,7 @@ import (
"google.golang.org/protobuf/reflect/protoreflect"
coretransaction "cosmossdk.io/core/transaction"
"cosmossdk.io/core/transaction"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
@ -16,7 +16,7 @@ import (
type (
// Msg defines the interface a transaction message needed to fulfill.
Msg = coretransaction.Msg
Msg = transaction.Msg
// LegacyMsg defines the interface a transaction message needed to fulfill up through
// v0.47.
@ -51,6 +51,8 @@ type (
// Tx defines an interface a transaction must fulfill.
Tx interface {
transaction.Tx
HasMsgs
// GetReflectMessages gets a reflected version of the transaction's messages

View File

@ -45,7 +45,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
NewSetUpContextDecorator(options.Environment), // outermost AnteDecorator. SetUpContext must be called first
NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
NewValidateBasicDecorator(options.Environment),
NewTxTimeoutHeightDecorator(),
NewTxTimeoutHeightDecorator(options.Environment),
NewValidateMemoDecorator(options.AccountKeeper),
NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),

View File

@ -1,6 +1,8 @@
package ante
import (
"context"
"cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
@ -30,20 +32,30 @@ func NewValidateBasicDecorator(env appmodule.Environment) ValidateBasicDecorator
}
}
func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
// AnteHandle implements an AnteHandler decorator for the ValidateBasicDecorator.
func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := vbd.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
// ValidateTx implements an TxValidator for ValidateBasicDecorator
func (vbd ValidateBasicDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
// no need to validate basic on recheck tx, call next antehandler
txService := vbd.env.TransactionService
if txService.ExecMode(ctx) == transaction.ExecModeReCheck {
return next(ctx, tx, false)
return nil
}
if validateBasic, ok := tx.(sdk.HasValidateBasic); ok {
if err := validateBasic.ValidateBasic(); err != nil {
return ctx, err
return err
}
}
return next(ctx, tx, false)
return nil
}
// ValidateMemoDecorator will validate memo given the parameters passed in
@ -59,24 +71,34 @@ func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator {
}
}
func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
// AnteHandle implements an AnteHandler decorator for the ValidateMemoDecorator.
func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := vmd.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
// ValidateTx implements an TxValidator for ValidateMemoDecorator
func (vmd ValidateMemoDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
memoTx, ok := tx.(sdk.TxWithMemo)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}
memoLength := len(memoTx.GetMemo())
if memoLength > 0 {
params := vmd.ak.GetParams(ctx)
if uint64(memoLength) > params.MaxMemoCharacters {
return ctx, errorsmod.Wrapf(sdkerrors.ErrMemoTooLarge,
return errorsmod.Wrapf(sdkerrors.ErrMemoTooLarge,
"maximum number of characters is %d but received %d characters",
params.MaxMemoCharacters, memoLength,
)
}
}
return next(ctx, tx, false)
return nil
}
// ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional
@ -98,14 +120,27 @@ func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator
}
}
func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
// AnteHandle implements an AnteHandler decorator for the ConsumeTxSizeGasDecorator.
func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := cgts.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
// ValidateTx implements an TxValidator for ConsumeTxSizeGasDecorator
func (cgts ConsumeTxSizeGasDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
}
params := cgts.ak.GetParams(ctx)
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*storetypes.Gas(len(ctx.TxBytes())), "txSize")
gasService := cgts.ak.GetEnvironment().GasService
if err := gasService.GasMeter(ctx).Consume(params.TxSizeCostPerByte*storetypes.Gas(len(tx.Bytes())), "txSize"); err != nil {
return err
}
// simulate gas cost for signatures in simulate mode
txService := cgts.ak.GetEnvironment().TransactionService
@ -113,7 +148,7 @@ func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ b
// in simulate mode, each element should be a nil signature
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
return err
}
n := len(sigs)
@ -147,11 +182,13 @@ func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ b
cost *= params.TxSigLimit
}
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize")
if err := gasService.GasMeter(ctx).Consume(params.TxSizeCostPerByte*cost, "txSize"); err != nil {
return err
}
}
}
return next(ctx, tx, false)
return nil
}
// isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes
@ -180,7 +217,9 @@ func isIncompleteSignature(data signing.SignatureData) bool {
type (
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
// tx height timeout.
TxTimeoutHeightDecorator struct{}
TxTimeoutHeightDecorator struct {
env appmodule.Environment
}
// TxWithTimeoutHeight defines the interface a tx must implement in order for
// TxTimeoutHeightDecorator to process the tx.
@ -193,26 +232,39 @@ type (
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
// tx height timeout.
func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator {
return TxTimeoutHeightDecorator{}
func NewTxTimeoutHeightDecorator(env appmodule.Environment) TxTimeoutHeightDecorator {
return TxTimeoutHeightDecorator{
env: env,
}
}
// AnteHandle implements an AnteHandler decorator for the TxTimeoutHeightDecorator
// type where the current block height is checked against the tx's height timeout.
// If a height timeout is provided (non-zero) and is less than the current block
// height, then an error is returned.
func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
timeoutTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
}
timeoutHeight := timeoutTx.GetTimeoutHeight()
if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight,
)
// AnteHandle implements an AnteHandler decorator for the TxHeightTimeoutDecorator.
func (txh TxTimeoutHeightDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := txh.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
// ValidateTx implements an TxValidator for the TxHeightTimeoutDecorator
// type where the current block height is checked against the tx's height timeout.
// If a height timeout is provided (non-zero) and is less than the current block
// height, then an error is returned.
func (txh TxTimeoutHeightDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
timeoutTx, ok := tx.(TxWithTimeoutHeight)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
}
timeoutHeight := timeoutTx.GetTimeoutHeight()
headerInfo := txh.env.HeaderService.HeaderInfo(ctx)
if timeoutHeight > 0 && uint64(headerInfo.Height) > timeoutHeight {
return errorsmod.Wrapf(
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", headerInfo.Height, timeoutHeight,
)
}
return nil
}

View File

@ -1,11 +1,14 @@
package ante_test
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/header"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth/ante"
@ -95,6 +98,8 @@ func TestValidateMemo(t *testing.T) {
}
func TestConsumeGasForTxSize(t *testing.T) {
t.Skip() // TODO(@julienrbrt) Fix after https://github.com/cosmos/cosmos-sdk/pull/20072
suite := SetupTestSuite(t, true)
// keys and addresses
@ -182,7 +187,8 @@ func TestConsumeGasForTxSize(t *testing.T) {
func TestTxHeightTimeoutDecorator(t *testing.T) {
suite := SetupTestSuite(t, true)
antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator())
mockHeaderService := &mockHeaderService{}
antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator(appmodule.Environment{HeaderService: mockHeaderService}))
// keys and addresses
priv1, _, addr1 := testdata.KeyTestPubAddr()
@ -221,9 +227,23 @@ func TestTxHeightTimeoutDecorator(t *testing.T) {
tx, err := suite.CreateTestTx(suite.ctx, privs, accNums, accSeqs, suite.ctx.ChainID(), signing.SignMode_SIGN_MODE_DIRECT)
require.NoError(t, err)
ctx := suite.ctx.WithBlockHeight(tc.height)
_, err = antehandler(ctx, tx, true)
mockHeaderService.WithBlockHeight(tc.height)
_, err = antehandler(suite.ctx, tx, true)
require.ErrorIs(t, err, tc.expectedErr)
})
}
}
type mockHeaderService struct {
header.Service
exp header.Info
}
func (m *mockHeaderService) HeaderInfo(_ context.Context) header.Info {
return m.exp
}
func (m *mockHeaderService) WithBlockHeight(height int64) {
m.exp.Height = height
}

View File

@ -460,27 +460,37 @@ func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator {
}
}
func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
// AnteHandler implements an ante decorator for ValidateSigCountDecorator
func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if err := vscd.ValidateTx(ctx, tx); err != nil {
return ctx, err
}
return next(ctx, tx, false)
}
// ValidateTx implements an TxValidator for ValidateSigCountDecorator
func (vscd ValidateSigCountDecorator) ValidateTx(ctx context.Context, tx sdk.Tx) error {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
}
params := vscd.ak.GetParams(ctx)
pubKeys, err := sigTx.GetPubKeys()
if err != nil {
return ctx, err
return err
}
sigCount := 0
for _, pk := range pubKeys {
sigCount += CountSubKeys(pk)
if uint64(sigCount) > params.TxSigLimit {
return ctx, errorsmod.Wrapf(sdkerrors.ErrTooManySignatures, "signatures: %d, limit: %d", sigCount, params.TxSigLimit)
return errorsmod.Wrapf(sdkerrors.ErrTooManySignatures, "signatures: %d, limit: %d", sigCount, params.TxSigLimit)
}
}
return next(ctx, tx, false)
return nil
}
// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas

View File

@ -181,4 +181,5 @@ replace (
cosmossdk.io/x/bank => ../bank
cosmossdk.io/x/consensus => ../consensus
cosmossdk.io/x/staking => ../staking
cosmossdk.io/x/tx => ../tx
)

View File

@ -12,8 +12,6 @@ cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc h1:R9O9d75e0qZYUsVV0zzi+
cosmossdk.io/store v1.1.1-0.20240418092142-896cdf1971bc/go.mod h1:amTTatOUV3u1PsKmNb87z6/galCxrRbz9kRdJkL0DyU=
cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5 h1:eb0kcGyaYHSS0do7+MIWg7UKlskSH01biRNENbm/zDA=
cosmossdk.io/x/accounts/defaults/lockup v0.0.0-20240417181816-5e7aae0db1f5/go.mod h1:drzY4oVisyWvSgpsM7ccQ7IX3efMuVIvd9Eij1Gm/6o=
cosmossdk.io/x/tx v0.13.3 h1:Ha4mNaHmxBc6RMun9aKuqul8yHiL78EKJQ8g23Zf73g=
cosmossdk.io/x/tx v0.13.3/go.mod h1:I8xaHv0rhUdIvIdptKIqzYy27+n2+zBVaxO6fscFhys=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=

View File

@ -9,14 +9,18 @@ import (
"google.golang.org/grpc"
"cosmossdk.io/core/appmodule"
appmodulev2 "cosmossdk.io/core/appmodule/v2"
"cosmossdk.io/core/legacy"
"cosmossdk.io/core/registry"
"cosmossdk.io/core/transaction"
"cosmossdk.io/x/auth/ante"
"cosmossdk.io/x/auth/keeper"
"cosmossdk.io/x/auth/simulation"
"cosmossdk.io/x/auth/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
)
@ -31,10 +35,10 @@ var (
_ module.AppModuleSimulation = AppModule{}
_ module.HasName = AppModule{}
_ appmodule.HasGenesis = AppModule{}
_ appmodule.AppModule = AppModule{}
_ appmodule.HasServices = AppModule{}
_ appmodule.HasMigrations = AppModule{}
_ appmodulev2.HasGenesis = AppModule{}
_ appmodulev2.AppModule = AppModule{}
_ appmodule.HasServices = AppModule{}
_ appmodulev2.HasMigrations = AppModule{}
)
// AppModule implements an application module for the auth module.
@ -150,7 +154,32 @@ func (am AppModule) ExportGenesis(ctx context.Context) (json.RawMessage, error)
return am.cdc.MarshalJSON(gs)
}
// ConsensusVersion implements HasConsensusVersion
// TxValidator implements appmodulev2.HasTxValidator.
// It replaces auth ante handlers for server/v2
func (am AppModule) TxValidator(ctx context.Context, tx transaction.Tx) error {
validators := []appmodulev2.TxValidator[sdk.Tx]{
ante.NewValidateBasicDecorator(am.accountKeeper.GetEnvironment()),
ante.NewTxTimeoutHeightDecorator(am.accountKeeper.GetEnvironment()),
ante.NewValidateMemoDecorator(am.accountKeeper),
ante.NewConsumeGasForTxSizeDecorator(am.accountKeeper),
ante.NewValidateSigCountDecorator(am.accountKeeper),
}
sdkTx, ok := tx.(sdk.Tx)
if !ok {
return fmt.Errorf("invalid tx type %T, expected sdk.Tx", tx)
}
for _, validator := range validators {
if err := validator.ValidateTx(ctx, sdkTx); err != nil {
return err
}
}
return nil
}
// ConsensusVersion implements appmodule.HasConsensusVersion
func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion }
// AppModuleSimulation functions

View File

@ -33,11 +33,11 @@ func newBuilder(addressCodec address.Codec, decoder *decode.Decoder, codec codec
}
func newBuilderFromDecodedTx(addrCodec address.Codec, decoder *decode.Decoder, codec codec.BinaryCodec, decoded *gogoTxWrapper) (*builder, error) {
signatures := make([][]byte, len(decoded.decodedTx.Tx.Signatures))
copy(signatures, decoded.decodedTx.Tx.Signatures)
signatures := make([][]byte, len(decoded.Tx.Signatures))
copy(signatures, decoded.Tx.Signatures)
sigInfos := make([]*tx.SignerInfo, len(decoded.decodedTx.Tx.AuthInfo.SignerInfos))
for i, sigInfo := range decoded.decodedTx.Tx.AuthInfo.SignerInfos {
sigInfos := make([]*tx.SignerInfo, len(decoded.Tx.AuthInfo.SignerInfos))
for i, sigInfo := range decoded.Tx.AuthInfo.SignerInfos {
modeInfoV1 := new(tx.ModeInfo)
fromV2ModeInfo(sigInfo.ModeInfo, modeInfoV1)
sigInfos[i] = &tx.SignerInfo{

View File

@ -61,7 +61,7 @@ type ModuleOutputs struct {
TxConfig client.TxConfig
TxConfigOptions tx.ConfigOptions
BaseAppOption runtime.BaseAppOption
BaseAppOption runtime.BaseAppOption // This is only useful for chains using baseapp. Server/v2 chains use TxValidator.
}
func ProvideProtoRegistry() txsigning.ProtoFileResolver {

View File

@ -16,7 +16,7 @@ func DefaultTxEncoder() sdk.TxEncoder {
if !ok {
return nil, fmt.Errorf("unexpected tx type: %T", tx)
}
return marshalOption.Marshal(gogoWrapper.decodedTx.TxRaw)
return marshalOption.Marshal(gogoWrapper.TxRaw)
}
}
@ -32,6 +32,6 @@ func DefaultJSONTxEncoder(cdc codec.Codec) sdk.TxEncoder {
if !ok {
return nil, fmt.Errorf("unexpected tx type: %T", tx)
}
return jsonMarshaler.Marshal(gogoWrapper.decodedTx.Tx)
return jsonMarshaler.Marshal(gogoWrapper.Tx)
}
}

View File

@ -27,7 +27,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/tx/signing"
)
func newWrapperFromDecodedTx(addrCodec address.Codec, cdc codec.BinaryCodec, decodedTx *decode.DecodedTx) (w *gogoTxWrapper, err error) {
func newWrapperFromDecodedTx(addrCodec address.Codec, cdc codec.BinaryCodec, decodedTx *decode.DecodedTx) (*gogoTxWrapper, error) {
// set msgsv1
msgs, err := decodeMsgsV1(cdc, decodedTx.Tx.Body.Messages)
if err != nil {
@ -79,8 +79,8 @@ func newWrapperFromDecodedTx(addrCodec address.Codec, cdc codec.BinaryCodec, dec
}
return &gogoTxWrapper{
DecodedTx: decodedTx,
cdc: cdc,
decodedTx: decodedTx,
reflectMsgs: reflectMsgs,
msgs: msgs,
fees: fees,
@ -92,8 +92,9 @@ func newWrapperFromDecodedTx(addrCodec address.Codec, cdc codec.BinaryCodec, dec
// gogoTxWrapper is a gogoTxWrapper around the tx.Tx proto.Message which retain the raw
// body and auth_info bytes.
type gogoTxWrapper struct {
decodedTx *decode.DecodedTx
cdc codec.BinaryCodec
*decode.DecodedTx
cdc codec.BinaryCodec
msgs []proto.Message
reflectMsgs []protoreflect.Message
@ -102,7 +103,7 @@ type gogoTxWrapper struct {
feeGranter []byte
}
func (w *gogoTxWrapper) String() string { return w.decodedTx.Tx.String() }
func (w *gogoTxWrapper) String() string { return w.Tx.String() }
var (
_ authsigning.Tx = &gogoTxWrapper{}
@ -129,21 +130,21 @@ func (w *gogoTxWrapper) GetReflectMessages() ([]protoreflect.Message, error) {
}
func (w *gogoTxWrapper) ValidateBasic() error {
if len(w.decodedTx.Tx.Signatures) == 0 {
if len(w.Tx.Signatures) == 0 {
return sdkerrors.ErrNoSignatures.Wrapf("empty signatures")
}
if len(w.decodedTx.Signers) != len(w.decodedTx.Tx.Signatures) {
return sdkerrors.ErrUnauthorized.Wrapf("invalid number of signatures: got %d signatures and %d signers", len(w.decodedTx.Tx.Signatures), len(w.decodedTx.Signers))
if len(w.Signers) != len(w.Tx.Signatures) {
return sdkerrors.ErrUnauthorized.Wrapf("invalid number of signatures: got %d signatures and %d signers", len(w.Tx.Signatures), len(w.Signers))
}
return nil
}
func (w *gogoTxWrapper) GetSigners() ([][]byte, error) {
return w.decodedTx.Signers, nil
return w.Signers, nil
}
func (w *gogoTxWrapper) GetPubKeys() ([]cryptotypes.PubKey, error) {
signerInfos := w.decodedTx.Tx.AuthInfo.SignerInfos
signerInfos := w.Tx.AuthInfo.SignerInfos
pks := make([]cryptotypes.PubKey, len(signerInfos))
for i, si := range signerInfos {
@ -168,7 +169,7 @@ func (w *gogoTxWrapper) GetPubKeys() ([]cryptotypes.PubKey, error) {
}
func (w *gogoTxWrapper) GetGas() uint64 {
return w.decodedTx.Tx.AuthInfo.Fee.GasLimit
return w.Tx.AuthInfo.Fee.GasLimit
}
func (w *gogoTxWrapper) GetFee() sdk.Coins { return w.fees }
@ -177,18 +178,18 @@ func (w *gogoTxWrapper) FeePayer() []byte { return w.feePayer }
func (w *gogoTxWrapper) FeeGranter() []byte { return w.feeGranter }
func (w *gogoTxWrapper) GetMemo() string { return w.decodedTx.Tx.Body.Memo }
func (w *gogoTxWrapper) GetMemo() string { return w.Tx.Body.Memo }
// GetTimeoutHeight returns the transaction's timeout height (if set).
func (w *gogoTxWrapper) GetTimeoutHeight() uint64 { return w.decodedTx.Tx.Body.TimeoutHeight }
func (w *gogoTxWrapper) GetTimeoutHeight() uint64 { return w.Tx.Body.TimeoutHeight }
// GetUnordered returns the transaction's unordered field (if set).
func (w *gogoTxWrapper) GetUnordered() bool { return w.decodedTx.Tx.Body.Unordered }
func (w *gogoTxWrapper) GetUnordered() bool { return w.Tx.Body.Unordered }
// GetSignaturesV2 returns the signatures of the Tx.
func (w *gogoTxWrapper) GetSignaturesV2() ([]signing.SignatureV2, error) {
signerInfos := w.decodedTx.Tx.AuthInfo.SignerInfos
sigs := w.decodedTx.Tx.Signatures
signerInfos := w.Tx.AuthInfo.SignerInfos
sigs := w.Tx.Signatures
pubKeys, err := w.GetPubKeys()
if err != nil {
return nil, err
@ -225,46 +226,46 @@ func (w *gogoTxWrapper) GetSignaturesV2() ([]signing.SignatureV2, error) {
// TODO: evaluate if this is even needed considering we have decoded tx.
func (w *gogoTxWrapper) GetSigningTxData() txsigning.TxData {
return txsigning.TxData{
Body: w.decodedTx.Tx.Body,
AuthInfo: w.decodedTx.Tx.AuthInfo,
BodyBytes: w.decodedTx.TxRaw.BodyBytes,
AuthInfoBytes: w.decodedTx.TxRaw.AuthInfoBytes,
BodyHasUnknownNonCriticals: w.decodedTx.TxBodyHasUnknownNonCriticals,
Body: w.Tx.Body,
AuthInfo: w.Tx.AuthInfo,
BodyBytes: w.TxRaw.BodyBytes,
AuthInfoBytes: w.TxRaw.AuthInfoBytes,
BodyHasUnknownNonCriticals: w.TxBodyHasUnknownNonCriticals,
}
}
func (w *gogoTxWrapper) GetExtensionOptions() []*codectypes.Any {
return intoAnyV1(w.decodedTx.Tx.Body.ExtensionOptions)
return intoAnyV1(w.Tx.Body.ExtensionOptions)
}
func (w *gogoTxWrapper) GetNonCriticalExtensionOptions() []*codectypes.Any {
return intoAnyV1(w.decodedTx.Tx.Body.NonCriticalExtensionOptions)
return intoAnyV1(w.Tx.Body.NonCriticalExtensionOptions)
}
func (w *gogoTxWrapper) AsTx() (*txtypes.Tx, error) {
body := new(txtypes.TxBody)
authInfo := new(txtypes.AuthInfo)
err := w.cdc.Unmarshal(w.decodedTx.TxRaw.BodyBytes, body)
err := w.cdc.Unmarshal(w.TxRaw.BodyBytes, body)
if err != nil {
return nil, err
}
err = w.cdc.Unmarshal(w.decodedTx.TxRaw.AuthInfoBytes, authInfo)
err = w.cdc.Unmarshal(w.TxRaw.AuthInfoBytes, authInfo)
if err != nil {
return nil, err
}
return &txtypes.Tx{
Body: body,
AuthInfo: authInfo,
Signatures: w.decodedTx.TxRaw.Signatures,
Signatures: w.TxRaw.Signatures,
}, nil
}
func (w *gogoTxWrapper) AsTxRaw() (*txtypes.TxRaw, error) {
return &txtypes.TxRaw{
BodyBytes: w.decodedTx.TxRaw.BodyBytes,
AuthInfoBytes: w.decodedTx.TxRaw.AuthInfoBytes,
Signatures: w.decodedTx.TxRaw.Signatures,
BodyBytes: w.TxRaw.BodyBytes,
AuthInfoBytes: w.TxRaw.AuthInfoBytes,
Signatures: w.TxRaw.Signatures,
}, nil
}

View File

@ -1 +0,0 @@
package tx

View File

@ -48,11 +48,11 @@ func (s signModeLegacyAminoJSONHandler) GetSignBytes(mode signingtypes.SignMode,
return nil, fmt.Errorf("can only handle a protobuf Tx, got %T", tx)
}
if protoTx.decodedTx.TxBodyHasUnknownNonCriticals {
if protoTx.TxBodyHasUnknownNonCriticals {
return nil, errorsmod.Wrap(sdkerrors.ErrInvalidRequest, aminoNonCriticalFieldsError)
}
body := protoTx.decodedTx.Tx.Body
body := protoTx.Tx.Body
if len(body.ExtensionOptions) != 0 || len(body.NonCriticalExtensionOptions) != 0 {
return nil, errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "%s does not support protobuf extension options", signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
@ -68,8 +68,8 @@ func (s signModeLegacyAminoJSONHandler) GetSignBytes(mode signingtypes.SignMode,
legacytx.StdFee{
Amount: protoTx.GetFee(),
Gas: protoTx.GetGas(),
Payer: protoTx.decodedTx.Tx.AuthInfo.Fee.Payer,
Granter: protoTx.decodedTx.Tx.AuthInfo.Fee.Granter,
Payer: protoTx.Tx.AuthInfo.Fee.Payer,
Granter: protoTx.Tx.AuthInfo.Fee.Granter,
},
tx.GetMsgs(), protoTx.GetMemo(),
), nil

View File

@ -1,12 +1,15 @@
package decode
import (
"crypto/sha256"
"errors"
"github.com/cosmos/cosmos-proto/anyutil"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/protoadapt"
v1beta1 "cosmossdk.io/api/cosmos/tx/v1beta1"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/tx/signing"
)
@ -18,8 +21,15 @@ type DecodedTx struct {
TxRaw *v1beta1.TxRaw
Signers [][]byte
TxBodyHasUnknownNonCriticals bool
// Cache for hash and full bytes
cachedHash [32]byte
cachedBytes []byte
cachedHashed bool
}
var _ transaction.Tx = &DecodedTx{}
// Decoder contains the dependencies required for decoding transactions.
type Decoder struct {
signingCtx *signing.Context
@ -126,3 +136,56 @@ func (d *Decoder) Decode(txBytes []byte) (*DecodedTx, error) {
Signers: signers,
}, nil
}
// Hash implements the interface for the Tx interface.
func (dtx *DecodedTx) Hash() [32]byte {
if !dtx.cachedHashed {
dtx.computeHashAndBytes()
}
return dtx.cachedHash
}
func (dtx *DecodedTx) GetGasLimit() (uint64, error) {
if dtx == nil || dtx.Tx == nil || dtx.Tx.AuthInfo == nil || dtx.Tx.AuthInfo.Fee == nil {
return 0, errors.New("gas limit not available or one or more required fields are nil")
}
return dtx.Tx.AuthInfo.Fee.GasLimit, nil
}
func (dtx *DecodedTx) GetMessages() ([]transaction.Msg, error) {
if dtx == nil || dtx.Messages == nil {
return nil, errors.New("messages not available or are nil")
}
msgs := make([]transaction.Msg, len(dtx.Messages))
for i, msg := range dtx.Messages {
msgs[i] = protoadapt.MessageV1Of(msg)
}
return msgs, nil
}
func (dtx *DecodedTx) GetSenders() ([][]byte, error) {
if dtx == nil || dtx.Signers == nil {
return nil, errors.New("senders not available or are nil")
}
return dtx.Signers, nil
}
func (dtx *DecodedTx) Bytes() []byte {
if !dtx.cachedHashed {
dtx.computeHashAndBytes()
}
return dtx.cachedBytes
}
func (dtx *DecodedTx) computeHashAndBytes() {
bz, err := proto.Marshal(dtx.TxRaw)
if err != nil {
panic(err)
}
dtx.cachedBytes = bz
dtx.cachedHash = sha256.Sum256(bz)
dtx.cachedHashed = true
}

View File

@ -21,7 +21,8 @@ require (
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.25.0 // indirect
@ -33,6 +34,8 @@ require (
gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace cosmossdk.io/core => ../../core
// NOTE: we do not want to replace to the development version of cosmossdk.io/api yet
// Until https://github.com/cosmos/cosmos-sdk/issues/19228 is resolved
// We are tagging x/tx from main and must keep using released versions of x/tx dependencies

View File

@ -1,7 +1,5 @@
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
@ -10,9 +8,11 @@ github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+R
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/gogoproto v1.4.12 h1:vB6Lbe/rtnYGjQuFxkPiPYiCybqFT8QvLipDZP8JpFE=
github.com/cosmos/gogoproto v1.4.12/go.mod h1:LnZob1bXRdUoqMMtwYlcR3wjiElmlC+FkjaZRv1/eLY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=