WIP: trying to migrate auth handlers to middlewares

This commit is contained in:
Sai Kumar 2022-04-20 17:20:55 +05:30
parent d377665800
commit 92791d5f9d
9 changed files with 1321 additions and 22 deletions

View File

@ -11,12 +11,16 @@ 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"
@ -111,7 +115,6 @@ import (
// evmrest "github.com/tharsis/ethermint/x/evm/client/rest"
storetypes "github.com/cosmos/cosmos-sdk/store/v2alpha1"
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
"github.com/tharsis/ethermint/x/feemarket"
feemarketkeeper "github.com/tharsis/ethermint/x/feemarket/keeper"
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
@ -608,35 +611,45 @@ func NewEthermintApp(
// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
// use Ethermint's custom AnteHandler
// maxGasWanted := cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted))
// 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,
// }
// if err := options.Validate(); err != nil {
// panic(err)
// }
// app.SetAnteHandler(ante.NewAnteHandler(options))
app.SetEndBlocker(app.EndBlocker)
// app.ScopedIBCKeeper = scopedIBCKeeper
// app.ScopedTransferKeeper = scopedTransferKeeper
maxGasWanted := cast.ToUint64(appOpts.Get(srvflags.EVMMaxTxGasWanted))
options := middleware.HandlerOptions{
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: authmiddleware.DefaultSigVerificationGasConsumer,
MaxTxGasWanted: maxGasWanted,
TxDecoder: encodingConfig.TxConfig.TxDecoder(),
}
app.setTxHandler(options, encodingConfig.TxConfig, cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents)))
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{}{}
}
txHandler, err := middleware.NewMiddleware(indexEventsStr, options)
if err != nil {
panic(err)
}
app.SetTxHandler(txHandler)
}
// Name returns the name of the App
func (app *EthermintApp) Name() string { return app.BaseApp.Name() }

288
app/middleware/eip792.go Normal file
View File

@ -0,0 +1,288 @@
package middleware
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"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
"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"
)
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 decorator runs
// CONTRACT: Tx must implement SigVerifiableTx interface
type Eip712SigVerificationDecorator struct {
next tx.Handler
ak evmtypes.AccountKeeper
signModeHandler authsigning.SignModeHandler
}
var _ tx.Handler = Eip712SigVerificationDecorator{}
// NewEip712SigVerificationDecorator creates a new Eip712SigVerificationDecorator
func NewEip712SigVerificationDecorator(ak evmtypes.AccountKeeper, signModeHandler authsigning.SignModeHandler) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return Eip712SigVerificationDecorator{
next: h,
ak: ak,
signModeHandler: signModeHandler,
}
}
}
// CheckTx implements tx.Handler
func (svd Eip712SigVerificationDecorator) 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 verify signatures on recheck tx
if ctx.IsReCheckTx() {
return svd.next.CheckTx(ctx, req, checkReq)
}
sigTx, ok := reqTx.(authsigning.SigVerifiableTx)
if !ok {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement authsigning.SigVerifiableTx", reqTx)
}
authSignTx, ok := reqTx.(authsigning.Tx)
if !ok {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "tx %T doesn't implement the authsigning.Tx interface", reqTx)
}
// 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{}, tx.ResponseCheckTx{}, err
}
signerAddrs := sigTx.GetSigners()
// EIP712 allows just one signature
if len(sigs) != 1 {
return tx.Response{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, 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])
if err != nil {
return tx.Response{}, tx.ResponseCheckTx{}, err
}
// retrieve pubkey
pubKey := acc.GetPubKey()
if pubKey == nil {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
}
// Check account sequence number.
if sig.Sequence != acc.GetSequence() {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(
sdkerrors.ErrWrongSequence,
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
)
}
// retrieve signer data
genesis := ctx.BlockHeight() == 0
chainID := ctx.ChainID()
var accNum uint64
if !genesis {
accNum = acc.GetAccountNumber()
}
signerData := authsigning.SignerData{
ChainID: chainID,
AccountNumber: accNum,
Sequence: acc.GetSequence(),
}
if 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{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg.Error())
}
return svd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (svd Eip712SigVerificationDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return svd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (svd Eip712SigVerificationDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return svd.next.SimulateTx(ctx, req)
}
// VerifySignature verifies a transaction signature contained in SignatureData abstracting over different signing modes
// and single vs multi-signatures.
func VerifySignature(
pubKey cryptotypes.PubKey,
signerData authsigning.SignerData,
sigData signing.SignatureData,
_ authsigning.SignModeHandler,
tx authsigning.Tx,
) error {
switch data := sigData.(type) {
case *signing.SingleSignatureData:
if data.SignMode != signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON {
return sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "unexpected SignatureData %T: wrong SignMode", sigData)
}
// Note: this prevents the user from sending thrash data in the signature field
if len(data.Signature) != 0 {
return sdkerrors.Wrap(sdkerrors.ErrTooManySignatures, "invalid signature value; EIP712 must have the cosmos transaction signature empty")
}
// @contract: this code is reached only when Msg has Web3Tx extension (so this custom Ante handler flow),
// and the signature is SIGN_MODE_LEGACY_AMINO_JSON which is supported for EIP712 for now
msgs := tx.GetMsgs()
if len(msgs) == 0 {
return sdkerrors.Wrap(sdkerrors.ErrNoSignatures, "tx doesn't contain any msgs to verify signature")
}
txBytes := legacytx.StdSignBytes(
signerData.ChainID,
signerData.AccountNumber,
signerData.Sequence,
tx.GetTimeoutHeight(),
legacytx.StdFee{
Amount: tx.GetFee(),
Gas: tx.GetGas(),
},
msgs, tx.GetMemo(), tx.GetTip(),
)
signerChainID, err := ethermint.ParseChainID(signerData.ChainID)
if err != nil {
return sdkerrors.Wrapf(err, "failed to parse chainID: %s", signerData.ChainID)
}
txWithExtensions, ok := tx.(middleware.HasExtensionOptionsTx)
if !ok {
return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain any extensions")
}
opts := txWithExtensions.GetExtensionOptions()
if len(opts) != 1 {
return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "tx doesnt contain expected amount of extension options")
}
var optIface ethermint.ExtensionOptionsWeb3TxI
if err := ethermintCodec.UnpackAny(opts[0], &optIface); err != nil {
return sdkerrors.Wrap(err, "failed to proto-unpack ExtensionOptionsWeb3Tx")
}
extOpt, ok := optIface.(*ethermint.ExtensionOptionsWeb3Tx)
if !ok {
return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "unknown extension option")
}
if extOpt.TypedDataChainID != signerChainID.Uint64() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidChainID, "invalid chainID")
}
if len(extOpt.FeePayer) == 0 {
return sdkerrors.Wrap(sdkerrors.ErrUnknownExtensionOptions, "no feePayer on ExtensionOptionsWeb3Tx")
}
feePayer, err := sdk.AccAddressFromBech32(extOpt.FeePayer)
if err != nil {
return sdkerrors.Wrap(err, "failed to parse feePayer from ExtensionOptionsWeb3Tx")
}
feeDelegation := &eip712.FeeDelegationOptions{
FeePayer: feePayer,
}
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")
}
sigHash, err := eip712.ComputeTypedDataHash(typedData)
if err != nil {
return err
}
feePayerSig := extOpt.FeePayerSig
if len(feePayerSig) != ethcrypto.SignatureLength {
return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "signature length doesn't match typical [R||S||V] signature 65 bytes")
}
// Remove the recovery offset if needed (ie. Metamask eip712 signature)
if feePayerSig[ethcrypto.RecoveryIDOffset] == 27 || feePayerSig[ethcrypto.RecoveryIDOffset] == 28 {
feePayerSig[ethcrypto.RecoveryIDOffset] -= 27
}
feePayerPubkey, err := secp256k1.RecoverPubkey(sigHash, feePayerSig)
if err != nil {
return sdkerrors.Wrap(err, "failed to recover delegated fee payer from sig")
}
ecPubKey, err := ethcrypto.UnmarshalPubkey(feePayerPubkey)
if err != nil {
return sdkerrors.Wrap(err, "failed to unmarshal recovered fee payer pubkey")
}
pk := &ethsecp256k1.PubKey{
Key: ethcrypto.CompressPubkey(ecPubKey),
}
if !pubKey.Equals(pk) {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "feePayer pubkey %s is different from transaction pubkey %s", pubKey, pk)
}
recoveredFeePayerAcc := sdk.AccAddress(pk.Address().Bytes())
if !recoveredFeePayerAcc.Equals(feePayer) {
return sdkerrors.Wrapf(sdkerrors.ErrorInvalidSigner, "failed to verify delegated fee payer %s signature", recoveredFeePayerAcc)
}
// VerifySignature of ethsecp256k1 accepts 64 byte signature [R||S]
// WARNING! Under NO CIRCUMSTANCES try to use pubKey.VerifySignature there
if !secp256k1.VerifySignature(pubKey.Bytes(), sigHash, feePayerSig[:len(feePayerSig)-1]) {
return sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, "unable to verify signer signature of EIP712 typed data")
}
return nil
default:
return sdkerrors.Wrapf(sdkerrors.ErrTooManySignatures, "unexpected SignatureData %T", sigData)
}
}

697
app/middleware/eth.go Normal file
View File

@ -0,0 +1,697 @@
package middleware
import (
context "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"
)
// EthSetupContextDecorator is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
// by setting the gas meter to infinite
type EthSetupContextDecorator struct {
next tx.Handler
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (esc EthSetupContextDecorator) 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(ctx, req, checkReq)
}
// 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(sdk.NewGasMeter(gasLimit))
}
// populateGas returns a new tx.Response with gas fields populated.
func populateGas(res tx.Response, sdkCtx sdk.Context) tx.Response {
res.GasWanted = sdkCtx.GasMeter().Limit()
res.GasUsed = sdkCtx.GasMeter().GasConsumed()
return res
}
// DeliverTx implements tx.Handler
func (esc EthSetupContextDecorator) 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(ctx, req)
}
// SimulateTx implements tx.Handler
func (esc EthSetupContextDecorator) 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(ctx, req)
}
var _ tx.Handler = EthSetupContextDecorator{}
func NewEthSetUpContextDecorator(evmKeeper EVMKeeper) tx.Middleware {
return func(txh tx.Handler) tx.Handler {
return EthSetupContextDecorator{
next: txh,
evmKeeper: evmKeeper,
}
}
}
// 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 {
next tx.Handler
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (mfd EthMempoolFeeDecorator) 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 EthMempoolFeeDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return mfd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (mfd EthMempoolFeeDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return mfd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthMempoolFeeDecorator{}
func NewEthMempoolFeeDecorator(ek EVMKeeper) tx.Middleware {
return func(txh tx.Handler) tx.Handler {
return EthMempoolFeeDecorator{
next: txh,
evmKeeper: ek,
}
}
}
// EthValidateBasicDecorator is adapted from ValidateBasicDecorator from cosmos-sdk, it ignores ErrNoSignatures
type EthValidateBasicDecorator struct {
next tx.Handler
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (vbd EthValidateBasicDecorator) 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.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 veified 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 EthValidateBasicDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return vbd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (vbd EthValidateBasicDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return vbd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthValidateBasicDecorator{}
// NewEthValidateBasicDecorator creates a new EthValidateBasicDecorator
func NewEthValidateBasicDecorator(ek EVMKeeper) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return EthValidateBasicDecorator{
next: h,
evmKeeper: ek,
}
}
}
// EthSigVerificationDecorator validates an ethereum signatures
type EthSigVerificationDecorator struct {
next tx.Handler
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (esvd EthSigVerificationDecorator) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, 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{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, 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 esvd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (esvd EthSigVerificationDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return esvd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (esvd EthSigVerificationDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return esvd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthSigVerificationDecorator{}
// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator
func NewEthSigVerificationDecorator(ek EVMKeeper) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return EthSigVerificationDecorator{
next: h,
evmKeeper: ek,
}
}
}
// EthAccountVerificationDecorator validates an account balance checks
type EthAccountVerificationDecorator struct {
next tx.Handler
ak evmtypes.AccountKeeper
bankKeeper evmtypes.BankKeeper
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (avd EthAccountVerificationDecorator) 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 EthAccountVerificationDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return avd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (avd EthAccountVerificationDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return avd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthAccountVerificationDecorator{}
// NewEthAccountVerificationDecorator creates a new EthAccountVerificationDecorator
func NewEthAccountVerificationDecorator(ak evmtypes.AccountKeeper, bankKeeper evmtypes.BankKeeper, ek EVMKeeper) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return EthAccountVerificationDecorator{
next: h,
ak: ak,
bankKeeper: bankKeeper,
evmKeeper: ek,
}
}
}
// EthGasConsumeDecorator validates enough intrinsic gas for the transaction and
// gas consumption.
type EthGasConsumeDecorator struct {
next tx.Handler
evmKeeper EVMKeeper
maxGasWanted uint64
}
// CheckTx implements tx.Handler
func (egcd EthGasConsumeDecorator) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, 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 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.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 tx.Response{}, tx.ResponseCheckTx{}, 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 egcd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (egcd EthGasConsumeDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return egcd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (egcd EthGasConsumeDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return egcd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthGasConsumeDecorator{}
// NewEthGasConsumeDecorator creates a new EthGasConsumeDecorator
func NewEthGasConsumeDecorator(
evmKeeper EVMKeeper,
maxGasWanted uint64,
) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return EthGasConsumeDecorator{
h,
evmKeeper,
maxGasWanted,
}
}
}
// CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block
// context rules.
type CanTransferDecorator struct {
next tx.Handler
evmKeeper EVMKeeper
}
// CheckTx implements tx.Handler
func (ctd CanTransferDecorator) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, 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{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, sdkerrors.Wrap(
evmtypes.ErrInvalidBaseFee,
"base fee is supported but evm block context value is nil",
)
}
if coreMsg.GasFeeCap().Cmp(baseFee) < 0 {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"max fee per gas less than block base fee (%s < %s)",
coreMsg.GasFeeCap(), baseFee,
)
}
}
}
return ctd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (ctd CanTransferDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return ctd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (ctd CanTransferDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return ctd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = CanTransferDecorator{}
// NewCanTransferDecorator creates a new CanTransferDecorator instance.
func NewCanTransferDecorator(evmKeeper EVMKeeper) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return CanTransferDecorator{
next: h,
evmKeeper: evmKeeper,
}
}
}
// EthIncrementSenderSequenceDecorator increments the sequence of the signers.
type EthIncrementSenderSequenceDecorator struct {
next tx.Handler
ak evmtypes.AccountKeeper
}
// CheckTx implements tx.Handler
func (issd EthIncrementSenderSequenceDecorator) CheckTx(cx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
ctx := sdk.UnwrapSDKContext(cx)
reqTx := req.Tx
for _, 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.Wrap(err, "failed to unpack tx data")
}
// increase sequence of sender
acc := issd.ak.GetAccount(ctx, msgEthTx.GetFrom())
if acc == nil {
return tx.Response{}, tx.ResponseCheckTx{}, 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{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(
sdkerrors.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txData.GetNonce(), nonce,
)
}
if err := acc.SetSequence(nonce + 1); err != nil {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(err, "failed to set sequence to %d", acc.GetSequence()+1)
}
issd.ak.SetAccount(ctx, acc)
}
return issd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (issd EthIncrementSenderSequenceDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return issd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (issd EthIncrementSenderSequenceDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return issd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = EthIncrementSenderSequenceDecorator{}
// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator.
func NewEthIncrementSenderSequenceDecorator(ak evmtypes.AccountKeeper) tx.Middleware {
return func(h tx.Handler) tx.Handler {
return EthIncrementSenderSequenceDecorator{
next: h,
ak: ak,
}
}
}

View File

@ -0,0 +1,33 @@
package middleware
import (
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
tx "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/tharsis/ethermint/x/evm/statedb"
evmtypes "github.com/tharsis/ethermint/x/evm/types"
)
// EVMKeeper defines the expected keeper interface used on the Eth AnteHandler
type EVMKeeper interface {
statedb.Keeper
ChainID() *big.Int
GetParams(ctx sdk.Context) evmtypes.Params
NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM
DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (sdk.Coins, error)
BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context)
}
type protoTxProvider interface {
GetProtoTx() *tx.Tx
}

View File

@ -0,0 +1,32 @@
package middleware
import (
context "context"
"github.com/cosmos/cosmos-sdk/types/tx"
)
type MD struct {
next tx.Handler
}
var _ tx.Handler = MD{}
func NewMiddleware(indexEventsStr []string, options HandlerOptions) (tx.Handler, error) {
return newEthAuthMiddleware(options)
}
// CheckTx implements tx.Handler
func (md MD) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
return md.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (md MD) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return md.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (md MD) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return md.next.SimulateTx(ctx, req)
}

175
app/middleware/options.go Normal file
View File

@ -0,0 +1,175 @@
package middleware
import (
"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 AnteHandler options by requiring the IBC
// channel keeper, EVM Keeper and Fee Market Keeper.
type HandlerOptions struct {
Debug bool
// 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 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 newEthAuthMiddleware(options HandlerOptions) (tx.Handler, error) {
return authmiddleware.ComposeMiddlewares(
authmiddleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter),
NewEthSetUpContextDecorator(options.EvmKeeper),
NewEthMempoolFeeDecorator(options.EvmKeeper),
NewEthValidateBasicDecorator(options.EvmKeeper),
NewEthSigVerificationDecorator(options.EvmKeeper),
NewEthAccountVerificationDecorator(options.AccountKeeper, options.BankKeeper, options.EvmKeeper),
NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted),
NewCanTransferDecorator(options.EvmKeeper),
NewEthIncrementSenderSequenceDecorator(options.AccountKeeper),
), nil
}
func newCosmosAuthMiddleware(options HandlerOptions) (tx.Handler, error) {
return authmiddleware.ComposeMiddlewares(
authmiddleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter),
// reject MsgEthereumTxs
NewRejectMessagesDecorator(),
authmiddleware.NewTxDecoderMiddleware(options.TxDecoder),
// 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),
), nil
}
func newCosmosAnteHandlerEip712(options HandlerOptions) (tx.Handler, error) {
return authmiddleware.ComposeMiddlewares(
authmiddleware.NewRunMsgsTxHandler(options.MsgServiceRouter, options.LegacyRouter),
// reject MsgEthereumTxs
NewRejectMessagesDecorator(),
authmiddleware.NewTxDecoderMiddleware(options.TxDecoder),
// 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
NewEip712SigVerificationDecorator(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),
), nil
}

52
app/middleware/reject.go Normal file
View File

@ -0,0 +1,52 @@
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"
)
// RejectMessagesDecorator prevents invalid msg types from being executed
type RejectMessagesDecorator struct {
next tx.Handler
}
func NewRejectMessagesDecorator() tx.Middleware {
return func(h tx.Handler) tx.Handler {
return RejectMessagesDecorator{
next: h,
}
}
}
// 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 RejectMessagesDecorator) CheckTx(ctx context.Context, req tx.Request, checkReq tx.RequestCheckTx) (tx.Response, tx.ResponseCheckTx, error) {
reqTx := req.Tx
for _, msg := range reqTx.GetMsgs() {
if _, ok := msg.(*evmtypes.MsgEthereumTx); ok {
return tx.Response{}, tx.ResponseCheckTx{}, sdkerrors.Wrapf(
sdkerrors.ErrInvalidType,
"MsgEthereumTx needs to be contained within a tx with 'ExtensionOptionsEthereumTx' option",
)
}
}
return rmd.next.CheckTx(ctx, req, checkReq)
}
// DeliverTx implements tx.Handler
func (rmd RejectMessagesDecorator) DeliverTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return rmd.next.DeliverTx(ctx, req)
}
// SimulateTx implements tx.Handler
func (rmd RejectMessagesDecorator) SimulateTx(ctx context.Context, req tx.Request) (tx.Response, error) {
return rmd.next.SimulateTx(ctx, req)
}
var _ tx.Handler = RejectMessagesDecorator{}

View File

@ -45,6 +45,14 @@ func (g *infiniteGasMeterWithLimit) Limit() sdk.Gas {
return g.limit
}
// GasRemaining returns the gas left in the GasMeter.
func (g *infiniteGasMeterWithLimit) GasRemaining() sdk.Gas {
if g.IsPastLimit() {
return 0
}
return g.limit - g.consumed
}
// addUint64Overflow performs the addition operation on two uint64 integers and
// returns a boolean on whether or not the result overflows.
func addUint64Overflow(a, b uint64) (uint64, bool) {

View File

@ -33,6 +33,7 @@ type BankKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error
}
// StakingKeeper returns the historical headers kept in store.