前言与版本

笔者最近在结合以太坊黄皮书以太坊源码,结合自己的理解解析下黄皮书内的公式,与大家共同学习进步,若大家在阅读以太坊黄皮书时,对公式产生理解上的困惑,可以参阅本文一起看。文章基于当前(2022/1/10)的黄皮书,版本号为 BERLIN VERSION fabef25 ,若有不准确之处,欢迎指出。由于该黄皮书内除附录外有 183 个公式,为了让文章篇幅不过长,该解析会由三部分组成一个系列,每个系列解析约 60 个公式,本文为下篇。

公式解析

(130)

  • 此处 Ξ 为 EVM 指令执行函数。
  • σ 为执行前世界状态。
  • σ’ 为执行后世界状态。
  • g 为执行前 Gas 剩余。
  • g’ 为计算后 Gas 剩余。
  • I 所包含的字段参阅公式 (91) - (99)。
  • A 为执行后子状态。
  • o 为执行输出内容(output)。

公式 (130) 给出了 EVM 执行函数的输入输出定义。

(144)

  • u 为机器状态(machine state),包含:
    • ug: 剩余可用 Gas 。
    • upc:当前执行程序计数器。
    • um:内存的内容。
    • ui:内存中激活的字节数。
    • us:栈的内容。

公式 (144) 定义了当前待执行指令 w ,即为待执行指令集位于索引 upc 中的指令,或者为 STOP 。

(145) (146)

  • Z 为异常检查函数。

公式 (146) 定义了指令 W 函数,当 w 的指令在 CREATE, CREATE2, SSTORE, SELFDESTRUCT 之一,在 LOG0 - LOG4 之一,或是 CALL 且对应栈索引上的数据不为空时。
公式 (145) 定义异常状态的可能情况:Gas 费不足、非法指令(指令 w 的 δw 未定义,详细后文会阐述)、栈中参数不足、JUMP\JUMPI 的目的地非法、新的栈大小超过 1024、企图在 static 调用中修改状态。

(150)

公式 (150) 定义了正常停止函数 H 。当指令会 RETURN 或 REVERT 时,会返回一个特殊函数 HRETURN 。若是 STOP 或 SELFDESTRUCT,则返回空序列,或者则返回空集合(表示返回不正常)。

(131) - (143)

公式 (140) 定义了 o 为正常停止函数 H 对当前机器状态和入参 I 的输出。
公式 (141) 定义了 · 操作会将其之后的项放入集合中。
公式 (142) (143) 表示机器状态的更新会伴随 Gas 的消耗。
公式 (139) 定义了指令执行函数 X ,它会被递归调用(公式中第 4 种情况),除非检测到异常(通过异常检查函数 Z 检查)、操作为 REVERT 或正常停止(通过正常停止函数 H 检查)。
公式 (133) - (138) 定义了机器状态 u 的初始状态。
公式 (131) - (132) 表示 Ξ EVM 执行函数的具体逻辑为对入参进行 X 函数(即公式(139))的调用。

(149)

公式 (149) 定义了 N 函数,接受当前指令索引以及指令,返回下一个有效指令在代码中的位置。会区分给定的指定入参是否为 PUSH1, PUSH2 。

(147) (148)

公式 (147) 的 D 函数返回正在运行的代码所给定的有效跳转地址集合。如果位置越界,则返回空集合。如果指令为 JUMPDSET ,则会递归调用 N 函数保存到一个位置集合,否则直接调用 N 函数返回下一个位置。

(151) - (154)

公式 (151) - (154) 描述了指令的栈执行模型,栈变化大小由入栈指令个数减去出栈指令个数(公式(152))。公式 (153) 表示栈是低位索引(lower-indexed)的,即栈顶索引为 0 。公式 (154) 则表示在指令从索引 0 开始往栈内推入后,会从栈顶开始一条条执行指令,索引也会逐渐变小。

(155) (156)

公式 (155) 表示,随着不同计算的执行,ug 会被相应的消耗。公式 (156) 则表示,upc 会根据是否是 JUMP/JUMPI 来判断,或者是调用公式 (149) 定义的 N 函数。

(157) - (160)

公式 (157) - (160) 则表示,假定在执行中,机器状态内的,内存(m, i),子状态(A)和世界状态(σ)不会改变。

(161) - (162)

  • Bt 为链上的总难度。
  • Bd 为当前区块难度。

公式 (162) 定义了 B’ 为当前区块的父区块。公式 (161) 则表达了链上的总难度是累计的。

(165)

公式 (165) 定义了如何判断两个区块是否是兄妹(sibling):即两个区块的父区块相同,两个区块自身是不同区块,相互不为叔块。

(164)

公式 (164) 定义了如果判断两个区块是否是亲属(kin):如果 n 代之内是兄妹(sibling),则为亲属。

(163)

  • V 为区块头验证函数,参阅公式 (52) 。

公式 (163) 表示,当前区块最多可以引用两个叔块,叔块都验证有效,且叔块都与当前区块在 6 代以内。

(164)

公式 (164) 表示,区块头中记录的总累计 Gas 使用量,必须为区块中最后一个区块执行后的累计 Gas 使用量相同。

(165) - (171)

  • Rblock 为当前挖出一个区块所能获得的奖励。

公式 (171) 定义了当初挖出这个叔块的矿工的收益,从公式可以看出,该叔块离当前区块越亲,则奖励越高,并且会把奖励加入到账户状态中(公式(170)),前提是收益账户不是空账户(公式(169))。公式 (168) 定义了挖出当前区块的矿工收益,除了固定的挖出区块所得收益之外,也与其引用的叔块的数量相关。公式 (167) 则将上述这些行为,统一定义为区块奖励函数 Ω 。

(172)

公式 (172) 定义了不同以太坊版本中,挖出区块的固定收益。

(173)

  • LS 为世界状态函数,具体定义参阅公式 (10) 。

公式 (173) 定义了函数 Γ ,映射区块 B 至它的初始世界状态。若区块 B 没有父区块(即它就是创世区块),那么为创世状态。否则就是它的初始世界状态就是其父区块的 storageRoot 。

(182)

公式 (182) 定义了区块状态转换函数 Π ,定义是从最新的世界状态,再加上接收过 Ω 函数奖励后的区块的状态。

(174) - (177)

公式 (174) - (177) 定义了区块状态转换函数 Φ :首先更新当前区块的 storageRoot 为父区块的世界加上区块中所有交易对状态的改变(公式(177));将矿工挖矿算出的最终结果记录在区块的 mixHash 中(公式(176));更新区块的 nonce (公式(175)),且最大值由区块的 difficulty 限定。

(179)

  • Υg 为区块交易 Gas 花销状态计算函数。

公式 (179) 表达了区块的 Gas 花销,会随着一个个交易的处理而累积。

(180)

  • Υl 为区块交易日志状态计算函数。

公式 (180) 表达了区块的日志,会随着一个个交易的处理而累积。

(181)

  • Υz 为区块交易状态码计算函数。

公式 (181) 表达了区块的状态码,会随着一个个交易的处理而更新。

(178)

  • Υ 为区块交易整体状态计算函数。

公式 (178) 是公式 (179) - (181) 定义了 σ[n] 为基于区块内的第 N 个交易后所产生的状态。表示区块状态会从区块的初始状态开始,通过一个个交易来累积更新状态。

(183)

公式 (183) 描述了工作量证明函数 PoW ,接受三个参数,第一个为不包含 nonce 和 mixHash 的新区块头状态,第二个参数为当前区块头 nonce,第三个参数为当前 DAG (用来计算 mixHash 的大型数据集合),输出 mixHash 和 nonce 。nonce 的最大值由区块的 difficulty 限定。