以太坊 黄皮书

admin
admin 2019年09月05日
  • 在其它设备中阅读本文章

写在篇头

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

一、区块链范型

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

σt+1Υ(σt,T)(1)

公式 1 表示 t + 1 时的状态,是由 t 时的状态经过交易 T 转变而来。转变函数为 Υ

如下图所示

Transaction Function σt+1Π(σt,B)(2)

B(...,(T0,T1,...))(3)
Π(σ,B)Ω(B,Υ(Υ(σ,T0),T1)...)(4)

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

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

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

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

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

    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 代表此账户创建的合约序号。用 σ[a]n 来表示。
  • balance:此地址拥有 Wei 的数量。1Ether=10^18Wei。用 σ[a]b 来表示。
  • storageRoot: 理论上是指 Merkle Patricia 树的根节点 256 位的 Hash 值。用 σ[a]s 来表示。公式 6 中有介绍。
  • codeHash:此账户 EVM 代码的 hash 值。对于外部拥有账户,codeHash 域是一个空字符串 的 Hash 值。对于合约账户,就是代码的 Hash 作为 codeHash 保存。用 σ[a]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 的补充 TRIE(LI(σ[a]s))σ[a]s(6)

LI((k,v))(KEC(k),RLP(v))(7)
kB32vN(8)

公式 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 (KEC(k), 与其数据 Account 内容的 RLP (RLP(v))

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

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

一些符号化定义

以太坊中的账户有两类,一类是外部账户,一类是合约账户。其中外部账户被私钥控制且没有任何代码与之关联。合约账户,被它们的合约代码控制且有代码与之关联。以下几个公式定义了账户的各种状态。 LS(σ){p(a):σ[a]}(9) 其中 p(a)(KEC(a),RLP((σ[a]n,σ[a]b,σ[a]s,σ[a]c)))(10)

a:σ[a]=(aB20v(σ[a]))(11)
v(x)xnN256xbN256xsB32xcB32(12)
EMPTY(σ,a)σ[a]c=KEC(())σ[a]n=0σ[a]b=0(13)
DEAD(σ,a)σ[a]=EMPTY(σ,a)(14)

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

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

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

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

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

2.2 交易

  • nonce: 与发送该交易的账户的 nonce 值一致。用 Tn 表示。
  • gasPrice: 表示每 gas 的单价为多少 wei。用 Tp 表示。
  • gasLimit: 执行该条交易最大被允许使用的 gas 数目。用 Tg 表示。
  • to:160 位的接受者地址。当交易位创建合约时,该值位空。用 Tt 表示。
  • value: 表示发送者发送的 wei 的数目。该值为向接受者转移的 wei 的数目,或者是创建合约时作为合约账户的初始 wei 数目。用 Tv 表示。
  • v,r,s:  交易的签名信息,用以决定交易的发送者。分别用 Tw,Tr,Ts 表示。
  • init: 如果是创建合约的交易, 则 init 表示一段不限长度的 EVM-Code 用以合约账户初始化的过程。用 Ti 表示。
  • data: 调用合约的交易,会包含一段不限长度的输入信息,用 Td 表示。
// 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:"-"`
}

一些符号化定义 LT(T){(Tn,Tp,Tg,Tt,Tv,Ti,Tw,Tr,Ts)(Tn,Tp,Tg,Tt,Tv,Td,Tw,Tr,Ts)ifTt=otherwise(15)

TnN256TgN256TsN256TvN256TwN5TdBTpN256TrN256TiB(16)
Nn={P:PNP<2n}(17)

Tt{B20B0ifTtotherwise(18)

以太坊中根据交易中的 to 值是否为空,可以判断交易是创建合约还是执行合约。

公式 15 表示,如果 to 的值 Tt 为空,则交易是创建合约的交易,需要有 init 数据。交易的 RLP 形式化可以表示为 nonce Tn,gasPrice Tp, gasLimit Tg, to Tt,value Tvinit Ti,“v ,r, s” Tw, Tr, Ts。如果 Tt 不为空,则交易是执行合约的交易,需要有 data 数据。交易的 RLP 形式化可以表示为 nonce Tn,gasPrice Tp, gasLimit Tg, to Tt,value Tvdata Td,“v ,r, s” Tw, Tr, Ts

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

公式 17,是对 Nn 的定义,即小于 2 n的正整数。

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

2.3 区块

以太坊中的一个区块由区块头 Header,以及交易列表 BT, 以及 ommerblock 的 header 集合BU 三部分组成。

Header 包括以下字段。

  • parentHash: 父节点的 hash 值。用 Hp 表示。
  • ommersHash: uncle 节点的 hash 值,这块是跟 GHOST 相关的,用 Ho 表示。
  • beneficiary: 矿工 address,用 Hc 表示。
  • stateRoot: 当所有交易都执行完毕后的世界状态树的根节点,用 Hr 表示。
  • transactionsRoot: 交易列表的根节点,用 Ht 表示。
  • receiptsRoot: 收据的根节点,用 He 表示。
  • logsBloom: 日志过滤器,用 Hb 表示。这个暂时没细看,不太确定。
  • difficulty: 区块难度,根据上一个区块的难度以及时间戳算出来的值,用 Hd 表示。
  • number: 区块号,用 Hi 表示。
  • gasLimit: 区块的 gas 数量限制,即区块中交易使用掉的 gas 值不应该超过该值。用 Hl 表示。
  • gasUsed: 区块使用掉的 gas 数量,用 Hg 表示。
  • timestamp: 时间戳,用 Hs 表示。
  • extraData: 额外的数据,合法的交易对长度有限制,用 Hx 表示。
  • mixHash: 与 nonce 一起用作工作量证明,用 Hm 表示。
  • nonce: 与 mixHash 一起用作工作量证明,用 Hn 表示。

image.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"`
}

区块的符号化定义 B(BH,BT,BU)(19) 公式 19,表示区块由三部分组成,区块头 BH,交易列表BT, 以及 ommerblock 的 header 集合BU

2.3.1 交易收据

以太坊中为了将交易的信息进行编码,以方便索引以及查找或者零知识证明等相关的东西,为每条交易定义了一定收据。对于第 i 个交易,其收据用 BR[i] 表示。

每条收据都是一个四元组,包括区块当前累计使用的 gas 值Ru,交易执行产生的 log Rl,日志过滤器Rb,以及状态码Rz
R(Ru,Rb,Rl,Rz)(20)

LR(R)(0B256,Ru,Rb,Rl)(21)
RzN(22)
RuNRbB256(23)
O(Oa,(Ot0,Ot1,...),Od)(24)

OaB20tOt:tB32OdB(25)

公式 20 对区块头中的收据作了定义,收据是个四元组,四元组定义如前文。

公式 21,收据的 RLP 形式化表示为 LR(R)。其中0B256 在之前版本的协议中是交易执行之前的 stateRoot。现在被替换为 0.

公式 22,表示 Rz 状态码是正整数。

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

公式 24 对交易执行的日志 Rl 进行了解释。

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

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

2.3.2 整体的合法性

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

  • 其区块头的状态树的根 stateRoot 也就是 Hr,是否确实是状态树的根。
  • 其区块头的 ommerHash 也就是 Ho, 是否与区块的 ommer 区块的头部分的 hash 值一致。
  • 其区块头中的 transactionRoot 也就是 Ht,f 即区块中交易的树的根,是否和区块存储的交易列表中的交易一一对应。一一对应关系见公式 32,是将交易所在列表中的索引的 RLP 作为键,交易内容 v 的 RLP 作为值的键值对。
  • 其区块头中的 recieptRoot 也就是He,即区块中收据的树的根,是否和区块存储的交易列表一一对应,是不是每条交易都有一条相应的收据。一一对应关系见公式 32,是将收据所在列表中的索引的 RLP 作为键,收据内容 v 的 RLP 作为值的键值对。
  • 区块头中 logsBloom 也就是Hb,是否包含了区块的交易的所有日志。
  • 执行该区块之前的状态树的根节点,是否与其父区块的中的根节点一致。见公式 33.
HrHoHtHeHbTRIE(LS(Π(σ,B)))KEC(RLP(LH(BU)))TRIE({i<BT,iP:p(i,LT(BT[i]))})TRIE({i<BR,iP:p(i,LR(BR[i]))})rBR(rb)(31)
p(k,v)(RLP(k),RLP(v))(32)

TRIE(LS(σ))=P(BH)Hr(33)

2.3.3 序列化

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

LH(H)(Hp,Ho,Hc,Hr,Ht,He,Hb,Hd,Hi,Hl,Hg,Hs,Hx,Hm,Hn)(34)
LB(B)(LH(BH),LT(BT),LH(BU))(35)
f((x0,x1,...))(f(x0),f(x1),...)(36)
HpB32HrB32HbB256HlNHxBHoB32HtB32HdNHgNHmB32HcB20HeB32HiNHsN256HnB8(37)

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

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

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

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

  • 各种 hash 值都是 32 位字符。包括 parentHash Hp,ommerHash Ho, stateRoot Hr, transactionRoot Ht, RecieptRoot He, mixHash Hm
  • 受益人也就是挖矿的人的地址 beneficiary Hc, 是 20 位字符。
  • 日志过滤器 logBoom Hb, 是 256 位字符。
  • 难度,区块号,gas 限制,用掉的 gas 等都是正整数。difficulty Hd, Number Hi,gasLimit Hl gasUsed Hg
  • 时间戳 timestamp Hs是个大的正整数,其值小于 2 256
  • 额外的数据 extraData Hx是未知长度字符。
  • nonce Hn是 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 的符号化表示 P(H)B:KEC(RLP(BH))=Hp(39)

HiP(H)Hi+1(40)

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

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

难度计算的符号化表示 D(H){D0max(D0,P(H)Hd+x×ς2+ϵ)ifHi=0otherwise(41)

D0131072(42)
xP(H)Hd2048(43)
ς2max(yHsP(H)Hs9,99)(44)
y{12ifP(H)U=0otherwise
ϵ2Hi÷1000002(45)
Himax(Hi3000000,0)(46)

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

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

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

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

公式 45,46,“difficulty bomb”, or “ice age” ϵ的定义。(看说明好像是为了将来切 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 限制的符号化表示 Hl<P(H)Hl+P(H)Hl1024Hl>P(H)HlP(H)Hl1024Hl5000(47)

公式 47 表示区块的 gasLimit 必须大于等于 5000,且其和上一个区块的 gasLimit 差值不超过 P(H)Hl1024

时间戳的符号化表示

Hs>P(H)Hs(48)

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

mixHash 和 nonce 相关符号化表示

n2256Hdm=Hm(49)

参考资料:

黄皮书解析
以太坊黄皮书中文版