以太坊黃皮書詳解(一)

寫在篇頭

本文是對以太坊的黃皮書的解析,并參照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}來表示。
  • 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}, \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} \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} = 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,解釋k,v對的時候的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_{\mathrmsyvs44x}表示。
// 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_{\mathbfemr1kks}, 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_{\mathbfbxu1ee1} \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_{\mathrmwfjxyqy},“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}表示。這個暫時沒細看,不太確定。
  • difficulty:區塊難度,根據上一個區塊的難度以及時間戳算出來的值,用H_{\mathrm1rfuyxd}表示。
  • 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},以及狀態碼R_{\mathrm{z}}。
\begin{equation} R \equiv (R_{\mathrm{u}}, R_{\mathrm}, 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}, 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} \in \mathbb{B}_{256} \tag{23} \end{equation}

\begin{equation} O \equiv (O_{\mathrm{a}}, ({O_{\mathbf{t}}}_0, {O_{\mathbf{t}}}_1, ...), O_{\mathbfngm32hv}) \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_{\mathbfypcviws} \in \mathbb{B} \tag{25} \end{equation}
公式20對區塊頭中的收據作了定義,收據是個四元組,四元組定義如前文。

公式21,收據的RLP形式化表示為L_{R}(R)。其中0 \in \mathbb{B}_{256}在之前版本的協議中是交易執行之前的stateRoot?,F在被替換為0.

公式22,表示R_{\mathrm{z}}狀態碼是正整數。

公式23對收據中當前累計使用的gas值R_{\mathrm{u}},和日志過濾器R_{\mathrm}進行了描述。顯然累計gas值R_{\mathrm{u}}是一個正整數。而日志過濾器R_{\mathrm}是256位字符。

公式24對交易執行的日志R_{\mathrm{l}}進行了解釋。

公式25對其限制進行了描述。R_{\mathrm{l}}是日志條目的序列。日志條目需要包括紀錄日志者的地址,以及日志話題分類,以及實際數據。日志條目用O來表示,用O_{\mathrm{a}}表示日志紀錄者的address,用O_{\mathrm{t}}來表示一些列32位字符的日志主題(log topics),用O_{\mathrm1ryxofg}來表示字符數據。其中日志紀錄者的address O_{\mathrm{a}}是20位字符,每一個日志分類話題O_{\mathrm{t}}是一個32位字符,而日志數據O_{\mathrmsmg4byd}是未知長度的字符。

公式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},是否包含了區塊的交易的所有日志。
  • 執行該區塊之前的狀態樹的根節點,是否與其父區塊的中的根節點一致。見公式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} &\equiv& \bigvee_{\mathbf{r} \in B_{\mathbf{R}}} \big( \mathbf{r}_{\mathrm} \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}, H_{\mathrmhocpvjx}, 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}} \in \mathbb{B}_{256} & \wedge & H_{\mathrmasg3jgg} \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},是256位字符。
  • 難度,區塊號,gas限制,用掉的gas等都是正整數。difficulty H_{\mathrmqlhe98q}, 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}}_{\mathrmh731zg1} + {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_{\mathrmwzw6is1}}}{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_{\mathrmj4wvjsg}} \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_{\mathrmgc6ufq6}} \wedge m = H_{\mathrm{m}} \quad \wedge \\ \nonumber & & H_{\mathrm3zn1ihe} = 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閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374