6

以太坊黄皮书学习笔记

 2 years ago
source link: https://learnblockchain.cn/article/3139
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这是此前学习以太坊黄皮书的笔记,因为结合了 go-ethereum 源码,并且笔记中杂合了一些测试和脚本以加深理解,结果笔记比较混乱..

最近觉得还是稍微整理发出来,抛砖引玉~

版本基于 VERSION 80085f7 – 2021-07-11

黄皮书混用了 = 和 ≡,时而表示赋值,时而表示等价指代,注意区分

2. The Blockchain Paradigm 区块链范式

以太坊可以看作是一个交易驱动的状态机

公式 (1)

σt+1​≡Υ(σt​,T)
  • Υ 某次状态转移的函数
  • 挖矿
    • 通过付出一定的工作量,与其他潜在区块竞争 一系列交易(一个区块) 的记账权
  • 系统激励
    • 以状态转换函数的形式,给指定账户增加 ETH

公式 (2) (3) (4)

σt+1​BΠ(σ,B)​≡Π(σt​,B)≡(...,(T0​,T1​,...),...)≡Ω(B,Υ(Υ(σ,T0​),T1​)...)​
  • Ω 区块奖励状态转换函数,参考公式 (169) (170) (171) (172)
  • B 表示一个区块,包含一系列交易和一些其他组成部分
  • Π 区块状态转换函数,参考公式 (177)

2.2 Which History? 如何选择历史?

agreed-upon scheme

  • blocktree 区块树
    • 去中心化
      • 每个参与者都有机会在前一个区块后,自己创建一个新的区块
      • 形成一棵树(blocktree)
  • 共识
    • 如何从根节点到叶节点形成一条区块链
  • 分叉
    • 无法达成共识
    • 各节点认可的 根节点到叶节点的路径(最佳区块链) 不同

β chain ID,参考 EIP-155: Simple replay attack protection

3. Conventions 约定

  • σ 世界状态(world-state)
  • μ 虚拟机状态(machine-state)
  • Υ 状态转换函数
  • C 费用,例如CSSTORE​ 是执行 SSTORE 操作的费用
  • KEC Keccak-256
  • T 一笔以太坊交易
  • n nonce 用于防止重放攻击
  • o 消息调用的输出
  • B32​ 长度为32的字节序列
  • N256​ 所有比 2^256 小的正整数
  • μs​[0] 虚拟机栈(stack)的栈顶元素
  • μm​[0..31] 虚拟机内存(memory)中的前32个条目
  • δ某个 opcode 的出栈个数

熟悉下面三种表示方式,对于后文理解状态转换非常关键

  • □ 原始值/状态
  • □′ 最终值/状态
  • □∗, □∗∗, ... 中间值/状态

公式 (6) ℓ 求序列的末尾元素

ℓ(x)≡x[∥x∥−1]

4. Blocks, State and Transactions 区块,状态与交易

4.1 World Stage 世界状态

  • 维护 账户地址 及其 账户状态 的映射
    • a
      • 大小为160位(20字节)
    • σ[a]
      • 含义
        • 编码方式 RLP
          • Recursive Length Prefix
          • 递归长度前缀编码
        • 用于序列化数据后传递到网络或存储
  • 注意
    • 世界状态不直接存储在区块链上

σ[a] 账户状态

  • σ[a]n​
    • nonce
    • 含义
      • 如果账户是钱包地址(非合约账户),表示由此账户发出的交易数量
      • 如果账户是智能合约,表示由此账户创建的合约数量
  • σ[a]b​
    • balance
  • σ[a]s​
    • storageRoot
    • 含义
      • 如果账户是智能合约
        • 每个账户有自己的一棵 MRT 树,存储合约的内部状态(变量)
          • MPT (Merkle Patricia Tree)
            • Merkle Tree + Patricia Tree
        • storageRoot 就是树的根节点
  • σ[a]c​
    • codeHash
    • 含义
      • 账户持有的代码 bytecode 的 hash
        • 创建后不可被修改
        • 没有代码表示智能合约
    • 推导
      • 用 b 来表示 代码
      • 则 KEC(b)=σ[a]c​

公式 (7)

TRIE(LI∗​(σ[a]s​))≡σ[a]s​

σ[a]s​ 表示账户的所有内部状态组成的 TRIE 树的根节点

对这棵树的所有节点做坍塌转换,即可得到根节点的 hash 值

其中 公式 (8) (9)

LI​((k,v))≡(KEC(k),RLP(v))
k∈B32​∧v∈N

(10) 世界状态坍塌函数 LS​

LS​(σ)≡{p(a):σ[a]​=∅}

其中 公式 (11)

p(a)≡(KEC(a),RLP((σ[a]n​,σ[a]b​,σ[a]s​,σ[a]c​)))

函数 LS​ 和 Trie 函数一起用来提供一个世界状态的简短标识(hash)

我们假定:

公式 (12)

∀a:σ[a]=∅∨(a∈B20​∧v(σ[a]))

公式 (13) v 表示账户有效性的验证函数

v(x)≡xn​∈N256​∧xb​∈N256​∧xs​∈B32​∧xc​∈B32​
  • xn​ nonce
  • xb​ balance
  • xs​ storageRoot
  • xc​ codeHash

公式 (14)

EMPTY(σ,a)≡σ[a]c​=KEC(())∧σ[a]n​=0∧σ[a]b​=0

EMPTY 账户

  • 没有 code
  • 且 nonce 为0
  • 且 balance 为0

公式 (15)

DEAD(σ,a)≡σ[a]=∅∨EMPTY(σ,a)

DEAD 账户

  • 账户对应的状态不存在
  • 或者它是 EMPTY 账户

4.2 The Transaction 交易

两种交易类型

  • 消息调用 message call
  • 合约创建 contract creation
  • Tn​
    • nonce
    • 由交易发送者发送的交易数量
  • Tp​
    • gasPrice
    • gas 单位价格
  • Tg​
    • gasLimit
    • 执行这个交易的最大 gas
  • Tt​
    • 交易接收地址
  • Tv​
    • value
    • 含义
      • 如果是消息调用交易
        • 转移到交易接收者的 wei 的数量
      • 如果是合约创建交易
        • 对新建合约的捐款
  • Tw​ , Tr​ , Ts​
    • v, r, s
    • 含义
      • ECDSA 签名的3个组成部分
      • 可以从中推导交易发起者 from address
  • Ti​
    • 含义
      • 是一段 EVM-code,它将返回 body,这是这个账户每次接收到消息调用时回执行的代码
      • init 代码仅在合约创建时被执行一次,然后就被丢弃
  • Td​
    • 含义
      • 用于指定消息调用的输入数据

公式 (16)

LT​(T)≡{(Tn​,Tp​,Tg​,Tt​,Tv​,Ti​,Tw​,Tr​,Ts​)(Tn​,Tp​,Tg​,Tt​,Tv​,Td​,Tw​,Tr​,Ts​)​ifTt​=∅otherwise​

在这里,我们假设除了任意长度的字节数组 Ti​ 和 Td​ 以外,所有变量都是作为整数来进行 RLP 编码

公式 (17)

​Tn​∈N256​Tg​∈N256​Ts​∈N256​​∧∧∧​Tv​∈N256​Tw​∈N256​Td​∈B​∧∧∧​Tp​∈N256​∧Tr​∈N256​∧Ti​∈B​

公式 (18)

Nn​={P:P∈N∧P<2n}

公式 (19)

Tt​∈{B20​B0​​ifTt​​=∅otherwise​

对于合约创建交易,Tt​ 是 空字节 的 RLP

4.3 The Block 区块

Block

  • H
    • 当前区块的区块头
  • T
    • 当前区块内的一系列交易
  • U
    • 当前区块内的叔块头列表

H (block header)

  • Hp​
    • parentHash
    • 父区块 block header 的 hash
  • Ho​
    • ommersHash
    • 当前区块的叔块列表的 hash
  • Hc​
    • beneficiary
    • 因为挖到当前区块而获得奖励收益的账户地址
  • Hr​
    • stateRoot
      • state trie 根节点的 hash
        • 交易被执行完且区块定稿后的状态
        • 区块内的所有交易得到的状态,组成一棵树
  • Ht​
    • transactionsRoot
      • transaction trie 根节点的 hash
        • 当前区块中所有交易组成的一棵树
          He​
    • receiptsRoot
      • receipt trie 根节点的 has
        • 当前区块中所有交易的收据组成的一棵树
  • Hb​
    • logsBloom
    • 当前区块中所有交易的收据数据中的可索引信息(产生日志的地址和日志主题)组成的 Bloom 过滤器
  • Hd​
    • difficulty
    • 含义
      当前区块的难度水平
      根据前一个区块的难度水平和时间戳计算得到
  • Hi​
    • number
    • 当前区块的祖先的数量
  • Hl​
    • gasLimit
    • 目前每个区块的 gas 开支上限
  • Hg​
    • gasUsed
    • 当前区块中所有交易所用掉的 gas 之和
  • Hs​
    • timestamp
    • 当前区块初始化时的 Unix 时间戳
  • Hx​
    • extraData
    • 含义
      • 与当前区块相关的任意字节数据
      • 必须在 32 字节以内
  • Hm​
    • mixHash
    • 含义
      • 一个 hash 值
      • 用于与 Hn​ 一起证明当前区块已经承载了足够的计算量
  • Hn​
    • nonce
    • 含义
      • 一个64位的随机整数
      • 用于与 Hm​ 一起证明当前区块已经承载了足够的计算量
    • 注意
      • 这里的 nonce 是个随机数,与账户下的 nonce 定义不同

公式 (20)

B≡(BH​,BT​,BU​)

4.3.1 Transaction Receipt 交易收据

每个交易一定会有一条对应的收据

  • Transaction Receipt
  • 含义
    • 每个交易执行过程中的特定信息,会被编码为交易收据
  • 用途
    • 零知识证明
  • BR​[i]
    • 当前区块第 i 个交易的收据
  • receiptsRoot
  • 含义
    • 区块内的收据组成 receipt trie
    • 其根节点的 hash 存储在 He​ 中

公式 (21)

R≡(Rz​,Ru​,Rb​,Rl​)

R 一条收据的组成

  • Ru​
    • 当前区块中,交易发生后的累积 gas 使用量
  • Rl​
    • 交易过程中创建的一系列日志
  • Rb​
    • 256字节大小的 hash
    • 由一系列日志构成的 Bloom 过滤器
  • Rz​
    • 交易的状态码

公式 (22)

Rz​∈N

公式 (23)

Ru​∈N∧Rb​∈B256​

公式 (24)

O≡(Oa​,(Ot​0​,Ot​1​,...),Od​)

只有被调用的智能合约,自身显式调用 LOG0LOG1LOG2LOG3LOG4 才会生成日志

  • 由一系列日志组成
  • (O0​,O1​,...)

O 一条日志的组成

  • Oa​
    • 当前被调用的智能合约的地址 (一定不会包括 EOA 地址)
    • 跟随消息调用上下文变化
    • 例子
      • EOA -> ContracA -> ContractB -> ContractC
  • Ot​
    • 一系列日志主题
// github.com/ethereum/[email protected]/core/vm/instructions.go

// make log instruction function
func makeLog(size int) executionFunc {
	return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
		topics := make([]common.Hash, size)
		stack := scope.Stack
		mStart, mSize := stack.pop(), stack.pop()
		for i := 0; i < size; i++ {
			addr := stack.pop()
			topics[i] = addr.Bytes32()
		}

		d := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
		interpreter.evm.StateDB.AddLog(&types.Log{
			Address: scope.Contract.Address(),
			Topics:  topics,
			Data:    d,
			// This is a non-consensus field, but assigned here because
			// core/state doesn't know the current block number.
			BlockNumber: interpreter.evm.Context.BlockNumber.Uint64(),
		})

		return nil, nil
	}
}

公式 (25)

Oa​∈B20​∧∀x∈Ot​:x∈B32​∧Od​∈B

公式 (26)

  • 定义
    • 计算一条日志 O 的摘要函数
  • 输入
    • x∈{Oa​}∪Ot​
      • 取并集
        • 日志地址 Oa​
          • 转成集合 {Oa​}
        • 一系列日志主题

  • 输出
    • 256字节的哈希
M(O)≡⋁x∈{Oa​}∪Ot​​(M3:2048​(x))

M3:2048​ 是个大小为256字节,共2048位的 Bloom 过滤器

  • 对于输入数据,计算它的 Keccak-256 哈希值
  • 取哈希值的前6个字节,两两组成一对
    • 一对字节有16个比特
    • 一共有3对
  • 遍历这3对字节
    • 取低11位,得到索引,则索引范围是 [0, 2047](2^11 == 2048)
    • 设置 Bloom 过滤器对应索引的位为1
  • 因此
    • 对于输入数据,会设置过滤器的随机3位

公式 (27) (28) (29) (30)

M3:2048​(x:x∈B)y∀i∈{0,2,4}m(x,i)​≡y:y∈B256​where:=(0,0,...,0)except::Bm(x,i)​(y)=1≡KEC(x)[i,i+1]mod2048​
  • B 表示位引用函数
  • Bj​(x)=1 表示设置字节数组 x 中的第 j 位(从0开始) 为1

如果仅看黄皮书中关于几个 LOG$ 的 opcode 说明,会误以为过滤器仅包含日志,容易对使用场景产生困惑。实际上,在 go-ethereum 实现中,会把产生日志的合约地址 log.Address 也记录在过滤器

// github.com/ethereum/[email protected]/core/types/bloom9.go

// CreateBloom creates a bloom filter out of the give Receipts (+Logs)
func CreateBloom(receipts Receipts) Bloom {
    buf := make([]byte, 6)
    var bin Bloom
    for _, receipt := range receipts {
        for _, log := range receipt.Logs {
            bin.add(log.Address.Bytes(), buf)
            for _, b := range log.Topics {
                bin.add(b[:], buf)
            }
        }
    }
    return bin
}

4.3.2 Holistic Validity 整体有效性

公式 (31)

Hr​Ho​Ht​He​Hb​​≡TRIE(LS​(Π(σ,B)))≡KEC(RLP(LH∗​(BU​)))≡TRIE({∀i<∥BT​∥,i∈N:p(i,LT​(BT​[i]))})≡TRIE({∀i<∥BR​∥,i∈N:p(i,BR​[i])})≡⋁r∈BR​​(rb​)​∧∧∧∧​
  • Hr​
    • storageRoot
  • Ho​
    • ommersHash
  • Ht​
    • transactionsRoot
  • He​
    • receiptsRoot
  • Hb​
    • logsBloom

其中 公式 (32)

p(k,v)≡(RLP(k),RLP(v))

而且 公式 (33)

TRIE(LS​(σ))=P(BH​)H​r​
  • P(BH​)
    • 表示区块 B 的父区块
  • Π(σ,B))
    • 参考公式 (4)
    • 参考公式 (177)
  • LS​
    • 世界状态坍塌函数
    • 参考公式 (10)
  • LH∗​
    • 参考公式 (34)和(36))
  • LT​
    • 参考公式 (16)

4.3.3 Serialization 序列化

公式 (34) 定义 H 的序列化函数 LH​ 如下

LH​(H)≡(Hp​,Ho​,Hc​,Hr​,Ht​,He​,Hb​,Hd​,Hi​,Hl​,Hg​,Hs​,Hx​,Hm​,Hn​)

公式 (35) 则 B 的序列化函数 LB​ 为

LB​(B)≡(LH​(BH​),LT∗​(BT​),LH∗​(BU​))

公式 (36)

  • LT​ 参考公式 (16)
  • LH​ 参考公式 (34)

LT∗​ 和 LH∗​ 分别是它们的 reduce 函数

reduce 函数定义如下: 对集合中的每一个元素,分别执行指定函数,得到的结果组成一个集合

f∗((x0​,x1​,...))≡(f(x0​),f(x1​),...)for any functionf

公式 (37) 值域/约束

​Hp​∈B32​Hr​∈B32​Hb​∈B256​Hl​∈NHx​∈B​∧∧∧∧∧​Ho​∈B32​Ht​∈B32​Hd​∈NHg​∈NHm​∈B32​​∧∧∧∧∧​Hc​∈B20​He​∈B32​Hi​∈NHs​∈N256​Hn​∈B8​​∧∧∧∧​

公式 (38)

Bn​={B:B∈B∧∥B∥=n}

4.3.4 Block Header Validity 区块头验证

公式 (39) 根据区块头 H 找到其父区块

P(H)≡B′:KEC(RLP(BH′​))=Hp​

公式 (40) 区块头 H 中的区块编号(block number)计算方式为

Hi​≡P(H)H​i​+1

公式 (41) 根据区块头 H 计算权威难度值的公式

D(H)≡{D0​max(D0​,P(H)H​d​+x×ς2​+ϵ)​ifHi​=0otherwise​
  • P(H)H​d​
    • 以父块难度作为难度调整基数
  • x×ς2​
    • 用于自适应难度调整,维持稳定的出块速度
  • ϵ
    • 表示设定的难度炸弹
// github.com/ethereum/[email protected]/consensus/ethash/consensus.go
func makeDifficultyCalculator(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int;

// github.com/ethereum/[email protected]/consensus/ethash/difficulty.go
func MakeDifficultyCalculatorU256(bombDelay *big.Int) func(time uint64, parent *types.Header) *big.Int;

注意两个函数,黄皮书与具体实现存在出入,主要是在 ϵ 的处理上与公式 (41) 不同,实现为

D(H)≡{D0​max(D0​,P(H)H​d​+x×ς2​)+ϵ​ifHi​=0otherwise​

公式 (42) 创世区块的难度值

D0​≡131072

$2^{17} = 131072$

公式 (43) 难度值调整的单位

x≡⌊2048P(H)H​d​​⌋

公式 (44) 难度值调整的系数

ς2​≡max(y−⌊9Hs​−P(H)H​s​​⌋,−99)
y≡{12​if∥P(H)U​∥=0otherwise​
  • y
    • 如果父区块中包含叔父块,则难度调整会大一个单位 (从1调整为2)
    • 因为包含叔父块时发行的货币量大,需要适当提高难度以保持货币发行量稳定
  • −99
    • 难度一次最多调整 −99 个单位
    • 主要是应对被黑客攻击或其他目前想不到的黑天鹅事件
  • y−⌊9Hs​−P(H)H​s​​⌋
    • Hs​
      • 本区块的时间戳,以秒为单位
    • P(H)H​s​
      • 父区块的时间戳,以秒为单位
    • 规定
      • Hs​>P(H)H​s​
    • 自适应调整
      • 出块时间过短则调大难度
      • 出块时间过长则调小难度
    • 例子
      • 出块时间在 [1,8] 之间
        • 出块时间过短
        • 难度调大一个单位
      • 出块时间在 [9,17] 之间
        • 出块时间可以接受
        • 难度保持不便
      • 出块时间在 [18,26] 之间
        • 出块时间过长
        • 难度调小一个单位

公式 (45) (46) (47) 难度炸弹

ϵ≡⌊2⌊Hi′​÷100000⌋−2⌋
Hi′​≡max(Hi​−κ,0)
κ≡⎩⎪⎪⎨⎪⎪⎧​300000050000009000000​ifFByzantium​⩽Hi​<FConstantinople​ifFConstantinople​⩽Hi​<FMuirGlacier​ifHi​⩾FMuirGlacier​​
FHomestead​FByzantium​FConstantinople​FMuirGlacier​​≡1150000≡4370000≡7280000≡9200000​

为什么设置难度炸弹?

  • 降低迁移到 PoS 协议时发生 fork 的风险
  • 到时挖矿难度非常大,矿工将被迫迁移到 PoS 协议
  • ϵ
    • 是2的指数函数,每十万个块扩大一倍
    • 后期增长非常快 ("炸弹")
  • Hi′​
    • 低估了 PoS 协议的开发难度,导致一再延迟
    • 炸弹威力显现后,通过假区块号(回退区块号)来降低难度
      • 同时把区块奖励从5个 ETH 降为3个 ETH

Difficulity Bomb

参考 what-is-the-difficulty-bomb

公式 (48) gasLimit 约束

Hl​<P(H)H​l​+⌊1024P(H)H​l​​⌋∧Hl​>P(H)H​l​−⌊1024P(H)H​l​​⌋∧Hl​⩾5000

公式 (49) timestamp 约束

Hs​>P(H)H​s​

公式 (41)难度计算公式的设计,保证了难度根据出块间隔长短的动态平衡

  • 如果最近的两个区块间隔较短,则会导致难度值增加,因此需要额外的计算量,大概率会延长下个区块的出块时间
  • 相反,如果最近的两个区块间隔过长,难度值和下一个区块的预期出块时间也会减少

公式 (50) nonce / mixHash 约束

必须同时满足

n⩽Hd​2256​∧m=Hm​with(n,m)=PoW(Hn​​,Hn​,d)
  • Hn​​
    • 当前区块 header H
    • 但不包含 nonce 和 mixHash components
  • d
    • 当前 DAG
    • 用于计算 mixHash Hm​
  • PoW
    • 工作量证明函数
    • 保证
      除了列举所有的可能性,没有更好的其他方法来找到一个低于要求阈值的 nonce
      攻击者必须拥有超过网络一半的算力,才能比其他人更快地找到 nonce

公式 (51)

综上所述,block header 的验证函数 V(H) 定义如下:

V(H)≡where​n⩽Hd​2256​∧m=Hm​Hd​=D(H)Hg​≤Hl​Hl​<P(H)H​l​+⌊1024P(H)H​l​​⌋Hl​>P(H)H​l​−⌊1024P(H)H​l​​⌋Hl​⩾5000Hs​>P(H)H​s​Hi​=P(H)H​i​+1∥Hx​∥≤32(n,m)=PoW(Hn​​,Hn​,d)​∧∧∧∧∧∧∧∧​

此外,extraData 最多为32字节

5. Gas and Payment Gas 与支付

一般来说,用于支付交易的 gas 费用,会被发往 beneficiary 地址,这个地址由矿工设置

6. Transaction Execution 交易执行

Υ 交易状态转换函数

Υ 在执行前,检查如下条件:

  1. 交易以 RLP 格式正确编码,且没有尾随多余的数据
  2. 交易签名正确
  3. nonce 有效(即等于交易发送者账户当前的 nonce)
  4. gas limit 不小于 g0​ (计算公式参考(55) (56) (57))
  5. 发送者账户 balance 不小于实际费用 v0​ (计算公式参考(58))

公式 (52) 交易状态转换函数

σ′=Υ(σ,T)
  • σ′
    • 交易完成后的世界状态
  • Υg
    • 交易实际消耗的 gas
  • Υl
    • 交易执行时产生的一系列日志
  • Υz
    • 交易返回的状态码

6.1 Substrate 子状态

公式 (53) 交易执行过程中产生的子状态

A≡(As​,Al​,At​,Ar​)
  • As​
    • 自毁集合
      • 交易执行完成后会被销毁的账户
  • Al​
    • 一系列日志
      • 方便外界旁观者简单跟踪合约调用
  • At​
    • 交易接触的账户
      • 其中的 EMPTY 账户会被删除
  • Ar​
    • refund balance
      • 累计需要归还的 gas

公式 (54) 空的交易子状态

A0≡(∅,(),∅,0)

6.2 Execution 执行

  • σ 原始状态
  • σ0​ 检查点状态
    • 发送者 balance 扣除 Tg​Tp​
    • 发送者 nonce 累加
  • σP​ 执行交易后临时状态

  • σ∗ 预备最终状态
    • 剩余的 gas 返回给发送者
    • 消耗的 gas 发给 beneficiary(由打包区块的矿工指定)
  • σ′ 最终状态
    • 删除自毁集合账户
    • 删除接触账户集合中的空账户

公式 (55) (56) (57) g0​ 交易执行前预付的基础 gas

g0​≡i∈Ti​,Td​∑​{Gtxdatazero​Gtxdatanonzero​​ifi=0otherwise​+{Gtxcreate​0​ifTt​=∅otherwise​+Gtransaction​
  • Ti​
    • 合约创建的 initcode
  • Td​
    • 消息调用的 inputdata
  • Gtxcreate​
    • 如果是合约创建,需要计入成本

公式 (58) v0​ 交易执行前预付的费用

v0​≡Tg​Tp​+Tv​

公式 (59)

S(T)σ[S(T)]Tn​g0​v0​Tg​​​=∅∧​=∅∧=σ[S(T)]n​∧⩽Tg​∧⩽σ[S(T)]b​∧⩽BH​l​−ℓ(BR​)u​​
  • S(T)
    • 根据交易查找其发送者的函数
  • Tg​
    • 交易的 gasLImit
  • ℓ(BR​)u​
    • 当前区块累计已经消耗的 gas
  • BH​l​
    • 当前区块的 gasLimit

公式 (60) (61) (62) 检查点状态 σ0​

σ0​σ0​[S(T)]b​σ0​[S(T)]n​​≡σexcept:≡σ[S(T)]b​−Tg​Tp​≡σ[S(T)]n​+1​

检查点状态 σ0​ 预扣了 Tg​Tp​

系统会在交易执行成功后,将剩余 gas 返还发送者

注意 XXX

公式 (61) 计算检查点状态 balance 时,不是扣除 v0​,而是只扣了 Tg​Tp​,未扣除 Tv​

实际上,v 的处理,是在公式 (63) 执行交易时,在执行具体函数代码前,会从发送者 Transfer 到接收者

gas 与 value 分开处理,理解如下

  • gas 本身与 value 语义本质量不同,不能混为一起
  • 转账 value 的双方 balance 的改变应该是原子的,不应拆成前后两个上下文
  • gas 一定是外部账户支付的,而 value 转账的扣款账户可以是外部账户,也可以是智能合约
// github.com/ethereum/[email protected]/core/vm/evm.go

// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
    //...
}

公式 (63) 执行交易后临时状态 σP​

(σP​,g′,A,z)≡{Λ4​(σ0​,S(T),To​,g,Tp​,Tv​,Ti​,0,∅,⊤)Θ4​(σ0​,S(T),To​,Tt​,Tt​,g,Tp​,Tv​,Tv​,Td​,0,⊤)​ifTt​=∅otherwise​
  • σ0​ 检查点状态
  • σP​ 执行交易后临时状态
    操作
  • 转换时需要区分交易类型
    • Tt​=∅ 未设置目标地址,说明是合约创建类型
    • 否则,是消息调用类型
  • σP​
    • 交易执行后的临时状态
  • g′
    • 剩余 gas
  • To​
    • original transactor
    • 如果是 inner-transaction,则这里是智能合约的地址,而非 sender
  • Λ4​
  • Θ4​
    • 数学符号,表示 bool 值
    • Λ4​ 和 Θ4​ 的末尾参数
      • 含义:是否有权修改状态
OPCODE 函数 末尾参数值 含义 CREATE Λ4​ Iw​ 保持调用时的读写权限 CREATE2 Λ4​ Iw​ 保持调用时的读写权限 STATICCALL Θ4​ ⊥ (falsum) 只读 CALL Θ4​ Iw​ 保持调用时的读写权限 CALLCODE Θ4​ Iw​ 保持调用时的读写权限 DELEGATECALL Θ4​ Iw​ 保持调用时的读写权限
// github.com/ethereum/[email protected]/core/vm/interpreter.go
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
  // ...

  // Make sure the readOnly is only set if we aren't in readOnly yet.
  // This also makes sure that the readOnly flag isn't removed for child calls.
  if readOnly && !in.readOnly {
    in.readOnly = true
    defer func() { in.readOnly = false }()
  }

  // ...

  for {
    // ...

    op = contract.GetOp(pc)
    operation := in.cfg.JumpTable[op]
    // If the operation is valid, enforce write restrictions
    if in.readOnly && in.evm.chainRules.IsByzantium {
      // If the interpreter is operating in readonly mode, make sure no
      // state-modifying operation is performed. The 3rd stack item
      // for a call operation is the value. Transferring value from one
      // account to the others means the state is modified and should also
      // return with an error.
      if operation.writes || (op == CALL && stack.Back(2).Sign() != 0) {
        return nil, ErrWriteProtection
      }
    }

    // ...
  }
  return nil, nil
}

//

// github.com/ethereum/[email protected]/core/vm/evm.go

// StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, true)
    // ...
}

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
    // ...
    ret, err = evm.interpreter.Run(contract, input, false)
    // ...
}

func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
    // ...
    ret, err = evm.interpreter.Run(contract, nil, false)
    // ...
}

公式 (64) 执行时最多能消耗的 gas

g≡Tg​−g0​

公式 (65) 交易执行后 refund 计数器的变化

Ar′​≡Ar​+i∈As​∑​Rselfdestruct​

公式 (66) 交易执行后剩余的 gas

g∗≡g′+min{⌊2Tg​−g′​⌋,Ar′​}
  • g′
    • 交易执行后最终剩余的 gas
  • Tg​−g′
    • 交易实际消耗的 gas
  • g∗
    • 交易执行后最终需要返还的 gas

公式 (67) (68) (69) (70) 预备最终状态 σ∗

σ∗σ∗[S(T)]b​σ∗[m]b​m​≡σP​except≡σP​[S(T)]b​+g∗Tp​≡σP​[m]b​+(Tg​−g∗)Tp​≡BH​c​​
  • σP​ 执行交易后临时状态
  • σ∗ 预备最终状态
    操作
  • 剩余的 gas 返回给发送者
  • 消耗的 gas 发给 BH​c​ (beneficiary)

公式 (71) (72) (73) 最终状态 σ′

σ′∀i∈As​:σ′[i]∀i∈At​:σ′[i]​≡σ∗except=∅=∅ifDEAD(σ∗,i)​
  • σ∗ 预备最终状态
  • σ′ 最终状态
    操作
  • 删除自毁集合账户
  • 删除接触账户集合中的空账户

公式 (74) (75) (76)

Υg(σ,T)Υl(σ,T)Υz(σ,T)​≡Tg​−g∗≡Al​≡z​
  • Υg
    • 交易一共消耗的 gas
  • Υl
    • 交易生成的一系列日志
  • Υz
    • 交易执行返回的状态码

7. Contract Creation 合约创建

  • 合约初始化

公式 (77) 盐

ζ∈B32​∪B0​

如果是通过 CREATE2 创建合约,则 ζ​=∅

公式 (78) 合约创建函数 Λ

(σ′,g′,A,z,o)≡Λ(σ,s,o,g,p,v,i,e,ζ,w)
  • s
    • 发送者 sender
  • o
    • 调用者 original transactor
  • g
    • available gas
  • p
    • gas price
  • v
    • 捐献: endowment
  • i
    • 用于初始化合约的 EVM 代码(二进制字符串)
  • e
    • 当前消息调用/合约创建堆栈的深度
  • ζ
    • 用于计算新合约地址的盐
    • 可能不存在: ζ=∅
  • w
    • 是否有权改变状态
    • 参考公式 (63)
  • σ′
    • 世界状态'
  • g′
    • 剩余 gas
  • A
  • o
    • 执行结果
      • 对于合约创建而言,成功的话这里得到的是合约的 body code
    • 参考公式 (145)

公式 (79) (80) (81) 新创建合约的地址 a

aADDR(s,n,ζ,i)LA​(s,n,ζ,i)​≡ADDR(s,σ[s]n​−1,ζ,i)≡B96..255​(KEC(LA​(s,n,ζ,i)))≡{RLP((s,n))(255)⋅s⋅ζ⋅KEC(i)​if ζ=∅otherwise​​
  • LA​
    • 判断合约创建方式是否为 CREATE2
    • 拼接字节数组
  • Ba..b​(X)
    • 取二进制字节数组的第 [a,b] 位
  • σ[x]
    • 地址 x 的世界状态
    • 不存在,则为 ∅
  • 公式(79) 中,传给合约地址计算函数 Λ 的参数 nonce,值是 σ[s]n​−1
  • 理解: 在调用合约创建之前,系统已经把发送者的 nonce 加一

公式 (82) (83) (84) (85) 合约创建后的世界状态 σ∗

σ∗σ∗[a]σ∗[s]a∗​≡σexcept:=(1,v+v′,TRIE(∅),KEC(()))={∅a∗​if σ[s]=∅ ∧ v=0otherwise​≡(σ[s]n​,σ[s]b​−v,σ[s]s​,σ[s]c​)​

公式 (86) v′ 合约地址在交易前就有的余额

v′≡{0σ[a]b​​ifσ[a]=∅otherwise​

到这里,新创建合约的地址的状态已经设置好了(公式(83))

公式 (87) 调用 Ξ 函数执行代码 i 来初始化合约

(σ∗∗,g∗∗,A,o)≡Ξ(σ∗,g,I,{s,a})

Ξ 函数的输出

  • σ∗∗
    • 合约初始化后的世界状态
  • g∗∗
    • 剩余的可用 gas
  • A
    • 累积的子状态
  • o
    • 新创建合约的 body code
    • 由合约初始化 i 代码得到

公式 (88) (89) (90) (91) (92) (93) (94) (95) (96) 合约初始化后的世界状态 σ∗

I 作为 Ξ 函数的输入参数

Ia​Io​Ip​Id​Is​Iv​Ib​Ie​Iw​​≡a≡o≡p≡()≡s≡v≡i≡e≡w​
  • Ia​
    • 新创建合约的地址
  • Io​
    • 调用者 original transactor
  • Ip​
    • gas Price
  • Id​
    • input data
    • 在这里为空,因为对于这个初始化调用而言没有 input
  • Is​
    • 发送者 sender
  • Iv​
    • 捐献: endowment
  • Ib​
    • 用于初始化合约的 EVM 代码(二进制字符串)
  • Ie​
    • 当前消息调用/合约创建堆栈的深度
  • Iw​
    • 是否有权修改状态
    • 参考公式 (63)

公式 (97) 合约初始化需要支付的 gas

c≡Gcodedeposit​×∥o∥
  • 出现异常
    • 初始化代码执行过程的异常
      • 例子
        • 因可用 gas g∗∗ 不够而导致 Out-Of-Gas 异常
    • 成功初始化后可用 gas 不够支付 c
      • g∗∗<c
      • 也会导致 Out-of-Gas
    • 其他场景
      • 例子 TODO
  • 导致
    • 最终剩余 gas g‘ 为0
    • 使初始化提前终止
    • 合约初始化后的世界状态 σ∗∗ 为空,即 ∅
    • 最终状态与合约创建前一致

公式 (98) (99) (100) (101) 最终状态 σ′

g′≡σ′≡z≡whereF≡​{0g∗∗−c​ifFotherwise​⎩⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎧​σσ∗∗except:σ′[a]=∅σ∗∗except:σ′[a]c​=KEC(o)​ifF ∨ σ∗∗=∅ifDEAD(σ∗∗,a)otherwise​{01​ifF ∨ σ∗∗=∅otherwise​(σ[a]​=∅ ∧ (σ[a]c​​=KEC(())∨σ[a]n​​=0))∨(σ∗∗=∅ ∧ o=∅)∨g∗∗<c∨∥o∥>24576​
  • g′
    • 最终剩余的 gas,需要返还给最初交易发起者
  • (σ[a]​=∅ ∧ (σ[a]c​​=KEC(())∨σ[a]n​​=0))
    • 目标地址已存在,且有 codeHash 或 nonce 不为0
  • o
    • 新创建合约的 body code
    • 由合约初始化 i 代码得到
  • σ′[a]c​=KEC(o)
    • 保存新建合约的 body code 的 hash 到 σ′[a]c​
  • σ∗∗=∅ ∧ o=∅
      • 执行 i 后得到的字节码,即合约代码 o 为空
    • 失败例子 Ropsten

      • SELFDESTRUCT
        • 结论
          • 根据执行结果,剩余 gas 有返还,所以不属于 F
pragma solidity >=0.7.0 <0.9.0;
      contract Storage {
          constructor(address user) payable {
              address payable u = payable(address(user));
              selfdestruct(u);
          }
      }
// github.com/ethereum/[email protected]/core/vm/interpreter.go

  // It's important to note that any errors returned by the interpreter should be
  // considered a revert-and-consume-all-gas operation except for
  // ErrExecutionReverted which means revert-and-keep-gas-left.
  func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
      // ...
  }
    • 结论
      • 不满足 F,因此 g′=g∗∗−c
        • 剩余 gas 和 value 会被归还
      • 满足 DEAD(σ∗∗,a),因此 σ′[a]=∅
        • 其余状态不变
    • 证明
      • 代码 (assert 和 require 都测试过,都返还了 gas,跟参考文章结论不一样)
pragma solidity >=0.7.0 <0.9.0;
      contract Storage {
          uint256 number;
          constructor(address user, uint256 num) payable {
              require (msg.sender == user);
              number = num;
          }
      }
  • 部署
    • 发起部署的地址与构造函数的地址不同,使 require/assert 失败
    • 根据公式(14),新创建的合约是个 EMPTY 账户
const targetContract = '0xbd83EF1a5A45b54d94895C1897aF2d00154520D5';

                const balance = await web3.eth.getBalance(targetContract);
                const nonce = await web3.eth.getTransactionCount(targetContract);
                const code = await web3.eth.getCode(targetContract);

                console.log(`balance: ${balance}`);
                console.log(`nonce: ${nonce}`);
                console.log(`code: ${code}`);

                // storageRoot
                //  无法从 web3 直接获得
                //  参考 https://github.com/medvedev1088/ethereum-merkle-patricia-trie-example

8. Message Call 消息调用 (internal Transaction)

公式 (102) 消息调用函数 Θ

(σ′,g′,A,z,o)≡Θ(σ,s,o,r,c,g,p,v,v~,d,e,w)
  • s
    • 发送者 sender
  • o
    • 调用者 original transactor
  • r
    • 接收者 recipient
  • c
  • g
    • available gas
  • p
    • gas price
  • v
    • 捐献: endowment
  • d
    • 消息调用的 input data (二进制字符串)
  • e
    • 当前消息调用/合约创建堆栈的深度
  • w
    • 是否有权改变状态
    • 参考公式 (63)
  • σ′
    • 世界状态'
  • g′
    • 剩余 gas
  • A
  • z
    • status code
  • o
    • output data
    • 参考公式 (145)
  • v
    • msg.value
  • v~
    • DELEGATECALL 指令上下文的中的 value

公式 (103) 临时状态 σ1​

​σ1​[r]b​≡σ[r]b​+v∧σ1​[s]b​≡σ[s]b​−vunlesss=r​

公式 (104) (105) (106) (107) (108) (109) 状态转换 σ -> σ1′​ -> σ1​

σ1​≡σ1′​except:
σ1​[s]≡{∅a1​​if σ1′​[s]=∅ ∧ v=0otherwise​
a1​≡(σ1′​[s]n​,σ1′​[s]b​−v,σ1′​[s]s​,σ1′​[s]c​)
andσ1′​≡σexcept:
⎩⎪⎪⎨⎪⎪⎧​σ1′​[r]≡(0,v,TRIE(∅),KEC(()))σ1′​[r]≡∅σ1′​[r]≡a1′​​ifσ[r]=∅∧v​=0ifσ[r]=∅∧v=0otherwise​
a1′​≡(σ[r]n​,σ[r]b​+v,σ[r]s​,σ[r]c​)

执行过程参考 #9 Execution Model

公式 (110) (111) ... (123) 最终状态 σ′

σ′g′z(σ∗∗,g∗∗,A,o)Ia​Io​Ip​Id​Is​Iv​Ie​Iw​twhere​≡{σσ∗∗​ifσ∗∗=∅otherwise​≡{0g∗∗​ifσ∗∗=∅ ∧o=∅otherwise​≡{01​ifσ∗∗=∅otherwise​≡Ξ≡r≡o≡p≡d≡s≡v~≡e≡w≡{s,r}​Ξ≡⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​ΞECREC​(σ1​,g,I,t)ΞSHA256​(σ1​,g,I,t)ΞRIP160​(σ1​,g,I,t)ΞID​(σ1​,g,I,t)ΞEXPMOD​(σ1​,g,I,t)ΞBN_ADD​(σ1​,g,I,t)ΞBN_MUL​(σ1​,g,I,t)ΞSNARKV​(σ1​,g,I,t)ΞBLAKE2_F​(σ1​,g,I,t)Ξ(σ1​,g,I,t)​ifc=1ifc=2ifc=3ifc=4ifc=5ifc=6ifc=7ifc=8ifc=9otherwise​
LetKEC(Ib​)=σ[c]c​
  • Ia​
    • 接收者地址
  • Io​
    • 调用者 original transactor
  • Ip​
    • gas Price
  • Id​
    • 消息调用的 input data (二进制字符串)
  • Is​
    • 发送者 sender
  • Iv​
    • DELEGATECALL 指令上下文的中的 value
  • Ie​
    • 当前消息调用/合约创建堆栈的深度
  • Iw​
    • 是否有权改变状态
    • 参考公式 (63)
  • 映射
    • 客户端会存储映射 KEC(Ib​) => Ib​
    • 这样可以方便根据 σ[c]c​ 索引并取出目标地址的代码 Ib​ 以执行
  • 预编译合约

9. Execution Model 执行模型

参考 go-ethereum 源码

// github.com/ethereum/[email protected]/core/state_processor.go
func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (*types.Receipt, error);

  // github.com/ethereum/[email protected]/core/state_transition.go
  func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error);
    func (st *StateTransition) TransitionDb() (*ExecutionResult, error);

      // github.com/ethereum/[email protected]/core/vm/evm.go
      func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error);

        // github.com/ethereum/[email protected]/core/vm/interpreter.go
        func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error);

          // github.com/ethereum/[email protected]/core/vm/jump_table.go
          type (
            executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error)
          )

          type operation struct {
            execute     executionFunc
          }

            // github.com/ethereum/[email protected]/core/vm/instructions.go

9.1 Basics 基础

9.2 Fees Overview 费用概述

  • 消耗
    • 进一步的 消息调用 或 合约创建
  • refund
    • 如果清除了一块存储,系统会免除这项操作的费用,且返还一定额度的 refund
    • 参考 # Appendix G. Fee Schedule
      • Rsclear​
      • Rselfdestruct​

9.3 Execution Environment 执行环境

I 执行环境

  • Ia​
    • 接收者地址
  • Io​
    • 调用者 original transactor
  • Ip​
    • gas Price
  • Id​
    • 消息调用的 input data (二进制字符串)
  • Is​
    • 发送者 sender
  • Iv​
    • value
  • Ib​
    • 接收者的代码,将被执行
  • IH​
    • 当前区块的区块头
  • Ie​
    • 当前消息调用/合约创建堆栈的深度
  • Iw​
    • 是否有权改变状态
    • 参考公式 (63)

公式 (124) 函数 Ξ

(σ′,g′,A,o)≡Ξ(σ,g,I)
  • o
    • 执行结果 output

公式 (125) 累计子状态 A

A≡(As​,Al​,At​,Ar​)
  • As​
    • 自毁集合
      • 交易执行完成后会被销毁的账户
  • Al​
    • 一系列日志
      • 方便外界旁观者简单跟踪合约调用
  • At​
    • 交易接触的账户
      • 其中的 EMPTY 账户会被删除
  • Ar​
    • refund balance
      • 累计需要归还的 gas

9.4 Execution Overview 执行概述

公式 (126) (127) (128) ... (137) (138) 函数 Ξ 的定义

Ξ(σ,g,I,T)(σ′,μ′,A,...,o)μg​μpc​μm​μi​μs​μo​​≡(σ′,μg′​,A,o)≡X((σ,μ,A0,I))≡g≡0≡(0,0,...)≡0≡()≡()​
X((σ,μ,A,I))≡⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧​(∅,μ,A0,I,∅)(∅,μ′,A0,I,o)O(σ,μ,A,I)⋅oX(O(σ,μ,A,I))​ifZ(σ,μ,I)ifw=REVERTifo​=∅otherwise​

where

o(a,b,c,d)⋅eμ′μg′​​≡H(μ,I)≡(a,b,c,d,e)≡μ except:≡μg​−C(σ,μ,I)​
  • X
    • 循环调用直到遇到异常或执行完成
  • O
    • 单步执行当前指令 w
    • 参考 (146)
  • Z
    • 检查是否异常
    • 参考 (140)
  • o
    • H 的返回结果
  • H
    • 检查是否正常终止
      • 返回 ∅ 空,表示继续执行
      • 返回 () 空集,表示停止执行
      • 否则,表示有意识的停止执行并附加执行结果 o
    • 参考 (145)
  • C(σ,μ,I)
    • 参考 (314)
    • Appendix H.1. Gas Cost
  • μg​
    • 剩余可用 gas
  • μpc​
    • program counter
  • μm​
    • memory contents
  • μi​
    • active number of words in memory
  • μs​
    • stack contents
  • μo​
    • 执行输出 output
  • 公式 (146),执行函数 Ξ 后,会丢弃 I′ 并记录 μg′​ 和 o

9.4.1 Machine State

机器状态 μ 组成为 (g,pc,m,i,s)

(139) 当前待执行的指令 w

w≡{Ib​[μpc​]STOP​ifμpc​<∥Ib​∥otherwise​
// github.com/ethereum/[email protected]/core/vm/interpreter.go
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error)
  op = contract.GetOp(pc)
	operation := in.cfg.JumpTable[op]

// github.com/ethereum/[email protected]/core/vm/contract.go
// GetOp returns the n'th element in the contract's byte array
func (c *Contract) GetOp(n uint64) OpCode {
	return OpCode(c.GetByte(n))
}

// GetByte returns the n'th byte in the contract's byte array
func (c *Contract) GetByte(n uint64) byte {
	if n < uint64(len(c.Code)) {
		return c.Code[n]
	}

	return 0 // STOP
}

9.4.2 Exception Halting

公式 (140) (141) 异常检查函数 Z

Z(σ,μ,I)≡​μg​<C(σ,μ,I)∨δw​=∅∨∥μs​∥<δw​∨(w=JUMP∧μs​[0]∈​D(Ib​))∨(w=JUMPI∧μs​[1]​=0∧μs​[0]∈​D(Ib​))∨(w=RETURNDATACOPY∧μs​[1]+μs​[2]>∥μo​∥)∨∥μs​∥−δw​+αw​>1024∨(¬Iw​∧W(w,μ))∨(w=SSTORE∧μg​⩽Gcallstipend​)​

where

W(w,μ)≡​w∈{CREATE,CREATE2,SSTORE,SELFDESTRUCT} ∨LOG0≤w∧w≤LOG4∨w=CALL∧μs​[2]​=0​
  • δ
    • 指令 w 的出栈个数
  • α
    • 指令 w 的入栈个数
  • 栈的索引
    • 从上往下增长
// github.com/ethereum/[email protected]/core/vm/errors.go

// List evm execution errors
var (
  ErrOutOfGas                 = errors.New("out of gas")
  ErrCodeStoreOutOfGas        = errors.New("contract creation code storage out of gas")
  ErrDepth                    = errors.New("max call depth exceeded")
  ErrInsufficientBalance      = errors.New("insufficient balance for transfer")
  ErrContractAddressCollision = errors.New("contract address collision")
  ErrExecutionReverted        = errors.New("execution reverted")
  ErrMaxCodeSizeExceeded      = errors.New("max code size exceeded")
  ErrInvalidJump              = errors.New("invalid jump destination")
  ErrWriteProtection          = errors.New("write protection")
  ErrReturnDataOutOfBounds    = errors.New("return data out of bounds")
  ErrGasUintOverflow          = errors.New("gas uint64 overflow")
  ErrInvalidCode              = errors.New("invalid code: must not begin with 0xef")
  ErrStackUnderflow           = errors.New(fmt.Sprintf("stack underflow (%d <=> %d)", e.stackLen, e.required))
  ErrStackOverflow            = errors.New(fmt.Sprintf("stack limit reached %d (%d)", e.stackLen, e.limit))
  ErrInvalidOpCode            = errors.New(fmt.Sprintf("invalid opcode: %s", e.opcode))
)

9.4.3 Jump Destionation Validity 跳转地址验证

公式 (142) (143) (144)

D(c)≡DJ​(c,0)

where:

DJ​(c,i)≡⎩⎪⎪⎨⎪⎪⎧​{}{i}∪DJ​(c,N(i,c[i]))DJ​(c,N(i,c[i]))​ifi⩾∥c∥ifc[i]=JUMPDESTotherwise​
N(i,w)≡{i+w−PUSH1+2i+1​ifw∈[PUSH1,PUSH32]otherwise​
  • c
    • 当前执行中的一段代码
  • D TODO
    • 计算 c 的有效跳转地址的集合
    • 按 JUMPDEST 指令的位置来定义
  • N TODO
    • 下一有效指令在代码 c 中的位置
    • 且忽略 PUSH1 指令的数据(如果有的话)

9.4.4 Normal Halting

(145) 正常停止检查函数 H

H(μ,I)≡⎩⎪⎪⎨⎪⎪⎧​HRETURN​(μ)()∅​ifw∈{RETURN,REVERT}ifw∈{STOP,SELFDESTRUCT}otherwise​​

有3种可能的输出

  • HRETURN​
  • () 空集
    • 表示停止执行
  • ∅ 空
    • 表示继续执行

9.5 The Execution Cycle 执行循环

公式 (146) (147) (148) (149)

O((σ,μ,A,I))Δ∥μs′​∥∀x∈[αw​,∥μs′​∥):μs′​[x]​≡(σ′,μ′,A′,I)≡αw​−δw​≡∥μs​∥+Δ≡μs​[x−Δ]​
  • δ
    • 指令 w 的出栈个数
  • α
    • 指令 w 的入栈个数
  • Δ
    • 栈大小的变化
  • 栈的索引
    • 从上往下增长

公式 (150) (151)

μg′​μpc′​​≡μg​−C(σ,μ,I)≡⎩⎪⎪⎨⎪⎪⎧​JJUMP​(μ)JJUMPI​(μ)N(μpc​,w)​ifw=JUMPifw=JUMPIotherwise​​
  • N
    • 参考 (144)

公式 (152) (153) (154) (155)

μm′​μi′​A′σ′​≡μm​≡μi​≡A≡σ​

通常我们假定内存(μm′​, μi′​),自毁集合,世界状态不会被修改

具体参考 # Appendix H. Virtual Machine Specification

10. Blocktree to Blockchain 区块树到区块链

(156) (157) 区块难度计算函数

Bt​B′​≡Bt′​+Bd​≡P(BH​)​
  • Bt​
    • 区块总难度
  • Bd​
    • 当前区块的难度

11. Block Finalization 区块定稿

区块定稿要经过 4 步验证

  • 验证叔块 Ommer Validation
  • 验证交易 Transaction Validation
  • 奖励发放 Reward Application
  • 验证状态和 nonce State & Nonce Validation

11.1 Ommer Validation 验证 Ommer

(158) (159) (160)

∥BU​∥⩽2U∈BU​⋀​V(U)∧k(U,P(BH​)H​,6)
k(U,H,n)≡{falses(U,H)∨k(U,P(H)H​,n−1)​ifn=0otherwise​
s(U,H)≡(P(H)=P(U)∧H​=U∧U∈​B(H)U​)
  • 当前区块头最多有2个叔块
  • 叔块头列表自身有效,且表示的叔块与当前区块在6代以内
  • k
    • is-kin 是亲属
  • s
    • is-sibling 是兄妹
  • V
    • 区块头验证函数
    • 参考公式 (51)

11.2 Transaction Validation 交易验证

BH​g​=ℓ(R)u​
  • BH​g​
    • 当前区块头中的累计使用 gas
  • ℓ(R)u​
    • 当前区块中最后一条收据的累计使用 gas

11.3 Reward Application 奖励发放

公式 (162) (163) (164) (165) (166) Ω 区块奖励函数

Ω(B,σ)σ′[BH​c​]b​∀U∈BU​:σ′[Uc​]a′R​≡σ′:σ′=σexcept:=σ[BH​c​]b​+(1+32∥BU​∥​)Rblock​={∅a′​if σ[Uc​]=∅ ∧ R=0otherwise​≡(σ[Uc​]n​,σ[Uc​]b​+R,σ[Uc​]s​,σ[Uc​]c​)≡(1+81​(Ui​−BH​i​))Rblock​​
  • BU​
    • 当前区块内的叔块头列表
  • BH​c​
    • 当前区块头中存储的 beneficiary
    • 即当前区块收益的归属地址
  • Uc​
    • 叔块收益的归属地址

(167) Rblock​ 定义

Rblock​=1018×⎩⎪⎪⎨⎪⎪⎧​532​if Hi​<FByzantium​if FByzantium​⩽Hi​<FConstantinople​if Hi​⩾FConstantinople​​

11.4 State & Nonce Validation 状态和 nonce 验证

(168) 映射 Block 到世界状态的函数 Γ

Γ(B)≡{σ0​σi​:TRIE(LS​(σi​))=P(BH​)H​r​​ifP(BH​)=∅otherwise​
  • LS​
    • 世界状态坍塌函数
    • 参考 (10)
  • TRIE(LS​(σi​))=P(BH​)H​r​
    • 判断 state TRIE 根节点的内容 等于 区块头的 stateRoot
    • 理解
      • 不像 Block 存储在区块链上,TRIE 树结构是通过存储在客户端数据库中的数据构造出来的
      • 因此需要比较区块头存储中的 stateRoot hash,是否与构造 TRIE 树时层层计算出来的根节点 hash 相等

(169) (170) (171) (172) 区块级别状转换函数 Φ

Φ(B)Bn′​Bm′​B∗​≡B′:B′=B∗except:=n:x⩽Hd​2256​=mwith (x,m)=PoW(Bn​∗​,n,d)≡Bexcept:Br∗​=r(Π(Γ(B),B))​

Φ 函数计算 nonce 和 mixHash 后分别设置到 Bn′​ 和 Bm′​

(173) 第 n 个状态 σ[n]

σ[n]={Γ(B)Υ(σ[n−1],BT​[n])​ifn<0otherwise​

(174) (175) (176) Υu Υl Υz 的定义/赋值

R[n]u​={0Υg(σ[n−1],BT​[n])+R[n−1]u​​ifn<0otherwise​​
R[n]l​=Υl(σ[n−1],BT​[n])
R[n]z​=Υz(σ[n−1],BT​[n])
  • Ru​
    • 当前区块中,交易发生后的累积 gas 使用量
  • Rl​
    • 交易过程中创建的一系列日志
  • Rz​
    • 交易的状态码
  • Rb​
    • 由一系列日志构成的 Bloom 过滤器
    • 参考公式 (26) (27) (28) (29) (30)

公式 (177) 区块级状态转换函数 Π

Π(σ,B)≡Ω(B,ℓ(σ))

对区块应用区块奖励函数 Ω,可以得到最新的世界状态,即最终的区块级状态转换

Appendix B. Recursive Length Prefix

参考 eth wiki: RLP

Appendix C. Hex-Prefix Encoding

公式 (189) (190)

HP(x,t):x∈Y≡{(16f(t),16x[0]+x[1],16x[2]+x[3],...)(16(f(t)+1)+x[0],16x[1]+x[2],16x[3]+x[4],...)​if∥x∥is evenotherwise​​
f(t)​≡​{20​ift​=0otherwise​​
// https://github.com/ethereum/go-ethereum/blob/master/trie/encoding.go

// https://github.com/sontuphan/debug-geth/blob/master/trie/encoding.go
// This block of code does Compact (hex-prefix) encoding as following table
// hex char    bits    |    node type partial    path length
// 0           0000    |    extension            even
// 1           0001    |    extension            odd
// 2           0010    |    terminating (leaf)   even
// 3           0011    |    terminating (leaf)   odd

func hexToCompact(hex []byte) []byte {
	terminator := byte(0)
	if hasTerm(hex) {
		terminator = 1
		hex = hex[:len(hex)-1]
	}
	buf := make([]byte, len(hex)/2+1)
	buf[0] = terminator << 5 // the flag byte
	if len(hex)&1 == 1 {
		buf[0] |= 1 << 4 // odd flag
		buf[0] |= hex[0] // first nibble is contained in the first byte
		hex = hex[1:]
	}
	decodeNibbles(hex, buf[1:])
	return buf
}
  • 输入
    • hex
      • 16进制明文字符串 path
        • 每个字符占用一个字节,取值范围是 [0-9, a-f]
      • 可能在 path 尾部追加最后一个字节,值为 0x10,表示 terminator
        • 即 hex 是个叶子节点(包含 value),而非中间节点
  • 输出
    • buf (Hex-Prefix 编码得到的二进制字符串 )
      • 添加半个字节(一个hex编码)到 hex 前面,用于表示
        • hex 是否包含 terminator
        • hex 移除 terminator 后(如果有的话),长度是奇数还是偶数
      • 确保 buf 的长度是偶数
Row Node Type Path Length Path Before Encoding(In HEX) Path After Encoding(In HEX) 0 Extension EVEN [0, 1, 2, 3, 4, 5] '00 01 23 45' 1 Extension ODD [1, 2, 3, 4, 5] '11 23 45' 2 Leaf(has terminator(10)) EVEN [0, f, 1, c, b, 8, 10] '20 0f 1c b8' 3 Leaf(has terminator(10)) ODD [f, 1, c, b, 8, 10] '3f 1c b8'

Appendix D. Modified Merkle Patricia Tree

(191) (192) 数据集合 I

I={(k0​∈B,v0​∈B),(k1​∈B,v1​∈B),...}∀I∈I:I≡(I0​,I1​)

(193) (194)

任何 bytes 都可以看为半字节(nibbles 4bit)组成的序列

y(I)∀n:∀i<2∥kn​∥:kn′​[i]​={(k0′​∈Y,v0​∈B),(k1′​∈Y,v1​∈B),...}≡{⌊kn​[i÷2]÷16⌋kn​[⌊i÷2⌋]mod16​ifiis evenotherwise​​

公式 (195) 根节点函数 TRIE

TRIE(I)≡KEC(RLP(c(I,0)))

公式 (196)

n(I,i)≡⎩⎪⎪⎨⎪⎪⎧​()c(I,i)KEC(RLP(c(I,i)))​ifI=∅if∥RLP(c(I,i))∥<32otherwise​

公式 (197)

c(I,i)≡⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧​(HP(I0​[i..(∥I0​∥−1)],1),I1​)(HP(I0​[i..(j−1)],0),n(I,j))(u(0),u(1),...,u(15),v)​if ∥I∥=1where ∃I:I∈Iif i​=j where j=max{x:∃l:∥l∥=x∧∀I∈I:I0​[0..(x−1)]=l}otherwise whereu(j)≡n({I:I∈I∧I0​[i]=j},i+1)v={I1​()​if ∃I:I∈I∧∥I0​∥=iotherwise​​​
  • 叶子节点 Lea
    • 包含两个字段
      • 第一个字段是剩下的 Key 的 HP 编码(HP 的第二个参数为1)
      • 第二个字段是 Value
  • 扩展节点 Extension
    • 包含两个字段
      • 第一个字段是剩下的 Key 的可以至少被两个剩下节点共享的最大公共前缀((HP 的第二个参数为0)
      • 第二个字段是 n(I,j)
  • 分支节点 Branch
    • 包含17个字段
      • 前16个项目对应于 [0-9,a-f]
      • 第17个字段是存储在当前结点结束的节点
        • 例如
          • 有三个key,分别是 abc,abd,ab
          • 第17个字段储存了ab节点的值

ethereum_blockchain_mechanism.jpg

merkle_trie_tree.png

图片来源 ELI5 How does a Merkle-Patricia-trie tree work?

Appendix H. Virtual Machine Specification

注意区分单位

μs​ / σ[a]s​ 以 32 字节为单位

μm​ 以 1 字节为单位

参考 SSTORE/SLOAD, MSTORE/MLOAD

H.2. Instruction Set

参考 Difference between CALL, CALLCODE and DELEGATECALL

DELEGATECALL was a new opcode that was a bug fix for CALLCODE which did not preserve msg.sender and msg.value. If Alice invokes Bob who does DELEGATECALL to Charlie, the msg.sender in the DELEGATECALL is Alice (whereas if CALLCODE was used the msg.sender would be Bob).


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK