port auth module here for old version of ante module
This commit is contained in:
parent
fc0ade482d
commit
47bafe4619
58
x/auth/ante/ante.go
Normal file
58
x/auth/ante/ante.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlerOptions are the options required for constructing a default SDK AnteHandler.
|
||||||
|
type HandlerOptions struct {
|
||||||
|
AccountKeeper AccountKeeper
|
||||||
|
BankKeeper types.BankKeeper
|
||||||
|
FeegrantKeeper FeegrantKeeper
|
||||||
|
SignModeHandler authsigning.SignModeHandler
|
||||||
|
SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnteHandler returns an AnteHandler that checks and increments sequence
|
||||||
|
// numbers, checks signatures & account numbers, and deducts fees from the first
|
||||||
|
// signer.
|
||||||
|
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
|
||||||
|
if options.AccountKeeper == nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.BankKeeper == nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for ante builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.SignModeHandler == nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigGasConsumer = options.SigGasConsumer
|
||||||
|
if sigGasConsumer == nil {
|
||||||
|
sigGasConsumer = DefaultSigVerificationGasConsumer
|
||||||
|
}
|
||||||
|
|
||||||
|
anteDecorators := []sdk.AnteDecorator{
|
||||||
|
NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
|
||||||
|
NewRejectExtensionOptionsDecorator(),
|
||||||
|
NewMempoolFeeDecorator(),
|
||||||
|
NewValidateBasicDecorator(),
|
||||||
|
NewTxTimeoutHeightDecorator(),
|
||||||
|
NewValidateMemoDecorator(options.AccountKeeper),
|
||||||
|
NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
|
||||||
|
NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper),
|
||||||
|
NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
|
||||||
|
NewValidateSigCountDecorator(options.AccountKeeper),
|
||||||
|
NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
|
||||||
|
NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||||
|
NewIncrementSequenceDecorator(options.AccountKeeper),
|
||||||
|
}
|
||||||
|
|
||||||
|
return sdk.ChainAnteDecorators(anteDecorators...), nil
|
||||||
|
}
|
1151
x/auth/ante/ante_test.go
Normal file
1151
x/auth/ante/ante_test.go
Normal file
File diff suppressed because it is too large
Load Diff
206
x/auth/ante/basic.go
Normal file
206
x/auth/ante/basic.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
|
||||||
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error.
|
||||||
|
// If ValidateBasic passes, decorator calls next AnteHandler in chain. Note,
|
||||||
|
// ValidateBasicDecorator decorator will not get executed on ReCheckTx since it
|
||||||
|
// is not dependent on application state.
|
||||||
|
type ValidateBasicDecorator struct{}
|
||||||
|
|
||||||
|
func NewValidateBasicDecorator() ValidateBasicDecorator {
|
||||||
|
return ValidateBasicDecorator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vbd ValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
// no need to validate basic on recheck tx, call next antehandler
|
||||||
|
if ctx.IsReCheckTx() {
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.ValidateBasic(); err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateMemoDecorator will validate memo given the parameters passed in
|
||||||
|
// If memo is too large decorator returns with error, otherwise call next AnteHandler
|
||||||
|
// CONTRACT: Tx must implement TxWithMemo interface
|
||||||
|
type ValidateMemoDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidateMemoDecorator(ak AccountKeeper) ValidateMemoDecorator {
|
||||||
|
return ValidateMemoDecorator{
|
||||||
|
ak: ak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vmd ValidateMemoDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
memoTx, ok := tx.(sdk.TxWithMemo)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := vmd.ak.GetParams(ctx)
|
||||||
|
|
||||||
|
memoLength := len(memoTx.GetMemo())
|
||||||
|
if uint64(memoLength) > params.MaxMemoCharacters {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrMemoTooLarge,
|
||||||
|
"maximum number of characters is %d but received %d characters",
|
||||||
|
params.MaxMemoCharacters, memoLength,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeTxSizeGasDecorator will take in parameters and consume gas proportional
|
||||||
|
// to the size of tx before calling next AnteHandler. Note, the gas costs will be
|
||||||
|
// slightly over estimated due to the fact that any given signing account may need
|
||||||
|
// to be retrieved from state.
|
||||||
|
//
|
||||||
|
// CONTRACT: If simulate=true, then signatures must either be completely filled
|
||||||
|
// in or empty.
|
||||||
|
// CONTRACT: To use this decorator, signatures of transaction must be represented
|
||||||
|
// as legacytx.StdSignature otherwise simulate mode will incorrectly estimate gas cost.
|
||||||
|
type ConsumeTxSizeGasDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsumeGasForTxSizeDecorator(ak AccountKeeper) ConsumeTxSizeGasDecorator {
|
||||||
|
return ConsumeTxSizeGasDecorator{
|
||||||
|
ak: ak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cgts ConsumeTxSizeGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
|
||||||
|
}
|
||||||
|
params := cgts.ak.GetParams(ctx)
|
||||||
|
|
||||||
|
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*sdk.Gas(len(ctx.TxBytes())), "txSize")
|
||||||
|
|
||||||
|
// simulate gas cost for signatures in simulate mode
|
||||||
|
if simulate {
|
||||||
|
// in simulate mode, each element should be a nil signature
|
||||||
|
sigs, err := sigTx.GetSignaturesV2()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
n := len(sigs)
|
||||||
|
|
||||||
|
for i, signer := range sigTx.GetSigners() {
|
||||||
|
// if signature is already filled in, no need to simulate gas cost
|
||||||
|
if i < n && !isIncompleteSignature(sigs[i].Data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var pubkey cryptotypes.PubKey
|
||||||
|
|
||||||
|
acc := cgts.ak.GetAccount(ctx, signer)
|
||||||
|
|
||||||
|
// use placeholder simSecp256k1Pubkey if sig is nil
|
||||||
|
if acc == nil || acc.GetPubKey() == nil {
|
||||||
|
pubkey = simSecp256k1Pubkey
|
||||||
|
} else {
|
||||||
|
pubkey = acc.GetPubKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
// use stdsignature to mock the size of a full signature
|
||||||
|
simSig := legacytx.StdSignature{ //nolint:staticcheck // this will be removed when proto is ready
|
||||||
|
Signature: simSecp256k1Sig[:],
|
||||||
|
PubKey: pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
sigBz := legacy.Cdc.MustMarshal(simSig)
|
||||||
|
cost := sdk.Gas(len(sigBz) + 6)
|
||||||
|
|
||||||
|
// If the pubkey is a multi-signature pubkey, then we estimate for the maximum
|
||||||
|
// number of signers.
|
||||||
|
if _, ok := pubkey.(*multisig.LegacyAminoPubKey); ok {
|
||||||
|
cost *= params.TxSigLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.GasMeter().ConsumeGas(params.TxSizeCostPerByte*cost, "txSize")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isIncompleteSignature tests whether SignatureData is fully filled in for simulation purposes
|
||||||
|
func isIncompleteSignature(data signing.SignatureData) bool {
|
||||||
|
if data == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data := data.(type) {
|
||||||
|
case *signing.SingleSignatureData:
|
||||||
|
return len(data.Signature) == 0
|
||||||
|
case *signing.MultiSignatureData:
|
||||||
|
if len(data.Signatures) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, s := range data.Signatures {
|
||||||
|
if isIncompleteSignature(s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
|
||||||
|
// tx height timeout.
|
||||||
|
TxTimeoutHeightDecorator struct{}
|
||||||
|
|
||||||
|
// TxWithTimeoutHeight defines the interface a tx must implement in order for
|
||||||
|
// TxHeightTimeoutDecorator to process the tx.
|
||||||
|
TxWithTimeoutHeight interface {
|
||||||
|
sdk.Tx
|
||||||
|
|
||||||
|
GetTimeoutHeight() uint64
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TxTimeoutHeightDecorator defines an AnteHandler decorator that checks for a
|
||||||
|
// tx height timeout.
|
||||||
|
func NewTxTimeoutHeightDecorator() TxTimeoutHeightDecorator {
|
||||||
|
return TxTimeoutHeightDecorator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnteHandle implements an AnteHandler decorator 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) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
timeoutTx, ok := tx.(TxWithTimeoutHeight)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "expected tx to implement TxWithTimeoutHeight")
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutHeight := timeoutTx.GetTimeoutHeight()
|
||||||
|
if timeoutHeight > 0 && uint64(ctx.BlockHeight()) > timeoutHeight {
|
||||||
|
return ctx, sdkerrors.Wrapf(
|
||||||
|
sdkerrors.ErrTxTimeoutHeight, "block height: %d, timeout height: %d", ctx.BlockHeight(), timeoutHeight,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
224
x/auth/ante/basic_test.go
Normal file
224
x/auth/ante/basic_test.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestValidateBasic() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{}
|
||||||
|
invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
vbd := ante.NewValidateBasicDecorator()
|
||||||
|
antehandler := sdk.ChainAnteDecorators(vbd)
|
||||||
|
_, err = antehandler(suite.ctx, invalidTx, false)
|
||||||
|
|
||||||
|
suite.Require().NotNil(err, "Did not error on invalid tx")
|
||||||
|
|
||||||
|
privs, accNums, accSeqs = []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, validTx, false)
|
||||||
|
suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err)
|
||||||
|
|
||||||
|
// test decorator skips on recheck
|
||||||
|
suite.ctx = suite.ctx.WithIsReCheckTx(true)
|
||||||
|
|
||||||
|
// decorator should skip processing invalidTx on recheck and thus return nil-error
|
||||||
|
_, err = antehandler(suite.ctx, invalidTx, false)
|
||||||
|
|
||||||
|
suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestValidateMemo() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
suite.txBuilder.SetMemo(strings.Repeat("01234567890", 500))
|
||||||
|
invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// require that long memos get rejected
|
||||||
|
vmd := ante.NewValidateMemoDecorator(suite.app.AccountKeeper)
|
||||||
|
antehandler := sdk.ChainAnteDecorators(vmd)
|
||||||
|
_, err = antehandler(suite.ctx, invalidTx, false)
|
||||||
|
|
||||||
|
suite.Require().NotNil(err, "Did not error on tx with high memo")
|
||||||
|
|
||||||
|
suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10))
|
||||||
|
validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// require small memos pass ValidateMemo Decorator
|
||||||
|
_, err = antehandler(suite.ctx, validTx, false)
|
||||||
|
suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestConsumeGasForTxSize() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
|
||||||
|
cgtsd := ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper)
|
||||||
|
antehandler := sdk.ChainAnteDecorators(cgtsd)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
sigV2 signing.SignatureV2
|
||||||
|
}{
|
||||||
|
{"SingleSignatureData", signing.SignatureV2{PubKey: priv1.PubKey()}},
|
||||||
|
{"MultiSignatureData", signing.SignatureV2{PubKey: priv1.PubKey(), Data: multisig.NewMultisig(2)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10))
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
txBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx)
|
||||||
|
suite.Require().Nil(err, "Cannot marshal tx: %v", err)
|
||||||
|
|
||||||
|
params := suite.app.AccountKeeper.GetParams(suite.ctx)
|
||||||
|
expectedGas := sdk.Gas(len(txBytes)) * params.TxSizeCostPerByte
|
||||||
|
|
||||||
|
// Set suite.ctx with TxBytes manually
|
||||||
|
suite.ctx = suite.ctx.WithTxBytes(txBytes)
|
||||||
|
|
||||||
|
// track how much gas is necessary to retrieve parameters
|
||||||
|
beforeGas := suite.ctx.GasMeter().GasConsumed()
|
||||||
|
suite.app.AccountKeeper.GetParams(suite.ctx)
|
||||||
|
afterGas := suite.ctx.GasMeter().GasConsumed()
|
||||||
|
expectedGas += afterGas - beforeGas
|
||||||
|
|
||||||
|
beforeGas = suite.ctx.GasMeter().GasConsumed()
|
||||||
|
suite.ctx, err = antehandler(suite.ctx, tx, false)
|
||||||
|
suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err)
|
||||||
|
|
||||||
|
// require that decorator consumes expected amount of gas
|
||||||
|
consumedGas := suite.ctx.GasMeter().GasConsumed() - beforeGas
|
||||||
|
suite.Require().Equal(expectedGas, consumedGas, "Decorator did not consume the correct amount of gas")
|
||||||
|
|
||||||
|
// simulation must not underestimate gas of this decorator even with nil signatures
|
||||||
|
txBuilder, err := suite.clientCtx.TxConfig.WrapTxBuilder(tx)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.Require().NoError(txBuilder.SetSignatures(tc.sigV2))
|
||||||
|
tx = txBuilder.GetTx()
|
||||||
|
|
||||||
|
simTxBytes, err := suite.clientCtx.TxConfig.TxJSONEncoder()(tx)
|
||||||
|
suite.Require().Nil(err, "Cannot marshal tx: %v", err)
|
||||||
|
// require that simulated tx is smaller than tx with signatures
|
||||||
|
suite.Require().True(len(simTxBytes) < len(txBytes), "simulated tx still has signatures")
|
||||||
|
|
||||||
|
// Set suite.ctx with smaller simulated TxBytes manually
|
||||||
|
suite.ctx = suite.ctx.WithTxBytes(simTxBytes)
|
||||||
|
|
||||||
|
beforeSimGas := suite.ctx.GasMeter().GasConsumed()
|
||||||
|
|
||||||
|
// run antehandler with simulate=true
|
||||||
|
suite.ctx, err = antehandler(suite.ctx, tx, true)
|
||||||
|
consumedSimGas := suite.ctx.GasMeter().GasConsumed() - beforeSimGas
|
||||||
|
|
||||||
|
// require that antehandler passes and does not underestimate decorator cost
|
||||||
|
suite.Require().Nil(err, "ConsumeTxSizeGasDecorator returned error: %v", err)
|
||||||
|
suite.Require().True(consumedSimGas >= expectedGas, "Simulate mode underestimates gas on AnteDecorator. Simulated cost: %d, expected cost: %d", consumedSimGas, expectedGas)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestTxHeightTimeoutDecorator() {
|
||||||
|
suite.SetupTest(true)
|
||||||
|
|
||||||
|
antehandler := sdk.ChainAnteDecorators(ante.NewTxTimeoutHeightDecorator())
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
timeout uint64
|
||||||
|
height int64
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{"default value", 0, 10, false},
|
||||||
|
{"no timeout (greater height)", 15, 10, false},
|
||||||
|
{"no timeout (same height)", 10, 10, false},
|
||||||
|
{"timeout (smaller height)", 9, 10, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
suite.Run(tc.name, func() {
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
suite.txBuilder.SetMemo(strings.Repeat("01234567890", 10))
|
||||||
|
suite.txBuilder.SetTimeoutHeight(tc.timeout)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
ctx := suite.ctx.WithBlockHeight(tc.height)
|
||||||
|
_, err = antehandler(ctx, tx, true)
|
||||||
|
suite.Require().Equal(tc.expectErr, err != nil, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
20
x/auth/ante/expected_keepers.go
Normal file
20
x/auth/ante/expected_keepers.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AccountKeeper defines the contract needed for AccountKeeper related APIs.
|
||||||
|
// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators.
|
||||||
|
type AccountKeeper interface {
|
||||||
|
GetParams(ctx sdk.Context) (params types.Params)
|
||||||
|
GetAccount(ctx sdk.Context, addr sdk.AccAddress) types.AccountI
|
||||||
|
SetAccount(ctx sdk.Context, acc types.AccountI)
|
||||||
|
GetModuleAddress(moduleName string) sdk.AccAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeegrantKeeper defines the expected feegrant keeper.
|
||||||
|
type FeegrantKeeper interface {
|
||||||
|
UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error
|
||||||
|
}
|
36
x/auth/ante/ext.go
Normal file
36
x/auth/ante/ext.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HasExtensionOptionsTx interface {
|
||||||
|
GetExtensionOptions() []*codectypes.Any
|
||||||
|
GetNonCriticalExtensionOptions() []*codectypes.Any
|
||||||
|
}
|
||||||
|
|
||||||
|
// RejectExtensionOptionsDecorator is an AnteDecorator that rejects all extension
|
||||||
|
// options which can optionally be included in protobuf transactions. Users that
|
||||||
|
// need extension options should create a custom AnteHandler chain that handles
|
||||||
|
// needed extension options properly and rejects unknown ones.
|
||||||
|
type RejectExtensionOptionsDecorator struct{}
|
||||||
|
|
||||||
|
// NewRejectExtensionOptionsDecorator creates a new RejectExtensionOptionsDecorator
|
||||||
|
func NewRejectExtensionOptionsDecorator() RejectExtensionOptionsDecorator {
|
||||||
|
return RejectExtensionOptionsDecorator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ types.AnteDecorator = RejectExtensionOptionsDecorator{}
|
||||||
|
|
||||||
|
// AnteHandle implements the AnteDecorator.AnteHandle method
|
||||||
|
func (r RejectExtensionOptionsDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) {
|
||||||
|
if hasExtOptsTx, ok := tx.(HasExtensionOptionsTx); ok {
|
||||||
|
if len(hasExtOptsTx.GetExtensionOptions()) != 0 {
|
||||||
|
return ctx, sdkerrors.ErrUnknownExtensionOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
36
x/auth/ante/ext_test.go
Normal file
36
x/auth/ante/ext_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestRejectExtensionOptionsDecorator() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
reod := ante.NewRejectExtensionOptionsDecorator()
|
||||||
|
antehandler := sdk.ChainAnteDecorators(reod)
|
||||||
|
|
||||||
|
// no extension options should not trigger an error
|
||||||
|
theTx := suite.txBuilder.GetTx()
|
||||||
|
_, err := antehandler(suite.ctx, theTx, false)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
extOptsTxBldr, ok := suite.txBuilder.(tx.ExtensionOptionsTxBuilder)
|
||||||
|
if !ok {
|
||||||
|
// if we can't set extension options, this decorator doesn't apply and we're done
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// setting any extension option should cause an error
|
||||||
|
any, err := types.NewAnyWithValue(testdata.NewTestMsg())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
extOptsTxBldr.SetExtensionOptions(any)
|
||||||
|
theTx = suite.txBuilder.GetTx()
|
||||||
|
_, err = antehandler(suite.ctx, theTx, false)
|
||||||
|
suite.Require().EqualError(err, "unknown extension options")
|
||||||
|
}
|
141
x/auth/ante/fee.go
Normal file
141
x/auth/ante/fee.go
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MempoolFeeDecorator will check if the transaction's fee is at least as large
|
||||||
|
// as the local validator's minimum gasFee (defined in validator config).
|
||||||
|
// If fee is too low, decorator returns error and tx is rejected from mempool.
|
||||||
|
// Note this only applies when ctx.CheckTx = true
|
||||||
|
// If fee is high enough or not CheckTx, then call next AnteHandler
|
||||||
|
// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator
|
||||||
|
type MempoolFeeDecorator struct{}
|
||||||
|
|
||||||
|
func NewMempoolFeeDecorator() MempoolFeeDecorator {
|
||||||
|
return MempoolFeeDecorator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mfd MempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
feeTx, ok := tx.(sdk.FeeTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
|
||||||
|
}
|
||||||
|
|
||||||
|
feeCoins := feeTx.GetFee()
|
||||||
|
gas := feeTx.GetGas()
|
||||||
|
|
||||||
|
// Ensure that the provided fees meet a minimum threshold for the validator,
|
||||||
|
// if this is a CheckTx. This is only for local mempool purposes, and thus
|
||||||
|
// is only ran on check tx.
|
||||||
|
if ctx.IsCheckTx() && !simulate {
|
||||||
|
minGasPrices := ctx.MinGasPrices()
|
||||||
|
if !minGasPrices.IsZero() {
|
||||||
|
requiredFees := make(sdk.Coins, len(minGasPrices))
|
||||||
|
|
||||||
|
// Determine the required fees by multiplying each required minimum gas
|
||||||
|
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
|
||||||
|
glDec := sdk.NewDec(int64(gas))
|
||||||
|
for i, gp := range minGasPrices {
|
||||||
|
fee := gp.Amount.Mul(glDec)
|
||||||
|
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !feeCoins.IsAnyGTE(requiredFees) {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeductFeeDecorator deducts fees from the first signer of the tx
|
||||||
|
// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error
|
||||||
|
// Call next AnteHandler if fees successfully deducted
|
||||||
|
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator
|
||||||
|
type DeductFeeDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
bankKeeper evmtypes.BankKeeper
|
||||||
|
feegrantKeeper FeegrantKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeductFeeDecorator(ak AccountKeeper, bk evmtypes.BankKeeper, fk FeegrantKeeper) DeductFeeDecorator {
|
||||||
|
return DeductFeeDecorator{
|
||||||
|
ak: ak,
|
||||||
|
bankKeeper: bk,
|
||||||
|
feegrantKeeper: fk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
feeTx, ok := tx.(sdk.FeeTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx")
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr := dfd.ak.GetModuleAddress(types.FeeCollectorName); addr == nil {
|
||||||
|
panic(fmt.Sprintf("%s module account has not been set", types.FeeCollectorName))
|
||||||
|
}
|
||||||
|
|
||||||
|
fee := feeTx.GetFee()
|
||||||
|
feePayer := feeTx.FeePayer()
|
||||||
|
feeGranter := feeTx.FeeGranter()
|
||||||
|
|
||||||
|
deductFeesFrom := feePayer
|
||||||
|
|
||||||
|
// if feegranter set deduct fee from feegranter account.
|
||||||
|
// this works with only when feegrant enabled.
|
||||||
|
if feeGranter != nil {
|
||||||
|
if dfd.feegrantKeeper == nil {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled")
|
||||||
|
} else if !feeGranter.Equals(feePayer) {
|
||||||
|
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deductFeesFrom = feeGranter
|
||||||
|
}
|
||||||
|
|
||||||
|
deductFeesFromAcc := dfd.ak.GetAccount(ctx, deductFeesFrom)
|
||||||
|
if deductFeesFromAcc == nil {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deduct the fees
|
||||||
|
if !feeTx.GetFee().IsZero() {
|
||||||
|
err = DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, feeTx.GetFee())
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
events := sdk.Events{sdk.NewEvent(sdk.EventTypeTx,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyFee, feeTx.GetFee().String()),
|
||||||
|
)}
|
||||||
|
ctx.EventManager().EmitEvents(events)
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeductFees deducts fees from the given account.
|
||||||
|
func DeductFees(bankKeeper evmtypes.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
|
||||||
|
if !fees.IsValid() {
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
|
||||||
|
if err != nil {
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
104
x/auth/ante/fee_test.go
Normal file
104
x/auth/ante/fee_test.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestEnsureMempoolFees() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
mfd := ante.NewMempoolFeeDecorator()
|
||||||
|
antehandler := sdk.ChainAnteDecorators(mfd)
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Set high gas price so standard test fee fails
|
||||||
|
atomPrice := sdk.NewDecCoinFromDec("atom", sdk.NewDec(200).Quo(sdk.NewDec(100000)))
|
||||||
|
highGasPrice := []sdk.DecCoin{atomPrice}
|
||||||
|
suite.ctx = suite.ctx.WithMinGasPrices(highGasPrice)
|
||||||
|
|
||||||
|
// Set IsCheckTx to true
|
||||||
|
suite.ctx = suite.ctx.WithIsCheckTx(true)
|
||||||
|
|
||||||
|
// antehandler errors with insufficient fees
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
suite.Require().NotNil(err, "Decorator should have errored on too low fee for local gasPrice")
|
||||||
|
|
||||||
|
// Set IsCheckTx to false
|
||||||
|
suite.ctx = suite.ctx.WithIsCheckTx(false)
|
||||||
|
|
||||||
|
// antehandler should not error since we do not check minGasPrice in DeliverTx
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
suite.Require().Nil(err, "MempoolFeeDecorator returned error in DeliverTx")
|
||||||
|
|
||||||
|
// Set IsCheckTx back to true for testing sufficient mempool fee
|
||||||
|
suite.ctx = suite.ctx.WithIsCheckTx(true)
|
||||||
|
|
||||||
|
atomPrice = sdk.NewDecCoinFromDec("atom", sdk.NewDec(0).Quo(sdk.NewDec(100000)))
|
||||||
|
lowGasPrice := []sdk.DecCoin{atomPrice}
|
||||||
|
suite.ctx = suite.ctx.WithMinGasPrices(lowGasPrice)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
suite.Require().Nil(err, "Decorator should not have errored on fee higher than local gasPrice")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestDeductFees() {
|
||||||
|
suite.SetupTest(false) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Set account with insufficient funds
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr1)
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
coins := sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(10)))
|
||||||
|
err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, coins)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
dfd := ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, nil)
|
||||||
|
antehandler := sdk.ChainAnteDecorators(dfd)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
|
||||||
|
suite.Require().NotNil(err, "Tx did not error when fee payer had insufficient funds")
|
||||||
|
|
||||||
|
// Set account with sufficient funds
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(200))))
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
|
||||||
|
suite.Require().Nil(err, "Tx errored after account has been set with sufficient funds")
|
||||||
|
}
|
233
x/auth/ante/feegrant_test.go
Normal file
233
x/auth/ante/feegrant_test.go
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
/* TODO: fix these tests
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/simulation"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
ante "github.com/tharsis/ethermint/x/auth/ante"
|
||||||
|
authsign "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/feegrant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestDeductFeesNoDelegation() {
|
||||||
|
suite.SetupTest(false)
|
||||||
|
// setup
|
||||||
|
app, ctx := suite.app, suite.ctx
|
||||||
|
|
||||||
|
protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes)
|
||||||
|
|
||||||
|
// this just tests our handler
|
||||||
|
dfd := ante.NewDeductFeeDecorator(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper)
|
||||||
|
feeAnteHandler := sdk.ChainAnteDecorators(dfd)
|
||||||
|
|
||||||
|
// this tests the whole stack
|
||||||
|
anteHandlerStack := suite.anteHandler
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
priv2, _, addr2 := testdata.KeyTestPubAddr()
|
||||||
|
priv3, _, addr3 := testdata.KeyTestPubAddr()
|
||||||
|
priv4, _, addr4 := testdata.KeyTestPubAddr()
|
||||||
|
priv5, _, addr5 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// Set addr1 with insufficient funds
|
||||||
|
err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// Set addr2 with more funds
|
||||||
|
err = simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// grant fee allowance from `addr2` to `addr3` (plenty to pay)
|
||||||
|
err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr3, &feegrant.BasicAllowance{
|
||||||
|
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)),
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// grant low fee allowance (20atom), to check the tx requesting more than allowed.
|
||||||
|
err = app.FeeGrantKeeper.GrantAllowance(ctx, addr2, addr4, &feegrant.BasicAllowance{
|
||||||
|
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)),
|
||||||
|
})
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
signerKey cryptotypes.PrivKey
|
||||||
|
signer sdk.AccAddress
|
||||||
|
feeAccount sdk.AccAddress
|
||||||
|
fee int64
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
"paying with low funds": {
|
||||||
|
signerKey: priv1,
|
||||||
|
signer: addr1,
|
||||||
|
fee: 50,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
"paying with good funds": {
|
||||||
|
signerKey: priv2,
|
||||||
|
signer: addr2,
|
||||||
|
fee: 50,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
"paying with no account": {
|
||||||
|
signerKey: priv3,
|
||||||
|
signer: addr3,
|
||||||
|
fee: 1,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
"no fee with real account": {
|
||||||
|
signerKey: priv1,
|
||||||
|
signer: addr1,
|
||||||
|
fee: 0,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
"no fee with no account": {
|
||||||
|
signerKey: priv5,
|
||||||
|
signer: addr5,
|
||||||
|
fee: 0,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
"valid fee grant without account": {
|
||||||
|
signerKey: priv3,
|
||||||
|
signer: addr3,
|
||||||
|
feeAccount: addr2,
|
||||||
|
fee: 50,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
"no fee grant": {
|
||||||
|
signerKey: priv3,
|
||||||
|
signer: addr3,
|
||||||
|
feeAccount: addr1,
|
||||||
|
fee: 2,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
"allowance smaller than requested fee": {
|
||||||
|
signerKey: priv4,
|
||||||
|
signer: addr4,
|
||||||
|
feeAccount: addr2,
|
||||||
|
fee: 50,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
"granter cannot cover allowed fee grant": {
|
||||||
|
signerKey: priv4,
|
||||||
|
signer: addr4,
|
||||||
|
feeAccount: addr1,
|
||||||
|
fee: 50,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, stc := range cases {
|
||||||
|
tc := stc // to make scopelint happy
|
||||||
|
suite.T().Run(name, func(t *testing.T) {
|
||||||
|
fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee))
|
||||||
|
msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)}
|
||||||
|
|
||||||
|
acc := app.AccountKeeper.GetAccount(ctx, tc.signer)
|
||||||
|
privs, accNums, seqs := []cryptotypes.PrivKey{tc.signerKey}, []uint64{0}, []uint64{0}
|
||||||
|
if acc != nil {
|
||||||
|
accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
_, err = feeAnteHandler(ctx, tx, false) // tests only feegrant ante
|
||||||
|
if tc.valid {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = anteHandlerStack(ctx, tx, false) // tests while stack
|
||||||
|
if tc.valid {
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't consume any gas
|
||||||
|
func SigGasNoConsumer(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params authtypes.Params) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTxWithFeeGranter(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, gas uint64, chainID string, accNums,
|
||||||
|
accSeqs []uint64, feeGranter sdk.AccAddress, priv ...cryptotypes.PrivKey) (sdk.Tx, error) {
|
||||||
|
sigs := make([]signing.SignatureV2, len(priv))
|
||||||
|
|
||||||
|
// create a random length memo
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100))
|
||||||
|
|
||||||
|
signMode := gen.SignModeHandler().DefaultMode()
|
||||||
|
|
||||||
|
// 1st round: set SignatureV2 with empty signatures, to set correct
|
||||||
|
// signer infos.
|
||||||
|
for i, p := range priv {
|
||||||
|
sigs[i] = signing.SignatureV2{
|
||||||
|
PubKey: p.PubKey(),
|
||||||
|
Data: &signing.SingleSignatureData{
|
||||||
|
SignMode: signMode,
|
||||||
|
},
|
||||||
|
Sequence: accSeqs[i],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := gen.NewTxBuilder()
|
||||||
|
err := tx.SetMsgs(msgs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = tx.SetSignatures(sigs...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tx.SetMemo(memo)
|
||||||
|
tx.SetFeeAmount(feeAmt)
|
||||||
|
tx.SetGasLimit(gas)
|
||||||
|
tx.SetFeeGranter(feeGranter)
|
||||||
|
|
||||||
|
// 2nd round: once all signer infos are set, every signer can sign.
|
||||||
|
for i, p := range priv {
|
||||||
|
signerData := authsign.SignerData{
|
||||||
|
ChainID: chainID,
|
||||||
|
AccountNumber: accNums[i],
|
||||||
|
Sequence: accSeqs[i],
|
||||||
|
}
|
||||||
|
signBytes, err := gen.SignModeHandler().GetSignBytes(signMode, signerData, tx.GetTx())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sig, err := p.Sign(signBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sigs[i].Data.(*signing.SingleSignatureData).Signature = sig
|
||||||
|
err = tx.SetSignatures(sigs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.GetTx(), nil
|
||||||
|
}
|
||||||
|
*/
|
76
x/auth/ante/setup.go
Normal file
76
x/auth/ante/setup.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ GasTx = (*legacytx.StdTx)(nil) // assert StdTx implements GasTx
|
||||||
|
)
|
||||||
|
|
||||||
|
// GasTx defines a Tx with a GetGas() method which is needed to use SetUpContextDecorator
|
||||||
|
type GasTx interface {
|
||||||
|
sdk.Tx
|
||||||
|
GetGas() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpContextDecorator sets the GasMeter in the Context and wraps the next AnteHandler with a defer clause
|
||||||
|
// to recover from any downstream OutOfGas panics in the AnteHandler chain to return an error with information
|
||||||
|
// on gas provided and gas used.
|
||||||
|
// CONTRACT: Must be first decorator in the chain
|
||||||
|
// CONTRACT: Tx must implement GasTx interface
|
||||||
|
type SetUpContextDecorator struct{}
|
||||||
|
|
||||||
|
func NewSetUpContextDecorator() SetUpContextDecorator {
|
||||||
|
return SetUpContextDecorator{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
// all transactions must implement GasTx
|
||||||
|
gasTx, ok := tx.(GasTx)
|
||||||
|
if !ok {
|
||||||
|
// Set a gas meter with limit 0 as to prevent an infinite gas meter attack
|
||||||
|
// during runTx.
|
||||||
|
newCtx = SetGasMeter(simulate, ctx, 0)
|
||||||
|
return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx")
|
||||||
|
}
|
||||||
|
|
||||||
|
newCtx = SetGasMeter(simulate, ctx, gasTx.GetGas())
|
||||||
|
|
||||||
|
// Decorator will catch an OutOfGasPanic caused in the next antehandler
|
||||||
|
// AnteHandlers must have their own defer/recover in order for the BaseApp
|
||||||
|
// to know how much gas was used! This is because the GasMeter is created in
|
||||||
|
// the AnteHandler, but if it panics the context won't be set properly in
|
||||||
|
// runTx's recover call.
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
switch rType := r.(type) {
|
||||||
|
case sdk.ErrorOutOfGas:
|
||||||
|
log := fmt.Sprintf(
|
||||||
|
"out of gas in location: %v; gasWanted: %d, gasUsed: %d",
|
||||||
|
rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed())
|
||||||
|
|
||||||
|
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
|
||||||
|
default:
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return next(newCtx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGasMeter returns a new context with a gas meter set from a given context.
|
||||||
|
func SetGasMeter(simulate bool, ctx sdk.Context, gasLimit uint64) sdk.Context {
|
||||||
|
// In various cases such as simulation and during the genesis block, we do not
|
||||||
|
// meter any gas utilization.
|
||||||
|
if simulate || ctx.BlockHeight() == 0 {
|
||||||
|
return ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.WithGasMeter(sdk.NewGasMeter(gasLimit))
|
||||||
|
}
|
99
x/auth/ante/setup_test.go
Normal file
99
x/auth/ante/setup_test.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestSetup() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
sud := ante.NewSetUpContextDecorator()
|
||||||
|
antehandler := sdk.ChainAnteDecorators(sud)
|
||||||
|
|
||||||
|
// Set height to non-zero value for GasMeter to be set
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
// Context GasMeter Limit not set
|
||||||
|
suite.Require().Equal(uint64(0), suite.ctx.GasMeter().Limit(), "GasMeter set with limit before setup")
|
||||||
|
|
||||||
|
newCtx, err := antehandler(suite.ctx, tx, false)
|
||||||
|
suite.Require().Nil(err, "SetUpContextDecorator returned error")
|
||||||
|
|
||||||
|
// Context GasMeter Limit should be set after SetUpContextDecorator runs
|
||||||
|
suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit(), "GasMeter not set correctly")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestRecoverPanic() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
// msg and signatures
|
||||||
|
msg := testdata.NewTestMsg(addr1)
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msg))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1}, []uint64{0}, []uint64{0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
sud := ante.NewSetUpContextDecorator()
|
||||||
|
antehandler := sdk.ChainAnteDecorators(sud, OutOfGasDecorator{})
|
||||||
|
|
||||||
|
// Set height to non-zero value for GasMeter to be set
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
newCtx, err := antehandler(suite.ctx, tx, false)
|
||||||
|
|
||||||
|
suite.Require().NotNil(err, "Did not return error on OutOfGas panic")
|
||||||
|
|
||||||
|
suite.Require().True(sdkerrors.ErrOutOfGas.Is(err), "Returned error is not an out of gas error")
|
||||||
|
suite.Require().Equal(gasLimit, newCtx.GasMeter().Limit())
|
||||||
|
|
||||||
|
antehandler = sdk.ChainAnteDecorators(sud, PanicDecorator{})
|
||||||
|
suite.Require().Panics(func() { antehandler(suite.ctx, tx, false) }, "Recovered from non-Out-of-Gas panic") // nolint:errcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutOfGasDecorator struct{}
|
||||||
|
|
||||||
|
// AnteDecorator that will throw OutOfGas panic
|
||||||
|
func (ogd OutOfGasDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
overLimit := ctx.GasMeter().Limit() + 1
|
||||||
|
|
||||||
|
// Should panic with outofgas error
|
||||||
|
ctx.GasMeter().ConsumeGas(overLimit, "test panic")
|
||||||
|
|
||||||
|
// not reached
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PanicDecorator struct{}
|
||||||
|
|
||||||
|
func (pd PanicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
panic("random error")
|
||||||
|
}
|
510
x/auth/ante/sigverify.go
Normal file
510
x/auth/ante/sigverify.go
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
package ante
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
||||||
|
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
|
||||||
|
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// simulation signature values used to estimate gas consumption
|
||||||
|
key = make([]byte, secp256k1.PubKeySize)
|
||||||
|
simSecp256k1Pubkey = &secp256k1.PubKey{Key: key}
|
||||||
|
simSecp256k1Sig [64]byte
|
||||||
|
|
||||||
|
_ authsigning.SigVerifiableTx = (*legacytx.StdTx)(nil) // assert StdTx implements SigVerifiableTx
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// This decodes a valid hex string into a sepc256k1Pubkey for use in transaction simulation
|
||||||
|
bz, _ := hex.DecodeString("035AD6810A47F073553FF30D2FCC7E0D3B1C0B74B61A1AAA2582344037151E143A")
|
||||||
|
copy(key, bz)
|
||||||
|
simSecp256k1Pubkey.Key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureVerificationGasConsumer is the type of function that is used to both
|
||||||
|
// consume gas when verifying signatures and also to accept or reject different types of pubkeys
|
||||||
|
// This is where apps can define their own PubKey
|
||||||
|
type SignatureVerificationGasConsumer = func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error
|
||||||
|
|
||||||
|
// SetPubKeyDecorator sets PubKeys in context for any signer which does not already have pubkey set
|
||||||
|
// PubKeys must be set in context for all signers before any other sigverify decorators run
|
||||||
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||||
|
type SetPubKeyDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSetPubKeyDecorator(ak AccountKeeper) SetPubKeyDecorator {
|
||||||
|
return SetPubKeyDecorator{
|
||||||
|
ak: ak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeys, err := sigTx.GetPubKeys()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
signers := sigTx.GetSigners()
|
||||||
|
|
||||||
|
for i, pk := range pubkeys {
|
||||||
|
// PublicKey was omitted from slice since it has already been set in context
|
||||||
|
if pk == nil {
|
||||||
|
if !simulate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pk = simSecp256k1Pubkey
|
||||||
|
}
|
||||||
|
// Only make check if simulate=false
|
||||||
|
if !simulate && !bytes.Equal(pk.Address(), signers[i]) {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey,
|
||||||
|
"pubKey does not match signer address %s with signer index: %d", signers[i], i)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := GetSignerAcc(ctx, spkd.ak, signers[i])
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
// account already has pubkey set,no need to reset
|
||||||
|
if acc.GetPubKey() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = acc.SetPubKey(pk)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, err.Error())
|
||||||
|
}
|
||||||
|
spkd.ak.SetAccount(ctx, acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also emit the following events, so that txs can be indexed by these
|
||||||
|
// indices:
|
||||||
|
// - signature (via `tx.signature='<sig_as_base64>'`),
|
||||||
|
// - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`).
|
||||||
|
sigs, err := sigTx.GetSignaturesV2()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var events sdk.Events
|
||||||
|
for i, sig := range sigs {
|
||||||
|
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signers[i], sig.Sequence)),
|
||||||
|
))
|
||||||
|
|
||||||
|
sigBzs, err := signatureDataToBz(sig.Data)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
for _, sigBz := range sigBzs {
|
||||||
|
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
|
||||||
|
sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.EventManager().EmitEvents(events)
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume parameter-defined amount of gas for each signature according to the passed-in SignatureVerificationGasConsumer function
|
||||||
|
// before calling the next AnteHandler
|
||||||
|
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
|
||||||
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||||
|
type SigGasConsumeDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
sigGasConsumer SignatureVerificationGasConsumer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSigGasConsumeDecorator(ak AccountKeeper, sigGasConsumer SignatureVerificationGasConsumer) SigGasConsumeDecorator {
|
||||||
|
return SigGasConsumeDecorator{
|
||||||
|
ak: ak,
|
||||||
|
sigGasConsumer: sigGasConsumer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := sgcd.ak.GetParams(ctx)
|
||||||
|
sigs, err := sigTx.GetSignaturesV2()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdSigs contains the sequence number, account number, and signatures.
|
||||||
|
// When simulating, this would just be a 0-length slice.
|
||||||
|
signerAddrs := sigTx.GetSigners()
|
||||||
|
|
||||||
|
for i, sig := range sigs {
|
||||||
|
signerAcc, err := GetSignerAcc(ctx, sgcd.ak, signerAddrs[i])
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := signerAcc.GetPubKey()
|
||||||
|
|
||||||
|
// In simulate mode the transaction comes with no signatures, thus if the
|
||||||
|
// account's pubkey is nil, both signature verification and gasKVStore.Set()
|
||||||
|
// shall consume the largest amount, i.e. it takes more gas to verify
|
||||||
|
// secp256k1 keys than ed25519 ones.
|
||||||
|
if simulate && pubKey == nil {
|
||||||
|
pubKey = simSecp256k1Pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a SignatureV2 with PubKey filled in from above
|
||||||
|
sig = signing.SignatureV2{
|
||||||
|
PubKey: pubKey,
|
||||||
|
Data: sig.Data,
|
||||||
|
Sequence: sig.Sequence,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sgcd.sigGasConsumer(ctx.GasMeter(), sig, params)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all signatures for a tx and return an error if any are invalid. Note,
|
||||||
|
// the SigVerificationDecorator decorator will not get executed on ReCheck.
|
||||||
|
//
|
||||||
|
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
|
||||||
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||||
|
type SigVerificationDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
signModeHandler authsigning.SignModeHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler authsigning.SignModeHandler) SigVerificationDecorator {
|
||||||
|
return SigVerificationDecorator{
|
||||||
|
ak: ak,
|
||||||
|
signModeHandler: signModeHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnlyLegacyAminoSigners checks SignatureData to see if all
|
||||||
|
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
|
||||||
|
// then the corresponding SignatureV2 struct will not have account sequence
|
||||||
|
// explicitly set, and we should skip the explicit verification of sig.Sequence
|
||||||
|
// in the SigVerificationDecorator's AnteHandler function.
|
||||||
|
func OnlyLegacyAminoSigners(sigData signing.SignatureData) bool {
|
||||||
|
switch v := sigData.(type) {
|
||||||
|
case *signing.SingleSignatureData:
|
||||||
|
return v.SignMode == signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
|
||||||
|
case *signing.MultiSignatureData:
|
||||||
|
for _, s := range v.Signatures {
|
||||||
|
if !OnlyLegacyAminoSigners(s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||||
|
// no need to verify signatures on recheck tx
|
||||||
|
if ctx.IsReCheckTx() {
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdSigs contains the sequence number, account number, and signatures.
|
||||||
|
// When simulating, this would just be a 0-length slice.
|
||||||
|
sigs, err := sigTx.GetSignaturesV2()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signerAddrs := sigTx.GetSigners()
|
||||||
|
|
||||||
|
// check that signer length and signature length are the same
|
||||||
|
if len(sigs) != len(signerAddrs) {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sig := range sigs {
|
||||||
|
acc, err := GetSignerAcc(ctx, svd.ak, signerAddrs[i])
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve pubkey
|
||||||
|
pubKey := acc.GetPubKey()
|
||||||
|
if !simulate && pubKey == nil {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check account sequence number.
|
||||||
|
if sig.Sequence != acc.GetSequence() {
|
||||||
|
return ctx, sdkerrors.Wrapf(
|
||||||
|
sdkerrors.ErrWrongSequence,
|
||||||
|
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve signer data
|
||||||
|
genesis := ctx.BlockHeight() == 0
|
||||||
|
chainID := ctx.ChainID()
|
||||||
|
var accNum uint64
|
||||||
|
if !genesis {
|
||||||
|
accNum = acc.GetAccountNumber()
|
||||||
|
}
|
||||||
|
signerData := authsigning.SignerData{
|
||||||
|
ChainID: chainID,
|
||||||
|
AccountNumber: accNum,
|
||||||
|
Sequence: acc.GetSequence(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if !simulate {
|
||||||
|
err := authsigning.VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, tx)
|
||||||
|
if err != nil {
|
||||||
|
var errMsg string
|
||||||
|
if OnlyLegacyAminoSigners(sig.Data) {
|
||||||
|
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
|
||||||
|
// and therefore communicate sequence number as a potential cause of error.
|
||||||
|
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
|
||||||
|
} else {
|
||||||
|
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s)", accNum, chainID)
|
||||||
|
}
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IncrementSequenceDecorator handles incrementing sequences of all signers.
|
||||||
|
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
|
||||||
|
// there is no need to execute IncrementSequenceDecorator on RecheckTX since
|
||||||
|
// CheckTx would already bump the sequence number.
|
||||||
|
//
|
||||||
|
// NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and
|
||||||
|
// sequential txs orginating from the same account cannot be handled correctly in
|
||||||
|
// a reliable way unless sequence numbers are managed and tracked manually by a
|
||||||
|
// client. It is recommended to instead use multiple messages in a tx.
|
||||||
|
type IncrementSequenceDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIncrementSequenceDecorator(ak AccountKeeper) IncrementSequenceDecorator {
|
||||||
|
return IncrementSequenceDecorator{
|
||||||
|
ak: ak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment sequence of all signers
|
||||||
|
for _, addr := range sigTx.GetSigners() {
|
||||||
|
acc := isd.ak.GetAccount(ctx, addr)
|
||||||
|
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isd.ak.SetAccount(ctx, acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateSigCountDecorator takes in Params and returns errors if there are too many signatures in the tx for the given params
|
||||||
|
// otherwise it calls next AnteHandler
|
||||||
|
// Use this decorator to set parameterized limit on number of signatures in tx
|
||||||
|
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||||
|
type ValidateSigCountDecorator struct {
|
||||||
|
ak AccountKeeper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValidateSigCountDecorator(ak AccountKeeper) ValidateSigCountDecorator {
|
||||||
|
return ValidateSigCountDecorator{
|
||||||
|
ak: ak,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vscd ValidateSigCountDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||||
|
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||||
|
if !ok {
|
||||||
|
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a sigTx")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := vscd.ak.GetParams(ctx)
|
||||||
|
pubKeys, err := sigTx.GetPubKeys()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigCount := 0
|
||||||
|
for _, pk := range pubKeys {
|
||||||
|
sigCount += CountSubKeys(pk)
|
||||||
|
if uint64(sigCount) > params.TxSigLimit {
|
||||||
|
return ctx, sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures,
|
||||||
|
"signatures: %d, limit: %d", sigCount, params.TxSigLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(ctx, tx, simulate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
|
||||||
|
// for signature verification based upon the public key type. The cost is fetched from the given params and is matched
|
||||||
|
// by the concrete type.
|
||||||
|
func DefaultSigVerificationGasConsumer(
|
||||||
|
meter sdk.GasMeter, sig signing.SignatureV2, params types.Params,
|
||||||
|
) error {
|
||||||
|
pubkey := sig.PubKey
|
||||||
|
switch pubkey := pubkey.(type) {
|
||||||
|
case *ed25519.PubKey:
|
||||||
|
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
|
||||||
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported")
|
||||||
|
|
||||||
|
case *secp256k1.PubKey:
|
||||||
|
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case *secp256r1.PubKey:
|
||||||
|
meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case multisig.PubKey:
|
||||||
|
multisignature, ok := sig.Data.(*signing.MultiSignatureData)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data)
|
||||||
|
}
|
||||||
|
err := ConsumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature
|
||||||
|
func ConsumeMultisignatureVerificationGas(
|
||||||
|
meter sdk.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey,
|
||||||
|
params types.Params, accSeq uint64,
|
||||||
|
) error {
|
||||||
|
|
||||||
|
size := sig.BitArray.Count()
|
||||||
|
sigIndex := 0
|
||||||
|
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
if !sig.BitArray.GetIndex(i) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sigV2 := signing.SignatureV2{
|
||||||
|
PubKey: pubkey.GetPubKeys()[i],
|
||||||
|
Data: sig.Signatures[sigIndex],
|
||||||
|
Sequence: accSeq,
|
||||||
|
}
|
||||||
|
err := DefaultSigVerificationGasConsumer(meter, sigV2, params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sigIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignerAcc returns an account for a given address that is expected to sign
|
||||||
|
// a transaction.
|
||||||
|
func GetSignerAcc(ctx sdk.Context, ak AccountKeeper, addr sdk.AccAddress) (types.AccountI, error) {
|
||||||
|
if acc := ak.GetAccount(ctx, addr); acc != nil {
|
||||||
|
return acc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "account %s does not exist", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountSubKeys counts the total number of keys for a multi-sig public key.
|
||||||
|
func CountSubKeys(pub cryptotypes.PubKey) int {
|
||||||
|
v, ok := pub.(*kmultisig.LegacyAminoPubKey)
|
||||||
|
if !ok {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
numKeys := 0
|
||||||
|
for _, subkey := range v.GetPubKeys() {
|
||||||
|
numKeys += CountSubKeys(subkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return numKeys
|
||||||
|
}
|
||||||
|
|
||||||
|
// signatureDataToBz converts a SignatureData into raw bytes signature.
|
||||||
|
// For SingleSignatureData, it returns the signature raw bytes.
|
||||||
|
// For MultiSignatureData, it returns an array of all individual signatures,
|
||||||
|
// as well as the aggregated signature.
|
||||||
|
func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
|
||||||
|
if data == nil {
|
||||||
|
return nil, fmt.Errorf("got empty SignatureData")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch data := data.(type) {
|
||||||
|
case *signing.SingleSignatureData:
|
||||||
|
return [][]byte{data.Signature}, nil
|
||||||
|
case *signing.MultiSignatureData:
|
||||||
|
sigs := [][]byte{}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for _, d := range data.Signatures {
|
||||||
|
nestedSigs, err := signatureDataToBz(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs = append(sigs, nestedSigs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
multisig := cryptotypes.MultiSignature{
|
||||||
|
Signatures: sigs,
|
||||||
|
}
|
||||||
|
aggregatedSig, err := multisig.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sigs = append(sigs, aggregatedSig)
|
||||||
|
|
||||||
|
return sigs, nil
|
||||||
|
default:
|
||||||
|
return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data)
|
||||||
|
}
|
||||||
|
}
|
44
x/auth/ante/sigverify_benchmark_test.go
Normal file
44
x/auth/ante/sigverify_benchmark_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
tmcrypto "github.com/tendermint/tendermint/crypto"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This benchmark is used to asses the ante.Secp256k1ToR1GasFactor value
|
||||||
|
func BenchmarkSig(b *testing.B) {
|
||||||
|
require := require.New(b)
|
||||||
|
msg := tmcrypto.CRandBytes(1000)
|
||||||
|
|
||||||
|
skK := secp256k1.GenPrivKey()
|
||||||
|
pkK := skK.PubKey()
|
||||||
|
skR, _ := secp256r1.GenPrivKey()
|
||||||
|
pkR := skR.PubKey()
|
||||||
|
|
||||||
|
sigK, err := skK.Sign(msg)
|
||||||
|
require.NoError(err)
|
||||||
|
sigR, err := skR.Sign(msg)
|
||||||
|
require.NoError(err)
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
b.Run("secp256k1", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ok := pkK.VerifySignature(msg, sigK)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("secp256r1", func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ok := pkR.VerifySignature(msg, sigR)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
390
x/auth/ante/sigverify_test.go
Normal file
390
x/auth/ante/sigverify_test.go
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
||||||
|
kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestSetPubKey() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
require := suite.Require()
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, pub1, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
priv2, pub2, addr2 := testdata.KeyTestPubAddr()
|
||||||
|
priv3, pub3, addr3 := testdata.KeyTestPubAddrSecp256R1(require)
|
||||||
|
|
||||||
|
addrs := []sdk.AccAddress{addr1, addr2, addr3}
|
||||||
|
pubs := []cryptotypes.PubKey{pub1, pub2, pub3}
|
||||||
|
|
||||||
|
msgs := make([]sdk.Msg, len(addrs))
|
||||||
|
// set accounts and create msg for each address
|
||||||
|
for i, addr := range addrs {
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
require.NoError(acc.SetAccountNumber(uint64(i)))
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
msgs[i] = testdata.NewTestMsg(addr)
|
||||||
|
}
|
||||||
|
require.NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
suite.txBuilder.SetFeeAmount(testdata.NewTestFeeAmount())
|
||||||
|
suite.txBuilder.SetGasLimit(testdata.NewTestGasLimit())
|
||||||
|
|
||||||
|
privs, accNums, accSeqs := []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper)
|
||||||
|
antehandler := sdk.ChainAnteDecorators(spkd)
|
||||||
|
|
||||||
|
ctx, err := antehandler(suite.ctx, tx, false)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Require that all accounts have pubkey set after Decorator runs
|
||||||
|
for i, addr := range addrs {
|
||||||
|
pk, err := suite.app.AccountKeeper.GetPubKey(ctx, addr)
|
||||||
|
require.NoError(err, "Error on retrieving pubkey from account")
|
||||||
|
require.True(pubs[i].Equals(pk),
|
||||||
|
"Wrong Pubkey retrieved from AccountKeeper, idx=%d\nexpected=%s\n got=%s", i, pubs[i], pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestConsumeSignatureVerificationGas() {
|
||||||
|
params := types.DefaultParams()
|
||||||
|
msg := []byte{1, 2, 3, 4}
|
||||||
|
cdc := simapp.MakeTestEncodingConfig().Amino
|
||||||
|
|
||||||
|
p := types.DefaultParams()
|
||||||
|
skR1, _ := secp256r1.GenPrivKey()
|
||||||
|
pkSet1, sigSet1 := generatePubKeysAndSignatures(5, msg, false)
|
||||||
|
multisigKey1 := kmultisig.NewLegacyAminoPubKey(2, pkSet1)
|
||||||
|
multisignature1 := multisig.NewMultisig(len(pkSet1))
|
||||||
|
expectedCost1 := expectedGasCostByKeys(pkSet1)
|
||||||
|
for i := 0; i < len(pkSet1); i++ {
|
||||||
|
stdSig := legacytx.StdSignature{PubKey: pkSet1[i], Signature: sigSet1[i]}
|
||||||
|
sigV2, err := legacytx.StdSignatureToSignatureV2(cdc, stdSig)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
err = multisig.AddSignatureV2(multisignature1, sigV2, pkSet1)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
meter sdk.GasMeter
|
||||||
|
sig signing.SignatureData
|
||||||
|
pubkey cryptotypes.PubKey
|
||||||
|
params types.Params
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
gasConsumed uint64
|
||||||
|
shouldErr bool
|
||||||
|
}{
|
||||||
|
{"PubKeyEd25519", args{sdk.NewInfiniteGasMeter(), nil, ed25519.GenPrivKey().PubKey(), params}, p.SigVerifyCostED25519, true},
|
||||||
|
{"PubKeySecp256k1", args{sdk.NewInfiniteGasMeter(), nil, secp256k1.GenPrivKey().PubKey(), params}, p.SigVerifyCostSecp256k1, false},
|
||||||
|
{"PubKeySecp256r1", args{sdk.NewInfiniteGasMeter(), nil, skR1.PubKey(), params}, p.SigVerifyCostSecp256r1(), false},
|
||||||
|
{"Multisig", args{sdk.NewInfiniteGasMeter(), multisignature1, multisigKey1, params}, expectedCost1, false},
|
||||||
|
{"unknown key", args{sdk.NewInfiniteGasMeter(), nil, nil, params}, 0, true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
sigV2 := signing.SignatureV2{
|
||||||
|
PubKey: tt.args.pubkey,
|
||||||
|
Data: tt.args.sig,
|
||||||
|
Sequence: 0, // Arbitrary account sequence
|
||||||
|
}
|
||||||
|
err := ante.DefaultSigVerificationGasConsumer(tt.args.meter, sigV2, tt.args.params)
|
||||||
|
|
||||||
|
if tt.shouldErr {
|
||||||
|
suite.Require().NotNil(err)
|
||||||
|
} else {
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
suite.Require().Equal(tt.gasConsumed, tt.args.meter.GasConsumed(), fmt.Sprintf("%d != %d", tt.gasConsumed, tt.args.meter.GasConsumed()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestSigVerification() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// make block height non-zero to ensure account numbers part of signBytes
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
priv2, _, addr2 := testdata.KeyTestPubAddr()
|
||||||
|
priv3, _, addr3 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
addrs := []sdk.AccAddress{addr1, addr2, addr3}
|
||||||
|
|
||||||
|
msgs := make([]sdk.Msg, len(addrs))
|
||||||
|
// set accounts and create msg for each address
|
||||||
|
for i, addr := range addrs {
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
suite.Require().NoError(acc.SetAccountNumber(uint64(i)))
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
msgs[i] = testdata.NewTestMsg(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
|
||||||
|
spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper)
|
||||||
|
svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler())
|
||||||
|
antehandler := sdk.ChainAnteDecorators(spkd, svd)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
privs []cryptotypes.PrivKey
|
||||||
|
accNums []uint64
|
||||||
|
accSeqs []uint64
|
||||||
|
recheck bool
|
||||||
|
shouldErr bool
|
||||||
|
}
|
||||||
|
testCases := []testCase{
|
||||||
|
{"no signers", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, false, true},
|
||||||
|
{"not enough signers", []cryptotypes.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0}, false, true},
|
||||||
|
{"wrong order signers", []cryptotypes.PrivKey{priv3, priv2, priv1}, []uint64{2, 1, 0}, []uint64{0, 0, 0}, false, true},
|
||||||
|
{"wrong accnums", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{7, 8, 9}, []uint64{0, 0, 0}, false, true},
|
||||||
|
{"wrong sequences", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{3, 4, 5}, false, true},
|
||||||
|
{"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, false, false},
|
||||||
|
{"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false},
|
||||||
|
}
|
||||||
|
for i, tc := range testCases {
|
||||||
|
suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck)
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test
|
||||||
|
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
if tc.shouldErr {
|
||||||
|
suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name)
|
||||||
|
} else {
|
||||||
|
suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test is exactly like the one above, but we set the codec explicitly to
|
||||||
|
// Amino.
|
||||||
|
// Once https://github.com/cosmos/cosmos-sdk/issues/6190 is in, we can remove
|
||||||
|
// this, since it'll be handled by the test matrix.
|
||||||
|
// In the meantime, we want to make double-sure amino compatibility works.
|
||||||
|
// ref: https://github.com/cosmos/cosmos-sdk/issues/7229
|
||||||
|
func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() {
|
||||||
|
suite.app, suite.ctx = createTestApp(true)
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
// Set up TxConfig.
|
||||||
|
aminoCdc := codec.NewLegacyAmino()
|
||||||
|
// We're using TestMsg amino encoding in some tests, so register it here.
|
||||||
|
txConfig := legacytx.StdTxConfig{Cdc: aminoCdc}
|
||||||
|
|
||||||
|
suite.clientCtx = client.Context{}.
|
||||||
|
WithTxConfig(txConfig)
|
||||||
|
|
||||||
|
anteHandler, err := ante.NewAnteHandler(
|
||||||
|
ante.HandlerOptions{
|
||||||
|
AccountKeeper: suite.app.AccountKeeper,
|
||||||
|
BankKeeper: suite.app.BankKeeper,
|
||||||
|
FeegrantKeeper: suite.app.FeeGrantKeeper,
|
||||||
|
SignModeHandler: txConfig.SignModeHandler(),
|
||||||
|
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.anteHandler = anteHandler
|
||||||
|
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// make block height non-zero to ensure account numbers part of signBytes
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
// keys and addresses
|
||||||
|
priv1, _, addr1 := testdata.KeyTestPubAddr()
|
||||||
|
priv2, _, addr2 := testdata.KeyTestPubAddr()
|
||||||
|
priv3, _, addr3 := testdata.KeyTestPubAddr()
|
||||||
|
|
||||||
|
addrs := []sdk.AccAddress{addr1, addr2, addr3}
|
||||||
|
|
||||||
|
msgs := make([]sdk.Msg, len(addrs))
|
||||||
|
// set accounts and create msg for each address
|
||||||
|
for i, addr := range addrs {
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
suite.Require().NoError(acc.SetAccountNumber(uint64(i)))
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
msgs[i] = testdata.NewTestMsg(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
|
||||||
|
spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper)
|
||||||
|
svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler())
|
||||||
|
antehandler := sdk.ChainAnteDecorators(spkd, svd)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
privs []cryptotypes.PrivKey
|
||||||
|
accNums []uint64
|
||||||
|
accSeqs []uint64
|
||||||
|
recheck bool
|
||||||
|
shouldErr bool
|
||||||
|
}
|
||||||
|
testCases := []testCase{
|
||||||
|
{"no signers", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, false, true},
|
||||||
|
{"not enough signers", []cryptotypes.PrivKey{priv1, priv2}, []uint64{0, 1}, []uint64{0, 0}, false, true},
|
||||||
|
{"wrong order signers", []cryptotypes.PrivKey{priv3, priv2, priv1}, []uint64{2, 1, 0}, []uint64{0, 0, 0}, false, true},
|
||||||
|
{"wrong accnums", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{7, 8, 9}, []uint64{0, 0, 0}, false, true},
|
||||||
|
{"wrong sequences", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{3, 4, 5}, false, true},
|
||||||
|
{"valid tx", []cryptotypes.PrivKey{priv1, priv2, priv3}, []uint64{0, 1, 2}, []uint64{0, 0, 0}, false, false},
|
||||||
|
{"no err on recheck", []cryptotypes.PrivKey{}, []uint64{}, []uint64{}, true, false},
|
||||||
|
}
|
||||||
|
for i, tc := range testCases {
|
||||||
|
suite.ctx = suite.ctx.WithIsReCheckTx(tc.recheck)
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() // Create new txBuilder for each test
|
||||||
|
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
tx, err := suite.CreateTestTx(tc.privs, tc.accNums, tc.accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
_, err = antehandler(suite.ctx, tx, false)
|
||||||
|
if tc.shouldErr {
|
||||||
|
suite.Require().NotNil(err, "TestCase %d: %s did not error as expected", i, tc.name)
|
||||||
|
} else {
|
||||||
|
suite.Require().Nil(err, "TestCase %d: %s errored unexpectedly. Err: %v", i, tc.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestSigIntegration() {
|
||||||
|
// generate private keys
|
||||||
|
privs := []cryptotypes.PrivKey{
|
||||||
|
secp256k1.GenPrivKey(),
|
||||||
|
secp256k1.GenPrivKey(),
|
||||||
|
secp256k1.GenPrivKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
params := types.DefaultParams()
|
||||||
|
initialSigCost := params.SigVerifyCostSecp256k1
|
||||||
|
initialCost, err := suite.runSigDecorators(params, false, privs...)
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
params.SigVerifyCostSecp256k1 *= 2
|
||||||
|
doubleCost, err := suite.runSigDecorators(params, false, privs...)
|
||||||
|
suite.Require().Nil(err)
|
||||||
|
|
||||||
|
suite.Require().Equal(initialSigCost*uint64(len(privs)), doubleCost-initialCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) runSigDecorators(params types.Params, _ bool, privs ...cryptotypes.PrivKey) (sdk.Gas, error) {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
// Make block-height non-zero to include accNum in SignBytes
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
suite.app.AccountKeeper.SetParams(suite.ctx, params)
|
||||||
|
|
||||||
|
msgs := make([]sdk.Msg, len(privs))
|
||||||
|
accNums := make([]uint64, len(privs))
|
||||||
|
accSeqs := make([]uint64, len(privs))
|
||||||
|
// set accounts and create msg for each address
|
||||||
|
for i, priv := range privs {
|
||||||
|
addr := sdk.AccAddress(priv.PubKey().Address())
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
suite.Require().NoError(acc.SetAccountNumber(uint64(i)))
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
msgs[i] = testdata.NewTestMsg(addr)
|
||||||
|
accNums[i] = uint64(i)
|
||||||
|
accSeqs[i] = uint64(0)
|
||||||
|
}
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
spkd := ante.NewSetPubKeyDecorator(suite.app.AccountKeeper)
|
||||||
|
svgc := ante.NewSigGasConsumeDecorator(suite.app.AccountKeeper, ante.DefaultSigVerificationGasConsumer)
|
||||||
|
svd := ante.NewSigVerificationDecorator(suite.app.AccountKeeper, suite.clientCtx.TxConfig.SignModeHandler())
|
||||||
|
antehandler := sdk.ChainAnteDecorators(spkd, svgc, svd)
|
||||||
|
|
||||||
|
// Determine gas consumption of antehandler with default params
|
||||||
|
before := suite.ctx.GasMeter().GasConsumed()
|
||||||
|
ctx, err := antehandler(suite.ctx, tx, false)
|
||||||
|
after := ctx.GasMeter().GasConsumed()
|
||||||
|
|
||||||
|
return after - before, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *AnteTestSuite) TestIncrementSequenceDecorator() {
|
||||||
|
suite.SetupTest(true) // setup
|
||||||
|
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()
|
||||||
|
|
||||||
|
priv, _, addr := testdata.KeyTestPubAddr()
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
suite.Require().NoError(acc.SetAccountNumber(uint64(50)))
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
|
||||||
|
msgs := []sdk.Msg{testdata.NewTestMsg(addr)}
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
privs := []cryptotypes.PrivKey{priv}
|
||||||
|
accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()}
|
||||||
|
accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()}
|
||||||
|
feeAmount := testdata.NewTestFeeAmount()
|
||||||
|
gasLimit := testdata.NewTestGasLimit()
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper)
|
||||||
|
antehandler := sdk.ChainAnteDecorators(isd)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
ctx sdk.Context
|
||||||
|
simulate bool
|
||||||
|
expectedSeq uint64
|
||||||
|
}{
|
||||||
|
{suite.ctx.WithIsReCheckTx(true), false, 1},
|
||||||
|
{suite.ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 2},
|
||||||
|
{suite.ctx.WithIsReCheckTx(true), false, 3},
|
||||||
|
{suite.ctx.WithIsReCheckTx(true), false, 4},
|
||||||
|
{suite.ctx.WithIsReCheckTx(true), true, 5},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
_, err := antehandler(tc.ctx, tx, tc.simulate)
|
||||||
|
suite.Require().NoError(err, "unexpected error; tc #%d, %v", i, tc)
|
||||||
|
suite.Require().Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence())
|
||||||
|
}
|
||||||
|
}
|
200
x/auth/ante/testutil_test.go
Normal file
200
x/auth/ante/testutil_test.go
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
package ante_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||||
|
xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestAccount represents an account used in the tests in x/auth/ante.
|
||||||
|
type TestAccount struct {
|
||||||
|
acc types.AccountI
|
||||||
|
priv cryptotypes.PrivKey
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnteTestSuite is a test suite to be used with ante handler tests.
|
||||||
|
type AnteTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
app *simapp.SimApp
|
||||||
|
anteHandler sdk.AnteHandler
|
||||||
|
ctx sdk.Context
|
||||||
|
clientCtx client.Context
|
||||||
|
txBuilder client.TxBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns context and app with params set on account keeper
|
||||||
|
func createTestApp(isCheckTx bool) (*simapp.SimApp, sdk.Context) {
|
||||||
|
app := simapp.Setup(isCheckTx)
|
||||||
|
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{})
|
||||||
|
app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams())
|
||||||
|
|
||||||
|
return app, ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupTest setups a new test, with new app, context, and anteHandler.
|
||||||
|
func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
|
||||||
|
suite.app, suite.ctx = createTestApp(isCheckTx)
|
||||||
|
suite.ctx = suite.ctx.WithBlockHeight(1)
|
||||||
|
|
||||||
|
// Set up TxConfig.
|
||||||
|
encodingConfig := simapp.MakeTestEncodingConfig()
|
||||||
|
// We're using TestMsg encoding in some tests, so register it here.
|
||||||
|
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
|
||||||
|
testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry)
|
||||||
|
|
||||||
|
suite.clientCtx = client.Context{}.
|
||||||
|
WithTxConfig(encodingConfig.TxConfig)
|
||||||
|
|
||||||
|
anteHandler, err := ante.NewAnteHandler(
|
||||||
|
ante.HandlerOptions{
|
||||||
|
AccountKeeper: suite.app.AccountKeeper,
|
||||||
|
BankKeeper: suite.app.BankKeeper,
|
||||||
|
FeegrantKeeper: suite.app.FeeGrantKeeper,
|
||||||
|
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||||
|
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.anteHandler = anteHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestAccounts creates `numAccs` accounts, and return all relevant
|
||||||
|
// information about them including their private keys.
|
||||||
|
func (suite *AnteTestSuite) CreateTestAccounts(numAccs int) []TestAccount {
|
||||||
|
var accounts []TestAccount
|
||||||
|
|
||||||
|
for i := 0; i < numAccs; i++ {
|
||||||
|
priv, _, addr := testdata.KeyTestPubAddr()
|
||||||
|
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
|
||||||
|
err := acc.SetAccountNumber(uint64(i))
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
|
||||||
|
someCoins := sdk.Coins{
|
||||||
|
sdk.NewInt64Coin("atom", 10000000),
|
||||||
|
}
|
||||||
|
err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, someCoins)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, addr, someCoins)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
accounts = append(accounts, TestAccount{acc, priv})
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestTx is a helper function to create a tx given multiple inputs.
|
||||||
|
func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) {
|
||||||
|
// First round: we gather all the signer infos. We use the "set empty
|
||||||
|
// signature" hack to do that.
|
||||||
|
var sigsV2 []signing.SignatureV2
|
||||||
|
for i, priv := range privs {
|
||||||
|
sigV2 := signing.SignatureV2{
|
||||||
|
PubKey: priv.PubKey(),
|
||||||
|
Data: &signing.SingleSignatureData{
|
||||||
|
SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(),
|
||||||
|
Signature: nil,
|
||||||
|
},
|
||||||
|
Sequence: accSeqs[i],
|
||||||
|
}
|
||||||
|
|
||||||
|
sigsV2 = append(sigsV2, sigV2)
|
||||||
|
}
|
||||||
|
err := suite.txBuilder.SetSignatures(sigsV2...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second round: all signer infos are set, so each signer can sign.
|
||||||
|
sigsV2 = []signing.SignatureV2{}
|
||||||
|
for i, priv := range privs {
|
||||||
|
signerData := xauthsigning.SignerData{
|
||||||
|
ChainID: chainID,
|
||||||
|
AccountNumber: accNums[i],
|
||||||
|
Sequence: accSeqs[i],
|
||||||
|
}
|
||||||
|
sigV2, err := tx.SignWithPrivKey(
|
||||||
|
suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData,
|
||||||
|
suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sigsV2 = append(sigsV2, sigV2)
|
||||||
|
}
|
||||||
|
err = suite.txBuilder.SetSignatures(sigsV2...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return suite.txBuilder.GetTx(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCase represents a test case used in test tables.
|
||||||
|
type TestCase struct {
|
||||||
|
desc string
|
||||||
|
malleate func()
|
||||||
|
simulate bool
|
||||||
|
expPass bool
|
||||||
|
expErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTestTx is a helper function to create a tx given multiple inputs.
|
||||||
|
func (suite *AnteTestSuite) RunTestCase(privs []cryptotypes.PrivKey, msgs []sdk.Msg, feeAmount sdk.Coins, gasLimit uint64, accNums, accSeqs []uint64, chainID string, tc TestCase) {
|
||||||
|
suite.Run(fmt.Sprintf("Case %s", tc.desc), func() {
|
||||||
|
suite.Require().NoError(suite.txBuilder.SetMsgs(msgs...))
|
||||||
|
suite.txBuilder.SetFeeAmount(feeAmount)
|
||||||
|
suite.txBuilder.SetGasLimit(gasLimit)
|
||||||
|
|
||||||
|
// Theoretically speaking, ante handler unit tests should only test
|
||||||
|
// ante handlers, but here we sometimes also test the tx creation
|
||||||
|
// process.
|
||||||
|
tx, txErr := suite.CreateTestTx(privs, accNums, accSeqs, chainID)
|
||||||
|
newCtx, anteErr := suite.anteHandler(suite.ctx, tx, tc.simulate)
|
||||||
|
|
||||||
|
if tc.expPass {
|
||||||
|
suite.Require().NoError(txErr)
|
||||||
|
suite.Require().NoError(anteErr)
|
||||||
|
suite.Require().NotNil(newCtx)
|
||||||
|
|
||||||
|
suite.ctx = newCtx
|
||||||
|
} else {
|
||||||
|
switch {
|
||||||
|
case txErr != nil:
|
||||||
|
suite.Require().Error(txErr)
|
||||||
|
suite.Require().True(errors.Is(txErr, tc.expErr))
|
||||||
|
|
||||||
|
case anteErr != nil:
|
||||||
|
suite.Require().Error(anteErr)
|
||||||
|
suite.Require().True(errors.Is(anteErr, tc.expErr))
|
||||||
|
|
||||||
|
default:
|
||||||
|
suite.Fail("expected one of txErr,anteErr to be an error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnteTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(AnteTestSuite))
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user