v0.7.0 带来了两个新的重磅特性——Krakatoa 应用层 mempool 和 BlockSTM 并行执行(含虚拟手续费收取)——以及一次大规模依赖栈升级(cosmos-sdk v0.54、cometbft v0.39、ibc-go v11、geth 1.17、Go 1.25)。这两个特性彼此独立。BlockSTM 始终为选择性启用;对于已经接入 v0.6 ExperimentalEVMMempool 的分叉链,Krakatoa 是必需的(该类型在 v0.7 中已移除);对于使用 CometBFT 默认 mempool 的分叉链,Krakatoa 则是可选的。无条件的迁移工作包括依赖升级,以及随之而来的 keeper 签名调整。 可选的重磅特性(每个都会开启一条独立路径;见步骤 5):
  • Krakatoa mempool(#1030、#1053、#1112)——将 EVM mempool 从 CometBFT 移到应用层。链将完全控制 CheckTx / RecheckTx、在 promote/demote 时进行 sudo recheck、可打包列表过滤,以及池级生命周期钩子。Krakatoa 是 v0.7 中唯一的应用侧 EVM mempool;v0.6 的 ExperimentalEVMMempool 类型已移除。如果你的 v0.6 分叉链接入了 ExperimentalEVMMempool,则必须迁移到 Krakatoa——见步骤 5a。如果你的 v0.6 分叉链运行在 CometBFT 默认 mempool 上,Krakatoa 则是可选的,你可以继续使用默认 CometBFT。
  • BlockSTM 并行执行(#589、#1082、#1132)——在软件事务内存调度器下执行并行状态转换函数,并为 bloom / 日志索引 / gas 记账提供按交易划分的对象存储,以及用于签名/鉴权验证的 incarnation cache。包含虚拟手续费收取(通过按交易划分的 bank 对象存储在 EndBlock 结算手续费,仅适用于 18 位小数链)——虚拟手续费与 BlockSTM 绑定提供,不是独立特性。未接入 STM runner 的链将继续使用串行执行。
本次发布中的其他特性(无条件):
  • ICS-02 client-router 预编译(#768)——Solidity 合约现在可以管理 IBC 轻客户端。
  • Authority 参数(#1130)——x/vm、x/erc20、x/feemarket 的 MsgServer 处理器会先查询共识模块中的 AuthorityParams,再回退到各模块自己的治理 authority。
  • OpenTelemetry tracing(#871、#863)——覆盖 x/vm 与 JSON-RPC 层的 span。
  • JSON-RPC filter 生命周期重构(#1008)——用空闲超时回收替代旧的全局 filter 数量上限。
其他次级破坏性变更——IBC callback 加固、EIP-712 校验、x/precisebank 废弃、abi.json 的严格 ABI 要求(#758)——共同构成了这次升级的其余部分。 参考实现 evmd 还启用了 optimistic execution(baseapp.SetOptimisticExecution())。这是一个 SDK 层特性,并不是 v0.7 新增的,但如果你的分叉链还未开启它,这次升级正是一个合适的时机——接线方式见步骤 5c。 如需进一步了解这些新特性的背景,请参阅: 如果你是跨版本升级,请先阅读 v0.5.x → v0.6.0 指南——其中关于 StateDB / callFromPrecompile 的 API 破坏性变更仍然适用。

目录

迁移步骤分为三组。必需步骤适用于所有分叉链;Krakatoa 和 BlockSTM 两组彼此独立,任意一组都可以跳过。(步骤 5c 介绍 optimistic execution——一个与 v0.7 无关的 SDK 特性——适用于尚未接入它的分叉链。) 必需(所有分叉链) Krakatoa 应用层 mempool(如果你之前使用了 ExperimentalEVMMempool 则必需,否则可选) BlockSTM 并行执行(可选,包含虚拟手续费收取) Optimistic execution(SDK 特性,不特定于 v0.7——选择性启用)

步骤 1:升级依赖

go get github.com/cosmos/[email protected]
go mod tidy
检查你的代码树中每一个 go.mod。 如果分叉链包含嵌套子模块(例如单独的 tests/systemtests/go.mod),需要分别应用依赖升级,并在每个模块中单独运行 go mod tidy——只升级根模块会让嵌套模块继续保留过时的 cosmossdk.io/log、cosmossdk.io/store 解析结果,从而导致编译失败。
v0.7.0 会通过传递依赖升级整个栈。你至少需要以下版本:
依赖项v0.6.xv0.7.0上游指南
Go toolchain1.231.25—
cosmos-sdkv0.53.xv0.54.2SDK
cometbftv0.38.xv0.39.3release notes
ibc-gov10v11v10→v11
go-ethereumv1.15v1.17 via Cosmos forkrelease notes
开始之前请完整阅读 SDK 和 ibc-go 的升级指南——步骤 4 中许多 keeper 签名修改都直接源于这些上游变更,而上游文档还覆盖了本指南只做摘要处理的边界情况(capability 迁移、store/v2 细节、gov vote tally 扩展点等)。 geth 的升级使用了 fork pin。请在你的 go.mod 中添加(或更新)以下 replace 指令:
replace github.com/ethereum/go-ethereum => github.com/cosmos/go-ethereum v1.17.2-cosmos-0
如果你直接依赖 geth API(自定义 tracer、导入 core/vm 的自定义 precompile),预计会遇到编译错误,请根据 geth 1.16 → 1.17 的变更日志更新兼容层。

步骤 1.5:迁移 import 路径

在 SDK v0.54 中,大量 cosmossdk.io/... 包迁移到了 github.com/cosmos/cosmos-sdk/...,并且 store 现在变为 store/v2。请在你的分叉链中执行以下查找替换:
旧 import新 import
cosmossdk.io/storegithub.com/cosmos/cosmos-sdk/store/v2
cosmossdk.io/store/typesgithub.com/cosmos/cosmos-sdk/store/v2/types
cosmossdk.io/store/snapshots/typesgithub.com/cosmos/cosmos-sdk/store/v2/snapshots/types
cosmossdk.io/store/prefixgithub.com/cosmos/cosmos-sdk/store/v2/prefix
cosmossdk.io/logcosmossdk.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/signinggithub.com/cosmos/cosmos-sdk/x/tx/signing
cosmossdk.io/systemtestsgithub.com/cosmos/cosmos-sdk/tools/systemtests
github.com/cosmos/ibc-go/v10/...github.com/cosmos/ibc-go/v11/...
如果你的分叉链有单独的 tests/systemtests/go.mod(推荐做法),也要在该子模块中应用这些重命名,并独立于主模块单独运行 go mod tidy。 Cosmos EVM 内部也有一批位置调整——顶层 github.com/cosmos/evm/config 包已删除,其中的符号被重新分配:
v0.6 符号v0.7 位置
config.MustGetDefaultNodeHomeevmd/config.MustGetDefaultNodeHome
config.InitAppConfigevmd/config.InitAppConfig
config.BlockedAddressesevmd/config.BlockedAddresses
config.GetMaccPermsevmd/config.GetMaccPerms
config.SetBip44CoinTypeevmd/config.SetBip44CoinType
config.GetChainIDFromHomeutils.GetChainIDFromHome
config.EVMChainID(常量)已移除——请使用 evmtypes.DefaultEVMChainID
config.GetCosmosPoolMaxTxserver.GetCosmosPoolMaxTx
config.GetLegacyPoolConfig 等合并进 server.ResolveMempoolConfig(见步骤 5a)
示例应用辅助函数(MustGetDefaultNodeHome、InitAppConfig、BlockedAddresses、GetMaccPerms)已从 github.com/cosmos/evm/config 移至 github.com/cosmos/evm/evmd/config。mempool / chain-id 辅助函数已移至 github.com/cosmos/evm/server 和 github.com/cosmos/evm/utils。请相应更新 evmd/cmd/evmd/cmd/root.go、evmd/cmd/evmd/main.go 和 evmd/cmd/evmd/cmd/testnet.go 中的所有 import。

步骤 2:更新 app.go 中的 store key

临时存储 evmtypes.TransientKey 和 feemarkettypes.TransientKey 都已移除。EVM keeper 仍然需要一个按交易划分的临时 scratch store 来保存 tx bloom 和 gas 记账,因此现在改为使用 evmtypes.ObjectKey(一个对象存储)。这一变更是无条件的——即使在串行执行路径上也同样适用。 请替换 transient store 声明,挂载新的对象存储,并为 EVM keeper 构建一个 nonTransientKeys 切片:
  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 是传给 evmkeeper.NewKeeper 的第 4 个参数(见步骤 4),EVM keeper 会用它进行跨模块 store 访问。如果你接入 BlockSTM runner,步骤 5 也会复用这个切片。
步骤 5(并行路径)会扩展 oKeys,加入 banktypes.ObjectStoreKey 并将其接入 bank keeper。如果你不采用虚拟手续费收取,可以跳过。
(对应的 EVMD 结构体字段重命名见步骤 3。)

第 3 步:更新 EVMD 结构体字段

  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 现在以指针形式保存(ibc-go v11)。EVMMempool 的字段类型已扩展为 ExtMempool 接口,以支持 Krakatoa 或未来任何自定义子池。如果你有基于这些字段类型的 getter/setter(例如 GetTransferKeeper、SetTransferKeeper),也需要同步更新它们的签名。 EVMD 上的 Close() 方法会对旧的 mempool 类型做类型断言,请将其更新为新的类型(如果你没有接入 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()
      }
      // ...
  }

第 4 步:更新 app.go 中的 NewExampleApp 和 keeper 构造函数

NewExampleApp 签名:移除 traceStore

traceStore io.Writer 参数已移除。请更新函数签名,以及你的应用构造函数的每一个调用点,包括 CLI 命令(newApp、appExport、root cmd 中的 appCreator 回调)、测试网络 fixture(例如 NewTestNetworkFixture)以及任何自定义集成测试启动逻辑。原本传 nil 的调用点也需要去掉这个 nil 参数。
  func NewExampleApp(
      logger log.Logger,
      db dbm.DB,
-     traceStore io.Writer,
      loadLatest bool,
      appOpts servertypes.AppOptions,
      baseAppOptions ...func(*baseapp.BaseApp),
  ) *EVMD {
函数内部也要移除对应的 tracer 接线:
- bApp.SetCommitMultiStoreTracer(traceStore)

feemarketkeeper.NewKeeper:移除 transient key

  app.FeeMarketKeeper = feemarketkeeper.NewKeeper(
      appCodec, authtypes.NewModuleAddress(govtypes.ModuleName),
      keys[feemarkettypes.StoreKey],
-     tkeys[feemarkettypes.TransientKey],
  )

ibckeeper.NewKeeper:移除 capability keeper

ibc-go v11 移除了 capability-keeper 参数。现有链无需迁移任何状态,因为这个参数本来就是 nil,但调用处必须把它删掉:
  app.IBCKeeper = ibckeeper.NewKeeper(
      appCodec,
      runtime.NewKVStoreService(keys[ibcexported.StoreKey]),
-     nil,
      app.UpgradeKeeper,
      authAddr,
  )

govkeeper.NewKeeper:参数顺序调整,新增投票结果函数

Cosmos SDK v0.54 调整了构造函数参数顺序,并新增了最后一个可插拔的计票函数:
  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:返回指针,内联 address codec,移除 ICS4Wrapper / 重复的 ChannelKeeper

ibc-go v11 会返回 *transferkeeper.Keeper。构造函数现在还会内联接收 EVM address codec(不再需要 SetAddressCodec),并移除了重复的 ICS4Wrapper / ChannelKeeper 参数:
  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()))
⚠️ 参考 evmd 会在 EVMKeeper 之前实例化 TransferKeeper,这样静态预编译才能拿到非空引用。v0.6 的构造顺序正好相反(先 EVMKeeper,再消费 &app.TransferKeeper 的 Erc20Keeper,最后 TransferKeeper)。如果要调整顺序,你还需要移动 Erc20Keeper,让它在 TransferKeeper 之后再构造;同时 Erc20Keeper 现在直接接收指针类型的 app.TransferKeeper,不再使用 &。请把这三个 keeper 的构造作为一个整体来调整,而不是逐个单独修改。

IBC callbacks middleware:改用 setter 包装调用

ibc-go v11 将构造和包装拆开了。请把单行的 NewIBCMiddleware 替换为下面这种三步 setter 形式:
- 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
两个 setter 都是必需的,因为 NewIBCMiddleware 本身已经不再接收这两个参数。如果构造出来的 middleware 没有调用 SetICS4Wrapper 或 SetUnderlyingApplication,虽然可以编译通过,但在第一次发送或接收数据包时会因为空指针解引用而 panic(构造函数只会在 contract keeper 为 nil 或 gas 为 0 时 panic,而 wrapper / underlying-app 字段是在运行时使用时才检查,不是在构造时检查)。

evmkeeper.NewKeeper:object store key、store-key 切片、用 BankKeeper 替换 PreciseBankKeeper

第三个参数是新的 EVM object-store key(第 2 步)。第四个参数已从 map[string]*storetypes.KVStoreKey 变为 []storetypes.StoreKey,keeper 会用它来做跨模块存储访问。这里请传入第 2 步中的 nonTransientKeys 切片;如果你在第 5 步选择启用 STM runner,STM runner 跟踪的也是同一个切片。
  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、解引用后的 TransferKeeper、ClientKeeper

TransferKeeper 已经是指针了(第 3 步),直接传它即可。IBCKeeper.ClientKeeper 是新的依赖,它为 ICS-02 client-router 预编译提供支持(#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、解引用后的 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:最早版本回调

  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()
+     })
  }

重启时补充初始化 EVM 全局变量(#1126)

无论你选择哪条路径,这一步都是必需的。在 LoadLatestVersion 之后(即 if loadLatest 内部),从 KV 补充初始化 EVM 全局变量,这样在任何 RPC handler 运行前,evmCoinInfo 就已经完成填充:
+ ctx := app.NewContextLegacy(true, cmtproto.Header{
+     Height:  app.LastBlockHeight(),
+     ChainID: app.ChainID(),
+ })
+ vmModule.HydrateGlobals(ctx)
如果不这样做,在 PreBlock 之前到达的 RPC 调用会因为 evmCoinInfo 为 nil 而 panic。 这里的 vmModule 指的是 vm.NewAppModule(...) 返回的值。v0.6 会在 app.ModuleManager = module.NewManager(...) 内联调用 vm.NewAppModule(...),因此在调用 HydrateGlobals 之前,你需要先做一次重构:先把结果绑定到一个局部变量,再把这个局部变量传给 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,
+     // ...
+ )
需要新增的导入:cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"。

接入 EVM tx runner(#1132)

无论选择哪条路径,这一步都必需。vmrunner.SetRunner 会安装 baseapp tx runner,并用 EVM 模块的 PatchTxResponses 包一层,在执行后修正日志和交易索引;如果不这样做,receipt 中的 log.Index 和 transactionIndex 会是错误的(这是 #1132 修复的问题)。这个包装器既适用于串行 runner,也适用于并行 runner,只有内部 runner 的选择因路径不同而不同:
  • 串行(默认):传入 txnrunner.NewDefaultRunner(txDecoder)。
  • 并行:传入 txnrunner.NewSTMRunner(...)(见第 5b 步)。
在 NewExampleApp 顶部先把 tx decoder 绑定到一个局部变量,便于 runner 复用(如果你想共用,也可以给 bApp := baseapp.NewBaseApp(...) 使用):
  txConfig := encodingConfig.TxConfig
+ txDecoder := encodingConfig.TxConfig.TxDecoder()
然后在 return app 之前加上 runner 调用。串行形式如下:
+ vmrunner.SetRunner(bApp, txnrunner.NewDefaultRunner(txDecoder))
需要新增的导入:"github.com/cosmos/cosmos-sdk/baseapp/txnrunner"、vmrunner "github.com/cosmos/evm/x/vm/runner"。

已移除的 EVMD 方法

这些方法已经被删除,请移除所有调用方:
  • GetTKey、GetMemKey:transient/mem store 不再以独立 map 存在。
  • GetAuthzKeeper:这个辅助方法是冗余的,如有需要,直接暴露 app.AuthzKeeper 即可。
  • GetPreciseBankKeeper:该模块已被移除(见第 6 步)。
  • SetClientCtx 和 clientCtx 字段:未被使用。

第 5 步:启用 Krakatoa 和/或 BlockSTM

v0.7 中有两个彼此独立的可选启用项:
  • Krakatoa(5a):如果你的分叉此前已经使用了 v0.6 的 ExperimentalEVMMempool,则这是必需的(该类型已被移除);如果你的分叉使用的是 CometBFT 默认 mempool,则这是可选项。
  • BlockSTM 与虚拟费用收集(5b):始终是可选启用项。跳过 5b 即可保持串行执行。
第 5c 步会单独说明 baseapp.SetOptimisticExecution()。这是一个 SDK 层面的功能,不是 v0.7 新增内容,但参考 evmd/app.go 已经启用了它,如果你的分叉还没采用,也可以借这次升级一并引入。
会破坏状态兼容的特性。 这些特性都会作为二进制的一部分发布;无论启用还是移除其中任何一个,都需要像其他共识变更一样,通过协调好的 MsgSoftwareUpgrade 和二进制切换来完成。

5a. Krakatoa 应用层 mempool

在 v0.7 中,Krakatoa 是唯一的应用侧 EVM mempool。v0.6 中的 ExperimentalEVMMempool 类型已经移除,因此这一步是否可选,取决于你的 v0.6 分叉此前使用了什么:
  • 如果你在 v0.6 中接入了 ExperimentalEVMMempool:这一步是必需的。构造签名已经变更,handler 集合也重新设计了;你必须迁移,否则你的分叉将无法编译。
  • 如果你在 v0.6 中使用的是 CometBFT 默认 mempool(没有应用侧 EVM mempool):这一步是可选的。你可以接入 Krakatoa 以获得应用级的 CheckTx / RecheckTx 控制,或者跳过它,继续使用 CometBFT 默认实现。
无论哪种情况,它都独立于 BlockSTM。 要从 ExperimentalEVMMempool 迁移,请将构造替换为 evmmempool.NewMempool 以及新的 handler 集合。完整参考见 evmd/mempool.go 中的 configureEVMMempool;在应用下面的 diff 之前先阅读它。它在本地构造的变量(mpConfig、txEncoder、evmRechecker、cosmosRechecker、cosmosPoolMaxTx、checkTxTimeout)正是新的 NewMempool 签名所需要的参数:
// 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 和 server.GetMempoolCheckTxTimeout 都位于 github.com/cosmos/evm/server;v0.6 中的 evmconfig.GetLegacyPoolConfig / GetBlockGasLimit / GetMinTip 辅助函数已经合并进 ResolveMempoolConfig。app.TxDecode 以及 SetInsertTxHandler / SetReapTxsHandler setter 是 cosmos-sdk v0.54 baseapp 中新增的。
顺序要求:app.SetAnteHandler(...) 必须先于 configureEVMMempool 执行。 ResolveMempoolConfig 会调用 app.GetAnteHandler(),并把结果存到 mpConfig.AnteHandler,随后 rechecker 会闭包捕获它。如果 ante handler 这时还没设置,GetAnteHandler 会返回 nil,链会在第一次 RecheckTx 时 panic。这里不会有编译期提示,顺序必须正确。 evmRechecker 和 cosmosRechecker 必须是不同实例,即使它们包装的是相同配置。它们分别服务于不同的子池(EVM eth-tx 池与 Cosmos 池),需要各自独立的状态来维护提升/降级记录。共享同一个实例可以正常编译,但会在 promote/demote 时产生静默的跨池状态干扰。
然后替换构造和 handler 接线:
- 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)
参考应用在 proposal 阶段校验给定 proposal 中的 tx 时,使用了 NewNoCheckProposalTxVerifier。这是一个性能优化,因为在 Krakatoa mempool 下,proposal 内的每笔 tx 都必须已经验证过,因此这一步检查是冗余的。如果你也选择采用这种模式,请复制参考实现中的 NewNoCheckProposalTxVerifier,并将其传给你的 PrepareProposalHandler。
如果你想完全退出这一能力,可以从 NewExampleApp 中移除 configureEVMMempool(...) 调用,或者在 app.toml 中设置 mempool.max-txs=-1(SDK 的 FlagMempoolMaxTxs,注意这里是顶层 [mempool] 键,而不是 evm.mempool.max-txs)。configureEVMMempool 会通过 server.GetCosmosPoolMaxTx 读取该值,在值为负时直接退出,并回退到 CometBFT 默认 mempool。
⚠️ 请在 config.toml 中设置 mempool.type = "app"。 当应用提供 EVM mempool 时,CometBFT v0.39 要求使用应用侧 mempool 类型。默认值是 "flood";如果保持默认值,Krakatoa 会在启动时报错。你需要在替换二进制的同时,对每个验证节点的 config.toml(或你的分叉中的等价配置)打上这一行补丁。选择不使用 Krakatoa 的分叉(见上一段)则不需要这样做。 还要注意,在 config.toml 中设置 mempool.type = "app" 后,可能还需要配置一些额外参数,它们会影响 Krakatoa mempool 的运行。更多信息请参阅 CometBFT application mempool documentation。

5b.(可选)BlockSTM 并行执行与虚拟手续费收集

BlockSTM 支持状态转换函数的并行执行,并集成了虚拟手续费收集(按 tx 记账银行余额,在 EndBlock 统一扣减)。这不是必需项,跳过本节的链会继续使用串行执行。它独立于 Krakatoa,也独立于乐观执行(5c)。 下面两部分是一起接线的;你选择接入的是整套能力,而不是单独的某几行。

将内部 tx runner 替换为 BlockSTM

步骤 4 已经通过串行的 txnrunner.NewDefaultRunner(txDecoder) 将 vmrunner.SetRunner(bApp, ...) 接到了内部 runner。若要启用并行执行,请把内部 runner 替换为 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
+     },
+ ))
EvmDenom 回调会从运行中的 multi-store 的 EVM 参数中读取值;不要把 sdk.DefaultBondDenom 写死,否则自定义过 EVM denom 的链会发生分叉。 额外 import:goruntime "runtime"。

虚拟手续费收集(仅适用于 18 位小数的链)

虚拟手续费收集是 BlockSTM 套件的一部分,它使手续费结算可以并行进行,而不需要每笔 tx 都去竞争 fee collector 账户。这里需要两部分。 首先,在步骤 2 的 oKeys 声明中加入 banktypes.ObjectStoreKey,并把它接入 bank keeper。顺序很重要:oKeys 声明必须在构建 nonTransientKeys 的 for _, k := range oKeys 循环之前包含 banktypes.ObjectStoreKey,否则 bank object store 不会进入 EVM keeper 能看到的切片。请在步骤 2 的声明位置修改,而不是之后再补:
- oKeys := storetypes.NewObjectStoreKeys(evmtypes.ObjectKey)
+ oKeys := storetypes.NewObjectStoreKeys(banktypes.ObjectStoreKey, evmtypes.ObjectKey)
然后,在 BankKeeper 构造完成后,把 object-store key 接进去:
+ app.BankKeeper = app.BankKeeper.WithObjStoreKey(oKeys[banktypes.ObjectStoreKey])
WithObjStoreKey(storetypes.StoreKey) BaseKeeper 是 cosmos-sdk v0.54 中 bankkeeper.Keeper 接口的一部分,因此无论你的 EVMD.BankKeeper 字段类型是接口(bankkeeper.Keeper,参考实现默认如此)还是具体类型 bankkeeper.BaseKeeper,这段调用都能工作。这个方法返回 BaseKeeper,但 BaseKeeper 实现了 Keeper,所以把结果重新赋回一个接口类型字段也能通过类型检查。不需要扩宽字段类型。 接着,在 WithStaticPrecompiles 之后,为 EVM keeper 启用虚拟手续费:
  app.EVMKeeper = evmkeeper.NewKeeper(/* ... */).WithStaticPrecompiles(/* ... */)
+ app.EVMKeeper.EnableVirtualFeeCollection()
⚠️ 如果你的 gas token 不是 18 位小数,不要调用 EnableVirtualFeeCollection()。 x/vm/keeper.DeductFees 会读取 EVM denom 的 bank metadata,并在 display denom 单位的 exponent 不等于 18 时 panic(x/vm/keeper/fees.go:156)。见步骤 6。非 18 位小数的链如果仍想使用 BlockSTM,就必须跳过这最后一部分;但并行路径最主要的吞吐提升恰恰来自虚拟手续费,因此实际建议是先迁移到 18 位小数。

5c.(可选)乐观执行

这在 v0.7 中不是新特性,baseapp.SetOptimisticExecution() 自 v0.50 起就是 SDK 的功能。之所以在这里说明,是因为参考实现 evmd/app.go 默认启用了它,而尚未采用它的分叉也可以顺手在这次升级中一并接线。它独立于 BlockSTM,也独立于 Krakatoa。 这个特性会将高度 H 的 FinalizeBlock 与高度 H+1 的 ProcessProposal 重叠执行:当 CometBFT 还在完成当前区块时,应用会推测性执行下一个提议区块。如果推测结果与之后 FinalizeBlock 真正要求提交的结果一致,就直接复用;如果不一致,就丢弃推测状态,回退到标准执行路径。 参考实现 evmd/app.go 已启用该功能。请在调用 baseapp.NewBaseApp(...) 之前,把这个选项加到 baseAppOptions 中,也就是在 baseAppOptions ...func(*baseapp.BaseApp) 参数进入作用域之后、构造函数消费它之前进行追加:
  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...,
      )
运维说明:
  • 确定性不受影响。 推测执行使用隔离的缓存;如果结果不匹配,状态会被丢弃而不是提交。对用户可见的行为与非乐观执行完全一致,变化的只是 FinalizeBlock 的延迟。
  • 可与 BlockSTM(5b)和 Krakatoa(5a)组合使用。 乐观执行会通过当前接入的 runner(vmrunner.SetRunner)来执行推测区块,因此如果同时启用了 5b,你会同时获得“推测阶段并行执行 + 最终阶段并行执行”。
  • 会破坏状态兼容性。 和 5a、5b 一样,这个能力会固化进二进制。启用或移除它都需要一次协调好的 MsgSoftwareUpgrade,不能通过运行时开关切换。见步骤 8。
  • 资源成本。 推测路径每个高度都会额外使用一个 goroutine 和一份工作状态缓存。对于内存紧张的节点,你可能更倾向于关闭它;本文顶部链接的 SDK 文档对这些权衡做了说明。

步骤 6:移除 x/precisebank

x/precisebank 已被弃用(#1019)。它只在 gas 代币不是 18 位小数时才有意义,因为它负责把这类代币桥接到 EVM 的 18 位小数世界中。参考版 v0.6 的 evmd/app.go 无条件接入了它,尽管内联注释写着“如果 SDK 对 gas coin 使用 18 位小数,则不需要 PreciseBank”,因此大多数 v0.6 分叉无论其 gas 代币的小数位是多少,都会在 evmkeeper、erc20keeper 和各类预编译中串接它。 在 v0.7 中,这个模块已经从主代码树中移除。EvmCoinInfo.Decimals 也被标记为已弃用(#1029)。
后续版本将不再完整支持非 18 位小数链。 v0.7.0 仍然在 contrib/x/precisebank 下提供 precisebank,但未来的 EVM 版本不会再对它进行测试或维护。你要么停留在 v0.6.x,直到可以迁移到 18 位小数;要么固定使用 cosmos/evm/contrib/x/precisebank,并且不要启用虚拟手续费收集(5b)。
如果你的分叉接入了 PreciseBankKeeper(无论你的 gas 代币是否为 18 位小数),请将其移除:
  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 { /* ... */ }
同时,从 keys 声明中移除 precisebanktypes.StoreKey,并从你的 SetOrderBeginBlockers / SetOrderEndBlockers / SetOrderInitGenesis 列表中移除该模块名。 把所有对 app.PreciseBankKeeper 的引用(包括 evmkeeper.NewKeeper、erc20keeper.NewKeeper、DefaultStaticPrecompiles 以及任何自定义模块)都替换为 app.BankKeeper。在每个调用点核对你对小数位的预期:凡是你之前默认 precisebank 会完成 denom 转换的地方,现在都需要直接处理 18 位小数值,或者自行完成转换。 引用 precisebank 的测试也需要清理。在参考仓库中,这意味着删除 evmd/tests/integration/x_precisebank_test.go,并从 evmd/tests/ibc/helper.go 中移除 PreciseBankMintEventCount / PreciseBankBurnEventCount 及其使用方。请检查你分叉的测试树中所有 precisebank 符号并将其删除,否则在 v0.7 下代码将无法编译。

步骤 7:更新自定义代码

自定义 ante handler / mempool 插件

用于 gas 计费和 feemarket 的 transient store 已被移除。请改为从 SDK context 中读取 gas-wanted,即 ctx.GasMeter().GasConsumed()。如果你的 decorator 会向 feemarket transient store 写入数据,请删除这部分逻辑,因为 feemarkettypes.TransientKey 以及 keeper 方法 GetTransientGasWanted、SetTransientBlockGasWanted、AddTransientGasWanted 都已移除。EVM keeper 也是同样情况:GetBlockBloomTransient、SetBlockBloomTransient、GetTxIndexTransient、SetTxIndexTransient 和 WithDefaultEvmCoinInfo 都被删除了。

自定义 BankKeeper / BankWrapper 实现

x/vm/types/interfaces.go 中的接口发生了变化。任何提供非默认 bank wrapper 的分叉都必须更新。 BankKeeper 新增了五个方法,用于支持虚拟手续费收集以及并行安全的余额记账:
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 则是一次硬性的 API 破坏性变更:MintAmountToAccount 和 BurnAmountFromAccount 已被移除,改为统一使用 SetBalance(ctx, account, amt *big.Int) error。调用方和实现方都必须修改。 EmitBlockBloomEvent 的参数类型已从 ethtypes.Bloom 改为 []byte。 新增了一个 VMKeeper 接口(GetEvmCoinInfo);有些辅助函数现在接收它,而不再接收具体 keeper。

自定义索引器 / receipt 消费方

  • 只有在执行后修补完成之后(#1132),才能读取 log.Index 和 log.TxIndex。如果从原始事件顺序重新计算,会偏离规范 receipt。
  • 删除任何针对 transactionIndex 上 MaxUint64 溢出的特殊处理逻辑,该问题已在 #1047 中修复。
  • 不要假设 receipt 数组中的 tx 数量一定等于原始区块中的 tx 数量。带有 StateDB 错误的 tx 在 receipt 转换期间现在会被跳过(#1107)。

预编译 abi.json 的分叉版本

PR #758 让预编译 ABI 文件变成严格校验模式,只接受有效的 Ethereum ABI 字段。任何 vendored 或覆盖任一预编译 abi.json 的分叉,都必须移除非 ABI 的扩展字段,否则启动时会解析失败。

已移除的辅助函数

  • Params.GetActiveStaticPrecompilesAddrs() 已被移除;如果你用到了它,请自行推导 []common.Address。

EVMKeeper 的 mock

请基于 v0.7.0 重新生成,因为构造函数签名(步骤 4)和静态预编译依赖(步骤 4)都变了。

步骤 8:协调升级

这是一次标准的 Cosmos 共识破坏性升级。
  • 升级后所有验证者都必须运行相同的 v0.7 二进制。 混用 v0.6 和 v0.7 二进制会导致分叉。
  • 步骤 5(Krakatoa、BlockSTM + 虚拟手续费、乐观执行)会破坏状态兼容性。 这些能力都会固化进二进制中;后续无论启用还是移除它们,都必须再发起一次协调好的 MsgSoftwareUpgrade,而不是运行时切换。
  • 这些开关没有链上治理标志。 之后若要切换路径,必须发布新的二进制并再次执行 MsgSoftwareUpgrade。
更新升级处理器中的 UpgradeName 常量,以及它的文档注释,使之与新版本保持一致。不同版本之间文档注释漂移是一个反复出现的坑点:系统测试会逐字读取这个常量,但读者和下游处理器往往会通过注释来 grep 上下文:
  // 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"
参考升级处理器会在空的 StoreUpgrades 下运行 RunMigrations,因为没有模块 schema 变更。请在目标高度提交一个 name: "v0.6.0-to-v0.7.0" 的 MsgSoftwareUpgrade,并在停机高度切换到新二进制。

步骤 9:验证

go build ./...
go test ./...

对升级路径做基本检查

make test-system
它会为 v0.6.0-to-v0.7.0 驱动一次 MsgSoftwareUpgrade,在停机高度切换到 v0.7 二进制,并运行一组存在账户争用的负载(每个区块中有大量 tx 命中同一个热点合约)。在并行路径上,这会覆盖升级边界前后 BlockSTM 调度器的冲突检测;在串行路径上,它同样是一个有价值的端到端冒烟测试。 该测试会从 tests/systemtests/binaries/v0.6/evmd 读取一个 v0.6 时代的二进制。对于你的分叉,这个二进制应当是你自己的链构建产物,并固定依赖 v0.6 版本的 cosmos-evm,而不是通用的 cosmos/[email protected] 二进制。旧版本构建中的 keeper、ante decorator 和模块集合必须与你的验证者在升级前实际运行的一致,否则测试覆盖的就是错误的初始状态。 请把你用于此流程的 make(或 shell)目标接好线,关键约束如下:
  • 输出路径应为 tests/systemtests/binaries/v0.6/evmd(或你的 chainupgrade/v6_v7.go 实际读取的位置)。
  • 你的 test-system Make 目标(或等价实现)必须按顺序依赖它以及当前二进制。
  • 如果你之前的版本在 test-system 中串接了 build-v05 目标,请把该依赖改为新的 build-v06(或你命名的等价目标)。

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/[email protected]
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:
Dependencyv0.6.xv0.7.0Upstream guide
Go toolchain1.231.25—
cosmos-sdkv0.53.xv0.54.2SDK
cometbftv0.38.xv0.39.3release notes
ibc-gov10v11v10→v11
go-ethereumv1.15v1.17 via Cosmos forkrelease 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 importNew import
cosmossdk.io/storegithub.com/cosmos/cosmos-sdk/store/v2
cosmossdk.io/store/typesgithub.com/cosmos/cosmos-sdk/store/v2/types
cosmossdk.io/store/snapshots/typesgithub.com/cosmos/cosmos-sdk/store/v2/snapshots/types
cosmossdk.io/store/prefixgithub.com/cosmos/cosmos-sdk/store/v2/prefix
cosmossdk.io/logcosmossdk.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/signinggithub.com/cosmos/cosmos-sdk/x/tx/signing
cosmossdk.io/systemtestsgithub.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 symbolv0.7 location
config.MustGetDefaultNodeHomeevmd/config.MustGetDefaultNodeHome
config.InitAppConfigevmd/config.InitAppConfig
config.BlockedAddressesevmd/config.BlockedAddresses
config.GetMaccPermsevmd/config.GetMaccPerms
config.SetBip44CoinTypeevmd/config.SetBip44CoinType
config.GetChainIDFromHomeutils.GetChainIDFromHome
config.EVMChainID (constant)removed — use evmtypes.DefaultEVMChainID
config.GetCosmosPoolMaxTxserver.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

make test-system
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/[email protected] 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).