區塊鏈
挖礦,比特幣,EOS,以太坊

2018以太坊的挖礦和難度調整過程

2018以太坊的挖礦和難度調整過程

比特幣的挖礦過程中,僅僅需要較為簡單的哈希運算,而不需要額外的計算資源(內存等),于是比特幣的挖礦過程逐漸成為了算力的競爭,于是就出現了ASIC礦機,這種礦機相比于個人計算機,進行普通的計算,其算力是個人計算機的數千倍,剛好適用于進行比特幣中的挖礦,因此,普通人要想挖礦,就得有更專業的設備,挖礦這一行業都出現了中心化的現象,這與比特幣當初設計之初的去中心化理念背道而馳了。

于是,設計以太坊挖礦的時候,采用了全新的挖礦過程以做到ASIC Resistance。ASIC只能進行運算,卻沒有額外存儲空間,于是以太坊在挖礦過程中設計了兩個數據結構,分別為Cache和dataset。其中Cache是16M大小,而dataset是1G大小,對于礦工來說,每次選取一個nonce之后的挖礦操作,都要從dataset中讀取數據,這就要求礦機有存儲能力,挖礦過程中引入了讀取內存的操作,這就極大的降低了ASIC礦機的算力,使其挖礦的優勢不那么明顯,而普通的個人計算機也能進行挖礦。具體的挖礦過程如下:

  1. 根據當前區塊信息生成一個Seed種子。
  2. 根據Seed種子生成16M大小的Cache,Cache是一個List結構,其數據前后相關。
  3. 根據Cache來生成1G大小的Dataset(又稱為Dag)。
  4. 礦機每次選取一個Nonce之后,從Dataset中讀取兩個數進行挖礦測試,直到找到合適的Nonce。

區塊鏈中每30000個區塊的時候,Cache和Dataset的大小都會增加11281128,也就是說Cache會增加128K,而Dataset會增加8M。生成Cache的算法如下:

def mkcache(cache_size, seed):
    cache = [hash(seed)]    for i in range(1,cache_size):
        cache.append(hash[cache[-1]])    return cache12345

dataset的生成來源于cache,具體來說,dataset[i]個元素的生成需要cache和cache[i]的參與。dataset中第i個元素的生成代碼如下:

def cal_dataset_i(cahce, i):# 計算dataset[i]
    cache_size = cache.size
    mix = hash(cache[i%cache_size]^i)# cache遠遠小于dataset,讓i也參與運算,從而使得mix不會重復
    for j in range(256):
        cache_index = get_int(mix);
        mix = make_item(mix,cache[cache_index%cache_size])    return hash(mix)1234567

上述代碼是偽代碼,省略了大部分細節,重點在于展示原理。

  1. 先通過cache中的第i%cache_size個元素生成初始的mix,因為兩個不同的dataset元素可能對應同一個cache中的元素,為了保證每個初始的mix都不同,注意到i也參與了哈希計算。
  2. 隨后循環256次,每次通過get_int來根據當前的mix值求得下一個要訪問的cache元素的下標,用這個cache元素和mix通過make_item求得新的mix值。注意到由于初始的mix值都不同,所以訪問cache的序列也都是不同的。
    最終返回mix的哈希值,得到第i個dataset中的元素。
  3. 多次調用這個函數,就可以得到完整的dataset。

通過cache生成dataset的元素時,下一個用到的cache中的元素的位置是通過當前用到的cache的元素的值計算得到的,這樣具體的訪問順序事先不可預知,滿足偽隨機性。生稱dataset的代碼如下:

def calc_dataset(full_size, cache):
    return [calc_dataset_item(cache,i) for i in range(full_size)]12

生成dataset之后礦工就可以開始挖礦,根據特定的過程計算出一個哈希值,其代碼如下所示。其中的循環64次并沒有額外的原因,就是想增加挖礦過程中的訪問內存操作。礦工為了增加挖礦速度,就必須要將dataset存儲在內存中。

# 根據nonce計算出一個哈希值def get_hash_value(header, nonce, full_size, dataset):
    hash_value = hash(header, nonce);    for i in range(64):
        dataset_index = get_int(hash_value )%full_size
        hash_value = make_item(hash_value , dataset[dataset_index)
        hash_value = make_item(hash_value , dataset[dataset_index+1])    return hash(hash_value )12345678

如果一個nonce不合適,就需要更換一個nonce,直到找到合適的nonce,整個挖礦過程偽代碼如下:

def mine(full_size, dataset, header, target):
    max_nonce = 2**64
    nonce = random.randint(0, max_nonce )    while get_hash_value(header, nonce, full_size, dataset) > target:
        nonce = (nonce+1)%max_nonce    return nonce123456

為什么要挖礦中設計cache呢,貌似礦工挖礦的時候根本沒有用到cache,為什么要多此一舉?這是為了方便輕節點對區塊進行驗證。對于輕節點來說,不可能存儲很大的dataset,但是輕節點可以通過存儲cache,驗證某個區塊塊頭時,根據cache生成dataset中某個元素,隨后驗證區塊的合法性。輕節點驗證區塊合法性的偽代碼如下:

def varify(header, nonce, full_size, cache):
    hash_value = hash(header, nonce)    for i in range(64):
        index = get_int(hash_value)%full_size
        hash_value = hash(hash_value, cal_dataset_i(cache,index))# 計算生成dataset中的數據
        hash_value = hash(hash_value,cal_dataset_i(cache,index+1))    return hash(hash_value)1234567

礦工需要驗證大量的nonce,若每次都要從16M的cache中重新生成,那么挖礦的效率就大打折扣,而且會有大量的重復計算:隨機選取的dataset的元素中有很多是重復的,可能是之前嘗試別的nonce時用過的。所以,礦工采取以空間換時間的策略,把整個dataset保存下來。而輕節點由于只驗證一個nonce,驗證的時候就直接生成要用到的dataset中的元素就行了。

以太坊挖礦難度調整

以太坊中的區塊的難度調整公式如下圖所示。

太坊挖礦

參數說明

  1. 區塊鏈難度調整中,創始塊的難度被設置為D0=131072D0=131072,此后每個區塊的難度都與其父區塊的難度相關。D(H)是本區塊的難度,由P(H)Hd+x×ζ2P(H)Hd+x×ζ2和難度炸彈??構成。
  2. P(H)HdP(H)Hd為父區塊的難度,每個區塊的難度都是在父區塊難度的基礎上進行調整。
  3. x×ζ2x×ζ2用于自適應調節出塊難度,維持穩定的出塊速度。
  4. ??表示難度炸彈。
  5. 難度有最低下限,即不能低于D0=131072D0=131072

其中xx和?2?2的計算方式如下圖所示。

太坊挖礦

  • xx是父區塊難度的1204812048的取整,是調整的單位。
  • ??是調整系數,其小只能是-99。
  • y的取值依賴于父區塊是否包含叔父區塊,如果包含,則y=2,否則y=1。
  • HSHS是本區塊的時間戳,P(H)HsP(H)Hs是父區塊的時間戳,單位為秒,并且HS>P(H)HsHS>P(H)Hs。
  • 難度降低的上界設置為?99 ,主要是應對被黑客攻擊或其他目前想不到的黑天鵝事件。

假設當父區塊不帶叔父區塊的時候(y=1),調整過程如下:

  • 出塊時間在[1,8]之間,出塊時間過短,難度調大一個單位。
  • 出塊時間在[9,17]之間,出塊時間可以接受,難度保持不變。
  • 出塊時間在[18,26]之間,出塊時間過長,難度調小一個單位。

  • 這里發現,出塊時間變長,區塊的整體難度就會調小,假若有的礦工,故意將區塊的時間戳改的比較晚,那么是不是就可以搶先發布區塊呢?比如說將時間戳延遲寫15秒,會怎么樣呢?這樣就會導致該礦工計算出來的難度比別的礦工計算的難度低,其他礦工15秒發布一個區塊,而該礦工可以在10秒內發布區塊,可以拿到區塊獎勵。但是問題在于假如剛好也有別的區塊在10秒內發布了區塊,此時根據POW的規則,另外一個礦工發布的區塊難度更大,因此其他礦工會以最大工作量標準,選擇15秒內挖出的區塊所在的鏈作為主鏈,而該礦工發布的區塊便成了叔父區塊。

難度炸彈計算公式如下圖所示。

  • ??是2的指數函數,每十萬個塊擴大一倍,后期增長非常快,這就是難度“炸彈”的由來。
  • H′iHi′稱為fake block number,由真正的block number HiHi減少三百萬得到。之所以減少三百萬,是因為目前proof of stake的工作量證明方式還存在一些問題,pos協議涉及不夠完善,但是難度炸彈已經導致挖礦時間變成了30秒左右,為了減小難度,就會減去三百萬。

設置難度炸彈的原因是要降低遷移到PoS協議時發生fork的風險,假若礦工聯合起來抵制POS的工作量證明模式,那就會導致以太坊產生硬分叉;有了難度炸彈,挖礦難度越來越大,礦工就有意愿遷移到PoS協議上了。難度炸彈的威力,可以通過下圖看出。

區塊數量到370萬之后,挖礦難度突然遞增,到430萬時,難度已經非常之大了,這時候挖礦時間已經變為為30秒,但是POS協議還沒有完善,于是以太坊將挖礦難度公式進行調整,使得每次計算時,當前區塊號減去三百萬,這樣就降低了挖礦難度,并且在這個時期,對以太坊出塊獎勵進行了調整,從原來的5個ETH變為3個ETH。

以太坊中難度計算公式如下圖所示,由于目前處于以太坊發展的Metropolis中的Byzantium階段,所以難度計算公式的函數名稱為calcDifficultyByzantium

贊(0)

評論 搶沙發

  • 昵稱 (必填)
  • 郵箱 (必填)
  • 網址
p3试机号99