BSC 客户端源码解析:用 evm t8n 计算 Ethash 难度——GrayGlacier(EIP-5133)测试用例全解
2026/9/18 16:56:36 网站建设 项目流程

BSC 客户端源码解析:用 evm t8n 计算 Ethash 难度——GrayGlacier(EIP-5133)测试用例全解

【免费下载链接】bscA BNB Smart Chain client based on the go-ethereum fork项目地址: https://gitcode.com/GitHub_Trending/bs/bsc

本篇技术指南围绕 bsc 仓库中 cmd/evm/testdata/19/readme.md 这一官方测试用例展开,讲解如何利用evm t8n(transition)子命令,在调用方不提供难度值时自动按 Ethash 规则计算区块难度,并重点演示GrayGlacier(EIP-5133)分叉下的计算行为。读完本文,你将掌握 t8n 输入/输出 JSON 的完整字段语义、难度计算底层的调用链,以及如何通过切换分叉名复现 London / ArrowGlacier / GrayGlacier 三种难度结果的差异。

一、测试用例概览:目录结构与文件清单

用例位于 cmd/evm/testdata/19/,共包含 7 个文件,职责如下:

文件作用
readme.md用例说明文档,给出完整运行命令与输出样例
env.json环境(区块上下文)输入,描述待构建区块与父区块信息
alloc.json初始账户状态输入
txs.json交易集合输入,本用例为空数组
exp_london.jsonLondon 分叉下的期望结果
exp_arrowglacier.jsonArrowGlacier(EIP-4345)分叉下的期望结果
exp_grayglacier.jsonGrayGlacier(EIP-5133)分叉下的期望结果

该用例的核心验证点是:--input.env中不提供currentDifficulty时,evm t8n会基于父区块的parentDifficulty与父区块时间戳,按指定分叉的 Ethash 难度算法自动推导当前区块难度。这正是 readme.md 开头所述 "This test shows how theevm t8ncan be used to calculate the (ethash) difficulty, if none is provided by the caller" 的含义。

二、输入文件逐字段解读

2.1 env.json:区块环境上下文

env.json 的完整内容如下:

{ "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", "currentGasLimit": "0x750a163df65e8a", "currentBaseFee": "0x500", "currentNumber": "13000000", "currentTimestamp": "100015", "parentTimestamp": "99999", "parentDifficulty": "0x2000000000000" }

各字段语义(对应 t8n 工具内部stEnv结构,见 cmd/evm/internal/t8ntool/execution.go):

字段说明
currentCoinbase0xc94f...f0b当前区块的矿工/出块地址(区块奖励接收方)
currentGasLimit0x750a163df65e8a当前区块 Gas 上限
currentBaseFee0x500当前区块基础费(1280 wei,London 之后必填)
currentNumber13000000当前区块高度,十进制表示
currentTimestamp100015当前区块时间戳
parentTimestamp99999父区块时间戳,用于难度的时间衰减因子计算
parentDifficulty0x2000000000000父区块难度,这是难度推导的关键输入

注意两点:

  1. 没有currentDifficulty字段——这正是本用例的主题:让工具自行计算;
  2. 没有currentRandomprevRandao——因为本用例仍处于 PoW(Ethash)阶段,尚未到 Merge。

2.2 alloc.json:初始账户状态

alloc.json 预置了两个账户:

{ "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { "balance": "0x5ffd4878be161d74", "code": "0x", "nonce": "0xac", "storage": {} }, "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { "balance": "0xfeedbead", "nonce": "0x00" } }

第一个账户带有空code与空storage,第二个账户仅声明balancenonce。由于本用例不执行任何交易,这两个账户最终会以原样状态参与状态根(stateRoot)的计算——这也解释了为什么三个分叉期望文件中的stateRoot完全相同(见第四节)。

2.3 txs.json:空交易集合

txs.json 内容仅为[],表示区块内没有交易。因此输出中gasUsed = 0x0receipts = []txRootreceiptsRoot都是空树的 RLP 根0x56e81f...

三、核心命令:如何用 evm t8n 计算难度

在 cmd/evm/testdata/19/readme.md 中给出的完整命令(在cmd/evm目录下运行已构建的evm二进制):

./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=GrayGlacier

命令分解如下:

参数含义
t8ntransition子命令的别名,在 cmd/evm/main.go 中注册,用途为 "Executes a full state transition"(执行完整状态转换)
--input.alloc初始状态 JSON 路径
--input.txs交易列表 JSON 路径
--input.env区块环境 JSON 路径
--output.result=stdout结果输出到标准输出而非写入文件
--state.fork=GrayGlacier指定状态转换所依据的分叉规则(即--state.fork标志对应的ForknameFlag

官方文档给出的完整运行日志与输出为:

[user@work evm]$ ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=GrayGlacier INFO [03-09|10:45:26.777] Trie dumping started root=6f0588..7f4bdc INFO [03-09|10:45:26.777] Trie dumping complete accounts=2 elapsed="176.471µs" INFO [03-09|10:45:26.777] Wrote file file=alloc.json { "result": { "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "receipts": [], "currentDifficulty": "0x2000000004000", "gasUsed": "0x0", "currentBaseFee": "0x500" } }

日志中的三行分别对应:Trie 状态树转储开始/完成(accounts=2与 alloc.json 中的两个账户一致),以及将处理后的状态写出为alloc.json快照文件(默认输出目录为当前目录,可通过--output.basedir调整)。

四、运行结果解读:currentDifficulty 从何而来

输出result对象中的关键字段(对应ExecutionResult结构,见 cmd/evm/internal/t8ntool/execution.go 与 gen_execresult.go):

字段解读
stateRoot0x6f0588...7f4bdc最终状态根,与 alloc.json 两个账户的初始状态一致(无交易改动状态)
txRoot0x56e81f...空交易树根
receiptsRoot0x56e81f...空收据树根(无交易则无收据)
logsHash/logsBloom空值形态无日志
receipts[]无收据
currentDifficulty0x2000000004000本用例的核心结果:工具自行计算的当前区块难度
gasUsed0x0无交易,Gas 用量为零
currentBaseFee0x500与输入的currentBaseFee一致(1280 wei)

对比父区块难度parentDifficulty = 0x2000000000000与计算结果currentDifficulty = 0x2000000004000:二者仅相差0x4000(即 16384)。这个微小的增量正是 Ethash 难度炸弹(difficulty bomb)在 GrayGlacier 规则下的残留贡献——由于 EIP-5133 将难度炸弹再次推迟约 100 天,在高度 13,000,000 处炸弹指数已被压低,对难度的抬升几乎可以忽略。

五、跨分叉对比:London / ArrowGlacier / GrayGlacier

本用例目录下预置了三个分叉的期望结果文件,输入完全一致(同一 env.json、alloc.json、空的 txs.json),仅--state.fork不同:

期望文件分叉currentDifficulty相对父难度的增量
exp_london.jsonLondon0x2000000200000较大
exp_arrowglacier.jsonArrowGlacier0x2000080000000最大
exp_grayglacier.jsonGrayGlacier0x2000000004000最小(仅0x4000

三个文件中stateRoottxRootreceiptsRootlogsHashlogsBloomreceiptsgasUsedcurrentBaseFee完全一致,唯一差异是currentDifficulty。这说明:状态转换与交易执行结果与分叉选择无关(毕竟没有交易),而难度计算高度依赖分叉规则。另外,期望文件中的result比 readme 日志输出多了一个"requests": null字段,这是更新版本 t8n 工具的扩展字段。

难度差异的根源在于各分叉对难度炸弹的**延迟量(bomb delay)**不同。Ethash 难度公式在父难度基础上叠加时间调整项与炸弹项2^((fakeNumber / 100000) - 2),其中fakeNumber = number - bombDelay,bombDelay 越大,同一高度下炸弹贡献越小。对应关系为:

  • London(EIP-3554):在高度 12,965,000 激活,延迟约 9,700,000 个区块;
  • ArrowGlacier(EIP-4345):在高度 13,773,000 激活,额外延迟约 10,700,000 个区块;
  • GrayGlacier(EIP-5133,即 readme 中标注的 "Eip 5133"):在高度 15,050,000 激活,在 ArrowGlacier 基础上再延迟 700,000 个区块(合计约 11,400,000,推迟约 100 天)。

主网上的激活高度可直接在 params/config.go 中查到:

LondonBlock: big.NewInt(12_965_000), ArrowGlacierBlock: big.NewInt(13_773_000), GrayGlacierBlock: big.NewInt(15_050_000),

由于本用例的currentNumber = 13000000恰好介于 London 与 ArrowGlacier 激活高度之间,处于"难度炸弹重新抬头"的时间窗口,因此三个分叉在同一高度下的难度差异被清晰放大——这正是该用例被设计出来的目的:直观展示不同炸弹延迟常数对难度数值的影响。

六、底层原理:calcDifficulty 的实现细节

t8n 的难度计算实现在 cmd/evm/internal/t8ntool/execution.go 的calcDifficulty函数中,其代码注释明确写着:"Note: this method only works for ethash engine."(该方法仅适用于 Ethash 引擎)。核心逻辑如下:

func calcDifficulty(config *params.ChainConfig, number, currentTime, parentTime uint64, parentDifficulty *big.Int, parentUncleHash common.Hash) *big.Int { uncleHash := parentUncleHash if uncleHash == (common.Hash{}) { uncleHash = types.EmptyUncleHash } parent := &types.Header{ ParentHash: common.Hash{}, UncleHash: uncleHash, Difficulty: parentDifficulty, Number: new(big.Int).SetUint64(number - 1), Time: parentTime, } // ...调用 ethash.CalcDifficulty(config, currentTime, parent) 完成实际计算 }

几个值得注意的实现要点:

  1. 合成父区块头:工具并不需要真实父区块,而是根据输入的parentDifficultyparentTimestamp以及currentNumber - 1合成一个最小化的父区块头,再交给 Ethash 引擎计算。
  2. 未提供 unclehash 的兜底:本用例的 env.json 中没有提供父区块的parentUncleHash,此时函数将其替换为types.EmptyUncleHash。readme 中特别强调 "no provided unclehash for the parent block",指的就是这一分支。叔叔哈希会影响难度公式中的调整项(有叔叔时难度调整因子不同),因此这里显式回退到"无叔叔"的默认值,保证结果可复现。
  3. 分叉规则选择:最终调用 Ethash 引擎的CalcDifficulty时,会依据--state.fork构建出的ChainConfig,判断当前区块(父高度 + 1)处于哪个分叉,从而选择对应的难度函数(London / ArrowGlacier / GrayGlacier),并套用各自不同的炸弹延迟常数。

这也解释了为什么"调用方不提供currentDifficulty也能算出难度":Ethash 难度本质上是parentDifficulty、时间戳差、区块高度与分叉常数的纯函数,t8n 只需补齐这些输入即可。

七、复现与验证:把输出和期望文件对齐

要验证本用例,可按以下步骤操作:

  1. 在仓库根目录构建evm工具:
    go build -o evm ./cmd/evm
  2. 进入 cmd/evm 目录,执行 readme 中的命令:
    ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=GrayGlacier
  3. 将输出的result.currentDifficulty与 exp_grayglacier.json 中的0x2000000004000对比,并核对stateRoottxRootreceiptsRoot等字段完全一致。

做交叉验证时,只需替换--state.fork的值:

# London 分叉 ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=London # ArrowGlacier 分叉 ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=ArrowGlacier

分别得到0x20000002000000x2000080000000,即与 exp_london.json、exp_arrowglacier.json 一致。三个分叉、三种难度、同一组状态根,一测便知。

八、实战扩展:t8n 的其他可用参数

除了本用例用到的参数外,t8n(transition)子命令在 cmd/evm/main.go 中注册了更完整的标志集,可用于更复杂的测试场景:

标志说明
--output.basedir输出文件的基础目录,默认当前目录
--output.alloc将处理后的状态(alloc)写到指定文件(默认写alloc.json
--output.body输出区块体(RLP 十六进制)
--state.chainid指定链 ID,影响交易签名校验与 EIP-155 行为
--state.reward指定区块奖励金额,可设为0关闭 PoW 奖励
--trace/--trace.tracer/--trace.tracerconfig启用交易级 trace,并选择内置 tracer(如callTracer4byteTracer)或自定义 JS tracer
--trace.nostack/--trace.nomemory/--trace.noreturndata控制 trace 输出内容粒度

借助这些参数,可以把 testdata/19 的骨架改造成自己的难度/状态转换验证脚本:例如在 env.json 中调整parentDifficultyparentTimestampcurrentTimestamp的差值,观察时间调整因子对难度的作用;或在 txs.json 中填入真实交易,验证包含执行负载时的完整状态转换流程。

九、小结

通过 cmd/evm/testdata/19/readme.md 这个精炼的测试用例,我们可以完整掌握:

  • evm t8n的输入输出 JSON 契约(env / alloc / txs → result);
  • 当调用方省略currentDifficulty时,工具如何基于parentDifficulty与父时间戳自动推导 Ethash 难度;
  • London、ArrowGlacier、GrayGlacier 三个分叉在相同高度下难度数值的差异,及其背后的难度炸弹延迟常数(EIP-3554 / EIP-4345 / EIP-5133);
  • 底层 calcDifficulty 的实现细节:合成父区块头、EmptyUncleHash兜底、"仅适用于 Ethash 引擎"的边界。

对于从事 BSC 主网同步、PoW 阶段区块回溯验证或自定义状态转换测试的开发者而言,这个用例既是 t8n 工具链的入门样板,也是一份可复现、可扩展的难度计算参考基准。

【免费下载链接】bscA BNB Smart Chain client based on the go-ethereum fork项目地址: https://gitcode.com/GitHub_Trending/bs/bsc

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询