DavidCai.eth

Posted on Jan 12, 2022Read on Mirror.xyz

以太坊黄皮书公式解析(中)

前言与版本

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

公式解析

(61) (62) (63)

  • S(T)b 为交易发送者账户余额。
  • S(T)n 为交易发送者账户 nonce 。
  • TgTp 为 GasPrice * GasLimit ,即交易预付款。

公式 (61) 定义了一个交易的起始状态,即会被扣除预付款(公式(62)),并且 nonce + 1 (公式(63))。

(66)

  • g0 为需要支付执行合约的基本费用。

公式 (66) 定义了 g 为交易的 GasLimit 减去执行交易的基本 Gas 数量。

(64) (65)

  • σp 是交易执行后的账户临时状态。
  • g' 是交易后剩余 Gas 。
  • A 是交易子状态。
  • z 是状态代码。
  • To 是交易的原始发起人,若是合约间交易调用,那么该值就是发起调用的合约地址。
  • g 在公式 (66) 已定义。
  • Λ4 是合约创建函数(Tt,即接收者地址为空),该函数定义后文会详细阐述,下标的 4 表示只取原函数返回值的前四个值。
  • Θ4 表示普通交易、发送调用消息函数,该函数定义后文会详细阐述,下标的 4 表示只取原函数返回值的前四个值。
  • T 为一个布尔值,表示对状态进行修改的许可。在后文对 Λ 和 Θ 的入参定义中,通常记作 w 。

公式 (64) 表达了交易后的临时状态 σp,根据是合约创建还是普通交易,而会有不同的入参。公式 (65) 定义当前入参子状态是公式 (55) 中定义的空子状态中各值与当前交易子状态中各值的与集(and)。

(67)

  • Ar 交易应返还的 Gas 数量。

公式 (67) 表示,在交易过程中,调用者若有通过调用 selfdestruct(addr); 销毁合约,则合约内的以太币(balance)会累计到退还余额中。

(68)

  • g* 为总计退还 Gas 数量。

公式 (68) 定义了总退还 Gas 数量的计算,与执行交易后剩余 Gas 数量 g' ,执行合约花费的 Gas 数量 (Tg - g')和包含了销毁合约退还数量的累积计数 A'r 相关。

(69) - (72)

  • σ* 为交易预备最终状态。
  • σp 为交易临时中间状态。
  • BHc 为区块的 beneficiary 值。

公式 (69) - (72) 定义了从交易临时中间状态到预备最终状态的转换。首先在交易者的余额中加上应退还的数量(公式(70))。在矿工的余额中加上消耗的以太币数量(公式(71))。并将矿工收益记录在区块的 beneficiary 值上(公式(72))。

(73) - (75)

  • σ' 为交易最终状态。

公式 (73) - (75) 定义了交易从预备最终状态到最终状态的转换。会先删除需要自毁的合约(公式(74)),再删除接触到的死合约(公式(75)),死合约的定义在公式 (15)。

(76) - (78)

  • Υg 为交易总共使用的 Gas 。
  • Υl 为交易所创建的所有日志。
  • Υz 为交易状态码。

公式 (76) - (78) 给出了三个交易相关状态的定义。

(79)

  • ζ 为合约创建时可选的一个参数,salt ,用于创建可预见的地址。使用场景可参阅这篇文章

公式 (79) 定义了创建合约时的参数 salt 是可选的,若提供 slat,需要是一个长度为 32 的字节。

(80)

  • Λ 为创建合约函数。
  • σ, A, s, o, g, p, v, i, e, ζ, w 为创建合约函数的参数,分别是:状态,子状态,发送者,原始发送者(考虑通过合约创建的情况),GasLimit,GasPrice,EVM 初始代码化代码,创建合约的调用所处的当前栈深度,salt 和 对状态进行修改的许可。
  • σ', g', A', z, o 为函数返回值,分别是:新的状态,剩余 Gas ,新的子状态,状态码,输出(output)内容。

公式 (80) 给出了创建合约函数的定义(输入,输出)。

(81) - (83)

  • s 为交易发起者(sender)地址。
  • n 为发起交易的 nonce 。
  • ζ 已在公式 (79) 定义。
  • i 为 EVM 初始化代码。

公式 (81) - (83) 为合约地址的产生规则。首先定义了函数 LA ,若未提供 salt ,则输出 n 与 s 的 RLP 编码结果,否则输出 (255) · s · ζ · KEC(i)(公式(83)),这也意味着同一个账户,对于同一个合约代码,可以创建可预见的合约地址。然后将 LA 的输出结果经过 Keccak-256 哈希后,取右边 160 位(公式(82))即可得到地址(公式(82))。公式 (81) 则是描述了需带入的实际参数。

(84)

公式 (84) 则表示新创建的合约地址,会被存入 Aa 交易子状态中(于公式 (54) 中定义)。

(89)

公式 (89) 定义了 v' ,若地址在之前就有余额,则会继承。

(85) - (88)

公式 (85) 定义了新的世界状态,在创建的地址上会出现一个新的合约,nonce 为 1, 余额为 v' 加上创建交易传入的以太币,以及空的 storageRoot 和 codeHash (公式(86))。创建者的地址上会扣除发送的以太币(公式(88)),然后保存其状态(公式(87))。

(90)

  • Ξ 为执行初始化 EVM 代码的代码执行函数。
  • σ** 合约初始化后的状态。
  • g** 为初始化后可用的剩余 Gas 。
  • A 为累计子状态。
  • o 为账户代码。
  • I 为一系列输入参数变量,会在后文阐述。

公式 (90) 定义了初始化代码执行函数的输入与输出。

(91) - (99)

  • I 为 Ξ 的其中一个参数,包含一系列变量。
  • Ia 为新创建合约的地址。
  • Io 为交易的原始发起人。
  • Ip 为交易的 Gas Price 。
  • Id 函数调用参数,这里为空,这个交易不可能会有具体函数调用参数 Input 。
  • Is 交易发送者(sender)地址。
  • Iv 发送给合约的初始以太币。
  • Ie 当前调用栈深度。
  • Iw 对状态进行修改的许可。

公式 (91) - (99) 定义了参数 I 所包含的项。

(100)

公式 (100) 表示合约创建开销,与合约代码大小成正比。

(101) - (105)

公式 (105) 定义了创建失败的一些场景:原地址不为空,且有 codeHash 或有 nonce;创建合约代码为空;gas 费不足;代码过大;

公式 (104) 定义了状态码 z ,如果创建失败,则为 0 ,成功则为 1 。

公式 (102) (103) 定义了,若创建失败,则不更新状态和子状态。

公式 (101) 定义了若创建失败,则不会收取代码创建开销。

(106)

  • Θ 为消息调用函数
  • σ, A, s, o, r, c, g, p, v, v˜ d, e, w 为消息调用函数参数,分别为:执行当前世界状态,子状态,发送人(sender)地址,原始发送者(考虑通过合约创建的情况),接收地址,执行代码位置(通常与 r 一致),GasLimit,GasPrice,发送的以太币数量(msg.value),通过 DELEGATECALL 而出现在新的执行上下文中的以太币数量,调用入参 input data 数据,当前堆栈深度和对状态进行修改的许可。
  • σ', g', A', z, o 为函数返回值,分别是:新的状态,剩余 Gas ,新的子状态,状态码,输出(output)内容。

(107)

公式 (107) 定义了 a1 这个交易中的第一个临时状态:除非发送者和接收者是同一个地址,否则跟随交易发送的以太币(msg.value)会被发送。

(108) - (113)

公式 (108) - (113) 描述的是公式 (107) 的具体流程。如果账户 a1[r] 是一个新地址,则会对账户进行状态初始化(公式(112)),并且在余额中加上交易转入的以太币(公式(113)),然后更新到临时状态(公式(111))。相应地,在发送人那边也减少对应的以太币(公式(110)),更新临时状态(公式(108))。

(114) - (128)

  • I 中各参数的定义可以参阅公式 (91) - (99) 。

公式 (118) 定义了执行消息调用函数 Ξ ,将会输出 σ** (执行后世界状态),g**(执行后剩余 Gas),A**(执行后子状态码)和 o (调用结果输出(output))。公式 (127) 描述了定义在地址 1 - 8 上的预留函数(参阅黄皮书附录 E),以及常规执行函数。公式 (120) 表达了客户端在部署完合约后,会在本地存储交易调用代码的地址及其哈希。公式 (114) - (117) 则表达了会根据 Ξ 函数输出的 σ** (执行后世界状态)是否为空来判断是否要用 Ξ 函数的其他输出来更新自身对应状态。

(129)

公式 (129) 定义了公式 (127) 中的地址集合为 π 。