Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@

### BUG FIXES

- [\#471](https://github.com/cosmos/evm/pull/471) Notify new block for mempool in time.

### IMPROVEMENTS

- [\#467](https://github.com/cosmos/evm/pull/467) Replace GlobalEVMMempool by passing to JSONRPC on initiate.
- [\#352](https://github.com/cosmos/evm/pull/352) Remove the creation of a Geth EVM instance, stateDB during the AnteHandler balance check.
- [\#467](https://github.com/cosmos/evm/pull/467) Ensure SetGlobalEVMMempool is thread-safe and only sets global mempool instance once.

### FEATURES

Expand Down
2 changes: 1 addition & 1 deletion evmd/ante/evm_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ func newMonoEVMAnteHandler(options ante.HandlerOptions) sdk.AnteHandler {
),
ante.NewTxListenerDecorator(options.PendingTxListener),
}

return sdk.ChainAnteDecorators(decorators...)
}
17 changes: 17 additions & 0 deletions evmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evmd

import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -1145,6 +1146,22 @@ func (app *EVMD) SetClientCtx(clientCtx client.Context) {
app.clientCtx = clientCtx
}

// Close unsubscribes from the CometBFT event bus (if set) and closes the underlying BaseApp.
func (app *EVMD) Close() error {
var err error
if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
err = m.Close()
}
err = errors.Join(err, app.BaseApp.Close())
msg := "Application gracefully shutdown"
if err == nil {
app.Logger().Info(msg)
} else {
app.Logger().Error(msg, "error", err)
}
return err
}

// AutoCliOpts returns the autocli options for the app.
func (app *EVMD) AutoCliOpts() autocli.AppOptions {
modules := make(map[string]appmodule.AppModule, 0)
Expand Down
24 changes: 18 additions & 6 deletions mempool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,6 @@ if evmtypes.GetChainConfig() != nil {
)
app.EVMMempool = evmMempool

// Set the global mempool for RPC access
if err := evmmempool.SetGlobalEVMMempool(evmMempool); err != nil {
panic(err)
}

// Replace BaseApp mempool
app.SetMempool(evmMempool)

Expand All @@ -103,6 +98,23 @@ if evmtypes.GetChainConfig() != nil {
)
app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
}

// Close unsubscribes from the CometBFT event bus (if set) and closes the underlying BaseApp.
func (app *EVMD) Close() error {
var err error
if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
err = m.Close()
}
err = errors.Join(err, app.BaseApp.Close())
msg := "Application gracefully shutdown"
if err == nil {
app.Logger().Info(msg)
} else {
app.Logger().Error(msg, "error", err)
}
return err
}

```

### Configuration Options
Expand Down Expand Up @@ -211,7 +223,7 @@ ERROR unable to publish transaction nonce=40 expected=12: invalid sequence
ERROR unable to publish transaction nonce=41 expected=12: invalid sequence
```

**Real-World Testing**: The [`tests/systemtests/Counter/script/SimpleSends.s.sol`](../../tests/systemtests/Counter/script/SimpleSends.s.sol) script demonstrates typical Ethereum tooling behavior - it sends 10 sequential transactions in a batch, which naturally arrive out of order and create nonce gaps. With the default Cosmos mempool, this script would fail with sequence errors. With the EVM mempool, all transactions are queued locally and promoted as gaps are filled, allowing the script to succeed.
**Real-World Testing**: The [`tests/systemtests/Counter/script/SimpleSends.s.sol`](../tests/systemtests/Counter/script/SimpleSends.s.sol) script demonstrates typical Ethereum tooling behavior - it sends 10 sequential transactions in a batch, which naturally arrive out of order and create nonce gaps. With the default Cosmos mempool, this script would fail with sequence errors. With the EVM mempool, all transactions are queued locally and promoted as gaps are filled, allowing the script to succeed.

### Design Principles

Expand Down
7 changes: 4 additions & 3 deletions mempool/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,17 +340,18 @@ func (i *EVMMempoolIterator) convertEVMToSDKTx(nextEVMTx *txpool.LazyTransaction
}

msgEthereumTx := &msgtypes.MsgEthereumTx{}
hash := nextEVMTx.Tx.Hash()
if err := msgEthereumTx.FromSignedEthereumTx(nextEVMTx.Tx, ethtypes.LatestSignerForChainID(i.chainID)); err != nil {
i.logger.Error("failed to convert signed Ethereum transaction", "error", err, "tx_hash", nextEVMTx.Tx.Hash().Hex())
i.logger.Error("failed to convert signed Ethereum transaction", "error", err, "tx_hash", hash)
return nil // Return nil for invalid tx instead of panicking
}

cosmosTx, err := msgEthereumTx.BuildTx(i.txConfig.NewTxBuilder(), i.bondDenom)
if err != nil {
i.logger.Error("failed to build Cosmos transaction from EVM transaction", "error", err, "tx_hash", nextEVMTx.Tx.Hash().Hex())
i.logger.Error("failed to build Cosmos transaction from EVM transaction", "error", err, "tx_hash", hash)
return nil
}

i.logger.Debug("successfully converted EVM transaction to Cosmos transaction", "tx_hash", nextEVMTx.Tx.Hash().Hex())
i.logger.Debug("successfully converted EVM transaction to Cosmos transaction", "tx_hash", hash)
return cosmosTx
}
52 changes: 40 additions & 12 deletions mempool/mempool.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"

cmttypes "github.com/cometbft/cometbft/types"

"github.com/cosmos/evm/mempool/miner"
"github.com/cosmos/evm/mempool/txpool"
"github.com/cosmos/evm/mempool/txpool/legacypool"
"github.com/cosmos/evm/rpc/stream"
"github.com/cosmos/evm/x/precisebank/types"
evmtypes "github.com/cosmos/evm/x/vm/types"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/log"
"cosmossdk.io/math"

Expand All @@ -27,6 +29,8 @@

var _ sdkmempool.ExtMempool = &ExperimentalEVMMempool{}

const SubscriberName = "evm"

type (
// ExperimentalEVMMempool is a unified mempool that manages both EVM and Cosmos SDK transactions.
// It provides a single interface for transaction insertion, selection, and removal while
Expand Down Expand Up @@ -54,6 +58,8 @@

/** Concurrency **/
mtx sync.Mutex

eventBus *cmttypes.EventBus
}
)

Expand Down Expand Up @@ -202,22 +208,18 @@
blockHeight := ctx.BlockHeight()

m.logger.Debug("inserting transaction into mempool", "block_height", blockHeight)

if blockHeight < 2 {
return errorsmod.Wrap(sdkerrors.ErrInvalidHeight, "Mempool is not ready. Please wait for block 1 to finalize.")
}

ethMsg, err := m.getEVMMessage(tx)
if err == nil {
// Insert into EVM pool
m.logger.Debug("inserting EVM transaction", "tx_hash", ethMsg.Hash)
hash := ethMsg.Hash()
m.logger.Debug("inserting EVM transaction", "tx_hash", hash)
ethTxs := []*ethtypes.Transaction{ethMsg.AsTransaction()}
errs := m.txPool.Add(ethTxs, true)
if len(errs) > 0 && errs[0] != nil {
m.logger.Error("failed to insert EVM transaction", "error", errs[0], "tx_hash", ethMsg.Hash)
m.logger.Error("failed to insert EVM transaction", "error", errs[0], "tx_hash", hash)
return errs[0]
}
m.logger.Debug("EVM transaction inserted successfully", "tx_hash", ethMsg.Hash)
m.logger.Debug("EVM transaction inserted successfully", "tx_hash", hash)
return nil
}

Expand Down Expand Up @@ -300,11 +302,12 @@
// We should not do this with EVM transactions because removing them causes the subsequent ones to
// be dequeued as temporarily invalid, only to be requeued a block later.
// The EVM mempool handles removal based on account nonce automatically.
hash := msg.Hash()
if m.shouldRemoveFromEVMPool(tx) {
m.logger.Debug("manually removing EVM transaction", "tx_hash", msg.Hash())
m.legacyTxPool.RemoveTx(msg.Hash(), false, true)
m.logger.Debug("manually removing EVM transaction", "tx_hash", hash)
m.legacyTxPool.RemoveTx(hash, false, true)
} else {
m.logger.Debug("skipping manual removal of EVM transaction, leaving to mempool to handle", "tx_hash", msg.Hash)
m.logger.Debug("skipping manual removal of EVM transaction, leaving to mempool to handle", "tx_hash", hash)
}
return nil
}
Expand Down Expand Up @@ -372,6 +375,31 @@
}
}

// SetEventBus sets CometBFT event bus to listen for new block header event.
func (m *ExperimentalEVMMempool) SetEventBus(eventBus *cmttypes.EventBus) {
if m.eventBus != nil {
m.eventBus.Unsubscribe(context.Background(), SubscriberName, stream.NewBlockHeaderEvents) //nolint: errcheck
}
m.eventBus = eventBus
sub, err := eventBus.Subscribe(context.Background(), SubscriberName, stream.NewBlockHeaderEvents)
if err != nil {
panic(err)
}
go func() {
for range sub.Out() {
m.GetBlockchain().NotifyNewBlock()
}
}()
}

// Close unsubscribes from the CometBFT event bus.
func (m *ExperimentalEVMMempool) Close() error {
if m.eventBus != nil {
return m.eventBus.Unsubscribe(context.Background(), SubscriberName, stream.NewBlockHeaderEvents)
}
return nil
}

// getEVMMessage validates that the transaction contains exactly one message and returns it if it's an EVM message.
// Returns an error if the transaction has no messages, multiple messages, or the single message is not an EVM transaction.
func (m *ExperimentalEVMMempool) getEVMMessage(tx sdk.Tx) (*evmtypes.MsgEthereumTx, error) {
Expand Down
3 changes: 2 additions & 1 deletion rpc/backend/sign_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"

"github.com/cosmos/evm/mempool"
evmtypes "github.com/cosmos/evm/x/vm/types"

errorsmod "cosmossdk.io/errors"
Expand Down Expand Up @@ -109,7 +110,7 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e
}
if err != nil {
// Check if this is a nonce gap error that was successfully queued
if strings.Contains(err.Error(), "tx nonce is higher than account nonce") {
if strings.Contains(err.Error(), mempool.ErrNonceGap.Error()) {
// Transaction was successfully queued due to nonce gap, return success to client
b.Logger.Debug("transaction queued due to nonce gap", "hash", txHash.Hex())
return txHash, nil
Expand Down
5 changes: 3 additions & 2 deletions rpc/stream/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ var (
cmttypes.EventTx,
sdk.EventTypeMessage,
sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
blockEvents = cmttypes.QueryForEvent(cmttypes.EventNewBlock).String()
evmTxHashKey = fmt.Sprintf("%s.%s", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash)
blockEvents = cmttypes.QueryForEvent(cmttypes.EventNewBlock).String()
evmTxHashKey = fmt.Sprintf("%s.%s", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash)
NewBlockHeaderEvents = cmtquery.MustCompile(fmt.Sprintf("%s='%s'", cmttypes.EventTypeKey, cmttypes.EventNewBlockHeader))
)

type RPCHeader struct {
Expand Down
7 changes: 6 additions & 1 deletion server/json_rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log/slog"
"net/http"
"time"

"github.com/ethereum/go-ethereum/common"
ethrpc "github.com/ethereum/go-ethereum/rpc"
Expand All @@ -24,6 +25,8 @@ import (
"github.com/cosmos/cosmos-sdk/server"
)

const shutdownTimeout = 5 * time.Second

type AppWithPendingTxStream interface {
RegisterPendingTxListener(listener func(common.Hash))
}
Expand Down Expand Up @@ -109,7 +112,9 @@ func StartJSONRPC(
// The calling process canceled or closed the provided context, so we must
// gracefully stop the JSON-RPC server.
logger.Info("stopping JSON-RPC server...", "address", config.JSONRPC.Address)
if err := httpSrv.Shutdown(context.Background()); err != nil {
ctxShutdown, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
defer cancel()
if err := httpSrv.Shutdown(ctxShutdown); err != nil {
logger.Error("failed to shutdown JSON-RPC server", "error", err.Error())
}
return nil
Expand Down
3 changes: 3 additions & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ func startInProcess(svrCtx *server.Context, clientCtx client.Context, opts Start
return err
}

if m, ok := evmApp.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
m.SetEventBus(tmNode.EventBus())
}
defer func() {
if tmNode.IsRunning() {
_ = tmNode.Stop()
Expand Down
4 changes: 1 addition & 3 deletions tests/jsonrpc/simulator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ go 1.23.8
require (
github.com/ethereum/go-ethereum v1.15.10
github.com/fatih/color v1.16.0
github.com/google/go-cmp v0.5.9
github.com/gorilla/websocket v1.4.2
github.com/status-im/keycard-go v0.2.0
github.com/xuri/excelize/v2 v2.8.1
gopkg.in/yaml.v2 v2.4.0
)

require (
Expand All @@ -24,7 +23,6 @@ require (
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/ethereum/go-verkle v0.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.3.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand Down
5 changes: 0 additions & 5 deletions tests/jsonrpc/simulator/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
Expand Down Expand Up @@ -197,9 +195,6 @@ golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
Loading