# 以太坊黄皮书公式解析（下）

By [DavidCai.eth](https://paragraph.com/@davidcai) · 2022-01-14

---

前言与版本
-----

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

公式解析
----

![(130)](https://storage.googleapis.com/papyrus_images/edc6c769555747641737de420ad566d8f2ab4aeea69503ff38a407b02ed137f2.png)

(130)

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

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

![(144)](https://storage.googleapis.com/papyrus_images/c6a0807f7decb0883280cec633d3aea009714d03ac3384ceee1e3298bd63c39a.png)

(144)

*   u 为机器状态（machine state），包含：
    
    *   ug： 剩余可用 Gas 。
        
    *   upc：当前执行程序计数器。
        
    *   um：内存的内容。
        
    *   ui：内存中激活的字节数。
        
    *   us：栈的内容。
        

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

![(145) (146)](https://storage.googleapis.com/papyrus_images/eb2c7a07dec89d72516dcf601aa53f540bcc652d57df07bb574d1882f1a7080d.png)

(145) (146)

*   Z 为异常检查函数。
    

公式 (146) 定义了 W 函数判断，当 w 的指令在 CREATE, CREATE2, SSTORE, SELFDESTRUCT 之一，在 LOG0 - LOG4 之一，或是 CALL 且对应栈索引上的数据不为空时，返回真。

公式 (145) 定义异常状态的可能情况：Gas 费不足、非法指令（指令 w 的 δw 未定义，详细后文会阐述）、栈中参数不足、JUMP\\JUMPI 的目的地非法、新的栈大小超过 1024、企图在 static 调用中修改状态。

![(150)](https://storage.googleapis.com/papyrus_images/3d2dc95c474ba0ed56fa38808ab7d34c6d41f6083d2db45c45c97c413be04169.png)

(150)

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

![(131) - (143)](https://storage.googleapis.com/papyrus_images/1167e5b99237e80acb337d5c7e8d8eb89ff24f8145892767a6c7b0a67c2d9345.png)

(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)](https://storage.googleapis.com/papyrus_images/2f26ee74914f75a456e6c676439d53becb478e7b9ce8ada909be26749ea6eb0a.png)

(149)

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

![(147) (148)](https://storage.googleapis.com/papyrus_images/508f9cfac809fc5adb56c664fc241d1d721b19ca36ad747e47c4145f3247e854.png)

(147) (148)

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

![(151) - (154)](https://storage.googleapis.com/papyrus_images/0780729ca1369c07265dbd616aee1111a26a803072ceba6f5042b9ca1536cc3a.png)

(151) - (154)

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

![(155) (156)](https://storage.googleapis.com/papyrus_images/da0b524bcf22ff9cc116fad33443eaee38959cf0ad8a2c46eaac6f2129e4bf27.png)

(155) (156)

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

![(157) - (160)](https://storage.googleapis.com/papyrus_images/efde7a351bcef5bfb7eed4ef2b5e2e71022d398eb677e9ccc2d5df59c14d3dbc.png)

(157) - (160)

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

![(161) - (162)](https://storage.googleapis.com/papyrus_images/895b3f5956cb94c2a293b64f2369f2175d19fd0d1550d4ccf379dcc80229ce6f.png)

(161) - (162)

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

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

![(165)](https://storage.googleapis.com/papyrus_images/a538d7e7f2c4a0b2eed8c7b63bf8c2550428e1f5a0cfa9078687ff6268973364.png)

(165)

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

![(164)](https://storage.googleapis.com/papyrus_images/778c0dde6076b228ed0d4aa33864e0d02e70895c5e95961db22cfc594093020c.png)

(164)

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

![(163)](https://storage.googleapis.com/papyrus_images/d2db227fdf86a6a868795b23c760ccc18aa38a9fd124a4a7d6b5fb40131a755e.png)

(163)

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

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

![(164)](https://storage.googleapis.com/papyrus_images/44e618c98e71cef731a6932c69816dc1df94e43489a5b9f988a453e583633757.png)

(164)

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

![(165) - (171)](https://storage.googleapis.com/papyrus_images/25bff9407a15fb78e3e4472580bcb328ec11b48ede4ca2a5702bf62e09f0ce0a.png)

(165) - (171)

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

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

![(172)](https://storage.googleapis.com/papyrus_images/db3e5be83848bfcb102064bd049b452e86f6d690ba6773deb1dbcc41598fb87d.png)

(172)

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

![(173)](https://storage.googleapis.com/papyrus_images/d2a90cd14cd62571c59d4a95c40409f0edb12f2f2d087f03361c1e3911b8e0d2.png)

(173)

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

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

![(182)](https://storage.googleapis.com/papyrus_images/11decd059b81cc0c347dad2286ffee51229486f57b5ef56d58f19127b909d0a8.png)

(182)

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

![(174) - (177)](https://storage.googleapis.com/papyrus_images/2973e0df1b8c56035e71e42fd2d964962bdba50e70a15d61dc4d352382712cff.png)

(174) - (177)

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

![(179)](https://storage.googleapis.com/papyrus_images/ec604a63e6991a5d35a0a5394a664153355873f7daa97b48461427bab837cfc3.png)

(179)

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

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

![(180)](https://storage.googleapis.com/papyrus_images/4dfca4c7986bdf58a56ea57b8e04fd5709cbc6d93414825b5f3639c90ce5722b.png)

(180)

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

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

![(181)](https://storage.googleapis.com/papyrus_images/fe90b9319426fe12edaebe4c62fdc7b3041e4e9bdd428b0d652e7a1b041639e7.png)

(181)

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

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

![(178)](https://storage.googleapis.com/papyrus_images/61eb76262c4549e92285e552fec8be1b0397772a11fd46009f13de8a092c2508.png)

(178)

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

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

![(183)](https://storage.googleapis.com/papyrus_images/4df23ddf9cbd41b07a6a52c1485a0f7b96c56f6194ed5f39629bff7755bb8508.png)

(183)

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

---

*Originally published on [DavidCai.eth](https://paragraph.com/@davidcai/nVPeEK7WjV5aguYbG0Rq)*
