以太坊(Ethereum ETH)是如何計算難度的
什麼是難度
難度(Difficulty)一詞來源於區塊鏈技術的先驅比特幣,用來度量挖出一個區塊平均需要的運算次數。挖礦本質上就是在求解一個謎題,不同的電子幣設置了不同的謎題。比如比特幣使用SHA-256、萊特幣使用Scrypt、以太坊使用Ethash。一個謎題的解的所有可能取值被稱為解的空間,挖礦就是在這些可能的取值中尋找一個解。
這些謎題都有如下共同的特點:
- 沒有比窮舉法更有效的求解方法
- 解在空間中均勻分布,從而使每一次窮舉嘗試找到一個解的概率基本一致
- 解的空間足夠大,保證一定能夠找到解
假設現在有一種電子幣,解所在的空間為0-99共100個數字,謎題為x<100。這個謎題非常簡單,空間中的任何一個數字都能滿足。如果想讓謎題更難以求解該怎麼做呢?把謎題改成x<50,現在空間中只有一半的數字能滿足了,也就是說,現在的難度比原來大了。並且我們還能知道難度大了多少,原來求解平均要嘗試1次,現在求解平均要嘗試2次了,也就是說,x<50的難度是x<100的2/1=2倍。同理,如果謎題變成x<10,難度就是x<100的100/10=10倍。
現在謎題多了個參數Difficulty,謎題變成了x<Difficulty,上面的100、50、10,都是Difficulty的取值,這個參數Difficulty就是我們常說的難度(Difficulty)。
難度(Difficulty)通過控制合格的解在空間中的數量來控制平均求解所需要嘗試的次數,也就可以間接的控制產生一個區塊需要的時間,這樣就可以使區塊以一個合理而穩定的速度產生。
當挖礦的人很多,單位時間能夠嘗試更多次時,難度就會增大,當挖礦的人減少,單位時間能夠嘗試的次數變少時,難度就降低。這樣產生一個區塊需要的時間就可以做到穩定。
有了難度,我們還需要一種演算法,用來確定和調整合理的難度值是多少。
以太坊是如何計算難度的
以太坊的代碼是完全開源的,官方使用Go語言實現了一個錢包,被稱為Geth。
Geth可以從這裡下載:Go Ethereum Downloads
源代碼可以在這裡找到:ethereum/go-ethereum
Geth的開發者對「共識」的理解相當深入,代碼也很容易閱讀。關於難度的代碼在consensus/ethash/consensus.go中。
func CalcDifficulty(config *params.ChainConfig, time uint64, parent *types.Header) *big.Int { next := new(big.Int).Add(parent.Number, big1) switch { case config.IsMetropolis(next): return calcDifficultyMetropolis(time, parent) case config.IsHomestead(next): return calcDifficultyHomestead(time, parent) default: return calcDifficultyFrontier(time, parent) }}
有三種計算難度的規則,分別對應以太坊的三個主要版本:已經成為歷史的Frontier、正在使用的Homestead和將要發布的Metropolis。
現在正在使用的Homestead的難度計算規則在calcDifficultyHomestead中實現。
func calcDifficultyHomestead(time uint64, parent *types.Header) *big.Int { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.mediawiki // algorithm: // diff = (parent_diff + // (parent_diff / 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -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) // 1 - (block_timestamp - parent_timestamp) // 10 x.Sub(bigTime, bigParentTime) x.Div(x, big10) x.Sub(big1, x) // max(1 - (block_timestamp - parent_timestamp) // 10, -99) if x.Cmp(bigMinus99) < 0 { x.Set(bigMinus99) } // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -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) } // for the exponential factor periodCount := new(big.Int).Add(parent.Number, big1) 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}
不過我不是來搬運代碼的,我們說人話。
在解釋具體計算過程之前,先來介紹一下,用到的幾種運算符。
- 整數除法,符號//
計算a//b時,先計算a/b,然後取不大於a/b的最大整數。
例如:
-11.3 // 5 = -3
11.3 // 5 = 2
- 取整,符號INT
計算INT(a)時,僅僅取整數部分,丟棄小數。
例如:
INT(3.7) = 3
INT(-3.7) = -3
- 最大值,符號MAX
計算MAX(a,b)時,結果為a和b中較大的那一個。
例如:
MAX(-1,0) = 0
MAX(7,10) = 10
計算一個區塊的難度時,需要以下輸入:
parent_timestamp:上一個區塊產生的時間
parent_diff:上一個區塊的難度
block_timestamp:當前區塊產生的時間
block_number:當前區塊的序號
block_diff = parent_diff + 難度調整 + 難度炸彈
難度調整 = parent_diff // 2048 * MAX(1 - (block_timestamp - parent_timestamp) // 10, -99)
難度炸彈 = INT(2**((block_number // 100000) - 2))
另外,區塊難度不能低於以太坊的創世區塊,創世區塊的難度為131072,這是以太坊難度的下限。
實踐一下
讓我們來計算一個區塊的難度。
以太坊的區塊鏈是公開的,可以在這裡查看:The ethereum blockchain explorer
現在我們將根據4212371區塊的難度來計算4212372區塊的難度。
先查看下4212371區塊的難度和時間戳,難度為2,117,963,098,883,076,時間戳是2017-08-28 11:24:23。
再來看下4212372區塊的時間戳,時間戳是2017-08-28 11:25:43。
現在需要的信息都有了。
parent_timestamp = 2017-08-28 11:24:23
parent_diff = 2,117,963,098,883,076
block_timestamp = 2017-08-28 11:25:43
block_number = 4212372
先算難度炸彈:
INT(2**((4212372 // 100000) - 2)) =
INT(2**(42 - 2)) = INT(2**40) = 2**40 = 1099511627776
再算難度調整:
2117963098883076 // 2048 * MAX(1 - (2017-08-28 11:25:43 - 2017-08-28 11:24:23) // 10, -99) =
2117963098883076 // 2048 * MAX(1 - (2017-08-28 11:25:43 - 2017-08-28 11:24:23) // 10, -99) =
2117963098883076 // 2048 * MAX(1 - 80 // 10, -99) =
2117963098883076 // 2048 * MAX(1 - 8, -99) =
2117963098883076 // 2048 * MAX(-7, -99) =
2117963098883076 // 2048 * -7 =
1034161669376 * -7 = -7239131685632
最終的難度為:
block_diff = 2117963098883076 - 7239131685632 + 1099511627776 = 2,111,823,478,825,220
檢查下是不是大於創世區塊的難度131072,顯然滿足條件。
是不是和查到的一模一樣?4212372區塊的難度為2,111,823,478,825,220。
以太坊難度的特點
以太坊的區塊難度以單個區塊為單位進行調整,可以非常迅速的適應算力的變化,正是這種機制,使以太坊在硬分叉出以太坊經典(ETC)以後沒有出現比特幣分叉出比特幣現金(BCC)後的算力「暴擊」問題。同時,以太坊的新區塊難度在老區塊的基礎上有限調整的機制也使區塊難度不會出現非常大的跳變。
下面是BTC和BCC的難度變化:
這是以太坊的難度變化:
可以看出以太坊難度的遞增比較平滑,出現的幾個跳變是由於難度炸彈造成的。
關於難度炸彈
其實大家談之色變的難度炸彈只是難度計算公式中的一部分而已,也就是下面的這部分。
INT(2**((block_number // 100000) - 2))
難度炸彈每100000個區塊就會翻倍,目前只有1T左右。但是由於是指數級遞增,還記得用銅錢鋪滿棋盤的故事么,指數遞增是很迅速的,用炸彈來形容倒是非常貼切。
可以看到,到5400000區塊時,難度將達到4500T,會使挖礦變得極為困難,甚至不能收回電費成本。
為什麼要這樣設置呢?
以太坊的開發者認為,現在的版本都不是最終的狀態,未來將會使用PoS方式分配新產生的以太坊而不是PoW。PoS又被稱為虛擬挖礦,可以理解為根據目前擁有的以太坊的多少來分配新產生的以太坊,屆時將不再需要礦工消耗大量電力挖礦,這將會使礦工們「失業」。
比特幣社區出現的礦工和用戶對立的情況警示了以太坊的開發者,為了避免礦工擁有過多的話語權,阻礙新技術的應用,於是設立了難度炸彈,到一定的時間點之後,礦工自然會因為無利可圖自動退場。不得不說,這個設定是非常英明的。
因為每個新區塊的難度包含上一區塊的難度,而上一區塊的難度又包含了一個難度炸彈,也就是說難度炸彈的難度會在每個區塊的難度中逐漸累積起來。這將使難度炸彈除了指數跳變的影響之外,又增加了較為平緩的長期影響。
歡迎在評論區留下你的觀點和疑惑。
如果覺得這篇文章有幫助的話,記得收藏或點贊哦。
推薦閱讀:
TAG:区块链Blockchain | 以太坊 | 挖矿 |