寫(xiě)在篇頭
本文是對(duì)以太坊的黃皮書(shū)的解析,并參照go-ethereum中的實(shí)現(xiàn),將相應(yīng)的代碼也列了出來(lái)。黃皮書(shū)中使用了大量的公式將以太坊的一些流程和狀態(tài)都公式化了。看不看得懂公式對(duì)理解的影響不大。本文中對(duì)公式進(jìn)行了解析。嫌麻煩的可以跳過(guò)每部分公式解析的部分。
一、區(qū)塊鏈范型
以太坊本質(zhì)是一個(gè)基于交易的狀態(tài)機(jī)(transaction-based state machine)。其以初始狀態(tài)(genesis state) 為起點(diǎn),通過(guò)執(zhí)行交易來(lái)到達(dá)新的狀態(tài)。
公式1表示t+1時(shí)的狀態(tài),是由t時(shí)的狀態(tài)經(jīng)過(guò)交易T轉(zhuǎn)變而來(lái)。轉(zhuǎn)變函數(shù)為。
如下圖所示
公式2-4是從區(qū)塊的角度來(lái)描述狀態(tài)的轉(zhuǎn)化過(guò)程。
公式2: t+1時(shí)的狀態(tài),是由t時(shí)的狀態(tài)經(jīng)過(guò)區(qū)塊B轉(zhuǎn)變而來(lái)。轉(zhuǎn)變函數(shù)為。
公式3: 區(qū)塊B是包含了一系列交易T的集合。
公式4: 區(qū)塊的狀態(tài)轉(zhuǎn)變函數(shù),相當(dāng)于逐條的執(zhí)行交易的狀態(tài)轉(zhuǎn)變
,然后完成所有交易轉(zhuǎn)變后再經(jīng)過(guò)
進(jìn)行一次狀態(tài)轉(zhuǎn)換。(這個(gè)地方
實(shí)際上是給礦工挖坑獎(jiǎng)勵(lì)。)
在以太坊中的實(shí)際情況就是區(qū)塊驗(yàn)證和執(zhí)行的過(guò)程。
- 逐一的執(zhí)行交易(也就是使用交易轉(zhuǎn)變函數(shù)操作
狀態(tài)集)。實(shí)際就是交易比方是A向B轉(zhuǎn)10ether,則A賬戶值-10,B賬戶值+10。(當(dāng)然執(zhí)行過(guò)程中還有g(shù)as消耗,這個(gè)后面詳述)
- 等整個(gè)block交易執(zhí)行完畢后,需要對(duì)礦工進(jìn)行獎(jiǎng)勵(lì)。也就是需要使用
進(jìn)行一次狀態(tài)轉(zhuǎn)換。
1.1 貨幣
以太坊中有以下四種單位的貨幣。以太坊中的各種計(jì)算都是以Wei為單位的。 (看有的地方好像有更多種單位,我這邊是直接按照黃皮書(shū)走的)
Multiplier | Name |
---|---|
100 | Wei |
1012 | Szabo |
1015 | Finney |
1018 | Ether |
1.2 分叉
以太坊的正確運(yùn)行建立在其鏈上只有一個(gè)鏈?zhǔn)怯行У模腥硕急仨氁邮芩碛卸鄠€(gè)狀態(tài)(或多個(gè)鏈)會(huì)摧毀這個(gè)系統(tǒng),因?yàn)樗谀膫€(gè)是正確狀態(tài)的問(wèn)題上不可能得到統(tǒng)一結(jié)果。如果鏈分叉了,你有可能在一條鏈上擁有10個(gè)幣,一條鏈上擁有20個(gè)幣,另一條鏈上擁有40個(gè)幣。在這種場(chǎng)景下,是沒(méi)有辦法確定哪個(gè)鏈才是最”有效的“。不論什么時(shí)候只要多個(gè)路徑產(chǎn)生了,一個(gè)”分叉“就會(huì)出現(xiàn)。
為了確定哪個(gè)路徑才是最有效的以及防止多條鏈的產(chǎn)生,以太坊使用了一個(gè)叫做“GHOST協(xié)議(GHOST protocol.)”的數(shù)學(xué)機(jī)制。
簡(jiǎn)單來(lái)說(shuō),GHOST協(xié)議就是讓我們必須選擇一個(gè)在其上完成計(jì)算最多的路徑。一個(gè)方法確定路徑就是使用最近一個(gè)區(qū)塊(葉子區(qū)塊)的區(qū)塊號(hào),區(qū)塊號(hào)代表著當(dāng)前路徑上總的區(qū)塊數(shù)(不包含創(chuàng)世紀(jì)區(qū)塊)。區(qū)塊號(hào)越大,路徑就會(huì)越長(zhǎng),就說(shuō)明越多的挖礦算力被消耗在此路徑上以達(dá)到葉子區(qū)塊。
二、 區(qū)塊、狀態(tài)與交易
2.1 世界狀態(tài)
以太坊中的世界狀態(tài)指地址(Address)與賬戶狀態(tài)(Account State)的集合。世界狀態(tài)并不是存儲(chǔ)在鏈上,而是通過(guò)Merkle Patricia tree來(lái)維護(hù)。
賬戶狀態(tài)(Account State)包含四個(gè)屬性。
- nonce: 如果賬戶是一個(gè)外部擁有賬戶,nonce代表從此賬戶地址發(fā)送的交易序號(hào)。如果賬戶是一個(gè)合約賬戶,nonce代表此賬戶創(chuàng)建的合約序號(hào)。用
來(lái)表示。
- balance:此地址擁有Wei的數(shù)量。1Ether=10^18Wei。用
來(lái)表示。
- storageRoot: 理論上是指Merkle Patricia樹(shù)的根節(jié)點(diǎn)256位的Hash值。用
來(lái)表示。公式6中有介紹。
- codeHash:此賬戶EVM代碼的hash值。對(duì)于外部擁有賬戶,codeHash域是一個(gè)空字符串
的Hash值。對(duì)于合約賬戶,就是代碼的Hash作為codeHash保存。用來(lái)表示。
// 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
}
關(guān)于storageRoot的補(bǔ)充
公式6, 由于有些時(shí)候我們不僅需要state的hash值的trie,而是需要其對(duì)應(yīng)的kv數(shù)據(jù)也包含其中。所以以太坊中的存儲(chǔ)State的樹(shù),不僅包含State的hash,同時(shí)也包含了存儲(chǔ)這個(gè)賬戶的address的hash和它對(duì)應(yīng)的data也就是其Account的值的數(shù)據(jù)對(duì)的集合。這里storageRoot實(shí)際上是這樣的樹(shù)的根節(jié)點(diǎn)hash值。
公式7,指state對(duì)應(yīng)的kv數(shù)據(jù)的RLP的形式化表示,是k的hash值作為key,value是v的RLP表示。也就是以太坊中實(shí)際存儲(chǔ)的state是賬戶address的hash ,與其數(shù)據(jù)Account內(nèi)容的RLP
。
公式8,指公式7中的k是32的字符數(shù)組。這個(gè)是由KECCAK256算法保證的。
注:原本公式8中要求的是v是個(gè)正整數(shù),但是我看來(lái)下代碼和下文公式10,感覺(jué)這里的v都應(yīng)該是Account的內(nèi)容
一些符號(hào)化定義
以太坊中的賬戶有兩類,一類是外部賬戶,一類是合約賬戶。其中外部賬戶被私鑰控制且沒(méi)有任何代碼與之關(guān)聯(lián)。合約賬戶,被它們的合約代碼控制且有代碼與之關(guān)聯(lián)。以下幾個(gè)公式定義了賬戶的各種狀態(tài)。
其中
公式9,定義了函數(shù),意思是若賬戶
不為空,則返回賬戶
。
公式10,定義,
其實(shí)就是我們上面公式7,解釋
,
對(duì)的時(shí)候的kv對(duì)。包括address的hash值,以及Account內(nèi)容的RLP結(jié)果。
公式11與公式12,對(duì)賬戶做了定義,表示賬戶要么為空,要么就是一個(gè)a為20個(gè)長(zhǎng)度的字符,其nonce值為小于2256的正整數(shù),balance值為小于2256的正整數(shù),storageRoot為32位的字符,codeHash為32的字符。
公式13,定義了空賬戶。若一個(gè)賬戶,其地址為空字符,并且該賬戶nonce值為0,balance值也為0.
公式14,定義了死賬戶,死賬戶要么為空,要么是一個(gè)EMPTY賬戶。
2.2 交易
- nonce: 與發(fā)送該交易的賬戶的nonce值一致。用
表示。
- gasPrice: 表示每gas的單價(jià)為多少wei。用
表示。
- gasLimit:執(zhí)行該條交易最大被允許使用的gas數(shù)目。用
表示。
- to:160位的接受者地址。當(dāng)交易位創(chuàng)建合約時(shí),該值位空。用
表示。
- value:表示發(fā)送者發(fā)送的wei的數(shù)目。該值為向接受者轉(zhuǎn)移的wei的數(shù)目,或者是創(chuàng)建合約時(shí)作為合約賬戶的初始wei數(shù)目。用
表示。
- v,r,s: 交易的簽名信息,用以決定交易的發(fā)送者。分別用
,
,
表示。
- init:如果是創(chuàng)建合約的交易,則init表示一段不限長(zhǎng)度的EVM-Code用以合約賬戶初始化的過(guò)程。用
表示。
- data: 調(diào)用合約的交易,會(huì)包含一段不限長(zhǎng)度的輸入信息,用
表示。
// 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:"-"`
}
一些符號(hào)化定義
以太坊中根據(jù)交易中的to值是否為空,可以判斷交易是創(chuàng)建合約還是執(zhí)行合約。
公式15表示,如果to的值為空,則交易是創(chuàng)建合約的交易,需要有init數(shù)據(jù)。交易的RLP形式化可以表示為nonce
,gasPrice
, gasLimit
, to
,value
,init
,“v ,r, s”
,
,
。如果
不為空,則交易是執(zhí)行合約的交易,需要有data數(shù)據(jù)。交易的RLP形式化可以表示為nonce
,gasPrice
, gasLimit
, to
,value
,data
,“v ,r, s”
,
,
。
公式16,是對(duì)交易的各個(gè)字段限制的符號(hào)化定義。其意思是nonce、value、gasPrice、gasLimit以及特殊的用來(lái)驗(yàn)證簽名的r和s 都是小于2256的正整數(shù),用來(lái)驗(yàn)證簽名的v ()是小于25的正整數(shù)。而init和data都是未知長(zhǎng)度的字符數(shù)組。
公式17,是對(duì)的定義,即小于2n的正整數(shù)。
公式18,是對(duì)交易中的to字段的符號(hào)化定義,當(dāng)其不為空的時(shí)候,是20位的字符,為空的時(shí)候是0位字符。
2.3 區(qū)塊
以太坊中的一個(gè)區(qū)塊由區(qū)塊頭Header,以及交易列表,以及ommerblock的header集合
三部分組成。
Header包括以下字段。
- parentHash: 父節(jié)點(diǎn)的hash值。用
表示。
- ommersHash: uncle節(jié)點(diǎn)的hash值,這塊是跟GHOST相關(guān)的,用
表示。
- beneficiary: 礦工address,用
表示。
- stateRoot: 當(dāng)所有交易都執(zhí)行完畢后的世界狀態(tài)樹(shù)的根節(jié)點(diǎn),用
表示。
- transactionsRoot:交易列表的根節(jié)點(diǎn),用
表示。
- receiptsRoot:收據(jù)的根節(jié)點(diǎn),用
表示。
- logsBloom:日志過(guò)濾器,用
表示。這個(gè)暫時(shí)沒(méi)細(xì)看,不太確定。
- difficulty:區(qū)塊難度,根據(jù)上一個(gè)區(qū)塊的難度以及時(shí)間戳算出來(lái)的值,用
表示。
- number:區(qū)塊號(hào),用
表示。
- gasLimit: 區(qū)塊的gas數(shù)量限制,即區(qū)塊中交易使用掉的gas值不應(yīng)該超過(guò)該值。用
表示。
- gasUsed: 區(qū)塊使用掉的gas數(shù)量,用
表示。
- timestamp:時(shí)間戳,用
表示。
- extraData:額外的數(shù)據(jù),合法的交易對(duì)長(zhǎng)度有限制,用
表示。
- mixHash: 與nonce一起用作工作量證明,用
表示。
- nonce:與mixHash一起用作工作量證明,用
表示。
// 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"`
}
區(qū)塊的符號(hào)化定義
公式19,表示區(qū)塊由三部分組成,區(qū)塊頭,交易列表
,以及ommerblock的header集合
。
2.3.1 交易收據(jù)
以太坊中為了將交易的信息進(jìn)行編碼,以方便索引以及查找或者零知識(shí)證明等相關(guān)的東西,為每條交易定義了一定收據(jù)。對(duì)于第i個(gè)交易,其收據(jù)用[i]表示。
每條收據(jù)都是一個(gè)四元組,包括區(qū)塊當(dāng)前累計(jì)使用的gas值,交易執(zhí)行產(chǎn)生的log
,日志過(guò)濾器
,以及狀態(tài)碼
。
公式20對(duì)區(qū)塊頭中的收據(jù)作了定義,收據(jù)是個(gè)四元組,四元組定義如前文。
公式21,收據(jù)的RLP形式化表示為。其中
在之前版本的協(xié)議中是交易執(zhí)行之前的stateRoot。現(xiàn)在被替換為0.
公式22,表示狀態(tài)碼是正整數(shù)。
公式23對(duì)收據(jù)中當(dāng)前累計(jì)使用的gas值,和日志過(guò)濾器
進(jìn)行了描述。顯然累計(jì)gas值
是一個(gè)正整數(shù)。而日志過(guò)濾器
是256位字符。
公式24對(duì)交易執(zhí)行的日志進(jìn)行了解釋。
公式25對(duì)其限制進(jìn)行了描述。是日志條目的序列。日志條目需要包括紀(jì)錄日志者的地址,以及日志話題分類,以及實(shí)際數(shù)據(jù)。日志條目用O來(lái)表示,用
表示日志紀(jì)錄者的address,用
來(lái)表示一些列32位字符的日志主題(log topics),用
來(lái)表示字符數(shù)據(jù)。其中日志紀(jì)錄者的address
是20位字符,每一個(gè)日志分類話題
是一個(gè)32位字符,而日志數(shù)據(jù)
是未知長(zhǎng)度的字符。
公式26-30.對(duì)日志過(guò)濾函數(shù)做了定義,這塊涉及到東西是數(shù)據(jù)操作層面的,不影響對(duì)流程的理解。暫不作解釋。
2.3.2 整體的合法性
上面介紹過(guò)了區(qū)塊包含區(qū)塊頭,區(qū)塊交易列表,以及區(qū)塊的ommer區(qū)塊的頭三部分。以太坊中判斷一個(gè)區(qū)塊是否合法,首先需要對(duì)區(qū)塊整體上做合法性判斷。見(jiàn)公式31
- 其區(qū)塊頭的狀態(tài)樹(shù)的根stateRoot也就是
,是否確實(shí)是狀態(tài)樹(shù)的根。
- 其區(qū)塊頭的ommerHash也就是
,是否與區(qū)塊的ommer區(qū)塊的頭部分的hash值一致。
- 其區(qū)塊頭中的transactionRoot也就是
即區(qū)塊中交易的樹(shù)的根,是否和區(qū)塊存儲(chǔ)的交易列表中的交易一一對(duì)應(yīng)。一一對(duì)應(yīng)關(guān)系見(jiàn)公式32,是將交易所在列表中的索引的RLP作為鍵,交易內(nèi)容v的RLP作為值的鍵值對(duì)。
- 其區(qū)塊頭中的recieptRoot也就是
,即區(qū)塊中收據(jù)的樹(shù)的根,是否和區(qū)塊存儲(chǔ)的交易列表一一對(duì)應(yīng),是不是每條交易都有一條相應(yīng)的收據(jù)。一一對(duì)應(yīng)關(guān)系見(jiàn)公式32,是將收據(jù)所在列表中的索引的RLP作為鍵,收據(jù)內(nèi)容v的RLP作為值的鍵值對(duì)。
- 區(qū)塊頭中l(wèi)ogsBloom也就是
,是否包含了區(qū)塊的交易的所有日志。
- 執(zhí)行該區(qū)塊之前的狀態(tài)樹(shù)的根節(jié)點(diǎn),是否與其父區(qū)塊的中的根節(jié)點(diǎn)一致。見(jiàn)公式33.
2.3.3 序列化
對(duì)區(qū)塊,以及區(qū)塊頭的序列化表示如下。
公式34是區(qū)塊頭的序列化表示。
公式35是區(qū)塊的序列化表示,即分別將區(qū)塊頭序列化,區(qū)塊的交易列表,ommer區(qū)塊頭序列化。其中交易序列化函數(shù)見(jiàn)公式15.
公式36表示列表序列化和當(dāng)個(gè)序列化的關(guān)系,列表的序列化,就是把列表中的元素分別序列化,然后將結(jié)果組成列表。
公式37是對(duì)區(qū)塊頭中屬性的限制規(guī)定:
- 各種hash值都是32位字符。包括parentHash
,ommerHash
, stateRoot
, transactionRoot
, RecieptRoot
, mixHash
。
- 受益人也就是挖礦的人的地址beneficiary
,是20位字符。
- 日志過(guò)濾器logBoom
,是256位字符。
- 難度,區(qū)塊號(hào),gas限制,用掉的gas等都是正整數(shù)。difficulty
, Number
,gasLimit
gasUsed
- 時(shí)間戳timestamp
是個(gè)大的正整數(shù),其值小于2256。
- 額外的數(shù)據(jù)extraData
是未知長(zhǎng)度字符。
- nonce
是8位的字符。
2.3.4 區(qū)塊頭的合法性
確定區(qū)塊是否合法除了整體性的合法校驗(yàn),還需要對(duì)區(qū)塊頭進(jìn)行更進(jìn)一步的校驗(yàn)。主要校驗(yàn)規(guī)則有以下幾點(diǎn):
- parentHash正確。即parentHash與其父區(qū)塊的頭的hash一致。
- number為父區(qū)塊number值加一。
- difficuilty難度正確。區(qū)塊合理的難度跟父區(qū)塊難度,以及當(dāng)前區(qū)塊時(shí)間戳和父區(qū)塊時(shí)間戳間隔以及區(qū)塊編號(hào)有關(guān)。難度可以起到一定的調(diào)節(jié)出塊時(shí)間的作用。,可以看出當(dāng)出塊變快(也就是出塊間隔變小之后)難度會(huì)增加,相反難度會(huì)減小。
- gasLimit和上一個(gè)區(qū)塊的差值在規(guī)定范圍內(nèi)。
- gasUsed小于等于gasLimit
- timestamp時(shí)間戳必須大于上一區(qū)塊的時(shí)間戳。
- mixHash和nonce必須滿足PoW。
- extraData最多為32個(gè)字節(jié)。
// 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
// 驗(yàn)證extraData的長(zhǎng)度
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
// 驗(yàn)證時(shí)間戳是否超過(guò)大小限制,是否過(guò)大,是否大于上一區(qū)塊的時(shí)間戳等
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
}
// 驗(yàn)證難度是否正確
// 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)
//驗(yàn)證gasLimit是否超了上限
if header.GasLimit > cap {
return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, cap)
}
//驗(yàn)證已用的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與父區(qū)塊的gasLimit差值是否在規(guī)定范圍內(nèi)
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
//驗(yàn)證區(qū)塊號(hào),是否是父區(qū)塊號(hào)+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
//驗(yàn)證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
}
校驗(yàn)區(qū)塊號(hào)和區(qū)塊hash的符號(hào)化表示
公式39表示,parentHash值應(yīng)該為父節(jié)點(diǎn)Header的hash值。
公式40表示,number為父節(jié)點(diǎn)number + 1.
難度計(jì)算的符號(hào)化表示
公式41-46為difficulty的計(jì)算方法。
公式41,42表示,當(dāng)區(qū)塊編號(hào)為0的時(shí)候其難度值是固定好的,在這里用表示,其值為131072.對(duì)于其他區(qū)塊,其難度值需要根據(jù)其父區(qū)塊難度值以及一些其他因素,出塊的間隔時(shí)間,區(qū)塊編號(hào)等有關(guān)進(jìn)行調(diào)節(jié)的,若小于
,則難度值調(diào)整為
。
公式43,調(diào)節(jié)系數(shù)(the adjustment factor )的定義。
公式44,難度系數(shù)(diculty parameter)的定義。該系數(shù)主要與出塊間隔時(shí)間有關(guān),當(dāng)間隔大的時(shí)候,系數(shù)變大,難度也會(huì)相應(yīng)變大,當(dāng)間隔小的時(shí)候,系數(shù)變小,難度也會(huì)變小。使得區(qū)塊鏈在整體上出塊時(shí)間是趨于穩(wěn)定的。其中
值根據(jù)父節(jié)點(diǎn)的uncle節(jié)點(diǎn)是否為空而有所區(qū)別,可以看出當(dāng)父節(jié)點(diǎn)的uncle不為空的時(shí)候,
值為2,說(shuō)明當(dāng)前的分叉程度較大,適當(dāng)調(diào)大難度,一定程度上會(huì)減少分叉。
公式45,46,“difficulty bomb”, or “ice age” 的定義。(看說(shuō)明好像是為了將來(lái)切PoS共識(shí)的時(shí)候,調(diào)節(jié)難度用)
// 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)
}
}
// 黃皮書(shū)中的介紹的是Byzantium難度協(xié)議,所以這里只給出相應(yīng)的代碼。其他幾種難度調(diào)節(jié)協(xié)議只是參數(shù)值上有區(qū)別。
// 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限制的符號(hào)化表示
公式47表示區(qū)塊的gasLimit必須大于等于5000,且其和上一個(gè)區(qū)塊的gasLimit差值不超過(guò)
時(shí)間戳的符號(hào)化表示
公式48表示當(dāng)前區(qū)塊的時(shí)間戳必須大于父區(qū)塊的時(shí)間戳。(代碼中要求區(qū)塊的時(shí)間戳不能比當(dāng)前時(shí)間大15秒以上)
mixHash和nonce相關(guān)符號(hào)化表示
公式49表示,nonce值和mixHash需要滿足PoW。
區(qū)塊頭驗(yàn)證的符號(hào)化表示
相關(guān)的含義見(jiàn)本小節(jié)開(kāi)始部分對(duì)區(qū)塊頭驗(yàn)證的那幾點(diǎn)。
圖片來(lái)源于網(wǎng)絡(luò)侵刪