Documentation Index
Fetch the complete documentation index at: https://docs.cosmos.network/llms.txt
Use this file to discover all available pages before exploring further.
v0.7.0 ships two new headline features — the Krakatoa application-layer mempool and BlockSTM parallel execution (with virtual fee collection) — plus a large dependency-stack bump (cosmos-sdk v0.54, cometbft v0.39, ibc-go v11, geth 1.17, Go 1.25). The two features are independent of each other. BlockSTM is always opt-in; Krakatoa is required for forks that already wired the v0.6 ExperimentalEVMMempool (the type is gone in v0.7) and optional for forks on CometBFT’s stock mempool. The unconditional migration work is the dependency bump and the keeper-signature shuffle that comes with it.
Optional headline features (each enables a distinct path; see Step 5):
- Krakatoa mempool (#1030, #1053, #1112) — moves the EVM mempool out of CometBFT and into the application layer. The chain gets full control over
CheckTx / RecheckTx, sudo-rechecking on promote/demote, reapable-list filtering, and pool-level lifecycle hooks. Krakatoa is the only app-side EVM mempool in v0.7; the v0.6 ExperimentalEVMMempool type is gone. If your v0.6 fork wired ExperimentalEVMMempool, migrating to Krakatoa is required — see Step 5a. If your v0.6 fork ran on CometBFT’s stock mempool, Krakatoa is optional and you can stay on stock CometBFT.
- BlockSTM parallel execution (#589, #1082, #1132) — parallel state-transition function under a software-transactional-memory scheduler, with per-tx object stores for bloom / log indexing / gas accounting and an incarnation cache for signature/auth verification. Includes virtual fee collection (
EndBlock fee settlement via per-tx bank object store, 18-decimal chains only) — virtual fees are bundled with BlockSTM, not a standalone feature. Chains that don’t wire the STM runner keep sequential execution.
Other features in this release (unconditional):
- ICS-02 client-router precompile (#768) — Solidity contracts can now manage IBC light clients.
- Authority params (#1130) —
x/vm, x/erc20, x/feemarket MsgServer handlers consult AuthorityParams from the consensus module before falling back to per-module gov authority.
- OpenTelemetry tracing (#871, #863) — spans across
x/vm and the JSON-RPC layer.
- JSON-RPC filter lifecycle overhaul (#1008) — idle-timeout reclamation replacing the old global filter cap.
Secondary breaking changes — IBC callback hardening, EIP-712 verification, the x/precisebank deprecation, the abi.json strict-ABI requirement (#758) — round out the upgrade.
The reference evmd also enables optimistic execution (baseapp.SetOptimisticExecution()). That’s an SDK-level feature, not new in v0.7, but if your fork hasn’t turned it on yet the upgrade is a convenient time — see Step 5c for the wiring.
For deeper background on the new features, see:
If you’re skipping a release, read the v0.5.x → v0.6.0 guide first — its StateDB / callFromPrecompile API break still applies.
Table of contents
The migration steps split into three groups. Required steps apply to every fork; the Krakatoa and BlockSTM groups are independent and either may be skipped. (Step 5c covers optimistic execution — an SDK feature unrelated to v0.7 — for forks that haven’t wired it.)
Required (every fork)
Krakatoa application-layer mempool (required if you previously used ExperimentalEVMMempool; otherwise optional)
BlockSTM parallel execution (optional, bundled with virtual fee collection)
Optimistic execution (SDK feature, not v0.7-specific — opt-in)
Step 1: Bump dependencies
go get github.com/cosmos/evm@v0.7.0
go mod tidy
Audit every go.mod in your tree. Forks with nested submodules (e.g., a separate tests/systemtests/go.mod) need the dependency bumps applied independently and go mod tidy run separately in each — root-only bumps leave the nested modules with stale cosmossdk.io/log, cosmossdk.io/store resolutions that fail to compile.
v0.7.0 transitively bumps the entire stack. The minimum versions you need:
| Dependency | v0.6.x | v0.7.0 | Upstream guide |
|---|
| Go toolchain | 1.23 | 1.25 | — |
cosmos-sdk | v0.53.x | v0.54.2 | SDK |
cometbft | v0.38.x | v0.39.3 | release notes |
ibc-go | v10 | v11 | v10→v11 |
go-ethereum | v1.15 | v1.17 via Cosmos fork | release notes |
Read the SDK and ibc-go guides end-to-end before starting — many of the keeper-signature edits in Step 4 are direct consequences of those upstream changes, and the upstream docs cover edge cases (capability migration, store/v2 nuances, gov vote-tally extension points) that this guide only summarizes.
The geth bump uses a fork pin. Add (or update) the replace directive in your go.mod:
replace github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.17.2-cosmos-0
If you depend on geth APIs directly (custom tracers, custom precompiles importing core/vm), expect compile errors and follow the geth 1.16 → 1.17 changelog for shim updates.
Step 1.5: Migrate import paths
A large block of cosmossdk.io/... packages moved into github.com/cosmos/cosmos-sdk/... in SDK v0.54, and store is now store/v2. Run these find-and-replaces across your fork:
| Old import | New import |
|---|
cosmossdk.io/store | github.com/cosmos/cosmos-sdk/store/v2 |
cosmossdk.io/store/types | github.com/cosmos/cosmos-sdk/store/v2/types |
cosmossdk.io/store/snapshots/types | github.com/cosmos/cosmos-sdk/store/v2/snapshots/types |
cosmossdk.io/store/prefix | github.com/cosmos/cosmos-sdk/store/v2/prefix |
cosmossdk.io/log | cosmossdk.io/log/v2 |
cosmossdk.io/x/upgrade{,/keeper,/types} | github.com/cosmos/cosmos-sdk/x/upgrade/... |
cosmossdk.io/x/evidence{,/keeper,/types} | github.com/cosmos/cosmos-sdk/x/evidence/... |
cosmossdk.io/x/feegrant{,/keeper,/module} | github.com/cosmos/cosmos-sdk/x/feegrant/... |
cosmossdk.io/x/tx/signing | github.com/cosmos/cosmos-sdk/x/tx/signing |
cosmossdk.io/systemtests | github.com/cosmos/cosmos-sdk/tools/systemtests |
github.com/cosmos/ibc-go/v10/... | github.com/cosmos/ibc-go/v11/... |
If your fork has a separate tests/systemtests/go.mod (recommended pattern), apply the rename there too and run go mod tidy in that submodule independently of the main module.
Internal Cosmos EVM relocations — the top-level github.com/cosmos/evm/config package was deleted and its symbols redistributed:
| v0.6 symbol | v0.7 location |
|---|
config.MustGetDefaultNodeHome | evmd/config.MustGetDefaultNodeHome |
config.InitAppConfig | evmd/config.InitAppConfig |
config.BlockedAddresses | evmd/config.BlockedAddresses |
config.GetMaccPerms | evmd/config.GetMaccPerms |
config.SetBip44CoinType | evmd/config.SetBip44CoinType |
config.GetChainIDFromHome | utils.GetChainIDFromHome |
config.EVMChainID (constant) | removed — use evmtypes.DefaultEVMChainID |
config.GetCosmosPoolMaxTx | server.GetCosmosPoolMaxTx |
config.GetLegacyPoolConfig etc. | folded into server.ResolveMempoolConfig (see Step 5a) |
The example-app helpers (MustGetDefaultNodeHome, InitAppConfig, BlockedAddresses, GetMaccPerms) moved from github.com/cosmos/evm/config to github.com/cosmos/evm/evmd/config. Mempool / chain-id helpers moved to github.com/cosmos/evm/server and github.com/cosmos/evm/utils. Update every import in evmd/cmd/evmd/cmd/root.go, evmd/cmd/evmd/main.go, and evmd/cmd/evmd/cmd/testnet.go accordingly.
Step 2: Update store keys in app.go
The transient stores evmtypes.TransientKey and feemarkettypes.TransientKey were both removed. The EVM keeper still needs a per-tx scratch store for tx bloom and gas accounting, so it now consumes evmtypes.ObjectKey (an object store) instead. This change is unconditional — it applies even on the sequential path.
Replace the transient-store declaration, mount the new object store, and build a nonTransientKeys slice for the EVM keeper:
keys := storetypes.NewKVStoreKeys(/* ... */)
- tkeys := storetypes.NewTransientStoreKeys(evmtypes.TransientKey, feemarkettypes.TransientKey)
+ oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey)
+
+ var nonTransientKeys []storetypes.StoreKey
+ for _, k := range keys {
+ nonTransientKeys = append(nonTransientKeys, k)
+ }
+ for _, k := range oKeys {
+ nonTransientKeys = append(nonTransientKeys, k)
+ }
- app.MountTransientStores(tkeys)
+ app.MountObjectStores(oKeys)
nonTransientKeys is the 4th argument to evmkeeper.NewKeeper (Step 4) and the EVM keeper uses it for cross-module store access. Step 5 reuses the same slice if you wire the BlockSTM runner.
Step 5 (parallel path) extends oKeys with banktypes.ObjectStoreKey and wires it into the bank keeper. Skip if you’re not adopting virtual fee collection.
(The matching EVMD struct field rename is in Step 3.)
Step 3: Update EVMD struct fields
type EVMD struct {
// ...
- keys map[string]*storetypes.KVStoreKey
- tkeys map[string]*storetypes.TransientStoreKey
- memKeys map[string]*storetypes.MemoryStoreKey
+ keys map[string]*storetypes.KVStoreKey
+ oKeys map[string]*storetypes.ObjectStoreKey
- clientCtx client.Context
-
// ...
- TransferKeeper transferkeeper.Keeper
+ TransferKeeper *transferkeeper.Keeper
- EVMMempool *evmmempool.ExperimentalEVMMempool
+ EVMMempool sdkmempool.ExtMempool
-
- PreciseBankKeeper precisebankkeeper.Keeper
}
TransferKeeper is now stored as a pointer (ibc-go v11). EVMMempool’s field type was widened to the ExtMempool interface to permit Krakatoa or any future custom subpool. If you have getters/setters keyed on these field types (e.g. GetTransferKeeper, SetTransferKeeper), update their signatures too.
The Close() method on EVMD does a type assertion against the old mempool type — update it to the new one (or remove if you’re not wiring an EVM mempool):
func (app *EVMD) Close() error {
var err error
- if m, ok := app.GetMempool().(*evmmempool.ExperimentalEVMMempool); ok {
+ if m, ok := app.EVMMempool.(*evmmempool.Mempool); ok && m != nil {
app.Logger().Info("Shutting down mempool")
err = m.Close()
}
// ...
}
Step 4: Update NewExampleApp and keeper constructors in app.go
NewExampleApp signature — drop traceStore
The traceStore io.Writer parameter is gone. Update the function signature and every call site of your app constructor — CLI commands (newApp, appExport, the appCreator callback in your root cmd), test-network fixtures (e.g., NewTestNetworkFixture), and any custom integration-test bootstrapping. nil-passing call sites need to drop the nil argument too.
func NewExampleApp(
logger log.Logger,
db dbm.DB,
- traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *EVMD {
Inside, drop the corresponding tracer wiring:
- bApp.SetCommitMultiStoreTracer(traceStore)
feemarketkeeper.NewKeeper — drop transient key
app.FeeMarketKeeper = feemarketkeeper.NewKeeper(
appCodec, authtypes.NewModuleAddress(govtypes.ModuleName),
keys[feemarkettypes.StoreKey],
- tkeys[feemarkettypes.TransientKey],
)
ibckeeper.NewKeeper — drop capability keeper
ibc-go v11 removed the capability-keeper argument. Existing chains have nothing to migrate — the parameter was already nil — but the call must lose it:
app.IBCKeeper = ibckeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[ibcexported.StoreKey]),
- nil,
app.UpgradeKeeper,
authAddr,
)
govkeeper.NewKeeper — reordered args, new vote-results function
Cosmos SDK v0.54 reordered the constructor and added a final pluggable vote-tally function:
govKeeper := govkeeper.NewKeeper(
appCodec,
runtime.NewKVStoreService(keys[govtypes.StoreKey]),
app.AccountKeeper,
app.BankKeeper,
- app.StakingKeeper,
app.DistrKeeper,
app.MsgServiceRouter(),
govConfig,
authAddr,
+ govkeeper.NewDefaultCalculateVoteResultsAndVotingPower(app.StakingKeeper),
)
transferkeeper.NewKeeper — pointer return, inline address codec, drop ICS4Wrapper / duplicate ChannelKeeper
ibc-go v11 returns *transferkeeper.Keeper. The constructor also takes the EVM address codec inline (no more SetAddressCodec) and drops the duplicate ICS4Wrapper / ChannelKeeper params:
app.TransferKeeper = transferkeeper.NewKeeper(
appCodec,
+ evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
runtime.NewKVStoreService(keys[ibctransfertypes.StoreKey]),
- nil, // ICS4Wrapper param
- app.IBCKeeper.ChannelKeeper,
app.IBCKeeper.ChannelKeeper,
app.MsgServiceRouter(),
app.AccountKeeper,
app.BankKeeper,
authAddr,
)
- app.TransferKeeper.SetAddressCodec(evmaddress.NewEvmCodec(sdk.GetConfig().GetBech32AccountAddrPrefix()))
⚠️ The reference evmd instantiates TransferKeeper before EVMKeeper so static precompiles receive a non-nil reference. v0.6 constructs them in the opposite order (EVMKeeper, then Erc20Keeper consuming &app.TransferKeeper, then TransferKeeper). To swap, you’ll also need to move Erc20Keeper so it’s constructed after TransferKeeper — and Erc20Keeper itself now takes the pointer-typed app.TransferKeeper directly, no &. Walk the three keeper constructions as a unit, not individually.
IBC callbacks middleware — wrap with setter calls
ibc-go v11 split the constructor and the wrapping. Replace the single-line NewIBCMiddleware with the three-step setter form:
- transferStack = ibccallbacks.NewIBCMiddleware(transferStack, app.IBCKeeper.ChannelKeeper, app.CallbackKeeper, maxCallbackGas)
+ callbacksMiddleware := ibccallbacks.NewIBCMiddleware(app.CallbackKeeper, maxCallbackGas)
+ callbacksMiddleware.SetICS4Wrapper(app.IBCKeeper.ChannelKeeper)
+ callbacksMiddleware.SetUnderlyingApplication(transferStack)
+ transferStack = callbacksMiddleware
Both setters are required — NewIBCMiddleware itself doesn’t accept either argument anymore. A middleware constructed without SetICS4Wrapper or SetUnderlyingApplication compiles but panics with a nil-pointer dereference on the first packet send / receive (the constructor does panic on a nil contract keeper or zero gas, but the wrapper / underlying-app fields are checked at use, not construction).
evmkeeper.NewKeeper — object store key, store-key slice, BankKeeper replaces PreciseBankKeeper
The third arg is the new EVM object-store key (Step 2). The fourth changed from map[string]*storetypes.KVStoreKey to []storetypes.StoreKey — the keeper uses it for cross-module store access. Pass the nonTransientKeys slice from Step 2; the same slice is also what the STM runner tracks if you opt into Step 5.
app.EVMKeeper = evmkeeper.NewKeeper(
- appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], keys,
+ appCodec, keys[evmtypes.StoreKey], oKeys[evmtypes.ObjectKey], nonTransientKeys,
authtypes.NewModuleAddress(govtypes.ModuleName),
app.AccountKeeper,
- app.PreciseBankKeeper,
+ app.BankKeeper,
app.StakingKeeper,
app.FeeMarketKeeper,
&app.ConsensusParamsKeeper,
&app.Erc20Keeper,
evmChainID,
tracer,
)
DefaultStaticPrecompiles — Bank, dereferenced TransferKeeper, ClientKeeper
TransferKeeper is already a pointer (Step 3); pass it directly. IBCKeeper.ClientKeeper is the new dependency — it backs the ICS-02 client-router precompile (#768):
precompiletypes.DefaultStaticPrecompiles(
*app.StakingKeeper,
app.DistrKeeper,
- app.PreciseBankKeeper,
+ app.BankKeeper,
&app.Erc20Keeper,
- &app.TransferKeeper,
+ app.TransferKeeper,
app.IBCKeeper.ChannelKeeper,
+ app.IBCKeeper.ClientKeeper,
app.GovKeeper,
app.SlashingKeeper,
appCodec,
)
erc20keeper.NewKeeper — Bank, dereferenced TransferKeeper
app.Erc20Keeper = erc20keeper.NewKeeper(
keys[erc20types.StoreKey],
appCodec,
authtypes.NewModuleAddress(govtypes.ModuleName),
app.AccountKeeper,
- app.PreciseBankKeeper,
+ app.BankKeeper,
app.EVMKeeper,
app.StakingKeeper,
- &app.TransferKeeper,
+ app.TransferKeeper,
)
node.RegisterNodeService — earliest-version callback
func (app *EVMD) RegisterNodeService(clientCtx client.Context, cfg config.Config) {
- node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg)
+ node.RegisterNodeService(clientCtx, app.GRPCQueryRouter(), cfg, func() int64 {
+ return app.CommitMultiStore().EarliestVersion()
+ })
}
Hydrate EVM globals on restart (#1126)
Required regardless of which path you choose. After LoadLatestVersion (inside if loadLatest), hydrate the EVM globals from KV so evmCoinInfo is populated before any RPC handler runs:
+ ctx := app.NewContextLegacy(true, cmtproto.Header{
+ Height: app.LastBlockHeight(),
+ ChainID: app.ChainID(),
+ })
+ vmModule.HydrateGlobals(ctx)
Without this, RPC calls that arrive before PreBlock panic on a nil evmCoinInfo.
vmModule here is the value returned by vm.NewAppModule(...). v0.6 calls vm.NewAppModule(...) inline inside app.ModuleManager = module.NewManager(...), so before the HydrateGlobals call you’ll need to refactor: bind the result to a local first, then pass that local into module.NewManager:
- app.ModuleManager = module.NewManager(
- // ... other modules
- vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec()),
- // ...
- )
+ vmModule := vm.NewAppModule(app.EVMKeeper, app.AccountKeeper, app.BankKeeper, app.AccountKeeper.AddressCodec())
+
+ app.ModuleManager = module.NewManager(
+ // ... other modules
+ vmModule,
+ // ...
+ )
Imports to add: cmtproto "github.com/cometbft/cometbft/proto/tendermint/types".
Wire the EVM tx runner (#1132)
Required regardless of path. vmrunner.SetRunner installs the baseapp tx runner wrapped with the EVM module’s PatchTxResponses post-execution log/tx index fix-up. Without it, log.Index and transactionIndex on receipts are wrong (the bug #1132 corrected). The wrapper works for both sequential and parallel inner runners — only the inner runner choice differs by path:
- Sequential (default) — pass
txnrunner.NewDefaultRunner(txDecoder).
- Parallel — pass
txnrunner.NewSTMRunner(...) (see Step 5b).
Bind the tx decoder to a local at the top of NewExampleApp so it can be reused by the runner (and by bApp := baseapp.NewBaseApp(...) if you want to share it):
txConfig := encodingConfig.TxConfig
+ txDecoder := encodingConfig.TxConfig.TxDecoder()
Then add the runner call just before return app. The sequential form:
+ vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder))
Imports to add: "github.com/cosmos/cosmos-sdk/baseapp/txnrunner", vmrunner "github.com/cosmos/evm/x/vm/runner".
Removed EVMD methods
These were deleted; remove any callers:
GetTKey, GetMemKey — transient/mem stores no longer exist as separate maps.
GetAuthzKeeper — the helper was redundant; expose app.AuthzKeeper directly if you need it.
GetPreciseBankKeeper — module is removed (Step 6).
SetClientCtx and the clientCtx field — unused.
Step 5: Enable Krakatoa and/or BlockSTM
Two independent v0.7 opt-ins:
- Krakatoa (5a) is required for forks that already used the v0.6
ExperimentalEVMMempool (the type was removed); optional for forks on CometBFT’s stock mempool.
- BlockSTM with virtual fee collection (5b) is always opt-in. Skip 5b to keep sequential execution.
Step 5c documents baseapp.SetOptimisticExecution() separately — it’s an SDK-level feature, not new in v0.7, but the reference evmd/app.go enables it and forks that haven’t yet adopted it can use this upgrade as the moment to do so.
State-breaking features. Each ships as part of the binary; adopting or dropping any of them requires a coordinated MsgSoftwareUpgrade and a binary swap, the same as any other consensus change.
5a. Krakatoa application-layer mempool
Krakatoa is the only app-side EVM mempool in v0.7. The ExperimentalEVMMempool type from v0.6 is gone, so optionality depends on what your v0.6 fork already used:
- If you wired
ExperimentalEVMMempool in v0.6: this step is required. The construction signature changed and the handler set was redesigned; you must migrate or your fork won’t compile.
- If you ran on CometBFT’s stock mempool in v0.6 (no app-side EVM mempool): this step is optional. Adopt Krakatoa to gain app-level
CheckTx / RecheckTx control, or skip it and stay on stock CometBFT.
Independent of BlockSTM either way.
To migrate from ExperimentalEVMMempool, replace the construction with evmmempool.NewMempool and the new handler set. The full reference is evmd/mempool.go’s configureEVMMempool — read it before applying the diff below; the local variables it constructs (mpConfig, txEncoder, evmRechecker, cosmosRechecker, cosmosPoolMaxTx, checkTxTimeout) are what the new NewMempool signature consumes:
// Setup block (in evmd/mempool.go's configureEVMMempool, all required):
mpConfig := server.ResolveMempoolConfig(app.GetAnteHandler(), appOpts, logger)
txEncoder := evmmempool.NewTxEncoder(app.txConfig)
evmRechecker := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder)
cosmosRechecker := evmmempool.NewTxRechecker(mpConfig.AnteHandler, txEncoder)
cosmosPoolMaxTx := server.GetCosmosPoolMaxTx(appOpts, logger)
checkTxTimeout := server.GetMempoolCheckTxTimeout(appOpts, logger)
server.ResolveMempoolConfig, server.GetCosmosPoolMaxTx, and server.GetMempoolCheckTxTimeout are all in github.com/cosmos/evm/server; the evmconfig.GetLegacyPoolConfig / GetBlockGasLimit / GetMinTip helpers from v0.6 collapsed into ResolveMempoolConfig. app.TxDecode and the SetInsertTxHandler / SetReapTxsHandler setters are new in cosmos-sdk v0.54 baseapp.
Ordering: app.SetAnteHandler(...) must run before configureEVMMempool. ResolveMempoolConfig calls app.GetAnteHandler() and stashes the result on mpConfig.AnteHandler, which the recheckers then close over. If the ante handler hasn’t been set yet, GetAnteHandler returns nil and the chain panics on first RecheckTx. There’s no compile-time signal — get the ordering right.
evmRechecker and cosmosRechecker must be distinct instances, even though they wrap identical config. They feed different subpools (the EVM eth-tx pool vs. the Cosmos pool) and need independent state for promotion/demotion bookkeeping. Sharing one instance compiles fine but produces silent cross-pool state interference at promote/demote time.
Then replace the construction and handler wiring:
- evmMempool := evmmempool.NewExperimentalEVMMempool(
- app.CreateQueryContext, logger,
- app.EVMKeeper, app.FeeMarketKeeper, app.txConfig, app.clientCtx,
- mempoolConfig, cosmosPoolMaxTx,
- )
- app.EVMMempool = evmMempool
- app.SetMempool(evmMempool)
- checkTxHandler := evmmempool.NewCheckTxHandler(evmMempool)
- app.SetCheckTxHandler(checkTxHandler)
- abciProposalHandler := baseapp.NewDefaultProposalHandler(evmMempool, app)
- abciProposalHandler.SetSignerExtractionAdapter(/* ... */)
- app.SetPrepareProposal(abciProposalHandler.PrepareProposalHandler())
+ mempool := evmmempool.NewMempool(
+ app.CreateQueryContext, logger,
+ app.EVMKeeper, app.FeeMarketKeeper, app.txConfig,
+ evmRechecker, cosmosRechecker, mpConfig, cosmosPoolMaxTx,
+ )
+ app.EVMMempool = mempool
+ app.SetPrepareProposal(baseapp.NewDefaultProposalHandler(mempool, NewNoCheckProposalTxVerifier(app.BaseApp)).PrepareProposalHandler())
+ app.SetInsertTxHandler(mempool.NewInsertTxHandler(app.TxDecode))
+ app.SetReapTxsHandler(mempool.NewReapTxsHandler())
+ app.SetCheckTxHandler(mempool.NewCheckTxHandler(app.TxDecode, checkTxTimeout))
+ app.SetMempool(mempool)
The reference application uses a NewNoCheckProposalTxVerifier at proposal time when verifying the txs in a given proposal. This is a performance optimization since with the Krakatoa mempool, it is a requirement that every tx within the proposal has already been validated, thus this check is redundant. If you choose to adopt this pattern as well copy the reference NewNoCheckProposalTxVerifier and pass this to your PrepareProposalHandler.
To opt out entirely, drop the configureEVMMempool(...) call from NewExampleApp, or set mempool.max-txs=-1 in app.toml (the SDK FlagMempoolMaxTxs — note this is the top-level [mempool] key, not evm.mempool.max-txs). configureEVMMempool reads it via server.GetCosmosPoolMaxTx, bails out on a negative value, and falls back to CometBFT’s stock mempool.
⚠️ Set mempool.type = "app" in config.toml. CometBFT v0.39 requires the app-side mempool type whenever the application supplies an EVM mempool. The default is "flood"; Krakatoa errors out at startup if it’s left as the default. This is a one-line patch to each validator’s config.toml (or your fork’s equivalent), applied at the same time as the binary swap. Forks that opt out of Krakatoa (the previous paragraph) don’t need this.
Note that by setting mempool.type = "app" in config.toml, there are additional configuration parameters you may want to configure that will affect Krakatoa mempool operations. Please see the CometBFT application mempool documentation for more info.
5b. (Optional) BlockSTM parallel execution and virtual fee collection
BlockSTM enables parallel execution of the state-transition function and bundles virtual fee collection (per-tx bank-balance accounting reduced at EndBlock). Not required — chains that skip this section keep sequential execution. Independent of Krakatoa, and independent of optimistic execution (5c).
The two pieces below are wired together; you opt into the bundle, not into individual lines.
Swap the inner tx runner to BlockSTM
Step 4 already wired vmrunner.SetRunner(bApp, ...) with the sequential txnrunner.NewDefaultRunner(txDecoder) as the inner runner. To opt into parallel execution, swap the inner runner for txnrunner.NewSTMRunner(...):
- vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder))
+ vmrunner.SetRunner(bApp, txnrunner.NewSTMRunner(
+ txDecoder,
+ nonTransientKeys,
+ min(goruntime.GOMAXPROCS(0), goruntime.NumCPU()),
+ true,
+ func(ms storetypes.MultiStore) string {
+ return app.EVMKeeper.GetParams(sdk.NewContext(ms, cmtproto.Header{}, false, log.NewNopLogger())).EvmDenom
+ },
+ ))
The EvmDenom callback reads from EVM params on the live multi-store; do not hard-code sdk.DefaultBondDenom — chains that customized their EVM denom will diverge.
Additional import: goruntime "runtime".
Virtual fee collection (18-decimal chains only)
Virtual fee collection is part of the BlockSTM bundle — it’s what lets fee settlement happen in parallel without contending the fee collector account on every tx. Two pieces are required.
First, extend the oKeys declaration from Step 2 with banktypes.ObjectStoreKey and wire it into the bank keeper. Order matters: the oKeys declaration must include banktypes.ObjectStoreKey before the for _, k := range oKeys loop that builds nonTransientKeys, otherwise the bank object store won’t be in the slice the EVM keeper sees. Apply the change at the Step 2 declaration site, not later:
- oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey)
+ oKeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey, evmtypes.ObjectKey)
Then, after BankKeeper is constructed, wire the object-store key into it:
+ app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey])
WithObjStoreKey(storetypes.StoreKey) BaseKeeper is part of the bankkeeper.Keeper interface in cosmos-sdk v0.54, so the call works whether your EVMD.BankKeeper field is the interface (bankkeeper.Keeper, the reference default) or the concrete bankkeeper.BaseKeeper. The method returns BaseKeeper, but BaseKeeper satisfies Keeper, so assigning the result back to an interface-typed field type-checks. No field-widening required.
Then, after WithStaticPrecompiles, enable virtual fees on the EVM keeper:
app.EVMKeeper = evmkeeper.NewKeeper(/* ... */).WithStaticPrecompiles(/* ... */)
+ app.EVMKeeper.EnableVirtualFeeCollection()
⚠️ Do not call EnableVirtualFeeCollection() if your gas token is not 18-decimal. x/vm/keeper.DeductFees reads the EVM denom’s bank metadata and panics if the display-denom unit’s exponent is not 18 (x/vm/keeper/fees.go:156). See Step 6. A non-18-decimal chain that still wants BlockSTM has to skip this final piece — but the parallel path’s main throughput win comes from virtual fees, so the practical recommendation is to migrate to 18 decimals first.
5c. (Optional) Optimistic execution
Not new in v0.7 — baseapp.SetOptimisticExecution() has been an SDK feature since v0.50. Documented here because the reference evmd/app.go ships with it enabled, and forks that haven’t yet adopted it can fold the wiring into this upgrade. Independent of BlockSTM and Krakatoa.
The feature overlaps FinalizeBlock for height H with ProcessProposal for height H+1: while CometBFT is still finalizing the current block, the app speculatively executes the next proposed block. If the speculative result matches what FinalizeBlock is later asked to commit, the result is reused; if it diverges, the speculative state is discarded and execution falls back to the standard path.
The reference evmd/app.go enables it. Add the option to baseAppOptions before baseapp.NewBaseApp(...) is called — append after the baseAppOptions ...func(*baseapp.BaseApp) parameter is in scope but before the constructor consumes it:
func NewExampleApp(
logger log.Logger,
db dbm.DB,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) *EVMD {
// ... encoding/txDecoder setup ...
+ // enable optimistic execution
+ baseAppOptions = append(
+ baseAppOptions,
+ baseapp.SetOptimisticExecution(),
+ )
bApp := baseapp.NewBaseApp(
appName, logger, db, txDecoder,
baseAppOptions...,
)
Operational notes:
- Determinism is preserved. Speculative execution uses a sandboxed cache; on a mismatch the state is dropped, not committed. The user-visible behavior is identical to non-optimistic execution — only the latency of
FinalizeBlock shifts.
- Composes with BlockSTM (5b) and Krakatoa (5a). Optimistic execution runs the speculative block through whichever runner is wired (
vmrunner.SetRunner), so you get parallel-on-speculative + parallel-on-final when 5b is also enabled.
- State-breaking. Like 5a and 5b, this is baked into the binary. Adopting or dropping it requires a coordinated
MsgSoftwareUpgrade, not a runtime toggle. See Step 8.
- Resource cost. The speculative path uses an extra goroutine and an extra working state cache per height. On memory-tight nodes you may prefer to disable it; the SDK doc linked at the top of this guide covers the trade-offs.
Step 6: Drop x/precisebank
x/precisebank is deprecated (#1019). It only ever made sense for non-18-decimal gas tokens — it bridged them into the EVM’s 18-decimal world. The reference v0.6 evmd/app.go wired it unconditionally despite the inline comment “PreciseBank is not needed if SDK use 18 decimals for gas coin”, so most v0.6 forks have it threaded through evmkeeper, erc20keeper, and the precompiles regardless of their gas token’s decimals.
In v0.7 the module is gone from the main tree. EvmCoinInfo.Decimals is also marked deprecated (#1029).
Non-18-decimal chains are not fully supported going forward. v0.7.0 still ships precisebank under contrib/x/precisebank, but future EVM releases will not test or maintain it. Either stay on v0.6.x until you can migrate to 18 decimals, or pin cosmos/evm/contrib/x/precisebank and do not enable virtual fee collection (5b).
If your fork wired PreciseBankKeeper (whether or not your gas token is 18-decimal), unwire it:
type EVMD struct {
// ... other keepers
- PreciseBankKeeper precisebankkeeper.Keeper
}
- // Set up PreciseBank keeper
- app.PreciseBankKeeper = precisebankkeeper.NewKeeper(/* ... */)
app.ModuleManager = module.NewManager(
// ... other modules
- precisebank.NewAppModule(app.PreciseBankKeeper, app.BankKeeper, app.AccountKeeper),
)
- func (app *EVMD) GetPreciseBankKeeper() *precisebankkeeper.Keeper { /* ... */ }
Also drop precisebanktypes.StoreKey from the keys declaration and the module name from your SetOrderBeginBlockers / SetOrderEndBlockers / SetOrderInitGenesis lists.
Replace every app.PreciseBankKeeper reference (in evmkeeper.NewKeeper, erc20keeper.NewKeeper, DefaultStaticPrecompiles, and any custom modules) with app.BankKeeper. Verify decimal expectations at each call site — anywhere your code assumed the precisebank denom translation was happening, you now need to either operate on 18-decimal values directly or do the conversion yourself.
Tests that referenced precisebank also need cleanup. In the reference repo this meant deleting evmd/tests/integration/x_precisebank_test.go and removing PreciseBankMintEventCount / PreciseBankBurnEventCount and their consumers from evmd/tests/ibc/helper.go. Audit your fork’s test tree for any precisebank symbols and drop them — the code won’t compile against v0.7 with them present.
Step 7: Update custom code
Custom ante handlers / mempool plugins
The transient stores backing gas accounting and the feemarket are gone. Read gas-wanted from the SDK context instead — ctx.GasMeter().GasConsumed(). If your decorator wrote to the feemarket transient store, remove that logic — feemarkettypes.TransientKey and the keeper methods GetTransientGasWanted, SetTransientBlockGasWanted, AddTransientGasWanted are all gone. The same applies to the EVM keeper: GetBlockBloomTransient, SetBlockBloomTransient, GetTxIndexTransient, SetTxIndexTransient, and WithDefaultEvmCoinInfo were removed.
Custom BankKeeper / BankWrapper implementations
The interfaces in x/vm/types/interfaces.go changed. Any fork that supplies a non-default bank wrapper must update.
BankKeeper gained five methods to support virtual fee collection and parallel-safe balance accounting:
SendCoinsFromModuleToAccountVirtual(
ctx context.Context, senderModule string,
recipientAddr sdk.AccAddress, amt sdk.Coins,
) error
SendCoinsFromAccountToModuleVirtual(
ctx context.Context, senderAddr sdk.AccAddress,
recipientModule string, amt sdk.Coins,
) error
UncheckedSetBalance(ctx context.Context, addr sdk.AccAddress, amt sdk.Coin) error
LockedCoins(ctx context.Context, addr sdk.AccAddress) sdk.Coins
BlockedAddr(addr sdk.AccAddress) bool
BankWrapper is a hard API break: MintAmountToAccount and BurnAmountFromAccount were removed and replaced by a single SetBalance(ctx, account, amt *big.Int) error. Callers and implementations must both change.
EmitBlockBloomEvent’s argument type changed from ethtypes.Bloom to []byte.
A new VMKeeper interface (GetEvmCoinInfo) was added; some helpers now take it instead of a concrete keeper.
Custom indexers / receipt consumers
- Read
log.Index and log.TxIndex only after post-execution patching (#1132). Recomputing from raw event order will diverge from canonical receipts.
- Drop any workaround that special-cased the
MaxUint64 overflow on transactionIndex — fixed in #1047.
- Don’t assume tx counts in receipt arrays match raw block tx counts. StateDB-error txs are now skipped during receipt conversion (#1107).
Forks of precompile abi.json
PR #758 made the precompile ABI files strict — only valid Ethereum ABI fields are accepted. Forks that vendor or override abi.json for any precompile must remove non-ABI extension fields, or they will fail to parse at startup.
Removed helpers
Params.GetActiveStaticPrecompilesAddrs() is gone — derive []common.Address yourself if you used it.
Mocks of EVMKeeper
Regenerate against v0.7.0 — the constructor signature (Step 4) and static-precompile dependencies (Step 4) shifted.
Step 8: Coordinate the upgrade
This is a normal Cosmos consensus-breaking upgrade.
- All validators must run the same v0.7 binary post-upgrade. Mixing v0.6 and v0.7 binaries will fork.
- Step 5 (Krakatoa, BlockSTM + virtual fees, optimistic execution) is state-breaking. Each is baked into the binary — adopting or dropping any of them later is another coordinated
MsgSoftwareUpgrade, not a runtime toggle.
- There is no on-chain governance flag for these toggles. Switching paths later requires a new binary release and another
MsgSoftwareUpgrade.
Update the upgrade handler’s UpgradeName constant and its doc comment to match the new release. The doc-comment drift between releases is a recurring foot-gun — the system test reads the constant verbatim, but readers and downstream handlers grep the comment for context:
// evmd/upgrades.go (or wherever your fork keeps its upgrade name)
- // NOTE: This upgrade defines a reference implementation of what an upgrade
- // could look like when an application is migrating from EVMD version
- // v0.4.0 to v0.5.x
- const UpgradeName = "v0.5.0-to-v0.6.0"
+ // NOTE: This upgrade defines a reference implementation of what an upgrade
+ // could look like when an application is migrating from EVMD version
+ // v0.6.x to v0.7.0
+ const UpgradeName = "v0.6.0-to-v0.7.0"
The reference upgrade handler runs RunMigrations with empty StoreUpgrades — no module schema changes. Submit a MsgSoftwareUpgrade with name: "v0.6.0-to-v0.7.0" at your target height and swap the binary at the halt height.
Step 9: Verify
go build ./...
go test ./...
Sanity check the upgrade path
Drives a MsgSoftwareUpgrade for v0.6.0-to-v0.7.0, swaps to the v0.7 binary at the halt height, and runs a contended-account workload (many txs hitting the same hot contract per block). On the parallel path this exercises the BlockSTM scheduler’s conflict detection across the upgrade boundary; on the sequential path it’s still a useful end-to-end smoke test.
The test reads a v0.6-era binary from tests/systemtests/binaries/v0.6/evmd. For your fork, that binary is your own chain build pinned to the v0.6 cosmos-evm dependency — not a generic cosmos/evm@v0.6.0 binary. The legacy build’s keepers, ante decorators, and module set must match what your validators are actually running pre-upgrade, otherwise the test exercises the wrong starting state.
Wire whatever make (or shell) target you use for this — the key constraints are:
- Output goes to
tests/systemtests/binaries/v0.6/evmd (or wherever your chainupgrade/v6_v7.go reads from).
- Your
test-system Make target (or equivalent) depends on it and on the current binary, in that order.
- If your previous release had a
build-v05 target chained from test-system, retarget that dependency to the new build-v06 (or whatever you name it).