WIP: trying to migrate auth handlers to middlewares
This commit is contained in:
parent
d377665800
commit
92791d5f9d
57
app/app.go
57
app/app.go
@ -11,12 +11,16 @@ import (
|
|||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
"github.com/spf13/cast"
|
"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/baseapp"
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||||
"github.com/cosmos/cosmos-sdk/db"
|
"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/api"
|
||||||
"github.com/cosmos/cosmos-sdk/server/config"
|
"github.com/cosmos/cosmos-sdk/server/config"
|
||||||
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
||||||
@ -111,7 +115,6 @@ import (
|
|||||||
// evmrest "github.com/tharsis/ethermint/x/evm/client/rest"
|
// evmrest "github.com/tharsis/ethermint/x/evm/client/rest"
|
||||||
storetypes "github.com/cosmos/cosmos-sdk/store/v2alpha1"
|
storetypes "github.com/cosmos/cosmos-sdk/store/v2alpha1"
|
||||||
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
|
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
|
||||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
|
||||||
"github.com/tharsis/ethermint/x/feemarket"
|
"github.com/tharsis/ethermint/x/feemarket"
|
||||||
feemarketkeeper "github.com/tharsis/ethermint/x/feemarket/keeper"
|
feemarketkeeper "github.com/tharsis/ethermint/x/feemarket/keeper"
|
||||||
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
|
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
|
||||||
@ -608,35 +611,45 @@ func NewEthermintApp(
|
|||||||
// initialize BaseApp
|
// initialize BaseApp
|
||||||
app.SetInitChainer(app.InitChainer)
|
app.SetInitChainer(app.InitChainer)
|
||||||
app.SetBeginBlocker(app.BeginBlocker)
|
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.SetEndBlocker(app.EndBlocker)
|
||||||
|
|
||||||
// app.ScopedIBCKeeper = scopedIBCKeeper
|
// app.ScopedIBCKeeper = scopedIBCKeeper
|
||||||
// app.ScopedTransferKeeper = scopedTransferKeeper
|
// 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
|
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
|
// Name returns the name of the App
|
||||||
func (app *EthermintApp) Name() string { return app.BaseApp.Name() }
|
func (app *EthermintApp) Name() string { return app.BaseApp.Name() }
|
||||||
|
|
||||||
|
288
app/middleware/eip792.go
Normal file
288
app/middleware/eip792.go
Normal 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 := ðsecp256k1.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
697
app/middleware/eth.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
33
app/middleware/interfaces.go
Normal file
33
app/middleware/interfaces.go
Normal 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
|
||||||
|
}
|
32
app/middleware/middleware.go
Normal file
32
app/middleware/middleware.go
Normal 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
175
app/middleware/options.go
Normal 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
52
app/middleware/reject.go
Normal 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{}
|
@ -45,6 +45,14 @@ func (g *infiniteGasMeterWithLimit) Limit() sdk.Gas {
|
|||||||
return g.limit
|
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
|
// addUint64Overflow performs the addition operation on two uint64 integers and
|
||||||
// returns a boolean on whether or not the result overflows.
|
// returns a boolean on whether or not the result overflows.
|
||||||
func addUint64Overflow(a, b uint64) (uint64, bool) {
|
func addUint64Overflow(a, b uint64) (uint64, bool) {
|
||||||
|
@ -33,6 +33,7 @@ type BankKeeper interface {
|
|||||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||||
MintCoins(ctx sdk.Context, moduleName 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
|
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.
|
// StakingKeeper returns the historical headers kept in store.
|
||||||
|
Loading…
Reference in New Issue
Block a user