forked from cerc-io/laconicd-deprecated
refactor: updated to ante handlers support
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
|
||||
tmlog "github.com/tendermint/tendermint/libs/log"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
|
||||
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
|
||||
)
|
||||
|
||||
const (
|
||||
secp256k1VerifyCost uint64 = 21000
|
||||
)
|
||||
|
||||
// NewAnteHandler returns an ante handler responsible for attempting to route an
|
||||
// Ethereum or SDK transaction to an internal ante handler for performing
|
||||
// transaction-level processing (e.g. fee payment, signature verification) before
|
||||
// being passed onto it's respective handler.
|
||||
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
return func(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
var anteHandler sdk.AnteHandler
|
||||
|
||||
defer Recover(ctx.Logger(), &err)
|
||||
|
||||
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
|
||||
if ok {
|
||||
opts := txWithExtensions.GetExtensionOptions()
|
||||
if len(opts) > 0 {
|
||||
switch typeURL := opts[0].GetTypeUrl(); typeURL {
|
||||
case "/ethermint.evm.v1.ExtensionOptionsEthereumTx":
|
||||
// handle as *evmtypes.MsgEthereumTx
|
||||
anteHandler = newEthAnteHandler(options)
|
||||
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
|
||||
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
|
||||
anteHandler = newCosmosAnteHandlerEip712(options)
|
||||
default:
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrUnknownExtensionOptions,
|
||||
"rejecting tx with unsupported extension option: %s", typeURL,
|
||||
)
|
||||
}
|
||||
|
||||
return anteHandler(ctx, tx, sim)
|
||||
}
|
||||
}
|
||||
|
||||
// handle as totally normal Cosmos SDK tx
|
||||
switch tx.(type) {
|
||||
case sdk.Tx:
|
||||
anteHandler = newCosmosAnteHandler(options)
|
||||
default:
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
|
||||
}
|
||||
|
||||
return anteHandler(ctx, tx, sim)
|
||||
}
|
||||
}
|
||||
|
||||
func Recover(logger tmlog.Logger, err *error) {
|
||||
if r := recover(); r != nil {
|
||||
*err = sdkerrors.Wrapf(sdkerrors.ErrPanic, "%v", r)
|
||||
|
||||
if e, ok := r.(error); ok {
|
||||
logger.Error(
|
||||
"ante handler panicked",
|
||||
"error", e,
|
||||
"stack trace", string(debug.Stack()),
|
||||
)
|
||||
} else {
|
||||
logger.Error(
|
||||
"ante handler panicked",
|
||||
"recover", fmt.Sprintf("%v", r),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ authante.SignatureVerificationGasConsumer = DefaultSigVerificationGasConsumer
|
||||
|
||||
// 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 authtypes.Params,
|
||||
) error {
|
||||
// support for ethereum ECDSA secp256k1 keys
|
||||
_, ok := sig.PubKey.(*ethsecp256k1.PubKey)
|
||||
if ok {
|
||||
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: eth_secp256k1")
|
||||
return nil
|
||||
}
|
||||
|
||||
return authante.DefaultSigVerificationGasConsumer(meter, sig, params)
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
package middleware_test
|
||||
package ante_test
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
@@ -16,7 +13,7 @@ import (
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
func (suite AnteTestSuite) TestAnteHandler() {
|
||||
suite.enableFeemarket = false
|
||||
suite.SetupTest() // reset
|
||||
|
||||
@@ -300,7 +297,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"success - DeliverTx EIP712 signed Cosmos Tx with MsgSend",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount)
|
||||
return txBuilder.GetTx()
|
||||
@@ -310,7 +307,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000))
|
||||
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20))
|
||||
amount := sdk.NewCoins(coinAmount)
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount)
|
||||
@@ -321,7 +318,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount)
|
||||
return txBuilder.GetTx()
|
||||
@@ -331,7 +328,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
|
||||
txBuilder.SetGasLimit(uint64(300000))
|
||||
@@ -343,7 +340,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with empty signature",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
|
||||
sigsV2 := signing.SignatureV2{}
|
||||
@@ -355,7 +352,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
|
||||
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
|
||||
@@ -375,7 +372,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
gas := uint64(200000)
|
||||
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
|
||||
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
|
||||
@@ -398,7 +395,8 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx)
|
||||
|
||||
// expConsumed := params.TxGasContractCreation + params.TxGas
|
||||
_, _, err := suite.anteHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.txFn()}, tx.RequestCheckTx{})
|
||||
_, err := suite.anteHandler(suite.ctx, tc.txFn(), false)
|
||||
|
||||
// suite.Require().Equal(consumed, ctx.GasMeter().GasConsumed())
|
||||
|
||||
if tc.expPass {
|
||||
@@ -411,7 +409,7 @@ func (suite MiddlewareTestSuite) TestAnteHandler() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestAnteHandlerWithDynamicTxFee() {
|
||||
func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() {
|
||||
addr, privKey := tests.NewAddrKey()
|
||||
to := tests.GenerateAddress()
|
||||
|
||||
@@ -676,7 +674,7 @@ func (suite MiddlewareTestSuite) TestAnteHandlerWithDynamicTxFee() {
|
||||
|
||||
suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx)
|
||||
suite.app.EvmKeeper.SetBalance(suite.ctx, addr, big.NewInt((ethparams.InitialBaseFee+10)*100000))
|
||||
_, _, err := suite.anteHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.txFn()}, tx.RequestCheckTx{})
|
||||
_, err := suite.anteHandler(suite.ctx, tc.txFn(), false)
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
@@ -0,0 +1,11 @@
|
||||
/*Package ante defines the SDK auth module's AnteHandler as well as an internal
|
||||
AnteHandler for an Ethereum transaction (i.e MsgEthereumTx).
|
||||
|
||||
During CheckTx, the transaction is passed through a series of
|
||||
pre-message execution validation checks such as signature and account
|
||||
verification in addition to minimum fees being checked. Otherwise, during
|
||||
DeliverTx, the transaction is simply passed to the EVM which will also
|
||||
perform the same series of checks. The distinction is made in CheckTx to
|
||||
prevent spam and DoS attacks.
|
||||
*/
|
||||
package ante
|
||||
@@ -1,104 +1,107 @@
|
||||
package middleware
|
||||
package ante
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
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/middleware"
|
||||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
|
||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
|
||||
"github.com/tharsis/ethermint/ethereum/eip712"
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// Eip712SigVerificationMiddleware Verify all signatures for a tx and return an error if any are invalid. Note,
|
||||
// the Eip712SigVerificationMiddleware middleware will not get executed on ReCheck.
|
||||
var ethermintCodec codec.ProtoCodecMarshaler
|
||||
|
||||
func init() {
|
||||
registry := codectypes.NewInterfaceRegistry()
|
||||
ethermint.RegisterInterfaces(registry)
|
||||
ethermintCodec = codec.NewProtoCodec(registry)
|
||||
}
|
||||
|
||||
// Eip712SigVerificationDecorator Verify all signatures for a tx and return an error if any are invalid. Note,
|
||||
// the Eip712SigVerificationDecorator decorator will not get executed on ReCheck.
|
||||
//
|
||||
// CONTRACT: Pubkeys are set in context for all signers before this middleware runs
|
||||
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
|
||||
// CONTRACT: Tx must implement SigVerifiableTx interface
|
||||
type Eip712SigVerificationMiddleware struct {
|
||||
appCodec codec.Codec
|
||||
next tx.Handler
|
||||
type Eip712SigVerificationDecorator struct {
|
||||
ak evmtypes.AccountKeeper
|
||||
signModeHandler authsigning.SignModeHandler
|
||||
}
|
||||
|
||||
var _ tx.Handler = Eip712SigVerificationMiddleware{}
|
||||
|
||||
// NewEip712SigVerificationMiddleware creates a new Eip712SigVerificationMiddleware
|
||||
func NewEip712SigVerificationMiddleware(appCodec codec.Codec, ak evmtypes.AccountKeeper, signModeHandler authsigning.SignModeHandler) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return Eip712SigVerificationMiddleware{
|
||||
appCodec: appCodec,
|
||||
next: h,
|
||||
ak: ak,
|
||||
signModeHandler: signModeHandler,
|
||||
}
|
||||
// NewEip712SigVerificationDecorator creates a new Eip712SigVerificationDecorator
|
||||
func NewEip712SigVerificationDecorator(ak evmtypes.AccountKeeper, signModeHandler authsigning.SignModeHandler) Eip712SigVerificationDecorator {
|
||||
return Eip712SigVerificationDecorator{
|
||||
ak: ak,
|
||||
signModeHandler: signModeHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func eipSigVerification(svd Eip712SigVerificationMiddleware, cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
|
||||
sigTx, ok := reqTx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", reqTx)
|
||||
// AnteHandle handles validation of EIP712 signed cosmos txs.
|
||||
// it is not run on RecheckTx
|
||||
func (svd Eip712SigVerificationDecorator) 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)
|
||||
}
|
||||
|
||||
authSignTx, ok := reqTx.(authsigning.Tx)
|
||||
sigTx, ok := tx.(authsigning.SigVerifiableTx)
|
||||
if !ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", reqTx)
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", tx)
|
||||
}
|
||||
|
||||
authSignTx, ok := tx.(authsigning.Tx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", tx)
|
||||
}
|
||||
|
||||
// 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 tx.Response{}, err
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
signerAddrs := sigTx.GetSigners()
|
||||
|
||||
// EIP712 allows just one signature
|
||||
if len(sigs) != 1 {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signers (%d); EIP712 signatures allows just one signature", len(sigs))
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signers (%d); EIP712 signatures allows just one signature", len(sigs))
|
||||
}
|
||||
|
||||
// check that signer length and signature length are the same
|
||||
if len(sigs) != len(signerAddrs) {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs))
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signerAddrs), len(sigs))
|
||||
}
|
||||
|
||||
// EIP712 has just one signature, avoid looping here and only read index 0
|
||||
i := 0
|
||||
sig := sigs[i]
|
||||
|
||||
acc, err := middleware.GetSignerAcc(ctx, svd.ak, signerAddrs[i])
|
||||
acc, err := authante.GetSignerAcc(ctx, svd.ak, signerAddrs[i])
|
||||
if err != nil {
|
||||
return tx.Response{}, err
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// retrieve pubkey
|
||||
pubKey := acc.GetPubKey()
|
||||
if pubKey == nil {
|
||||
return tx.Response{}, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
|
||||
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 tx.Response{}, sdkerrors.Wrapf(
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrWrongSequence,
|
||||
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
|
||||
)
|
||||
@@ -119,45 +122,21 @@ func eipSigVerification(svd Eip712SigVerificationMiddleware, cx context.Context,
|
||||
Sequence: acc.GetSequence(),
|
||||
}
|
||||
|
||||
if err := VerifySignature(svd.appCodec, pubKey, signerData, sig.Data, svd.signModeHandler, authSignTx); err != nil {
|
||||
if simulate {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
if err := VerifySignature(pubKey, signerData, sig.Data, svd.signModeHandler, authSignTx); err != nil {
|
||||
errMsg := fmt.Errorf("signature verification failed; please verify account number (%d) and chain-id (%s): %w", accNum, chainID, err)
|
||||
return tx.Response{}, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg.Error())
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg.Error())
|
||||
}
|
||||
|
||||
return tx.Response{}, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (svd Eip712SigVerificationMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
if _, err := eipSigVerification(svd, ctx, req); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return svd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (svd Eip712SigVerificationMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := eipSigVerification(svd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return svd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (svd Eip712SigVerificationMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := eipSigVerification(svd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return svd.next.SimulateTx(ctx, req)
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// VerifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes
|
||||
// and single vs multi-signatures.
|
||||
func VerifySignature(
|
||||
appCodec codec.Codec,
|
||||
pubKey cryptotypes.PubKey,
|
||||
signerData authsigning.SignerData,
|
||||
sigData signing.SignatureData,
|
||||
@@ -200,7 +179,7 @@ func VerifySignature(
|
||||
return sdkerrors.Wrapf(err, "failed to parse chainID: %s", signerData.ChainID)
|
||||
}
|
||||
|
||||
txWithExtensions, ok := tx.(middleware.HasExtensionOptionsTx)
|
||||
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
|
||||
if !ok {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain any extensions")
|
||||
}
|
||||
@@ -211,7 +190,7 @@ func VerifySignature(
|
||||
|
||||
var optIface ethermint.ExtensionOptionsWeb3TxI
|
||||
|
||||
if err := appCodec.UnpackAny(opts[0], &optIface); err != nil {
|
||||
if err := ethermintCodec.UnpackAny(opts[0], &optIface); err != nil {
|
||||
return sdkerrors.Wrap(err, "failed to proto-unpack ExtensionOptionsWeb3Tx")
|
||||
}
|
||||
|
||||
@@ -236,7 +215,7 @@ func VerifySignature(
|
||||
FeePayer: feePayer,
|
||||
}
|
||||
|
||||
typedData, err := eip712.WrapTxToTypedData(appCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation)
|
||||
typedData, err := eip712.WrapTxToTypedData(ethermintCodec, extOpt.TypedDataChainID, msgs[0], txBytes, feeDelegation)
|
||||
if err != nil {
|
||||
return sdkerrors.Wrap(err, "failed to pack tx data in EIP712 object")
|
||||
}
|
||||
+534
@@ -0,0 +1,534 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
|
||||
"github.com/tharsis/ethermint/x/evm/statedb"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// EthSigVerificationDecorator validates an ethereum signatures
|
||||
type EthSigVerificationDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator
|
||||
func NewEthSigVerificationDecorator(ek EVMKeeper) EthSigVerificationDecorator {
|
||||
return EthSigVerificationDecorator{
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle validates checks that the registered chain id is the same as the one on the message, and
|
||||
// that the signer address matches the one defined on the message.
|
||||
// It's not skipped for RecheckTx, because it set `From` address which is critical from other ante handler to work.
|
||||
// Failure in RecheckTx will prevent tx to be included into block, especially when CheckTx succeed, in which case user
|
||||
// won't see the error message.
|
||||
func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
chainID := esvd.evmKeeper.ChainID()
|
||||
|
||||
params := esvd.evmKeeper.GetParams(ctx)
|
||||
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
blockNum := big.NewInt(ctx.BlockHeight())
|
||||
signer := ethtypes.MakeSigner(ethCfg, blockNum)
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
sender, err := signer.Sender(msgEthTx.AsTransaction())
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrorInvalidSigner,
|
||||
"couldn't retrieve sender address ('%s') from the ethereum transaction: %s",
|
||||
msgEthTx.From,
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
// set up the sender to the transaction field if not already
|
||||
msgEthTx.From = sender.Hex()
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthAccountVerificationDecorator validates an account balance checks
|
||||
type EthAccountVerificationDecorator struct {
|
||||
ak evmtypes.AccountKeeper
|
||||
bankKeeper evmtypes.BankKeeper
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// NewEthAccountVerificationDecorator creates a new EthAccountVerificationDecorator
|
||||
func NewEthAccountVerificationDecorator(ak evmtypes.AccountKeeper, bankKeeper evmtypes.BankKeeper, ek EVMKeeper) EthAccountVerificationDecorator {
|
||||
return EthAccountVerificationDecorator{
|
||||
ak: ak,
|
||||
bankKeeper: bankKeeper,
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle validates checks that the sender balance is greater than the total transaction cost.
|
||||
// The account will be set to store if it doesn't exis, i.e cannot be found on store.
|
||||
// This AnteHandler decorator will fail if:
|
||||
// - any of the msgs is not a MsgEthereumTx
|
||||
// - from address is empty
|
||||
// - account balance is lower than the transaction cost
|
||||
func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
if !ctx.IsCheckTx() {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
for i, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrapf(err, "failed to unpack tx data any for tx %d", i)
|
||||
}
|
||||
|
||||
// sender address should be in the tx cache from the previous AnteHandle call
|
||||
from := msgEthTx.GetFrom()
|
||||
if from.Empty() {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "from address cannot be empty")
|
||||
}
|
||||
|
||||
// check whether the sender address is EOA
|
||||
fromAddr := common.BytesToAddress(from)
|
||||
acct := avd.evmKeeper.GetAccount(ctx, fromAddr)
|
||||
|
||||
if acct == nil {
|
||||
acc := avd.ak.NewAccountWithAddress(ctx, from)
|
||||
avd.ak.SetAccount(ctx, acc)
|
||||
acct = statedb.NewEmptyAccount()
|
||||
} else if acct.IsContract() {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,
|
||||
"the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash)
|
||||
}
|
||||
|
||||
if err := evmkeeper.CheckSenderBalance(sdk.NewIntFromBigInt(acct.Balance), txData); err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to check sender balance")
|
||||
}
|
||||
|
||||
}
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and
|
||||
// gas consumption.
|
||||
type EthGasConsumeDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
maxGasWanted uint64
|
||||
}
|
||||
|
||||
// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator
|
||||
func NewEthGasConsumeDecorator(
|
||||
evmKeeper EVMKeeper,
|
||||
maxGasWanted uint64,
|
||||
) EthGasConsumeDecorator {
|
||||
return EthGasConsumeDecorator{
|
||||
evmKeeper,
|
||||
maxGasWanted,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle validates that the Ethereum tx message has enough to cover intrinsic gas
|
||||
// (during CheckTx only) and that the sender has enough balance to pay for the gas cost.
|
||||
//
|
||||
// Intrinsic gas for a transaction is the amount of gas that the transaction uses before the
|
||||
// transaction is executed. The gas is a constant value plus any cost inccured by additional bytes
|
||||
// of data supplied with the transaction.
|
||||
//
|
||||
// This AnteHandler decorator will fail if:
|
||||
// - the message is not a MsgEthereumTx
|
||||
// - sender account cannot be found
|
||||
// - transaction's gas limit is lower than the intrinsic gas
|
||||
// - user doesn't have enough balance to deduct the transaction fees (gas_limit * gas_price)
|
||||
// - transaction or block gas meter runs out of gas
|
||||
// - sets the gas meter limit
|
||||
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
params := egcd.evmKeeper.GetParams(ctx)
|
||||
|
||||
ethCfg := params.ChainConfig.EthereumConfig(egcd.evmKeeper.ChainID())
|
||||
|
||||
blockHeight := big.NewInt(ctx.BlockHeight())
|
||||
homestead := ethCfg.IsHomestead(blockHeight)
|
||||
istanbul := ethCfg.IsIstanbul(blockHeight)
|
||||
london := ethCfg.IsLondon(blockHeight)
|
||||
evmDenom := params.EvmDenom
|
||||
gasWanted := uint64(0)
|
||||
var events sdk.Events
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to unpack tx data")
|
||||
}
|
||||
|
||||
if ctx.IsCheckTx() {
|
||||
// We can't trust the tx gas limit, because we'll refund the unused gas.
|
||||
if txData.GetGas() > egcd.maxGasWanted {
|
||||
gasWanted += egcd.maxGasWanted
|
||||
} else {
|
||||
gasWanted += txData.GetGas()
|
||||
}
|
||||
} else {
|
||||
gasWanted += txData.GetGas()
|
||||
}
|
||||
|
||||
fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
|
||||
ctx,
|
||||
*msgEthTx,
|
||||
txData,
|
||||
evmDenom,
|
||||
homestead,
|
||||
istanbul,
|
||||
london,
|
||||
)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrapf(err, "failed to deduct transaction costs from user balance")
|
||||
}
|
||||
|
||||
events = append(events, sdk.NewEvent(sdk.EventTypeTx, sdk.NewAttribute(sdk.AttributeKeyFee, fees.String())))
|
||||
}
|
||||
|
||||
// TODO: change to typed events
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
// TODO: deprecate after https://github.com/cosmos/cosmos-sdk/issues/9514 is fixed on SDK
|
||||
blockGasLimit := ethermint.BlockGasLimit(ctx)
|
||||
|
||||
// NOTE: safety check
|
||||
if blockGasLimit > 0 {
|
||||
// generate a copy of the gas pool (i.e block gas meter) to see if we've run out of gas for this block
|
||||
// if current gas consumed is greater than the limit, this funcion panics and the error is recovered on the Baseapp
|
||||
gasPool := sdk.NewGasMeter(blockGasLimit)
|
||||
gasPool.ConsumeGas(ctx.GasMeter().GasConsumedToLimit(), "gas pool check")
|
||||
}
|
||||
|
||||
// Set ctx.GasMeter with a limit of GasWanted (gasLimit)
|
||||
gasConsumed := ctx.GasMeter().GasConsumed()
|
||||
ctx = ctx.WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted))
|
||||
ctx.GasMeter().ConsumeGas(gasConsumed, "copy gas consumed")
|
||||
|
||||
// we know that we have enough gas on the pool to cover the intrinsic gas
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block
|
||||
// context rules.
|
||||
type CanTransferDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// NewCanTransferDecorator creates a new CanTransferDecorator instance.
|
||||
func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator {
|
||||
return CanTransferDecorator{
|
||||
evmKeeper: evmKeeper,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to
|
||||
// see if the address can execute the transaction.
|
||||
func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
params := ctd.evmKeeper.GetParams(ctx)
|
||||
ethCfg := params.ChainConfig.EthereumConfig(ctd.evmKeeper.ChainID())
|
||||
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight()))
|
||||
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
|
||||
coreMsg, err := msgEthTx.AsMessage(signer, baseFee)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
err,
|
||||
"failed to create an ethereum core.Message from signer %T", signer,
|
||||
)
|
||||
}
|
||||
|
||||
// NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below
|
||||
cfg := &evmtypes.EVMConfig{
|
||||
ChainConfig: ethCfg,
|
||||
Params: params,
|
||||
CoinBase: common.Address{},
|
||||
BaseFee: baseFee,
|
||||
}
|
||||
stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())))
|
||||
evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB)
|
||||
|
||||
// check that caller has enough balance to cover asset transfer for **topmost** call
|
||||
// NOTE: here the gas consumed is from the context with the infinite gas meter
|
||||
if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInsufficientFunds,
|
||||
"failed to transfer %s from address %s using the EVM block context transfer function",
|
||||
coreMsg.Value(),
|
||||
coreMsg.From(),
|
||||
)
|
||||
}
|
||||
|
||||
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
|
||||
if baseFee == nil {
|
||||
return ctx, sdkerrors.Wrap(
|
||||
evmtypes.ErrInvalidBaseFee,
|
||||
"base fee is supported but evm block context value is nil",
|
||||
)
|
||||
}
|
||||
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInsufficientFee,
|
||||
"max fee per gas less than block base fee (%s < %s)",
|
||||
coreMsg.GasFeeCap(), baseFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthIncrementSenderSequenceDecorator increments the sequence of the signers.
|
||||
type EthIncrementSenderSequenceDecorator struct {
|
||||
ak evmtypes.AccountKeeper
|
||||
}
|
||||
|
||||
// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator.
|
||||
func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) EthIncrementSenderSequenceDecorator {
|
||||
return EthIncrementSenderSequenceDecorator{
|
||||
ak: ak,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle handles incrementing the sequence of the signer (i.e sender). If the transaction is a
|
||||
// contract creation, the nonce will be incremented during the transaction execution and not within
|
||||
// this AnteHandler decorator.
|
||||
func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to unpack tx data")
|
||||
}
|
||||
|
||||
// increase sequence of sender
|
||||
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
|
||||
if acc == nil {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrUnknownAddress,
|
||||
"account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()),
|
||||
)
|
||||
}
|
||||
nonce := acc.GetSequence()
|
||||
|
||||
// we merged the nonce verification to nonce increment, so when tx includes multiple messages
|
||||
// with same sender, they'll be accepted.
|
||||
if txData.GetNonce() != nonce {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidSequence,
|
||||
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
|
||||
)
|
||||
}
|
||||
|
||||
if err := acc.SetSequence(nonce + 1); err != nil {
|
||||
return ctx, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
|
||||
}
|
||||
|
||||
issd.ak.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures
|
||||
type EthValidateBasicDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator
|
||||
func NewEthValidateBasicDecorator(ek EVMKeeper) EthValidateBasicDecorator {
|
||||
return EthValidateBasicDecorator{
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle handles basic validation of tx
|
||||
func (vbd EthValidateBasicDecorator) 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)
|
||||
}
|
||||
|
||||
err := tx.ValidateBasic()
|
||||
// ErrNoSignatures is fine with eth tx
|
||||
if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) {
|
||||
return ctx, sdkerrors.Wrap(err, "tx basic validation failed")
|
||||
}
|
||||
|
||||
// For eth type cosmos tx, some fields should be veified as zero values,
|
||||
// since we will only verify the signature against the hash of the MsgEthereumTx.Data
|
||||
if wrapperTx, ok := tx.(protoTxProvider); ok {
|
||||
protoTx := wrapperTx.GetProtoTx()
|
||||
body := protoTx.Body
|
||||
if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest,
|
||||
"for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty")
|
||||
}
|
||||
|
||||
if len(body.ExtensionOptions) != 1 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")
|
||||
}
|
||||
|
||||
txFee := sdk.Coins{}
|
||||
txGasLimit := uint64(0)
|
||||
|
||||
for _, msg := range protoTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
txGasLimit += msgEthTx.GetGas()
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
|
||||
}
|
||||
|
||||
params := vbd.evmKeeper.GetParams(ctx)
|
||||
chainID := vbd.evmKeeper.ChainID()
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType {
|
||||
return ctx, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
|
||||
}
|
||||
|
||||
txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee())))
|
||||
}
|
||||
|
||||
authInfo := protoTx.AuthInfo
|
||||
if len(authInfo.SignerInfos) > 0 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty")
|
||||
}
|
||||
|
||||
if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")
|
||||
}
|
||||
|
||||
if !authInfo.Fee.Amount.IsEqual(txFee) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)
|
||||
}
|
||||
|
||||
if authInfo.Fee.GasLimit != txGasLimit {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)
|
||||
}
|
||||
|
||||
sigs := protoTx.Signatures
|
||||
if len(sigs) > 0 {
|
||||
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx Signatures should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
|
||||
// by setting the gas meter to infinite
|
||||
type EthSetupContextDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) EthSetupContextDecorator {
|
||||
return EthSetupContextDecorator{
|
||||
evmKeeper: evmKeeper,
|
||||
}
|
||||
}
|
||||
|
||||
func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
// all transactions must implement GasTx
|
||||
_, ok := tx.(authante.GasTx)
|
||||
if !ok {
|
||||
return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx")
|
||||
}
|
||||
|
||||
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
|
||||
// Reset transient gas used to prepare the execution of current cosmos tx.
|
||||
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
|
||||
esc.evmKeeper.ResetTransientGasUsed(ctx)
|
||||
return next(newCtx, tx, simulate)
|
||||
}
|
||||
|
||||
// EthMempoolFeeDecorator will check if the transaction's effective 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 EthMempoolFeeDecorator struct {
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
|
||||
return EthMempoolFeeDecorator{
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle ensures 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.
|
||||
// It only do the check if london hardfork not enabled or feemarket not enabled, because in that case feemarket will take over the task.
|
||||
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
if ctx.IsCheckTx() && !simulate {
|
||||
params := mfd.evmKeeper.GetParams(ctx)
|
||||
ethCfg := params.ChainConfig.EthereumConfig(mfd.evmKeeper.ChainID())
|
||||
baseFee := mfd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
if baseFee == nil {
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
evmDenom := params.EvmDenom
|
||||
feeAmt := ethMsg.GetFee()
|
||||
glDec := sdk.NewDec(int64(ethMsg.GetGas()))
|
||||
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
|
||||
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
|
||||
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
package middleware_test
|
||||
package ante_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
ante "github.com/tharsis/ethermint/app/middleware"
|
||||
"github.com/tharsis/ethermint/app/ante"
|
||||
"github.com/tharsis/ethermint/server/config"
|
||||
"github.com/tharsis/ethermint/tests"
|
||||
"github.com/tharsis/ethermint/x/evm/statedb"
|
||||
@@ -16,8 +14,12 @@ import (
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
func (suite MiddlewareTestSuite) TestEthSigVerificationDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthSigVerificationMiddleware(suite.app.EvmKeeper))
|
||||
func nextFn(ctx sdk.Context, _ sdk.Tx, _ bool) (sdk.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (suite AnteTestSuite) TestEthSigVerificationDecorator() {
|
||||
dec := ante.NewEthSigVerificationDecorator(suite.app.EvmKeeper)
|
||||
addr, privKey := tests.NewAddrKey()
|
||||
|
||||
signedTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
|
||||
@@ -44,7 +46,7 @@ func (suite MiddlewareTestSuite) TestEthSigVerificationDecorator() {
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
@@ -55,10 +57,10 @@ func (suite MiddlewareTestSuite) TestEthSigVerificationDecorator() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestNewEthAccountVerificationDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthAccountVerificationMiddleware(
|
||||
func (suite AnteTestSuite) TestNewEthAccountVerificationDecorator() {
|
||||
dec := ante.NewEthAccountVerificationDecorator(
|
||||
suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.EvmKeeper,
|
||||
))
|
||||
)
|
||||
|
||||
addr := tests.GenerateAddress()
|
||||
|
||||
@@ -132,7 +134,8 @@ func (suite MiddlewareTestSuite) TestNewEthAccountVerificationDecorator() {
|
||||
tc.malleate()
|
||||
suite.Require().NoError(vmdb.Commit())
|
||||
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(tc.checkTx)), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(tc.checkTx), tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
@@ -142,9 +145,10 @@ func (suite MiddlewareTestSuite) TestNewEthAccountVerificationDecorator() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestEthNonceVerificationDecorator() {
|
||||
func (suite AnteTestSuite) TestEthNonceVerificationDecorator() {
|
||||
suite.SetupTest()
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthIncrementSenderSequenceMiddleware(suite.app.AccountKeeper))
|
||||
dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper)
|
||||
|
||||
addr := tests.GenerateAddress()
|
||||
|
||||
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
|
||||
@@ -186,7 +190,7 @@ func (suite MiddlewareTestSuite) TestEthNonceVerificationDecorator() {
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
tc.malleate()
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, err := dec.AnteHandle(suite.ctx.WithIsReCheckTx(tc.reCheckTx), tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
@@ -197,8 +201,8 @@ func (suite MiddlewareTestSuite) TestEthNonceVerificationDecorator() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestEthGasConsumeDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthGasConsumeMiddleware(suite.app.EvmKeeper, config.DefaultMaxTxGasWanted))
|
||||
func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
|
||||
dec := ante.NewEthGasConsumeDecorator(suite.app.EvmKeeper, config.DefaultMaxTxGasWanted)
|
||||
|
||||
addr := tests.GenerateAddress()
|
||||
|
||||
@@ -283,24 +287,24 @@ func (suite MiddlewareTestSuite) TestEthGasConsumeDecorator() {
|
||||
|
||||
if tc.expPanic {
|
||||
suite.Require().Panics(func() {
|
||||
_, _, _ = txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewGasMeter(1))), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, _ = dec.AnteHandle(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewGasMeter(1)), tc.tx, false, nextFn)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewInfiniteGasMeter())), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
|
||||
ctx, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true).WithGasMeter(sdk.NewInfiniteGasMeter()), tc.tx, false, nextFn)
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
suite.Require().Equal(tc.gasLimit, ctx.GasMeter().Limit())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestCanTransferDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewCanTransferMiddleware(suite.app.EvmKeeper))
|
||||
func (suite AnteTestSuite) TestCanTransferDecorator() {
|
||||
dec := ante.NewCanTransferDecorator(suite.app.EvmKeeper)
|
||||
|
||||
addr, privKey := tests.NewAddrKey()
|
||||
|
||||
@@ -372,7 +376,8 @@ func (suite MiddlewareTestSuite) TestCanTransferDecorator() {
|
||||
tc.malleate()
|
||||
suite.Require().NoError(vmdb.Commit())
|
||||
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx.WithIsCheckTx(true)), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, err := dec.AnteHandle(suite.ctx.WithIsCheckTx(true), tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
@@ -382,8 +387,8 @@ func (suite MiddlewareTestSuite) TestCanTransferDecorator() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthIncrementSenderSequenceMiddleware(suite.app.AccountKeeper))
|
||||
func (suite AnteTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
dec := ante.NewEthIncrementSenderSequenceDecorator(suite.app.AccountKeeper)
|
||||
addr, privKey := tests.NewAddrKey()
|
||||
|
||||
contract := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 0, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
|
||||
@@ -450,17 +455,12 @@ func (suite MiddlewareTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
|
||||
if tc.expPanic {
|
||||
suite.Require().Panics(func() {
|
||||
_, _, _ = txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, _ = dec.AnteHandle(suite.ctx, tc.tx, false, nextFn)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
_, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
@@ -478,8 +478,8 @@ func (suite MiddlewareTestSuite) TestEthIncrementSenderSequenceDecorator() {
|
||||
}
|
||||
}
|
||||
|
||||
func (suite MiddlewareTestSuite) TestEthSetupContextDecorator() {
|
||||
txHandler := middleware.ComposeMiddlewares(noopTxHandler, ante.NewEthSetUpContextMiddleware(suite.app.EvmKeeper))
|
||||
func (suite AnteTestSuite) TestEthSetupContextDecorator() {
|
||||
dec := ante.NewEthSetUpContextDecorator(suite.app.EvmKeeper)
|
||||
tx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), 1000, big.NewInt(1), nil, nil, nil, nil)
|
||||
|
||||
testCases := []struct {
|
||||
@@ -497,7 +497,8 @@ func (suite MiddlewareTestSuite) TestEthSetupContextDecorator() {
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
_, _, err := txHandler.CheckTx(sdk.WrapSDKContext(suite.ctx), txtypes.Request{Tx: tc.tx}, txtypes.RequestCheckTx{})
|
||||
_, err := dec.AnteHandle(suite.ctx, tc.tx, false, nextFn)
|
||||
|
||||
if tc.expPass {
|
||||
suite.Require().NoError(err)
|
||||
} else {
|
||||
@@ -0,0 +1,103 @@
|
||||
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"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
|
||||
ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante"
|
||||
ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper"
|
||||
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC
|
||||
// channel keeper, EVM Keeper and Fee Market Keeper.
|
||||
type HandlerOptions struct {
|
||||
AccountKeeper evmtypes.AccountKeeper
|
||||
BankKeeper evmtypes.BankKeeper
|
||||
IBCKeeper *ibckeeper.Keeper
|
||||
FeeMarketKeeper evmtypes.FeeMarketKeeper
|
||||
EvmKeeper EVMKeeper
|
||||
FeegrantKeeper ante.FeegrantKeeper
|
||||
SignModeHandler authsigning.SignModeHandler
|
||||
SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
|
||||
MaxTxGasWanted uint64
|
||||
TxFeeChecker ante.TxFeeChecker
|
||||
}
|
||||
|
||||
func (options HandlerOptions) Validate() error {
|
||||
if options.AccountKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler")
|
||||
}
|
||||
if options.BankKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler")
|
||||
}
|
||||
if options.SignModeHandler == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
|
||||
}
|
||||
if options.FeeMarketKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "fee market keeper is required for AnteHandler")
|
||||
}
|
||||
if options.EvmKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "evm keeper is required for AnteHandler")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
return sdk.ChainAnteDecorators(
|
||||
NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first
|
||||
NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices
|
||||
NewEthValidateBasicDecorator(options.EvmKeeper),
|
||||
NewEthSigVerificationDecorator(options.EvmKeeper),
|
||||
NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
|
||||
NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted),
|
||||
NewCanTransferDecorator(options.EvmKeeper),
|
||||
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper), // innermost AnteDecorator.
|
||||
)
|
||||
}
|
||||
|
||||
func newCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
return sdk.ChainAnteDecorators(
|
||||
RejectMessagesDecorator{}, // reject MsgEthereumTxs
|
||||
ante.NewSetUpContextDecorator(),
|
||||
ante.NewValidateBasicDecorator(),
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.AccountKeeper),
|
||||
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
|
||||
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
|
||||
// SetPubKeyDecorator must be called before all signature verification decorators
|
||||
ante.NewSetPubKeyDecorator(options.AccountKeeper),
|
||||
ante.NewValidateSigCountDecorator(options.AccountKeeper),
|
||||
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
|
||||
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
|
||||
ibcante.NewAnteDecorator(options.IBCKeeper),
|
||||
)
|
||||
}
|
||||
|
||||
func newCosmosAnteHandlerEip712(options HandlerOptions) sdk.AnteHandler {
|
||||
return sdk.ChainAnteDecorators(
|
||||
RejectMessagesDecorator{}, // reject MsgEthereumTxs
|
||||
ante.NewSetUpContextDecorator(),
|
||||
// NOTE: extensions option decorator removed
|
||||
// ante.NewRejectExtensionOptionsDecorator(),
|
||||
ante.NewValidateBasicDecorator(),
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.AccountKeeper),
|
||||
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
|
||||
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
|
||||
// SetPubKeyDecorator must be called before all signature verification decorators
|
||||
ante.NewSetPubKeyDecorator(options.AccountKeeper),
|
||||
ante.NewValidateSigCountDecorator(options.AccountKeeper),
|
||||
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
|
||||
// Note: signature verification uses EIP instead of the cosmos signature validator
|
||||
NewEip712SigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
|
||||
ibcante.NewAnteDecorator(options.IBCKeeper),
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package middleware
|
||||
package ante
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
@@ -0,0 +1,25 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// RejectMessagesDecorator prevents invalid msg types from being executed
|
||||
type RejectMessagesDecorator struct{}
|
||||
|
||||
// AnteHandle rejects messages that requires ethereum-specific authentication.
|
||||
// For example `MsgEthereumTx` requires fee to be deducted in the antehandler in
|
||||
// order to perform the refund.
|
||||
func (rmd RejectMessagesDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
for _, msg := range tx.GetMsgs() {
|
||||
if _, ok := msg.(*evmtypes.MsgEthereumTx); ok {
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidType,
|
||||
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
|
||||
)
|
||||
}
|
||||
}
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package middleware_test
|
||||
package ante_test
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
func (suite MiddlewareTestSuite) TestSignatures() {
|
||||
func (suite AnteTestSuite) TestSignatures() {
|
||||
suite.enableFeemarket = false
|
||||
suite.SetupTest() // reset
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
package middleware_test
|
||||
package ante_test
|
||||
|
||||
import (
|
||||
context "context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clientTx "github.com/cosmos/cosmos-sdk/client/tx"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
|
||||
types2 "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
types3 "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/tharsis/ethermint/app/middleware"
|
||||
"github.com/tharsis/ethermint/ethereum/eip712"
|
||||
"github.com/tharsis/ethermint/server/config"
|
||||
"github.com/tharsis/ethermint/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@@ -24,69 +19,48 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
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"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
"github.com/tharsis/ethermint/app"
|
||||
ante "github.com/tharsis/ethermint/app/ante"
|
||||
"github.com/tharsis/ethermint/encoding"
|
||||
"github.com/tharsis/ethermint/tests"
|
||||
"github.com/tharsis/ethermint/x/evm/statedb"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
|
||||
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
)
|
||||
|
||||
// customTxHandler is a test middleware that will run a custom function.
|
||||
type customTxHandler struct {
|
||||
fn func(context.Context, tx.Request) (tx.Response, error)
|
||||
}
|
||||
|
||||
var _ tx.Handler = customTxHandler{}
|
||||
|
||||
func (h customTxHandler) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return h.fn(ctx, req)
|
||||
}
|
||||
func (h customTxHandler) CheckTx(ctx context.Context, req tx.Request, _ tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
res, err := h.fn(ctx, req)
|
||||
return res, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
func (h customTxHandler) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return h.fn(ctx, req)
|
||||
}
|
||||
|
||||
// noopTxHandler is a test middleware that returns an empty response.
|
||||
var noopTxHandler = customTxHandler{func(_ context.Context, _ tx.Request) (tx.Response, error) {
|
||||
return tx.Response{}, nil
|
||||
}}
|
||||
|
||||
type MiddlewareTestSuite struct {
|
||||
type AnteTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
ctx sdk.Context
|
||||
app *app.EthermintApp
|
||||
clientCtx client.Context
|
||||
anteHandler tx.Handler
|
||||
anteHandler sdk.AnteHandler
|
||||
ethSigner ethtypes.Signer
|
||||
enableFeemarket bool
|
||||
enableLondonHF bool
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) StateDB() *statedb.StateDB {
|
||||
func (suite *AnteTestSuite) StateDB() *statedb.StateDB {
|
||||
return statedb.New(suite.ctx, suite.app.EvmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(suite.ctx.HeaderHash().Bytes())))
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) SetupTest() {
|
||||
func (suite *AnteTestSuite) SetupTest() {
|
||||
checkTx := false
|
||||
|
||||
suite.app = app.Setup(suite.T(), checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState {
|
||||
suite.app = app.Setup(checkTx, func(app *app.EthermintApp, genesis simapp.GenesisState) simapp.GenesisState {
|
||||
if suite.enableFeemarket {
|
||||
// setup feemarketGenesis params
|
||||
feemarketGenesis := feemarkettypes.DefaultGenesisState()
|
||||
@@ -121,37 +95,32 @@ func (suite *MiddlewareTestSuite) SetupTest() {
|
||||
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
|
||||
|
||||
suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
|
||||
maxGasWanted := cast.ToUint64(config.DefaultMaxTxGasWanted)
|
||||
|
||||
options := middleware.HandlerOptions{
|
||||
TxDecoder: suite.clientCtx.TxConfig.TxDecoder(),
|
||||
AccountKeeper: suite.app.AccountKeeper,
|
||||
BankKeeper: suite.app.BankKeeper,
|
||||
EvmKeeper: suite.app.EvmKeeper,
|
||||
FeegrantKeeper: suite.app.FeeGrantKeeper,
|
||||
Codec: suite.app.AppCodec(),
|
||||
Debug: suite.app.Trace(),
|
||||
LegacyRouter: suite.app.LegacyRouter,
|
||||
MsgServiceRouter: suite.app.MsgSvcRouter,
|
||||
FeeMarketKeeper: suite.app.FeeMarketKeeper,
|
||||
SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(),
|
||||
SigGasConsumer: middleware.DefaultSigVerificationGasConsumer,
|
||||
MaxTxGasWanted: maxGasWanted,
|
||||
options := ante.HandlerOptions{
|
||||
AccountKeeper: suite.app.AccountKeeper,
|
||||
BankKeeper: suite.app.BankKeeper,
|
||||
EvmKeeper: suite.app.EvmKeeper,
|
||||
FeegrantKeeper: suite.app.FeeGrantKeeper,
|
||||
IBCKeeper: suite.app.IBCKeeper,
|
||||
FeeMarketKeeper: suite.app.FeeMarketKeeper,
|
||||
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
}
|
||||
|
||||
suite.Require().NoError(options.Validate())
|
||||
suite.anteHandler = middleware.NewTxHandler(options)
|
||||
|
||||
suite.anteHandler = ante.NewAnteHandler(options)
|
||||
suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
|
||||
}
|
||||
|
||||
func TestMiddlewareTestSuite(t *testing.T) {
|
||||
suite.Run(t, &MiddlewareTestSuite{
|
||||
func TestAnteTestSuite(t *testing.T) {
|
||||
suite.Run(t, &AnteTestSuite{
|
||||
enableLondonHF: true,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateTestTx is a helper function to create a tx given multiple inputs.
|
||||
func (suite *MiddlewareTestSuite) CreateTestTx(
|
||||
func (suite *AnteTestSuite) CreateTestTx(
|
||||
msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool,
|
||||
unsetExtensionOptions ...bool,
|
||||
) authsigning.Tx {
|
||||
@@ -159,7 +128,7 @@ func (suite *MiddlewareTestSuite) CreateTestTx(
|
||||
}
|
||||
|
||||
// CreateTestTxBuilder is a helper function to create a tx builder given multiple inputs.
|
||||
func (suite *MiddlewareTestSuite) CreateTestTxBuilder(
|
||||
func (suite *AnteTestSuite) CreateTestTxBuilder(
|
||||
msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey, accNum uint64, signCosmosTx bool,
|
||||
unsetExtensionOptions ...bool,
|
||||
) client.TxBuilder {
|
||||
@@ -215,7 +184,7 @@ func (suite *MiddlewareTestSuite) CreateTestTxBuilder(
|
||||
AccountNumber: accNum,
|
||||
Sequence: txData.GetNonce(),
|
||||
}
|
||||
sigV2, err = clientTx.SignWithPrivKey(
|
||||
sigV2, err = tx.SignWithPrivKey(
|
||||
suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData,
|
||||
txBuilder, priv, suite.clientCtx.TxConfig, txData.GetNonce(),
|
||||
)
|
||||
@@ -230,22 +199,22 @@ func (suite *MiddlewareTestSuite) CreateTestTxBuilder(
|
||||
return txBuilder
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgSend(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
// Build MsgSend
|
||||
recipient := sdk.AccAddress(common.Address{}.Bytes())
|
||||
msgSend := types2.NewMsgSend(from, recipient, sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(1))))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
func (suite *AnteTestSuite) CreateTestEIP712TxBuilderMsgDelegate(from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins) client.TxBuilder {
|
||||
// Build MsgSend
|
||||
valEthAddr := tests.GenerateAddress()
|
||||
valAddr := sdk.ValAddress(valEthAddr.Bytes())
|
||||
msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(200000)))
|
||||
msgSend := types3.NewMsgDelegate(from, valAddr, sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewInt(20)))
|
||||
return suite.CreateTestEIP712CosmosTxBuilder(from, priv, chainId, gas, gasAmount, msgSend)
|
||||
}
|
||||
|
||||
func (suite *MiddlewareTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
func (suite *AnteTestSuite) CreateTestEIP712CosmosTxBuilder(
|
||||
from sdk.AccAddress, priv cryptotypes.PrivKey, chainId string, gas uint64, gasAmount sdk.Coins, msg sdk.Msg,
|
||||
) client.TxBuilder {
|
||||
var err error
|
||||
+31
-43
@@ -11,21 +11,19 @@ import (
|
||||
"github.com/rakyll/statik/fs"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/tharsis/ethermint/app/middleware"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
"github.com/cosmos/cosmos-sdk/db"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
"github.com/cosmos/cosmos-sdk/server/api"
|
||||
"github.com/cosmos/cosmos-sdk/server/config"
|
||||
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
|
||||
"github.com/tharsis/ethermint/app/ante"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
|
||||
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
|
||||
"github.com/cosmos/cosmos-sdk/store/streaming"
|
||||
@@ -36,7 +34,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
|
||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
@@ -219,8 +216,6 @@ type EthermintApp struct {
|
||||
cdc *codec.LegacyAmino
|
||||
appCodec codec.Codec
|
||||
interfaceRegistry types.InterfaceRegistry
|
||||
MsgSvcRouter *authmiddleware.MsgServiceRouter
|
||||
LegacyRouter sdk.Router
|
||||
invCheckPeriod uint
|
||||
|
||||
// keys to access the substores
|
||||
@@ -347,6 +342,7 @@ func NewEthermintApp(
|
||||
appName,
|
||||
logger,
|
||||
db,
|
||||
encodingConfig.TxConfig.TxDecoder(),
|
||||
baseAppOptions...,
|
||||
)
|
||||
bApp.SetCommitMultiStoreTracer(traceStore)
|
||||
@@ -367,8 +363,6 @@ func NewEthermintApp(
|
||||
appCodec: appCodec,
|
||||
interfaceRegistry: interfaceRegistry,
|
||||
invCheckPeriod: invCheckPeriod,
|
||||
LegacyRouter: authmiddleware.NewLegacyRouter(),
|
||||
MsgSvcRouter: authmiddleware.NewMsgServiceRouter(interfaceRegistry),
|
||||
keys: keys,
|
||||
tkeys: tkeys,
|
||||
memKeys: memKeys,
|
||||
@@ -422,7 +416,7 @@ func NewEthermintApp(
|
||||
stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
|
||||
)
|
||||
|
||||
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgSvcRouter, app.AccountKeeper)
|
||||
app.AuthzKeeper = authzkeeper.NewKeeper(keys[authzkeeper.StoreKey], appCodec, app.MsgServiceRouter(), app.AccountKeeper)
|
||||
|
||||
tracer := cast.ToString(appOpts.Get(srvflags.EVMTracer))
|
||||
|
||||
@@ -479,7 +473,7 @@ func NewEthermintApp(
|
||||
|
||||
govKeeper := govkeeper.NewKeeper(
|
||||
appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
|
||||
&stakingKeeper, govRouter, app.MsgSvcRouter, govConfig,
|
||||
&stakingKeeper, govRouter, app.MsgServiceRouter(), govConfig,
|
||||
)
|
||||
|
||||
app.GovKeeper = *govKeeper.SetHooks(
|
||||
@@ -650,8 +644,8 @@ func NewEthermintApp(
|
||||
)
|
||||
|
||||
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
||||
app.mm.RegisterRoutes(app.LegacyRouter, app.QueryRouter(), encodingConfig.Amino)
|
||||
app.configurator = module.NewConfigurator(app.appCodec, app.MsgSvcRouter, app.GRPCQueryRouter())
|
||||
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
|
||||
app.configurator = module.NewConfigurator(app.appCodec, app.MsgServiceRouter(), app.GRPCQueryRouter())
|
||||
app.mm.RegisterServices(app.configurator)
|
||||
|
||||
// add test gRPC service for testing gRPC queries in isolation
|
||||
@@ -686,24 +680,26 @@ func NewEthermintApp(
|
||||
app.SetInitChainer(app.InitChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
|
||||
// use Ethermint's custom AnteHandler
|
||||
|
||||
maxGasWanted := cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted))
|
||||
options := middleware.HandlerOptions{
|
||||
Codec: app.appCodec,
|
||||
Debug: app.Trace(),
|
||||
LegacyRouter: app.LegacyRouter,
|
||||
MsgServiceRouter: app.MsgSvcRouter,
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
EvmKeeper: app.EvmKeeper,
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
FeeMarketKeeper: app.FeeMarketKeeper,
|
||||
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||
SigGasConsumer: middleware.DefaultSigVerificationGasConsumer,
|
||||
MaxTxGasWanted: maxGasWanted,
|
||||
TxDecoder: encodingConfig.TxConfig.TxDecoder(),
|
||||
options := ante.HandlerOptions{
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
EvmKeeper: app.EvmKeeper,
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
IBCKeeper: app.IBCKeeper,
|
||||
FeeMarketKeeper: app.FeeMarketKeeper,
|
||||
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
MaxTxGasWanted: maxGasWanted,
|
||||
}
|
||||
|
||||
app.setTxHandler(options, encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents)))
|
||||
if err := options.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
app.SetAnteHandler(ante.NewAnteHandler(options))
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
|
||||
app.ScopedIBCKeeper = scopedIBCKeeper
|
||||
@@ -712,18 +708,6 @@ func NewEthermintApp(
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *EthermintApp) setTxHandler(options middleware.HandlerOptions, txConfig client.TxConfig, indexEventsStr []string) {
|
||||
indexEvents := map[string]struct{}{}
|
||||
for _, e := range indexEventsStr {
|
||||
indexEvents[e] = struct{}{}
|
||||
}
|
||||
|
||||
options.IndexEvents = indexEvents
|
||||
|
||||
txHandler := middleware.NewTxHandler(options)
|
||||
app.SetTxHandler(txHandler)
|
||||
}
|
||||
|
||||
// Name returns the name of the App
|
||||
func (app *EthermintApp) Name() string { return app.BaseApp.Name() }
|
||||
|
||||
@@ -842,8 +826,7 @@ func (app *EthermintApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.
|
||||
// Register new tendermint queries routes from grpc-gateway.
|
||||
tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||
|
||||
// Register legacy and grpc-gateway routes for all modules.
|
||||
ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router)
|
||||
// Register grpc-gateway routes for all modules.
|
||||
ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||
|
||||
// register swagger API from root so that other applications can override easily
|
||||
@@ -857,7 +840,12 @@ func (app *EthermintApp) RegisterTxService(clientCtx client.Context) {
|
||||
}
|
||||
|
||||
func (app *EthermintApp) RegisterTendermintService(clientCtx client.Context) {
|
||||
tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry)
|
||||
tmservice.RegisterTendermintService(
|
||||
clientCtx,
|
||||
app.BaseApp.GRPCQueryRouter(),
|
||||
app.interfaceRegistry,
|
||||
app.Query,
|
||||
)
|
||||
}
|
||||
|
||||
// RegisterSwaggerAPI registers swagger route with API Server
|
||||
|
||||
@@ -1,757 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
|
||||
"github.com/tharsis/ethermint/x/evm/statedb"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// EthSetupContextMiddleware is adapted from SetUpContextMiddleware from cosmos-sdk, it ignores gas consumption
|
||||
// by setting the gas meter to infinite
|
||||
type EthSetupContextMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// gasContext returns a new context with a gas meter set from a given context.
|
||||
func gasContext(ctx sdk.Context, tx sdk.Tx, isSimulate bool) (sdk.Context, error) {
|
||||
// all transactions must implement GasTx
|
||||
gasTx, ok := tx.(middleware.GasTx)
|
||||
if !ok {
|
||||
// Set a gas meter with limit 0 as to prevent an infinite gas meter attack
|
||||
// during runTx.
|
||||
newCtx := setGasMeter(ctx, 0, isSimulate)
|
||||
return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx")
|
||||
}
|
||||
|
||||
return setGasMeter(ctx, gasTx.GetGas(), isSimulate), nil
|
||||
}
|
||||
|
||||
// setGasMeter returns a new context with a gas meter set from a given context.
|
||||
func setGasMeter(ctx sdk.Context, gasLimit uint64, simulate bool) 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(ethermint.NewInfiniteGasMeterWithLimit(gasLimit))
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (esc EthSetupContextMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, false)
|
||||
if err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
// Reset transient gas used to prepare the execution of current cosmos tx.
|
||||
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
|
||||
esc.evmKeeper.ResetTransientGasUsed(sdkCtx)
|
||||
return esc.next.CheckTx(sdkCtx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (esc EthSetupContextMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, false)
|
||||
if err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
// Reset transient gas used to prepare the execution of current cosmos tx.
|
||||
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
|
||||
esc.evmKeeper.ResetTransientGasUsed(sdkCtx)
|
||||
return esc.next.DeliverTx(sdkCtx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (esc EthSetupContextMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
sdkCtx, err := gasContext(sdk.UnwrapSDKContext(ctx), req.Tx, false)
|
||||
if err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
// Reset transient gas used to prepare the execution of current cosmos tx.
|
||||
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
|
||||
esc.evmKeeper.ResetTransientGasUsed(sdkCtx)
|
||||
return esc.next.SimulateTx(sdkCtx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthSetupContextMiddleware{}
|
||||
|
||||
func NewEthSetUpContextMiddleware(evmKeeper EVMKeeper) tx.Middleware {
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return EthSetupContextMiddleware{
|
||||
next: txh,
|
||||
evmKeeper: evmKeeper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EthMempoolFeeMiddleware will check if the transaction's effective fee is at least as large
|
||||
// as the local validator's minimum gasFee (defined in validator config).
|
||||
// If fee is too low, Middleware 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 MempoolFeeMiddleware
|
||||
type EthMempoolFeeMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (mfd EthMempoolFeeMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
|
||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||
params := mfd.evmKeeper.GetParams(sdkCtx)
|
||||
ethCfg := params.ChainConfig.EthereumConfig(mfd.evmKeeper.ChainID())
|
||||
baseFee := mfd.evmKeeper.BaseFee(sdkCtx, ethCfg)
|
||||
if baseFee == nil {
|
||||
for _, msg := range req.Tx.GetMsgs() {
|
||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
evmDenom := params.EvmDenom
|
||||
feeAmt := ethMsg.GetFee()
|
||||
glDec := sdk.NewDec(int64(ethMsg.GetGas()))
|
||||
requiredFee := sdkCtx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
|
||||
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mfd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (mfd EthMempoolFeeMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return mfd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (mfd EthMempoolFeeMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return mfd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthMempoolFeeMiddleware{}
|
||||
|
||||
func NewEthMempoolFeeMiddleware(ek EVMKeeper) tx.Middleware {
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return EthMempoolFeeMiddleware{
|
||||
next: txh,
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EthValidateBasicMiddleware is adapted from ValidateBasicMiddleware from cosmos-sdk, it ignores ErrNoSignatures
|
||||
type EthValidateBasicMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (vbd EthValidateBasicMiddleware) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
|
||||
// no need to validate basic on recheck tx, call next antehandler
|
||||
if ctx.IsReCheckTx() {
|
||||
return vbd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
err := reqTx.ValidateBasic()
|
||||
// ErrNoSignatures is fine with eth tx
|
||||
if err != nil && !errors.Is(err, sdkerrors.ErrNoSignatures) {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(err, "tx basic validation failed")
|
||||
}
|
||||
|
||||
// For eth type cosmos tx, some fields should be verified as zero values,
|
||||
// since we will only verify the signature against the hash of the MsgEthereumTx.Data
|
||||
if wrapperTx, ok := reqTx.(protoTxProvider); ok {
|
||||
protoTx := wrapperTx.GetProtoTx()
|
||||
body := protoTx.Body
|
||||
if body.Memo != "" || body.TimeoutHeight != uint64(0) || len(body.NonCriticalExtensionOptions) > 0 {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest,
|
||||
"for eth tx body Memo TimeoutHeight NonCriticalExtensionOptions should be empty")
|
||||
}
|
||||
|
||||
if len(body.ExtensionOptions) != 1 {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx length of ExtensionOptions should be 1")
|
||||
}
|
||||
|
||||
txFee := sdk.Coins{}
|
||||
txGasLimit := uint64(0)
|
||||
|
||||
for _, msg := range protoTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
txGasLimit += msgEthTx.GetGas()
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(err, "failed to unpack MsgEthereumTx Data")
|
||||
}
|
||||
|
||||
params := vbd.evmKeeper.GetParams(ctx)
|
||||
chainID := vbd.evmKeeper.ChainID()
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
baseFee := vbd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
if baseFee == nil && txData.TxType() == ethtypes.DynamicFeeTxType {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(ethtypes.ErrTxTypeNotSupported, "dynamic fee tx not supported")
|
||||
}
|
||||
|
||||
txFee = txFee.Add(sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee())))
|
||||
}
|
||||
|
||||
authInfo := protoTx.AuthInfo
|
||||
if len(authInfo.SignerInfos) > 0 {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo SignerInfos should be empty")
|
||||
}
|
||||
|
||||
if authInfo.Fee.Payer != "" || authInfo.Fee.Granter != "" {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx AuthInfo Fee payer and granter should be empty")
|
||||
}
|
||||
|
||||
if !authInfo.Fee.Amount.IsEqual(txFee) {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee Amount (%s != %s)", authInfo.Fee.Amount, txFee)
|
||||
}
|
||||
|
||||
if authInfo.Fee.GasLimit != txGasLimit {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid AuthInfo Fee GasLimit (%d != %d)", authInfo.Fee.GasLimit, txGasLimit)
|
||||
}
|
||||
|
||||
sigs := protoTx.Signatures
|
||||
if len(sigs) > 0 {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "for eth tx Signatures should be empty")
|
||||
}
|
||||
}
|
||||
|
||||
return vbd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (vbd EthValidateBasicMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return vbd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (vbd EthValidateBasicMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return vbd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthValidateBasicMiddleware{}
|
||||
|
||||
// NewEthValidateBasicMiddleware creates a new EthValidateBasicMiddleware
|
||||
func NewEthValidateBasicMiddleware(ek EVMKeeper) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return EthValidateBasicMiddleware{
|
||||
next: h,
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// EthSigVerificationMiddleware validates an ethereum signatures
|
||||
type EthSigVerificationMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
func ethSigVerificationMiddleware(esvd EthSigVerificationMiddleware, cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
chainID := esvd.evmKeeper.ChainID()
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
|
||||
params := esvd.evmKeeper.GetParams(ctx)
|
||||
|
||||
ethCfg := params.ChainConfig.EthereumConfig(chainID)
|
||||
blockNum := big.NewInt(ctx.BlockHeight())
|
||||
signer := ethtypes.MakeSigner(ethCfg, blockNum)
|
||||
|
||||
for _, msg := range reqTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
sender, err := signer.Sender(msgEthTx.AsTransaction())
|
||||
if err != nil {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrorInvalidSigner,
|
||||
"couldn't retrieve sender address ('%s') from the ethereum transaction: %s",
|
||||
msgEthTx.From,
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
// set up the sender to the transaction field if not already
|
||||
msgEthTx.From = sender.Hex()
|
||||
}
|
||||
|
||||
return tx.Response{}, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (esvd EthSigVerificationMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
if _, err := ethSigVerificationMiddleware(esvd, ctx, req); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return esvd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (esvd EthSigVerificationMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := ethSigVerificationMiddleware(esvd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return esvd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (esvd EthSigVerificationMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := ethSigVerificationMiddleware(esvd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return esvd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthSigVerificationMiddleware{}
|
||||
|
||||
// NewEthSigVerificationMiddleware creates a new EthSigVerificationMiddleware
|
||||
func NewEthSigVerificationMiddleware(ek EVMKeeper) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return EthSigVerificationMiddleware{
|
||||
next: h,
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EthAccountVerificationMiddleware validates an account balance checks
|
||||
type EthAccountVerificationMiddleware struct {
|
||||
next tx.Handler
|
||||
ak evmtypes.AccountKeeper
|
||||
bankKeeper evmtypes.BankKeeper
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (avd EthAccountVerificationMiddleware) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
reqTx := req.Tx
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
if !ctx.IsCheckTx() {
|
||||
return avd.next.CheckTx(cx, req, checkReq)
|
||||
}
|
||||
|
||||
for i, msg := range reqTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(err, "failed to unpack tx data any for tx %d", i)
|
||||
}
|
||||
|
||||
// sender address should be in the tx cache from the previous AnteHandle call
|
||||
from := msgEthTx.GetFrom()
|
||||
if from.Empty() {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "from address cannot be empty")
|
||||
}
|
||||
|
||||
// check whether the sender address is EOA
|
||||
fromAddr := common.BytesToAddress(from)
|
||||
acct := avd.evmKeeper.GetAccount(ctx, fromAddr)
|
||||
|
||||
if acct == nil {
|
||||
acc := avd.ak.NewAccountWithAddress(ctx, from)
|
||||
avd.ak.SetAccount(ctx, acc)
|
||||
acct = statedb.NewEmptyAccount()
|
||||
} else if acct.IsContract() {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType,
|
||||
"the sender is not EOA: address %s, codeHash <%s>", fromAddr, acct.CodeHash)
|
||||
}
|
||||
|
||||
if err := evmkeeper.CheckSenderBalance(sdk.NewIntFromBigInt(acct.Balance), txData); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(err, "failed to check sender balance")
|
||||
}
|
||||
}
|
||||
return avd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (avd EthAccountVerificationMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return avd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (avd EthAccountVerificationMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
return avd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthAccountVerificationMiddleware{}
|
||||
|
||||
// NewEthAccountVerificationMiddleware creates a new EthAccountVerificationMiddleware
|
||||
func NewEthAccountVerificationMiddleware(ak evmtypes.AccountKeeper, bankKeeper evmtypes.BankKeeper, ek EVMKeeper) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return EthAccountVerificationMiddleware{
|
||||
next: h,
|
||||
ak: ak,
|
||||
bankKeeper: bankKeeper,
|
||||
evmKeeper: ek,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EthGasConsumeMiddleware validates enough intrinsic gas for the transaction and
|
||||
// gas consumption.
|
||||
type EthGasConsumeMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
maxGasWanted uint64
|
||||
}
|
||||
|
||||
func ethGasMiddleware(egcd EthGasConsumeMiddleware, cx context.Context, req tx.Request) (context.Context, tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
|
||||
params := egcd.evmKeeper.GetParams(ctx)
|
||||
|
||||
ethCfg := params.ChainConfig.EthereumConfig(egcd.evmKeeper.ChainID())
|
||||
|
||||
blockHeight := big.NewInt(ctx.BlockHeight())
|
||||
homestead := ethCfg.IsHomestead(blockHeight)
|
||||
istanbul := ethCfg.IsIstanbul(blockHeight)
|
||||
london := ethCfg.IsLondon(blockHeight)
|
||||
evmDenom := params.EvmDenom
|
||||
gasWanted := uint64(0)
|
||||
var events sdk.Events
|
||||
|
||||
for _, msg := range reqTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return ctx, tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return ctx, tx.Response{}, sdkerrors.Wrap(err, "failed to unpack tx data")
|
||||
}
|
||||
|
||||
if ctx.IsCheckTx() {
|
||||
// We can't trust the tx gas limit, because we'll refund the unused gas.
|
||||
if txData.GetGas() > egcd.maxGasWanted {
|
||||
gasWanted += egcd.maxGasWanted
|
||||
} else {
|
||||
gasWanted += txData.GetGas()
|
||||
}
|
||||
} else {
|
||||
gasWanted += txData.GetGas()
|
||||
}
|
||||
|
||||
fees, err := egcd.evmKeeper.DeductTxCostsFromUserBalance(
|
||||
ctx,
|
||||
*msgEthTx,
|
||||
txData,
|
||||
evmDenom,
|
||||
homestead,
|
||||
istanbul,
|
||||
london,
|
||||
)
|
||||
if err != nil {
|
||||
return ctx, tx.Response{}, sdkerrors.Wrapf(err, "failed to deduct transaction costs from user balance")
|
||||
}
|
||||
|
||||
events = append(events, sdk.NewEvent(sdk.EventTypeTx, sdk.NewAttribute(sdk.AttributeKeyFee, fees.String())))
|
||||
}
|
||||
|
||||
// TODO: change to typed events
|
||||
ctx.EventManager().EmitEvents(events)
|
||||
|
||||
// TODO: deprecate after https://github.com/cosmos/cosmos-sdk/issues/9514 is fixed on SDK
|
||||
blockGasLimit := ethermint.BlockGasLimit(ctx)
|
||||
|
||||
// NOTE: safety check
|
||||
if blockGasLimit > 0 {
|
||||
// generate a copy of the gas pool (i.e block gas meter) to see if we've run out of gas for this block
|
||||
// if current gas consumed is greater than the limit, this funcion panics and the error is recovered on the Baseapp
|
||||
gasPool := sdk.NewGasMeter(blockGasLimit)
|
||||
gasPool.ConsumeGas(ctx.GasMeter().GasConsumedToLimit(), "gas pool check")
|
||||
}
|
||||
|
||||
// Set ctx.GasMeter with a limit of GasWanted (gasLimit)
|
||||
gasConsumed := ctx.GasMeter().GasConsumed()
|
||||
ctx = ctx.WithGasMeter(ethermint.NewInfiniteGasMeterWithLimit(gasWanted))
|
||||
ctx.GasMeter().ConsumeGas(gasConsumed, "copy gas consumed")
|
||||
|
||||
return ctx, tx.Response{}, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (egcd EthGasConsumeMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
newCtx, _, err := ethGasMiddleware(egcd, ctx, req)
|
||||
if err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return egcd.next.CheckTx(newCtx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (egcd EthGasConsumeMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
newCtx, _, err := ethGasMiddleware(egcd, ctx, req)
|
||||
if err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return egcd.next.DeliverTx(newCtx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (egcd EthGasConsumeMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
newCtx, _, err := ethGasMiddleware(egcd, ctx, req)
|
||||
if err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return egcd.next.SimulateTx(newCtx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthGasConsumeMiddleware{}
|
||||
|
||||
// NewEthGasConsumeMiddleware creates a new EthGasConsumeMiddleware
|
||||
func NewEthGasConsumeMiddleware(
|
||||
evmKeeper EVMKeeper,
|
||||
maxGasWanted uint64,
|
||||
) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return EthGasConsumeMiddleware{
|
||||
h,
|
||||
evmKeeper,
|
||||
maxGasWanted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CanTransferMiddleware checks if the sender is allowed to transfer funds according to the EVM block
|
||||
// context rules.
|
||||
type CanTransferMiddleware struct {
|
||||
next tx.Handler
|
||||
evmKeeper EVMKeeper
|
||||
}
|
||||
|
||||
func canTransfer(ctd CanTransferMiddleware, cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
params := ctd.evmKeeper.GetParams(ctx)
|
||||
ethCfg := params.ChainConfig.EthereumConfig(ctd.evmKeeper.ChainID())
|
||||
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(ctx.BlockHeight()))
|
||||
|
||||
for _, msg := range reqTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
baseFee := ctd.evmKeeper.BaseFee(ctx, ethCfg)
|
||||
|
||||
coreMsg, err := msgEthTx.AsMessage(signer, baseFee)
|
||||
if err != nil {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
err,
|
||||
"failed to create an ethereum core.Message from signer %T", signer,
|
||||
)
|
||||
}
|
||||
|
||||
// NOTE: pass in an empty coinbase address and nil tracer as we don't need them for the check below
|
||||
cfg := &evmtypes.EVMConfig{
|
||||
ChainConfig: ethCfg,
|
||||
Params: params,
|
||||
CoinBase: common.Address{},
|
||||
BaseFee: baseFee,
|
||||
}
|
||||
stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes())))
|
||||
evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB)
|
||||
|
||||
// check that caller has enough balance to cover asset transfer for **topmost** call
|
||||
// NOTE: here the gas consumed is from the context with the infinite gas meter
|
||||
if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInsufficientFunds,
|
||||
"failed to transfer %s from address %s using the EVM block context transfer function",
|
||||
coreMsg.Value(),
|
||||
coreMsg.From(),
|
||||
)
|
||||
}
|
||||
|
||||
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) {
|
||||
if baseFee == nil {
|
||||
return tx.Response{}, sdkerrors.Wrap(
|
||||
evmtypes.ErrInvalidBaseFee,
|
||||
"base fee is supported but evm block context value is nil",
|
||||
)
|
||||
}
|
||||
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInsufficientFee,
|
||||
"max fee per gas less than block base fee (%s < %s)",
|
||||
coreMsg.GasFeeCap(), baseFee,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Response{}, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (ctd CanTransferMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
if _, err := canTransfer(ctd, ctx, req); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return ctd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (ctd CanTransferMiddleware) DeliverTx(cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
if _, err := canTransfer(ctd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return ctd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (ctd CanTransferMiddleware) SimulateTx(cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
if _, err := canTransfer(ctd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return ctd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = CanTransferMiddleware{}
|
||||
|
||||
// NewCanTransferMiddleware creates a new CanTransferMiddleware instance.
|
||||
func NewCanTransferMiddleware(evmKeeper EVMKeeper) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return CanTransferMiddleware{
|
||||
next: h,
|
||||
evmKeeper: evmKeeper,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EthIncrementSenderSequenceMiddleware increments the sequence of the signers.
|
||||
type EthIncrementSenderSequenceMiddleware struct {
|
||||
next tx.Handler
|
||||
ak evmtypes.AccountKeeper
|
||||
}
|
||||
|
||||
func ethIncrementSenderSequence(issd EthIncrementSenderSequenceMiddleware, cx context.Context, req tx.Request) (tx.Response, error) {
|
||||
ctx := sdk.UnwrapSDKContext(cx)
|
||||
reqTx := req.Tx
|
||||
|
||||
for _, msg := range reqTx.GetMsgs() {
|
||||
msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
|
||||
}
|
||||
|
||||
txData, err := evmtypes.UnpackTxData(msgEthTx.Data)
|
||||
if err != nil {
|
||||
return tx.Response{}, sdkerrors.Wrap(err, "failed to unpack tx data")
|
||||
}
|
||||
|
||||
// increase sequence of sender
|
||||
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
|
||||
if acc == nil {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrUnknownAddress,
|
||||
"account %s is nil", common.BytesToAddress(msgEthTx.GetFrom().Bytes()),
|
||||
)
|
||||
}
|
||||
nonce := acc.GetSequence()
|
||||
|
||||
// we merged the nonce verification to nonce increment, so when tx includes multiple messages
|
||||
// with same sender, they'll be accepted.
|
||||
if txData.GetNonce() != nonce {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidSequence,
|
||||
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
|
||||
)
|
||||
}
|
||||
|
||||
if err := acc.SetSequence(nonce + 1); err != nil {
|
||||
return tx.Response{}, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
|
||||
}
|
||||
|
||||
issd.ak.SetAccount(ctx, acc)
|
||||
}
|
||||
|
||||
return tx.Response{}, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (issd EthIncrementSenderSequenceMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
if _, err := ethIncrementSenderSequence(issd, ctx, req); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return issd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (issd EthIncrementSenderSequenceMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := ethIncrementSenderSequence(issd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return issd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (issd EthIncrementSenderSequenceMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := ethIncrementSenderSequence(issd, ctx, req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return issd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ tx.Handler = EthIncrementSenderSequenceMiddleware{}
|
||||
|
||||
// NewEthIncrementSenderSequenceMiddleware creates a new EthIncrementSenderSequenceMiddleware.
|
||||
func NewEthIncrementSenderSequenceMiddleware(ak evmtypes.AccountKeeper) tx.Middleware {
|
||||
return func(h tx.Handler) tx.Handler {
|
||||
return EthIncrementSenderSequenceMiddleware{
|
||||
next: h,
|
||||
ak: ak,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
"github.com/tharsis/ethermint/crypto/ethsecp256k1"
|
||||
)
|
||||
|
||||
const (
|
||||
secp256k1VerifyCost uint64 = 21000
|
||||
)
|
||||
|
||||
type txRouter struct {
|
||||
eth, cosmos, eip712 tx.Handler
|
||||
}
|
||||
|
||||
var _ tx.Handler = txRouter{}
|
||||
|
||||
func NewTxHandler(options HandlerOptions) tx.Handler {
|
||||
return authmiddleware.ComposeMiddlewares(
|
||||
authmiddleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter),
|
||||
authmiddleware.NewTxDecoderMiddleware(options.TxDecoder),
|
||||
NewTxRouterMiddleware(options),
|
||||
)
|
||||
}
|
||||
|
||||
func NewTxRouterMiddleware(options HandlerOptions) tx.Middleware {
|
||||
ethMiddleware := newEthAuthMiddleware(options)
|
||||
cosmoseip712 := newCosmosMiddlewareEip712(options)
|
||||
cosmosMiddleware := newCosmosAuthMiddleware(options)
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return txRouter{
|
||||
eth: ethMiddleware(txh),
|
||||
cosmos: cosmosMiddleware(txh),
|
||||
eip712: cosmoseip712(txh),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (txh txRouter) route(req tx.Request) (tx.Handler, error) {
|
||||
txWithExtensions, ok := req.Tx.(authmiddleware.HasExtensionOptionsTx)
|
||||
if ok {
|
||||
opts := txWithExtensions.GetExtensionOptions()
|
||||
if len(opts) > 0 {
|
||||
var next tx.Handler
|
||||
switch typeURL := opts[0].GetTypeUrl(); typeURL {
|
||||
case "/ethermint.evm.v1.ExtensionOptionsEthereumTx":
|
||||
// handle as *evmtypes.MsgEthereumTx
|
||||
next = txh.eth
|
||||
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
|
||||
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
|
||||
next = txh.eip712
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrUnknownExtensionOptions,
|
||||
"rejecting tx with unsupported extension option: %s", typeURL,
|
||||
)
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
}
|
||||
// handle as totally normal Cosmos SDK tx
|
||||
return txh.cosmos, nil
|
||||
}
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (txh txRouter) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (res tx.Response, rct tx.ResponseCheckTx, err error) {
|
||||
next, err := txh.route(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (txh txRouter) DeliverTx(ctx context.Context, req tx.Request) (res tx.Response, err error) {
|
||||
next, err := txh.route(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (txh txRouter) SimulateTx(ctx context.Context, req tx.Request) (res tx.Response, err error) {
|
||||
next, err := txh.route(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
var _ = DefaultSigVerificationGasConsumer
|
||||
|
||||
// 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 authtypes.Params,
|
||||
) error {
|
||||
// support for ethereum ECDSA secp256k1 keys
|
||||
_, ok := sig.PubKey.(*ethsecp256k1.PubKey)
|
||||
if ok {
|
||||
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: eth_secp256k1")
|
||||
return nil
|
||||
}
|
||||
|
||||
return authmiddleware.DefaultSigVerificationGasConsumer(meter, sig, params)
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authmiddleware "github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
// HandlerOptions extend the SDK's TxHandler options by requiring the IBC
|
||||
// channel keeper, EVM Keeper and Fee Market Keeper.
|
||||
type HandlerOptions struct {
|
||||
Debug bool
|
||||
|
||||
Codec codec.Codec
|
||||
// TxDecoder is used to decode the raw tx bytes into a sdk.Tx.
|
||||
TxDecoder sdk.TxDecoder
|
||||
|
||||
// IndexEvents defines the set of events in the form {eventType}.{attributeKey},
|
||||
// which informs Tendermint what to index. If empty, all events will be indexed.
|
||||
IndexEvents map[string]struct{}
|
||||
|
||||
LegacyRouter sdk.Router
|
||||
MsgServiceRouter *authmiddleware.MsgServiceRouter
|
||||
|
||||
ExtensionOptionChecker authmiddleware.ExtensionOptionChecker
|
||||
TxFeeChecker authmiddleware.TxFeeChecker
|
||||
|
||||
AccountKeeper evmtypes.AccountKeeper
|
||||
BankKeeper evmtypes.BankKeeper
|
||||
FeeMarketKeeper evmtypes.FeeMarketKeeper
|
||||
EvmKeeper EVMKeeper
|
||||
FeegrantKeeper authmiddleware.FeegrantKeeper
|
||||
SignModeHandler authsigning.SignModeHandler
|
||||
SigGasConsumer func(meter sdk.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
|
||||
MaxTxGasWanted uint64
|
||||
}
|
||||
|
||||
func (options HandlerOptions) Validate() error {
|
||||
if options.TxDecoder == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "txDecoder is required for middlewares")
|
||||
}
|
||||
if options.SignModeHandler == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for middlewares")
|
||||
}
|
||||
if options.AccountKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for middlewares")
|
||||
}
|
||||
if options.BankKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for middlewares")
|
||||
}
|
||||
if options.SignModeHandler == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for middlewares")
|
||||
}
|
||||
if options.FeeMarketKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "fee market keeper is required for middlewares")
|
||||
}
|
||||
if options.EvmKeeper == nil {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "evm keeper is required for middlewares")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newEthAuthMiddleware(options HandlerOptions) tx.Middleware {
|
||||
stack := []tx.Middleware{
|
||||
NewEthSetUpContextMiddleware(options.EvmKeeper),
|
||||
NewEthMempoolFeeMiddleware(options.EvmKeeper),
|
||||
authmiddleware.NewIndexEventsTxMiddleware(options.IndexEvents),
|
||||
NewEthValidateBasicMiddleware(options.EvmKeeper),
|
||||
NewEthSigVerificationMiddleware(options.EvmKeeper),
|
||||
NewEthAccountVerificationMiddleware(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
|
||||
NewEthGasConsumeMiddleware(options.EvmKeeper, options.MaxTxGasWanted),
|
||||
NewCanTransferMiddleware(options.EvmKeeper),
|
||||
NewEthIncrementSenderSequenceMiddleware(options.AccountKeeper),
|
||||
}
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return authmiddleware.ComposeMiddlewares(txh, stack...)
|
||||
}
|
||||
}
|
||||
|
||||
func newCosmosAuthMiddleware(options HandlerOptions) tx.Middleware {
|
||||
stack := []tx.Middleware{
|
||||
NewRejectMessagesMiddleware,
|
||||
// Set a new GasMeter on sdk.Context.
|
||||
//
|
||||
// Make sure the Gas middleware is outside of all other middlewares
|
||||
// that reads the GasMeter. In our case, the Recovery middleware reads
|
||||
// the GasMeter to populate GasInfo.
|
||||
authmiddleware.GasTxMiddleware,
|
||||
// Recover from panics. Panics outside of this middleware won't be
|
||||
// caught, be careful!
|
||||
authmiddleware.RecoveryTxMiddleware,
|
||||
// Choose which events to index in Tendermint. Make sure no events are
|
||||
// emitted outside of this middleware.
|
||||
authmiddleware.NewIndexEventsTxMiddleware(options.IndexEvents),
|
||||
// Reject all extension options other than the ones needed by the feemarket.
|
||||
authmiddleware.NewExtensionOptionsMiddleware(options.ExtensionOptionChecker),
|
||||
authmiddleware.ValidateBasicMiddleware,
|
||||
authmiddleware.TxTimeoutHeightMiddleware,
|
||||
authmiddleware.ValidateMemoMiddleware(options.AccountKeeper),
|
||||
authmiddleware.ConsumeTxSizeGasMiddleware(options.AccountKeeper),
|
||||
// No gas should be consumed in any middleware above in a "post" handler part. See
|
||||
// ComposeMiddlewares godoc for details.
|
||||
// `DeductFeeMiddleware` and `IncrementSequenceMiddleware` should be put outside of `WithBranchedStore` middleware,
|
||||
// so their storage writes are not discarded when tx fails.
|
||||
authmiddleware.DeductFeeMiddleware(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
|
||||
authmiddleware.SetPubKeyMiddleware(options.AccountKeeper),
|
||||
authmiddleware.ValidateSigCountMiddleware(options.AccountKeeper),
|
||||
authmiddleware.SigGasConsumeMiddleware(options.AccountKeeper, options.SigGasConsumer),
|
||||
authmiddleware.SigVerificationMiddleware(options.AccountKeeper, options.SignModeHandler),
|
||||
authmiddleware.IncrementSequenceMiddleware(options.AccountKeeper),
|
||||
// Creates a new MultiStore branch, discards downstream writes if the downstream returns error.
|
||||
// These kinds of middlewares should be put under this:
|
||||
// - Could return error after messages executed succesfully.
|
||||
// - Storage writes should be discarded together when tx failed.
|
||||
authmiddleware.WithBranchedStore,
|
||||
// Consume block gas. All middlewares whose gas consumption after their `next` handler
|
||||
// should be accounted for, should go below this middleware.
|
||||
authmiddleware.ConsumeBlockGasMiddleware,
|
||||
authmiddleware.NewTipMiddleware(options.BankKeeper),
|
||||
}
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return authmiddleware.ComposeMiddlewares(txh, stack...)
|
||||
}
|
||||
}
|
||||
|
||||
func newCosmosMiddlewareEip712(options HandlerOptions) tx.Middleware {
|
||||
stack := []tx.Middleware{
|
||||
NewRejectMessagesMiddleware,
|
||||
// Set a new GasMeter on sdk.Context.
|
||||
//
|
||||
// Make sure the Gas middleware is outside of all other middlewares
|
||||
// that reads the GasMeter. In our case, the Recovery middleware reads
|
||||
// the GasMeter to populate GasInfo.
|
||||
authmiddleware.GasTxMiddleware,
|
||||
// Recover from panics. Panics outside of this middleware won't be
|
||||
// caught, be careful!
|
||||
authmiddleware.RecoveryTxMiddleware,
|
||||
// Choose which events to index in Tendermint. Make sure no events are
|
||||
// emitted outside of this middleware.
|
||||
authmiddleware.NewIndexEventsTxMiddleware(options.IndexEvents),
|
||||
// Reject all extension options other than the ones needed by the feemarket.
|
||||
// authmiddleware.NewExtensionOptionsMiddleware(options.ExtensionOptionChecker),
|
||||
authmiddleware.ValidateBasicMiddleware,
|
||||
authmiddleware.TxTimeoutHeightMiddleware,
|
||||
authmiddleware.ValidateMemoMiddleware(options.AccountKeeper),
|
||||
authmiddleware.ConsumeTxSizeGasMiddleware(options.AccountKeeper),
|
||||
// No gas should be consumed in any middleware above in a "post" handler part. See
|
||||
// ComposeMiddlewares godoc for details.
|
||||
// `DeductFeeMiddleware` and `IncrementSequenceMiddleware` should be put outside of `WithBranchedStore` middleware,
|
||||
// so their storage writes are not discarded when tx fails.
|
||||
authmiddleware.DeductFeeMiddleware(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
|
||||
authmiddleware.SetPubKeyMiddleware(options.AccountKeeper),
|
||||
authmiddleware.ValidateSigCountMiddleware(options.AccountKeeper),
|
||||
authmiddleware.SigGasConsumeMiddleware(options.AccountKeeper, options.SigGasConsumer),
|
||||
// Note: signature verification uses EIP instead of the cosmos signature validator
|
||||
NewEip712SigVerificationMiddleware(options.Codec, options.AccountKeeper, options.SignModeHandler),
|
||||
authmiddleware.IncrementSequenceMiddleware(options.AccountKeeper),
|
||||
// Creates a new MultiStore branch, discards downstream writes if the downstream returns error.
|
||||
// These kinds of middlewares should be put under this:
|
||||
// - Could return error after messages executed succesfully.
|
||||
// - Storage writes should be discarded together when tx failed.
|
||||
authmiddleware.WithBranchedStore,
|
||||
// Consume block gas. All middlewares whose gas consumption after their `next` handler
|
||||
// should be accounted for, should go below this middleware.
|
||||
authmiddleware.ConsumeBlockGasMiddleware,
|
||||
authmiddleware.NewTipMiddleware(options.BankKeeper),
|
||||
}
|
||||
return func(txh tx.Handler) tx.Handler {
|
||||
return authmiddleware.ComposeMiddlewares(txh, stack...)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// RejectMessagesMiddleware prevents invalid msg types from being executed
|
||||
type RejectMessagesMiddleware struct {
|
||||
next tx.Handler
|
||||
}
|
||||
|
||||
func NewRejectMessagesMiddleware(txh tx.Handler) tx.Handler {
|
||||
return RejectMessagesMiddleware{
|
||||
next: txh,
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware rejects messages that requires ethereum-specific authentication.
|
||||
// For example `MsgEthereumTx` requires fee to be deducted in the antehandler in
|
||||
// order to perform the refund.
|
||||
|
||||
// CheckTx implements tx.Handler
|
||||
func (rmd RejectMessagesMiddleware) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
|
||||
if _, err := reject(req); err != nil {
|
||||
return tx.Response{}, tx.ResponseCheckTx{}, err
|
||||
}
|
||||
|
||||
return rmd.next.CheckTx(ctx, req, checkReq)
|
||||
}
|
||||
|
||||
// DeliverTx implements tx.Handler
|
||||
func (rmd RejectMessagesMiddleware) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := reject(req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return rmd.next.DeliverTx(ctx, req)
|
||||
}
|
||||
|
||||
// SimulateTx implements tx.Handler
|
||||
func (rmd RejectMessagesMiddleware) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
|
||||
if _, err := reject(req); err != nil {
|
||||
return tx.Response{}, err
|
||||
}
|
||||
|
||||
return rmd.next.SimulateTx(ctx, req)
|
||||
}
|
||||
|
||||
func reject(req tx.Request) (tx.Response, error) {
|
||||
reqTx := req.Tx
|
||||
for _, msg := range reqTx.GetMsgs() {
|
||||
if _, ok := msg.(*evmtypes.MsgEthereumTx); ok {
|
||||
return tx.Response{}, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrInvalidType,
|
||||
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Response{}, nil
|
||||
}
|
||||
|
||||
var _ tx.Handler = RejectMessagesMiddleware{}
|
||||
Reference in New Issue
Block a user