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 数量上限。
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 特性——适用于尚未接入它的分叉链。) 必需(所有分叉链)- 步骤 1:升级依赖
- 步骤 1.5:迁移 import 路径
- 步骤 2:更新
app.go中的 store key - 步骤 3:更新
EVMD结构体字段 - 步骤 4:更新
app.go中的NewExampleApp和 keeper 构造函数 - 步骤 6:移除
x/precisebank(18 位小数链) - 步骤 7:更新自定义代码
- 步骤 8:协调升级
- 步骤 9:验证
ExperimentalEVMMempool 则必需,否则可选)
BlockSTM 并行执行(可选,包含虚拟手续费收取)
Optimistic execution(SDK 特性,不特定于 v0.7——选择性启用)
步骤 1:升级依赖
检查你的代码树中每一个v0.7.0 会通过传递依赖升级整个栈。你至少需要以下版本:go.mod。 如果分叉链包含嵌套子模块(例如单独的tests/systemtests/go.mod),需要分别应用依赖升级,并在每个模块中单独运行go mod tidy——只升级根模块会让嵌套模块继续保留过时的cosmossdk.io/log、cosmossdk.io/store解析结果,从而导致编译失败。
| 依赖项 | v0.6.x | v0.7.0 | 上游指南 |
|---|---|---|---|
| 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 |
go.mod 中添加(或更新)以下 replace 指令:
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/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/... |
tests/systemtests/go.mod(推荐做法),也要在该子模块中应用这些重命名,并独立于主模块单独运行 go mod tidy。
Cosmos EVM 内部也有一批位置调整——顶层 github.com/cosmos/evm/config 包已删除,其中的符号被重新分配:
| v0.6 符号 | v0.7 位置 |
|---|---|
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(常量) | 已移除——请使用 evmtypes.DefaultEVMChainID |
config.GetCosmosPoolMaxTx | server.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 切片:
nonTransientKeys 是传给 evmkeeper.NewKeeper 的第 4 个参数(见步骤 4),EVM keeper 会用它进行跨模块 store 访问。如果你接入 BlockSTM runner,步骤 5 也会复用这个切片。
步骤 5(并行路径)会扩展(对应的oKeys,加入banktypes.ObjectStoreKey并将其接入 bank keeper。如果你不采用虚拟手续费收取,可以跳过。
EVMD 结构体字段重命名见步骤 3。)
第 3 步:更新 EVMD 结构体字段
TransferKeeper 现在以指针形式保存(ibc-go v11)。EVMMempool 的字段类型已扩展为 ExtMempool 接口,以支持 Krakatoa 或未来任何自定义子池。如果你有基于这些字段类型的 getter/setter(例如 GetTransferKeeper、SetTransferKeeper),也需要同步更新它们的签名。
EVMD 上的 Close() 方法会对旧的 mempool 类型做类型断言,请将其更新为新的类型(如果你没有接入 EVM mempool,也可以删除):
第 4 步:更新 app.go 中的 NewExampleApp 和 keeper 构造函数
NewExampleApp 签名:移除 traceStore
traceStore io.Writer 参数已移除。请更新函数签名,以及你的应用构造函数的每一个调用点,包括 CLI 命令(newApp、appExport、root cmd 中的 appCreator 回调)、测试网络 fixture(例如 NewTestNetworkFixture)以及任何自定义集成测试启动逻辑。原本传 nil 的调用点也需要去掉这个 nil 参数。
feemarketkeeper.NewKeeper:移除 transient key
ibckeeper.NewKeeper:移除 capability keeper
ibc-go v11 移除了 capability-keeper 参数。现有链无需迁移任何状态,因为这个参数本来就是 nil,但调用处必须把它删掉:
govkeeper.NewKeeper:参数顺序调整,新增投票结果函数
Cosmos SDK v0.54 调整了构造函数参数顺序,并新增了最后一个可插拔的计票函数:
transferkeeper.NewKeeper:返回指针,内联 address codec,移除 ICS4Wrapper / 重复的 ChannelKeeper
ibc-go v11 会返回 *transferkeeper.Keeper。构造函数现在还会内联接收 EVM address codec(不再需要 SetAddressCodec),并移除了重复的 ICS4Wrapper / ChannelKeeper 参数:
⚠️ 参考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 形式:
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 跟踪的也是同一个切片。
DefaultStaticPrecompiles:Bank、解引用后的 TransferKeeper、ClientKeeper
TransferKeeper 已经是指针了(第 3 步),直接传它即可。IBCKeeper.ClientKeeper 是新的依赖,它为 ICS-02 client-router 预编译提供支持(#768):
erc20keeper.NewKeeper:Bank、解引用后的 TransferKeeper
node.RegisterNodeService:最早版本回调
重启时补充初始化 EVM 全局变量(#1126)
无论你选择哪条路径,这一步都是必需的。在LoadLatestVersion 之后(即 if loadLatest 内部),从 KV 补充初始化 EVM 全局变量,这样在任何 RPC handler 运行前,evmCoinInfo 就已经完成填充:
PreBlock 之前到达的 RPC 调用会因为 evmCoinInfo 为 nil 而 panic。
这里的 vmModule 指的是 vm.NewAppModule(...) 返回的值。v0.6 会在 app.ModuleManager = module.NewManager(...) 内联调用 vm.NewAppModule(...),因此在调用 HydrateGlobals 之前,你需要先做一次重构:先把结果绑定到一个局部变量,再把这个局部变量传给 module.NewManager:
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(...) 使用):
return app 之前加上 runner 调用。串行形式如下:
"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 即可保持串行执行。
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 默认实现。
ExperimentalEVMMempool 迁移,请将构造替换为 evmmempool.NewMempool 以及新的 handler 集合。完整参考见 evmd/mempool.go 中的 configureEVMMempool;在应用下面的 diff 之前先阅读它。它在本地构造的变量(mpConfig、txEncoder、evmRechecker、cosmosRechecker、cosmosPoolMaxTx、checkTxTimeout)正是新的 NewMempool 签名所需要的参数:
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 中新增的。
顺序要求:然后替换构造和 handler 接线:app.SetAnteHandler(...)必须先于configureEVMMempool执行。ResolveMempoolConfig会调用app.GetAnteHandler(),并把结果存到mpConfig.AnteHandler,随后 rechecker 会闭包捕获它。如果 ante handler 这时还没设置,GetAnteHandler会返回 nil,链会在第一次RecheckTx时 panic。这里不会有编译期提示,顺序必须正确。evmRechecker和cosmosRechecker必须是不同实例,即使它们包装的是相同配置。它们分别服务于不同的子池(EVM eth-tx 池与 Cosmos 池),需要各自独立的状态来维护提升/降级记录。共享同一个实例可以正常编译,但会在 promote/demote 时产生静默的跨池状态干扰。
参考应用在 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(...):
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 的声明位置修改,而不是之后再补:
BankKeeper 构造完成后,把 object-store key 接进去:
WithObjStoreKey(storetypes.StoreKey) BaseKeeper 是 cosmos-sdk v0.54 中 bankkeeper.Keeper 接口的一部分,因此无论你的 EVMD.BankKeeper 字段类型是接口(bankkeeper.Keeper,参考实现默认如此)还是具体类型 bankkeeper.BaseKeeper,这段调用都能工作。这个方法返回 BaseKeeper,但 BaseKeeper 实现了 Keeper,所以把结果重新赋回一个接口类型字段也能通过类型检查。不需要扩宽字段类型。
接着,在 WithStaticPrecompiles 之后,为 EVM keeper 启用虚拟手续费:
⚠️ 如果你的 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) 参数进入作用域之后、构造函数消费它之前进行追加:
- 确定性不受影响。 推测执行使用隔离的缓存;如果结果不匹配,状态会被丢弃而不是提交。对用户可见的行为与非乐观执行完全一致,变化的只是
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 位小数),请将其移除:
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 新增了五个方法,用于支持虚拟手续费收集以及并行安全的余额记账:
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 上下文:
StoreUpgrades 下运行 RunMigrations,因为没有模块 schema 变更。请在目标高度提交一个 name: "v0.6.0-to-v0.7.0" 的 MsgSoftwareUpgrade,并在停机高度切换到新二进制。
步骤 9:验证
对升级路径做基本检查
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-systemMake 目标(或等价实现)必须按顺序依赖它以及当前二进制。 - 如果你之前的版本在
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.6ExperimentalEVMMempooltype is gone. If your v0.6 fork wiredExperimentalEVMMempool, 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 (
EndBlockfee 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.
- ICS-02 client-router precompile (#768) — Solidity contracts can now manage IBC light clients.
- Authority params (#1130) —
x/vm,x/erc20,x/feemarketMsgServerhandlers consultAuthorityParamsfrom the consensus module before falling back to per-module gov authority. - OpenTelemetry tracing (#871, #863) — spans across
x/vmand the JSON-RPC layer. - JSON-RPC filter lifecycle overhaul (#1008) — idle-timeout reclamation replacing the old global filter cap.
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)- Step 1: Bump dependencies
- Step 1.5: Migrate import paths
- Step 2: Update store keys in
app.go - Step 3: Update
EVMDstruct fields - Step 4: Update
NewExampleAppand keeper constructors inapp.go - Step 6: Drop
x/precisebank(18-decimal chains) - Step 7: Update custom code
- Step 8: Coordinate the upgrade
- Step 9: Verify
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
Audit everyv0.7.0 transitively bumps the entire stack. The minimum versions you need:go.modin your tree. Forks with nested submodules (e.g., a separatetests/systemtests/go.mod) need the dependency bumps applied independently andgo mod tidyrun separately in each — root-only bumps leave the nested modules with stalecosmossdk.io/log,cosmossdk.io/storeresolutions that fail to compile.
| 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 |
go.mod:
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 ofcosmossdk.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/... |
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) |
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:
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(The matchingoKeyswithbanktypes.ObjectStoreKeyand wires it into the bank keeper. Skip if you’re not adopting virtual fee collection.
EVMD struct field rename is in Step 3.)
Step 3: Update EVMD struct fields
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):
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.
feemarketkeeper.NewKeeper — drop transient key
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:
govkeeper.NewKeeper — reordered args, new vote-results function
Cosmos SDK v0.54 reordered the constructor and added a final pluggable vote-tally function:
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:
⚠️ The referenceevmdinstantiatesTransferKeeperbeforeEVMKeeperso 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 moveErc20Keeperso it’s constructed afterTransferKeeper— andErc20Keeperitself now takes the pointer-typedapp.TransferKeeperdirectly, 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-lineNewIBCMiddleware with the three-step setter form:
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.
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):
erc20keeper.NewKeeper — Bank, dereferenced TransferKeeper
node.RegisterNodeService — earliest-version callback
Hydrate EVM globals on restart (#1126)
Required regardless of which path you choose. AfterLoadLatestVersion (inside if loadLatest), hydrate the EVM globals from KV so evmCoinInfo is populated before any RPC handler runs:
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:
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).
NewExampleApp so it can be reused by the runner (and by bApp := baseapp.NewBaseApp(...) if you want to share it):
return app. The sequential form:
"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; exposeapp.AuthzKeeperdirectly if you need it.GetPreciseBankKeeper— module is removed (Step 6).SetClientCtxand theclientCtxfield — 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.
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. TheExperimentalEVMMempool type from v0.6 is gone, so optionality depends on what your v0.6 fork already used:
- If you wired
ExperimentalEVMMempoolin 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/RecheckTxcontrol, or skip it and stay on stock CometBFT.
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:
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:Then replace the construction and handler wiring:app.SetAnteHandler(...)must run beforeconfigureEVMMempool.ResolveMempoolConfigcallsapp.GetAnteHandler()and stashes the result onmpConfig.AnteHandler, which the recheckers then close over. If the ante handler hasn’t been set yet,GetAnteHandlerreturns nil and the chain panics on firstRecheckTx. There’s no compile-time signal — get the ordering right.evmRecheckerandcosmosRecheckermust 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.
The reference application uses aTo opt out entirely, drop theNewNoCheckProposalTxVerifierat 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 referenceNewNoCheckProposalTxVerifierand pass this to yourPrepareProposalHandler.
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.
⚠️ Setmempool.type = "app"inconfig.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’sconfig.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 settingmempool.type = "app"inconfig.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 atEndBlock). 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 wiredvmrunner.SetRunner(bApp, ...) with the sequential txnrunner.NewDefaultRunner(txDecoder) as the inner runner. To opt into parallel execution, swap the inner runner for txnrunner.NewSTMRunner(...):
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 theoKeys 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:
BankKeeper is constructed, wire the object-store key into it:
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:
⚠️ Do not callEnableVirtualFeeCollection()if your gas token is not 18-decimal.x/vm/keeper.DeductFeesreads 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:
- 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
FinalizeBlockshifts. - 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 underIf your fork wiredcontrib/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 pincosmos/evm/contrib/x/precisebankand do not enable virtual fee collection (5b).
PreciseBankKeeper (whether or not your gas token is 18-decimal), unwire it:
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:
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.Indexandlog.TxIndexonly after post-execution patching (#1132). Recomputing from raw event order will diverge from canonical receipts. - Drop any workaround that special-cased the
MaxUint64overflow ontransactionIndex— 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.Addressyourself 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.
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:
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
Sanity check the upgrade path
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 yourchainupgrade/v6_v7.goreads from). - Your
test-systemMake target (or equivalent) depends on it and on the current binary, in that order. - If your previous release had a
build-v05target chained fromtest-system, retarget that dependency to the newbuild-v06(or whatever you name it).