以太坊黄皮书详解(一)

写在篇头

本文是对以太坊的黄皮书的解析,并参照go-ethereum中的实现,将相应的代码也列了出来。黄皮书中使用了大量的公式将以太坊的一些流程和状态都公式化了。看不看得懂公式对理解的影响不大。本文中对公式进行了解析。嫌麻烦的可以跳过每部分公式解析的部分。

一、区块链范型

以太坊本质是一个基于交易的状态机(transaction-based state machine)。其以初始状态(genesis state) 为起点,通过执行交易来到达新的状态。

\begin{equation} {\sigma}_{t+1} \equiv \Upsilon(\boldsymbol{\sigma}_{t}, T) \tag{1} \end{equation}

公式1表示t+1时的状态,是由t时的状态经过交易T转变而来。转变函数为\Upsilon
如下图所示

ethereumtransaction.png

\begin{equation} \boldsymbol{\sigma}_{t+1} \equiv {\Pi}(\boldsymbol{\sigma}_{t}, B) \tag{2} \end{equation}

\begin{equation} B \equiv (..., (T_0, T_1, ...) ) \tag{3} \end{equation}

\begin{equation} \Pi(\boldsymbol{\sigma}, B) \equiv {\Omega}(B,{\Upsilon}(\Upsilon(\boldsymbol{\sigma}, T_0), T_1) ...) \tag{4} \end{equation}

公式2-4是从区块的角度来描述状态的转化过程。
公式2: t+1时的状态,是由t时的状态经过区块B转变而来。转变函数为{\Pi}

公式3: 区块B是包含了一系列交易T的集合。

公式4: 区块的状态转变函数{\Pi},相当于逐条的执行交易的状态转变\Upsilon,然后完成所有交易转变后再经过{\Omega}进行一次状态转换。(这个地方{\Omega}实际上是给矿工挖坑奖励。)

在以太坊中的实际情况就是区块验证和执行的过程。

  • 逐一的执行交易(也就是使用交易转变函数操作\Upsilon状态集)。实际就是交易比方是A向B转10ether,则A账户值-10,B账户值+10。(当然执行过程中还有gas消耗,这个后面详述)
  • 等整个block交易执行完毕后,需要对矿工进行奖励。也就是需要使用{\Omega}进行一次状态转换。

1.1 货币

以太坊中有以下四种单位的货币。以太坊中的各种计算都是以Wei为单位的。 (看有的地方好像有更多种单位,我这边是直接按照黄皮书走的)

Multiplier Name
100 Wei
1012 Szabo
1015 Finney
1018 Ether

1.2 分叉

以太坊的正确运行建立在其链上只有一个链是有效的,所有人都必须要接受它。拥有多个状态(或多个链)会摧毁这个系统,因为它在哪个是正确状态的问题上不可能得到统一结果。如果链分叉了,你有可能在一条链上拥有10个币,一条链上拥有20个币,另一条链上拥有40个币。在这种场景下,是没有办法确定哪个链才是最”有效的“。不论什么时候只要多个路径产生了,一个”分叉“就会出现。
为了确定哪个路径才是最有效的以及防止多条链的产生,以太坊使用了一个叫做“GHOST协议(GHOST protocol.)”的数学机制。

简单来说,GHOST协议就是让我们必须选择一个在其上完成计算最多的路径。一个方法确定路径就是使用最近一个区块(叶子区块)的区块号,区块号代表着当前路径上总的区块数(不包含创世纪区块)。区块号越大,路径就会越长,就说明越多的挖矿算力被消耗在此路径上以达到叶子区块。

二、 区块、状态与交易

2.1 世界状态

以太坊中的世界状态指地址(Address)与账户状态(Account State)的集合。世界状态并不是存储在链上,而是通过Merkle Patricia tree来维护。
账户状态(Account State)包含四个属性。

  • nonce: 如果账户是一个外部拥有账户,nonce代表从此账户地址发送的交易序号。如果账户是一个合约账户,nonce代表此账户创建的合约序号。用\boldsymbol{\sigma}[a]_{\mathbf{n}}来表示。
  • balance:此地址拥有Wei的数量。1Ether=10^18Wei。用\boldsymbol{\sigma}[a]_{\mathbf{b}}来表示。
  • storageRoot: 理论上是指Merkle Patricia树的根节点256位的Hash值。用\boldsymbol{\sigma}[a]_{\mathbf{s}}来表示。公式6中有介绍。
  • codeHash:此账户EVM代码的hash值。对于外部拥有账户,codeHash域是一个空字符串
    的Hash值。对于合约账户,就是代码的Hash作为codeHash保存。用\boldsymbol{\sigma}[a]_{\mathbf{c}}来表示。
// github.com/ethereum/go-ethereum/core/state/state_object.go
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

关于storageRoot的补充
\begin{equation} {\small TRIE}\big(L_{I}^*(\boldsymbol{\sigma}[a]_{\mathbf{s}})\big) \equiv \boldsymbol{\sigma}[a]_{\mathrm{s}} \tag{6} \end{equation}

\begin{equation} L_{I}\big( (k, v) \big) \equiv \big({\small KEC}(k), {\small RLP}(v)\big) \tag{7} \end{equation}

\begin{equation} k \in \mathbb{B}_{32} \quad \wedge \quad v \in \mathbb{N} \tag{8} \end{equation}

公式6, 由于有些时候我们不仅需要state的hash值的trie,而是需要其对应的kv数据也包含其中。所以以太坊中的存储State的树,不仅包含State的hash,同时也包含了存储这个账户的address的hash和它对应的data也就是其Account的值的数据对的集合。这里storageRoot实际上是这样的树的根节点hash值。

公式7,指state对应的kv数据的RLP的形式化表示,是k的hash值作为key,value是v的RLP表示。也就是以太坊中实际存储的state是账户address的hash ({\small KEC}(k)),与其数据Account内容的RLP ({\small RLP}(v))

公式8,指公式7中的k是32的字符数组。这个是由KECCAK256算法保证的。

注:原本公式8中要求的是v是个正整数,但是我看来下代码和下文公式10,感觉这里的v都应该是Account的内容

一些符号化定义

以太坊中的账户有两类,一类是外部账户,一类是合约账户。其中外部账户被私钥控制且没有任何代码与之关联。合约账户,被它们的合约代码控制且有代码与之关联。以下几个公式定义了账户的各种状态。
\begin{equation} L_{S}(\boldsymbol{\sigma}) \equiv \{ p(a): \boldsymbol{\sigma}[a] \neq \varnothing \} \tag{9} \end{equation}
其中
\begin{equation} p(a) \equiv \big({\small KEC}(a), {\small RLP}\big( (\boldsymbol{\sigma}[a]_{\mathrm{n}}, \boldsymbol{\sigma}[a]_{\mathrm{b}}, \boldsymbol{\sigma}[a]_{\mathrm{s}}, \boldsymbol{\sigma}[a]_{\mathrm{c}}) \big) \big) \tag{10} \end{equation}

\begin{equation} \forall a: \boldsymbol{\sigma}[a] = \varnothing \vee (a \in \mathbb{B}_{20} \wedge v(\boldsymbol{\sigma}[a])) \tag{11} \end{equation}

\begin{equation} v(x) \equiv {x}_{\mathrm{n}} \in \mathbb{N}_{256} \wedge {x}_{\mathrm{b}} \in \mathbb{N}_{256} \wedge {x}_{\mathrm{s}} \in \mathbb{B}_{32} \wedge {x}_{\mathrm{c}} \in \mathbb{B}_{32} \tag{12} \end{equation}

\begin{equation} \mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a) \quad \equiv \quad \boldsymbol{\sigma}[a]_{\mathrm{c}} = {\small KEC} \big(()\big) \wedge \boldsymbol{\sigma}[a]_{\mathrm{n}} = 0 \wedge \boldsymbol{\sigma}[a]_{\mathrm{b}} = 0 \tag{13} \end{equation}

\begin{equation} \mathtt{\tiny DEAD}(\boldsymbol{\sigma}, a) \quad\equiv\quad \boldsymbol{\sigma}[a] = \varnothing \vee \mathtt{\tiny EMPTY}(\boldsymbol{\sigma}, a) \tag{14} \end{equation}

公式9,定义了函数L_{S},意思是若账户a不为空,则返回账户p(a)

公式10,定义p(a),p(a)其实就是我们上面公式7,解释kv对的时候的kv对。包括address的hash值,以及Account内容的RLP结果。

公式11与公式12,对账户a做了定义,表示账户要么为空,要么就是一个a为20个长度的字符,其nonce值为小于2256的正整数,balance值为小于2256的正整数,storageRoot为32位的字符,codeHash为32的字符。

公式13,定义了空账户。若一个账户,其地址为空字符,并且该账户nonce值为0,balance值也为0.

公式14,定义了死账户,死账户要么为空\varnothing,要么是一个EMPTY账户。

2.2 交易

  • nonce: 与发送该交易的账户的nonce值一致。用T_{\mathrm{n}}表示。
  • gasPrice: 表示每gas的单价为多少wei。用T_{\mathrm{p}}表示。
  • gasLimit:执行该条交易最大被允许使用的gas数目。用T_{\mathrm{g}}表示。
  • to:160位的接受者地址。当交易位创建合约时,该值位空。用T_{\mathrm{t}}表示。
  • value:表示发送者发送的wei的数目。该值为向接受者转移的wei的数目,或者是创建合约时作为合约账户的初始wei数目。用T_{\mathrm{v}}表示。
  • v,r,s: 交易的签名信息,用以决定交易的发送者。分别用T_{\mathrm{w}},T_{\mathrm{r}},T_{\mathrm{s}}表示。
  • init:如果是创建合约的交易,则init表示一段不限长度的EVM-Code用以合约账户初始化的过程。用T_{\mathrm{i}}表示。
  • data: 调用合约的交易,会包含一段不限长度的输入信息,用T_{\mathrm{d}}表示。
// github.com/ethereum/go-ethereum/core/types/transaction.go
type Transaction struct {
    data txdata
    // caches
    hash atomic.Value
    size atomic.Value
    from atomic.Value
}
type txdata struct {
    AccountNonce uint64          `json:"nonce"    gencodec:"required"`
    Price        *big.Int        `json:"gasPrice" gencodec:"required"`
    GasLimit     uint64          `json:"gas"      gencodec:"required"`
    Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
    Amount       *big.Int        `json:"value"    gencodec:"required"`
    Payload      []byte          `json:"input"    gencodec:"required"`

    // Signature values
    V *big.Int `json:"v" gencodec:"required"`
    R *big.Int `json:"r" gencodec:"required"`
    S *big.Int `json:"s" gencodec:"required"`

    // This is only used when marshaling to JSON.
    Hash *common.Hash `json:"hash" rlp:"-"`
}

一些符号化定义
\begin{equation} L_{T}(T) \equiv \begin{cases} (T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{i}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{if} \; T_{\mathrm{t}} = \varnothing\\ (T_{\mathrm{n}}, T_{\mathrm{p}}, T_{\mathrm{g}}, T_{\mathrm{t}}, T_{\mathrm{v}}, T_{\mathbf{d}}, T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}) & \text{otherwise} \end{cases} \tag{15} \end{equation}

\begin{equation} \begin{array} \quad T_{\mathrm{n}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{v}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{p}} \in \mathbb{N}_{256} & \wedge \\ T_{\mathrm{g}} \in \mathbb{N}_{256} & \wedge & T_{\mathrm{w}} \in \mathbb{N}_5 & \wedge & T_{\mathrm{r}} \in \mathbb{N}_{256} & \wedge \\ T_{\mathrm{s}} \in \mathbb{N}_{256} & \wedge & T_{\mathbf{d}} \in \mathbb{B} & \wedge & T_{\mathbf{i}} \in \mathbb{B} \end{array} \tag{16} \end{equation}

\begin{equation} \mathbb{N}_{\mathrm{n}} = \{ P: P \in \mathbb{N} \wedge P < 2^n \} \tag{17} \end{equation}

\begin{equation} T_{\mathbf{t}} \in \begin{cases} \mathbb{B}_{20} & \text{if} \quad T_{\mathrm{t}} \neq \varnothing \\ \mathbb{B}_{0} & \text{otherwise}\end{cases} \tag{18} \end{equation}
以太坊中根据交易中的to值是否为空,可以判断交易是创建合约还是执行合约。

公式15表示,如果to的值T_{\mathrm{t}}为空,则交易是创建合约的交易,需要有init数据。交易的RLP形式化可以表示为nonce T_{\mathrm{n}},gasPrice T_{\mathrm{p}}, gasLimit T_{\mathrm{g}}, to T_{\mathrm{t}},value T_{\mathrm{v}}init T_{\mathrm{i}},“v ,r, s” T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}。如果T_{\mathrm{t}}不为空,则交易是执行合约的交易,需要有data数据。交易的RLP形式化可以表示为nonce T_{\mathrm{n}},gasPrice T_{\mathrm{p}}, gasLimit T_{\mathrm{g}}, to T_{\mathrm{t}},value T_{\mathrm{v}}data T_{\mathrm{d}},“v ,r, s” T_{\mathrm{w}}, T_{\mathrm{r}}, T_{\mathrm{s}}

公式16,是对交易的各个字段限制的符号化定义。其意思是nonce、value、gasPrice、gasLimit以及特殊的用来验证签名的r和s 都是小于2256的正整数,用来验证签名的v (T_{\mathrm{w}})是小于25的正整数。而init和data都是未知长度的字符数组。

公式17,是对\mathbb{N}_{\mathrm{n}}的定义,即小于2n的正整数。

公式18,是对交易中的to字段的符号化定义,当其不为空的时候,是20位的字符,为空的时候是0位字符。

2.3 区块

以太坊中的一个区块由区块头Header,以及交易列表B_{\mathbf{T}},以及ommerblock的header集合B_{\mathbf{U}}三部分组成。

Header包括以下字段。

  • parentHash: 父节点的hash值。用H_{\mathrm{p}}表示。
  • ommersHash: uncle节点的hash值,这块是跟GHOST相关的,用H_{\mathrm{o}}表示。
  • beneficiary: 矿工address,用H_{\mathrm{c}}表示。
  • stateRoot: 当所有交易都执行完毕后的世界状态树的根节点,用H_{\mathrm{r}}表示。
  • transactionsRoot:交易列表的根节点,用H_{\mathrm{t}}表示。
  • receiptsRoot:收据的根节点,用H_{\mathrm{e}}表示。
  • logsBloom:日志过滤器,用H_{\mathrm{b}}表示。这个暂时没细看,不太确定。
  • difficulty:区块难度,根据上一个区块的难度以及时间戳算出来的值,用H_{\mathrm{d}}表示。
  • number:区块号,用H_{\mathrm{i}}表示。
  • gasLimit: 区块的gas数量限制,即区块中交易使用掉的gas值不应该超过该值。用H_{\mathrm{l}}表示。
  • gasUsed: 区块使用掉的gas数量,用H_{\mathrm{g}}表示。
  • timestamp:时间戳,用H_{\mathrm{s}}表示。
  • extraData:额外的数据,合法的交易对长度有限制,用H_{\mathrm{x}}表示。
  • mixHash: 与nonce一起用作工作量证明,用H_{\mathrm{m}}表示。
  • nonce:与mixHash一起用作工作量证明,用H_{\mathrm{n}}表示。
ethereumblock.png
// github.com/ethereum/go-ethereum/core/types/block.go
// "external" block encoding. used for eth protocol, etc.
type extblock struct {
    Header *Header
    Txs    []*Transaction
    Uncles []*Header
}

// Header represents a block header in the Ethereum blockchain.
type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
    Coinbase    common.Address `json:"miner"            gencodec:"required"`
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
    Number      *big.Int       `json:"number"           gencodec:"required"`
    GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
    GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`
    Extra       []byte         `json:"extraData"        gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}
// Receipt represents the results of a transaction.
type Receipt struct {
    // Consensus fields
    PostState         []byte `json:"root"`
    Status            uint   `json:"status"`
    CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
    Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
    Logs              []*Log `json:"logs"              gencodec:"required"`

    // Implementation fields (don't reorder!)
    TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
    ContractAddress common.Address `json:"contractAddress"`
    GasUsed         uint64         `json:"gasUsed" gencodec:"required"`
}

区块的符号化定义
\begin{equation} B \equiv (B_{H}, B_{\mathbf{T}}, B_{\mathbf{U}}) \tag{19} \end{equation}
公式19,表示区块由三部分组成,区块头B_{H},交易列表B_{\mathbf{T}},以及ommerblock的header集合B_{\mathbf{U}}

2.3.1 交易收据

以太坊中为了将交易的信息进行编码,以方便索引以及查找或者零知识证明等相关的东西,为每条交易定义了一定收据。对于第i个交易,其收据用B_R[i]表示。
每条收据都是一个四元组,包括区块当前累计使用的gas值R_{\mathrm{u}},交易执行产生的log R_{\mathrm{l}},日志过滤器R_{\mathrm{b}},以及状态码R_{\mathrm{z}}
\begin{equation} R \equiv (R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}}, R_{\mathrm{z}}) \tag{20} \end{equation}

\begin{equation} L_{R}(R) \equiv (0 \in \mathbb{B}_{256}, R_{\mathrm{u}}, R_{\mathrm{b}}, R_{\mathbf{l}}) \tag{21} \end{equation}

\begin{equation} R_{\mathrm{z}} \in \mathbb{N} \tag{22} \end{equation}

\begin{equation} R_{\mathrm{u}} \in \mathbb{N} \quad \wedge \quad R_{\mathrm{b}} \in \mathbb{B}_{256} \tag{23} \end{equation}

\begin{equation} O \equiv (O_{\mathrm{a}}, ({O_{\mathbf{t}}}_0, {O_{\mathbf{t}}}_1, ...), O_{\mathbf{d}}) \tag{24} \end{equation}

\begin{equation} O_{\mathrm{a}} \in \mathbb{B}_{20} \wedge \forall_{t \in O_{\mathbf{t}}}: t \in \mathbb{B}_{32} \wedge O_{\mathbf{d}} \in \mathbb{B} \tag{25} \end{equation}
公式20对区块头中的收据作了定义,收据是个四元组,四元组定义如前文。

公式21,收据的RLP形式化表示为L_{R}(R)。其中0 \in \mathbb{B}_{256}在之前版本的协议中是交易执行之前的stateRoot。现在被替换为0.

公式22,表示R_{\mathrm{z}}状态码是正整数。

公式23对收据中当前累计使用的gas值R_{\mathrm{u}},和日志过滤器R_{\mathrm{b}}进行了描述。显然累计gas值R_{\mathrm{u}}是一个正整数。而日志过滤器R_{\mathrm{b}}是256位字符。

公式24对交易执行的日志R_{\mathrm{l}}进行了解释。

公式25对其限制进行了描述。R_{\mathrm{l}}是日志条目的序列。日志条目需要包括纪录日志者的地址,以及日志话题分类,以及实际数据。日志条目用O来表示,用O_{\mathrm{a}}表示日志纪录者的address,用O_{\mathrm{t}}来表示一些列32位字符的日志主题(log topics),用O_{\mathrm{d}}来表示字符数据。其中日志纪录者的address O_{\mathrm{a}}是20位字符,每一个日志分类话题O_{\mathrm{t}}是一个32位字符,而日志数据O_{\mathrm{d}}是未知长度的字符。

公式26-30.对日志过滤函数做了定义,这块涉及到东西是数据操作层面的,不影响对流程的理解。暂不作解释。

2.3.2 整体的合法性

上面介绍过了区块包含区块头,区块交易列表,以及区块的ommer区块的头三部分。以太坊中判断一个区块是否合法,首先需要对区块整体上做合法性判断。见公式31

  • 其区块头的状态树的根stateRoot也就是 H_{\mathrm{r}},是否确实是状态树的根。
  • 其区块头的ommerHash也就是 H_{\mathrm{o}},是否与区块的ommer区块的头部分的hash值一致。
  • 其区块头中的transactionRoot也就是H_{\mathrm{t}},f即区块中交易的树的根,是否和区块存储的交易列表中的交易一一对应。一一对应关系见公式32,是将交易所在列表中的索引的RLP作为键,交易内容v的RLP作为值的键值对。
  • 其区块头中的recieptRoot也就是H_{\mathrm{e}},即区块中收据的树的根,是否和区块存储的交易列表一一对应,是不是每条交易都有一条相应的收据。一一对应关系见公式32,是将收据所在列表中的索引的RLP作为键,收据内容v的RLP作为值的键值对。
  • 区块头中logsBloom也就是H_{\mathrm{b}},是否包含了区块的交易的所有日志。
  • 执行该区块之前的状态树的根节点,是否与其父区块的中的根节点一致。见公式33.

\begin{equation} \begin{array} {}H_{\mathrm{r}} &\equiv& \mathtt{\small TRIE}(L_S(\Pi(\boldsymbol{\sigma}, B))) & \wedge \\ {}H_{\mathrm{o}} &\equiv& \mathtt{\small KEC}(\mathtt{\small RLP}(L_H^*(B_{\mathbf{U}}))) & \wedge \\ {}H_{\mathrm{t}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{T}} \rVert, i \in \mathbb{P}: \quad p (i, L_{T}(B_{\mathbf{T}}[i]))\}) & \wedge \\ {}H_{\mathrm{e}} &\equiv& \mathtt{\small TRIE}(\{\forall i < \lVert B_{\mathbf{R}} \rVert, i \in \mathbb{P}: \quad p(i, {L_{R}}(B_{\mathbf{R}}[i]))\}) & \wedge \\ {}H_{\mathrm{b}} &\equiv& \bigvee_{\mathbf{r} \in B_{\mathbf{R}}} \big( \mathbf{r}_{\mathrm{b}} \big) \end{array} \tag{31} \end{equation}

\begin{equation} p(k, v) \equiv \big( \mathtt{\small RLP}(k), \mathtt{\small RLP}(v) \big) \tag{32} \end{equation}

\begin{equation} \mathtt{\small TRIE}(L_{S}(\boldsymbol{\sigma})) = {P(B_H)_H}_{\mathrm{r}} \tag{33} \end{equation}

2.3.3 序列化

对区块,以及区块头的序列化表示如下。

\begin{equation} \quad L_{H}(H) \equiv ({}H_{\mathrm{p}}, H_{\mathrm{o}}, H_{\mathrm{c}}, H_{\mathrm{r}}, H_{\mathrm{t}}, H_{\mathrm{e}}, H_{\mathrm{b}}, H_{\mathrm{d}}, H_{\mathrm{i}}, H_{\mathrm{l}}, H_{\mathrm{g}}, H_{\mathrm{s}}, H_{\mathrm{x}}, H_{\mathrm{m}}, H_{\mathrm{n}} ) \tag{34} \end{equation}

\begin{equation} \quad L_{B}(B) \equiv \big( L_{H}(B_{H}), L_{T}^*(B_{\mathbf{T}}), L_{H}^*({B_{\mathbf{U}}}) \big) \tag{35} \end{equation}

\begin{equation} {f^*}\big( (x_0, x_1, ...) \big) \equiv \big( f(x_0), f(x_1), ... \big) \quad \tag{36} \end{equation}

\begin{equation} \begin{array}[t]{lclclcl} {H_{\mathrm{p}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{o}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{c}} \in \mathbb{B}_{20} & \wedge \\ {H_{\mathrm{r}}} \in \mathbb{B}_{32} & \wedge & H_{\mathrm{t}} \in \mathbb{B}_{32} & \wedge & {H_{\mathrm{e}}} \in \mathbb{B}_{32} & \wedge \\ {H_{\mathrm{b}}} \in \mathbb{B}_{256} & \wedge & H_{\mathrm{d}} \in \mathbb{N} & \wedge & {H_{\mathrm{i}}} \in \mathbb{N} & \wedge \\ {H_{\mathrm{l}}} \in \mathbb{N} & \wedge & H_{\mathrm{g}} \in \mathbb{N} & \wedge & {H_{\mathrm{s}}} \in \mathbb{N}_{256} & \wedge \\ {H_{\mathrm{x}}} \in \mathbb{B} & \wedge & H_{\mathrm{m}} \in \mathbb{B}_{32} & \wedge & {H_{\mathrm{n}}} \in \mathbb{B}_{8} \end{array} \tag{37} \end{equation}

公式34是区块头的序列化表示。

公式35是区块的序列化表示,即分别将区块头序列化,区块的交易列表,ommer区块头序列化。其中交易序列化函数L_T见公式15.

公式36表示列表序列化和当个序列化的关系,列表的序列化,就是把列表中的元素分别序列化,然后将结果组成列表。

公式37是对区块头中属性的限制规定:

  • 各种hash值都是32位字符。包括parentHash H_{\mathrm{p}},ommerHash H_{\mathrm{o}}, stateRoot H_{\mathrm{r}}, transactionRoot H_{\mathrm{t}}, RecieptRoot H_{\mathrm{e}}, mixHash H_{\mathrm{m}}
  • 受益人也就是挖矿的人的地址beneficiary H_{\mathrm{c}},是20位字符。
  • 日志过滤器logBoom H_{\mathrm{b}},是256位字符。
  • 难度,区块号,gas限制,用掉的gas等都是正整数。difficulty H_{\mathrm{d}}, Number H_{\mathrm{i}},gasLimit H_{\mathrm{l}} gasUsed H_{\mathrm{g}}
  • 时间戳timestamp H_{\mathrm{s}}是个大的正整数,其值小于2256
  • 额外的数据extraData H_{\mathrm{x}}是未知长度字符。
  • nonce H_{\mathrm{n}}是8位的字符。

2.3.4 区块头的合法性

确定区块是否合法除了整体性的合法校验,还需要对区块头进行更进一步的校验。主要校验规则有以下几点:

  • parentHash正确。即parentHash与其父区块的头的hash一致。
  • number为父区块number值加一。
  • difficuilty难度正确。区块合理的难度跟父区块难度,以及当前区块时间戳和父区块时间戳间隔以及区块编号有关。难度可以起到一定的调节出块时间的作用。,可以看出当出块变快(也就是出块间隔变小之后)难度会增加,相反难度会减小。
  • gasLimit和上一个区块的差值在规定范围内。
  • gasUsed小于等于gasLimit
  • timestamp时间戳必须大于上一区块的时间戳。
  • mixHash和nonce必须满足PoW。
  • extraData最多为32个字节。
// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
// See YP section 4.3.4. "Block Header Validity"
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
    // Ensure that the header's extra-data section is of a reasonable size
      // 验证extraData的长度
    if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
        return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
    }
    // Verify the header's timestamp
    // 验证时间戳是否超过大小限制,是否过大,是否大于上一区块的时间戳等
    if uncle {
        if header.Time.Cmp(math.MaxBig256) > 0 {
            return errLargeBlockTime
        }
    } else {
        if header.Time.Cmp(big.NewInt(time.Now().Add(allowedFutureBlockTime).Unix())) > 0 {
            return consensus.ErrFutureBlock
        }
    }
    if header.Time.Cmp(parent.Time) <= 0 {
        return errZeroBlockTime
    }

      // 验证难度是否正确
    // Verify the block's difficulty based in it's timestamp and parent's difficulty
    expected := ethash.CalcDifficulty(chain, header.Time.Uint64(), parent)

    if expected.Cmp(header.Difficulty) != 0 {
        return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
    }
    // Verify that the gas limit is <= 2^63-1
    cap := uint64(0x7fffffffffffffff)
     //验证gasLimit是否超了上限
    if header.GasLimit > cap {
        return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
    }
     //验证已用的gas值是否小于等于gasLimit
    // Verify that the gasUsed is <= gasLimit
    if header.GasUsed > header.GasLimit {
        return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit)
    }

    // Verify that the gas limit remains within allowed bounds
    //判断gasLimit与父区块的gasLimit差值是否在规定范围内
    diff := int64(parent.GasLimit) - int64(header.GasLimit)
    if diff < 0 {
        diff *= -1
    }
    limit := parent.GasLimit / params.GasLimitBoundDivisor

    if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit {
        return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit)
    }
    // Verify that the block number is parent's +1
        //验证区块号,是否是父区块号+1
    if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
        return consensus.ErrInvalidNumber
    }
    // Verify the engine specific seal securing the block
     //验证PoW
    if seal {
        if err := ethash.VerifySeal(chain, header); err != nil {
            return err
        }
    }
    // If all checks passed, validate any special fields for hard forks
    if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
        return err
    }
    if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
        return err
    }
    return nil
}

校验区块号和区块hash的符号化表示
\begin{equation} P(H) \equiv B': \mathtt{\tiny KEC}(\mathtt{\tiny RLP}(B'_{H})) = {H_{\mathrm{p}}} \tag{39} \end{equation}

\begin{equation} H_{\mathrm{i}} \equiv P(H)_{H_{\mathrm{i}}} + 1 \tag{40} \end{equation}

公式39表示,parentHash值应该为父节点Header的hash值。

公式40表示,number为父节点number + 1.

难度计算的符号化表示
\begin{equation} D(H) \equiv \begin{cases} D_0 & \text{if} \quad H_{\mathrm{i}} = 0\\ \text{max}\!\left(D_0, {P(H)_{H}}_{\mathrm{d}} + {x}\times{\varsigma_2} + {\epsilon} \right) & \text{otherwise}\\ \end{cases} \tag{41} \end{equation}

\begin{equation} D_0 \equiv 131072 \tag{42} \end{equation}

\begin{equation} {x} \equiv \left\lfloor\frac{P(H)_{H_{\mathrm{d}}}}{2048}\right\rfloor \tag{43} \end{equation}

\begin{equation} {\varsigma_2} \equiv \text{max}\left( y - \left\lfloor\frac{H_{\mathrm{s}} - {P(H)_{H}}_{\mathrm{s}}}{9}\right\rfloor, -99 \right) \tag{44} \end{equation}

\begin{equation*} y \equiv \begin{cases} 1 & \text{if} \, \lVert P(H)_{\mathbf{U}}\rVert = 0 \\ 2 & \text{otherwise} \end{cases} \end{equation*}

\begin{equation} \epsilon \equiv \left\lfloor 2^{ \left\lfloor H'_{\mathrm{i}} \div 100000 \right\rfloor - 2 } \right\rfloor \tag{45} \end{equation}

\begin{equation} H'_{\mathrm{i}} \equiv \max(H_{\mathrm{i}} - 3000000, 0) \tag{46} \end{equation}

公式41-46为difficulty的计算方法。

公式41,42表示,当区块编号为0的时候其难度值是固定好的,在这里用D_0表示,其值为131072.对于其他区块,其难度值需要根据其父区块难度值以及一些其他因素,出块的间隔时间,区块编号等有关进行调节的,若小于D_0,则难度值调整为D_0

公式43,调节系数(the adjustment factor )x的定义。

公式44,难度系数(diculty parameter){\varsigma_2}的定义。该系数主要与出块间隔时间有关,当间隔大的时候,系数变大,难度也会相应变大,当间隔小的时候,系数变小,难度也会变小。使得区块链在整体上出块时间是趋于稳定的。其中y值根据父节点的uncle节点是否为空而有所区别,可以看出当父节点的uncle不为空的时候,y值为2,说明当前的分叉程度较大,适当调大难度,一定程度上会减少分叉。

公式45,46,“difficulty bomb”, or “ice age” {\epsilon}的定义。(看说明好像是为了将来切PoS共识的时候,调节难度用)

// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
func (ethash *Ethash) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
    return CalcDifficulty(chain.Config(), time, parent)
}

// CalcDifficulty is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time
// given the parent block's time and difficulty.
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int {
    next := new(big.Int).Add(parent.Number, big1)
    switch {
    case config.IsByzantium(next):
        return calcDifficultyByzantium(time, parent)
    case config.IsHomestead(next):
        return calcDifficultyHomestead(time, parent)
    default:
        return calcDifficultyFrontier(time, parent)
    }
}

// 黄皮书中的介绍的是Byzantium难度协议,所以这里只给出相应的代码。其他几种难度调节协议只是参数值上有区别。
// calcDifficultyByzantium is the difficulty adjustment algorithm. It returns
// the difficulty that a new block should have when created at time given the
// parent block's time and difficulty. The calculation uses the Byzantium rules.
func calcDifficultyByzantium(time uint64, parent *types.Header) *big.Int {
    // https://github.com/ethereum/EIPs/issues/100.
    // algorithm:
    // diff = (parent_diff +
    //         (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
    //        ) + 2^(periodCount - 2)

    bigTime := new(big.Int).SetUint64(time)
    bigParentTime := new(big.Int).Set(parent.Time)

    // holds intermediate values to make the algo easier to read & audit
    x := new(big.Int)
    y := new(big.Int)

    // (2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9
    x.Sub(bigTime, bigParentTime)
    x.Div(x, big9)
    if parent.UncleHash == types.EmptyUncleHash {
        x.Sub(big1, x)
    } else {
        x.Sub(big2, x)
    }
    // max((2 if len(parent_uncles) else 1) - (block_timestamp - parent_timestamp) // 9, -99)
    if x.Cmp(bigMinus99) < 0 {
        x.Set(bigMinus99)
    }
    // parent_diff + (parent_diff / 2048 * max((2 if len(parent.uncles) else 1) - ((timestamp - parent.timestamp) // 9), -99))
    y.Div(parent.Difficulty, params.DifficultyBoundDivisor)
    x.Mul(y, x)
    x.Add(parent.Difficulty, x)

    // minimum difficulty can ever be (before exponential factor)
    if x.Cmp(params.MinimumDifficulty) < 0 {
        x.Set(params.MinimumDifficulty)
    }
    // calculate a fake block number for the ice-age delay:
    //   https://github.com/ethereum/EIPs/pull/669
    //   fake_block_number = min(0, block.number - 3_000_000
    fakeBlockNumber := new(big.Int)
    if parent.Number.Cmp(big2999999) >= 0 {
        fakeBlockNumber = fakeBlockNumber.Sub(parent.Number, big2999999) // Note, parent is 1 less than the actual block number
    }
    // for the exponential factor
    periodCount := fakeBlockNumber
    periodCount.Div(periodCount, expDiffPeriod)

    // the exponential factor, commonly referred to as "the bomb"
    // diff = diff + 2^(periodCount - 2)
    if periodCount.Cmp(big1) > 0 {
        y.Sub(periodCount, big2)
        y.Exp(big2, y, nil)
        x.Add(x, y)
    }
    return x
}

gasLimit限制的符号化表示
\begin{eqnarray} \nonumber & & H_{\mathrm{l}} < {P(H)_{H_{\mathrm{l}}}} + \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber & & H_{\mathrm{l}} > {P(H)_{H_{\mathrm{l}}}} - \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber & & H_{\mathrm{l}} \geqslant 5000 \tag{47} \end{eqnarray}

公式47表示区块的gasLimit必须大于等于5000,且其和上一个区块的gasLimit差值不超过\left\lfloor\frac{P(H)_{H_l}}{1024}\right\rfloor

时间戳的符号化表示

\begin{equation} H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}} \tag{48} \end{equation}

公式48表示当前区块的时间戳必须大于父区块的时间戳。(代码中要求区块的时间戳不能比当前时间大15秒以上)

mixHash和nonce相关符号化表示

\begin{equation} n \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \quad \wedge \quad m = H_{\mathrm{m}} \tag{49} \end{equation}

公式49表示,nonce值和mixHash需要满足PoW。

区块头验证的符号化表示

\begin{eqnarray} \tag{50} V(H) & \equiv & n \leqslant \frac{2^{256}}{H_{\mathrm{d}}} \wedge m = H_{\mathrm{m}} \quad \wedge \\ \nonumber & & H_{\mathrm{d}} = D(H) \quad \wedge \\ \nonumber& & H_{\mathrm{g}} \le H_{\mathrm{l}} \quad \wedge \\ \nonumber& & H_{\mathrm{l}} < {P(H)_{H}}_{\mathrm{l}} + \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber& & H_{\mathrm{l}} > {P(H)_{H}}_{\mathrm{l}} - \left\lfloor\frac{P(H)_{H_{\mathrm{l}}}}{1024}\right\rfloor \quad \wedge \\ \nonumber& & H_{\mathrm{l}} \geqslant 5000 \quad \wedge \\ \nonumber& & H_{\mathrm{s}} > {P(H)_{H}}_{\mathrm{s}} \quad \wedge \\ \nonumber& & H_{\mathrm{i}} = {P(H)_{H}}_{\mathrm{i}} +1 \quad \wedge \\ \nonumber& & \lVert H_{\mathrm{x}} \rVert \le 32 \end{eqnarray}

相关的含义见本小节开始部分对区块头验证的那几点。

图片来源于网络侵删

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350