跳至主要內容
Change page

Ethash

Ethash 曾是以太坊的工作量證明 (PoW) 挖礦演算法。工作量證明現已完全關閉,以太坊現在改由權益證明 (PoS)來保障安全。閱讀更多關於合併權益證明質押的資訊。本頁面僅供歷史參考!

Ethash 是 Dagger-Hashimoto 演算法的修改版本。Ethash 工作量證明是記憶體密集型 (memory hard) (opens in a new tab),這被認為能使該演算法具備抗 ASIC 的特性。雖然最終還是開發出了 Ethash ASIC,但在工作量證明關閉之前,GPU 挖礦仍然是一個可行的選擇。Ethash 仍被用於在其他非以太坊的工作量證明網路上挖掘其他代幣。

Ethash 是如何運作的?

記憶體密集型是透過一種工作量證明演算法來實現的,該演算法需要根據隨機數與區塊頭來選擇固定資源的子集。這個資源(大小為數 GB)被稱為 DAG。DAG 每 30000 個區塊更換一次,這段約 125 小時的期間稱為一個紀元(大約 5.2 天),且生成 DAG 需要花費一些時間。由於 DAG 僅取決於區塊高度,因此可以預先生成;但如果沒有預先生成,用戶端需要等到此過程結束才能產生區塊。如果用戶端沒有提前預先生成並快取 DAG,網路在每次紀元轉換時可能會經歷大規模的區塊延遲。請注意,驗證工作量證明時不需要生成 DAG,這基本上允許在低 CPU 和小記憶體的情況下進行驗證。

該演算法的一般流程如下:

  1. 存在一個種子 (seed),可以透過掃描到該點為止的區塊頭來為每個區塊計算出該種子。
  2. 從種子中,可以計算出一個 16 MB 的偽隨機快取。輕節點會儲存這個快取。
  3. 從快取中,我們可以生成一個 1 GB 的資料集,其特性是資料集中的每個項目僅依賴於快取中的少數項目。全節點和礦工會儲存這個資料集。該資料集會隨時間線性增長。
  4. 挖礦涉及抓取資料集的隨機片段並將它們一起進行雜湊運算。驗證可以在低記憶體的情況下完成,方法是使用快取來重新生成你需要的特定資料集片段,因此你只需要儲存快取。

大型資料集每 30000 個區塊更新一次,因此礦工的絕大部分工作將是讀取資料集,而不是對其進行更改。

定義

我們採用以下定義:

「SHA3」的使用

以太坊的開發與 SHA3 標準的開發同時進行,而標準化過程在已定案的雜湊演算法的填充 (padding) 上做出了較晚的更改,因此以太坊的「sha3_256」和「sha3_512」雜湊並非標準的 sha3 雜湊,而是一種在其他情境中通常被稱為「Keccak-256」和「Keccak-512」的變體。請參閱討論,例如這裡 (opens in a new tab)這裡 (opens in a new tab)這裡 (opens in a new tab)

在下方演算法的描述中提及「sha3」雜湊時,請牢記這一點。

參數

Ethash 快取和資料集的參數取決於區塊號。快取大小和資料集大小皆呈線性增長;然而,我們總是取低於線性增長閾值的最大質數,以降低意外規律性導致循環行為的風險。

附錄中提供了資料集和快取大小值的表格。

快取生成

現在,我們指定用於生成快取的函式:

快取生成過程包括首先依序填滿 32 MB 的記憶體,然後執行兩次 Sergio Demian Lerner 在 Strict Memory Hard Hashing Functions (2014) (opens in a new tab) 中提出的 RandMemoHash 演算法。輸出是一組 524288 個 64 位元組的值。

資料聚合函式

在某些情況下,我們使用受 FNV 雜湊 (opens in a new tab)啟發的演算法作為 XOR 的非關聯替代方案。請注意,我們將質數與完整的 32 位元輸入相乘,這與 FNV-1 規範中將質數依次與一個位元組(八位元)相乘的做法不同。

FNV_PRIME = 0x01000193

def fnv(v1, v2):
    return ((v1 * FNV_PRIME) ^ v2) % 2**32

請注意,即使黃皮書將 fnv 指定為 v1*(FNV_PRIME ^ v2),目前所有的實作都一致使用上述定義。

完整資料集計算

完整的 1 GB 資料集中的每個 64 位元組項目計算如下:

基本上,我們結合來自 256 個偽隨機選擇的快取節點的資料,並對其進行雜湊運算以計算資料集節點。然後整個資料集由以下方式生成:

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

主迴圈

現在,我們指定類似「hashimoto」的主迴圈,我們在其中聚合來自完整資料集的資料,以便為特定的區塊頭和隨機數產生最終值。在下方程式碼中,header 代表_截斷的_區塊頭(即排除 mixHashnonce 欄位的區塊頭)的 RLP 表示形式的 SHA3-256 雜湊nonce 是大端序中 64 位元無號整數的八個位元組。因此 nonce[::-1] 是該值的八位元組小端序表示形式:

基本上,我們維護一個 128 位元組寬的「混合 (mix)」,並重複依序從完整資料集中擷取 128 位元組,然後使用 fnv 函式將其與混合結合。使用 128 位元組的循序存取是為了讓演算法的每一回合總是從 RAM 中擷取一個完整的頁面,從而將轉譯後備緩衝區 (TLB) 的未命中率降至最低,這在理論上是 ASIC 能夠避免的。

如果此演算法的輸出低於預期目標,則該隨機數有效。請注意,最後額外應用的 sha3_256 確保存在一個中間隨機數,可以提供該隨機數來證明至少完成了一小部分工作;這種快速的外部工作量證明驗證可用於防範 DDoS 攻擊。它還用於提供統計保證,確保結果是一個無偏差的 256 位元數字。

挖礦

挖礦演算法定義如下:

def mine(full_size, dataset, header, difficulty):
    # 將目標補零,以便與相同位數的雜湊進行比較
    target = zpad(encode_int(2**256 // difficulty), 64)[::-1]
    from random import randint
    nonce = randint(0, 2**64)
    while hashimoto_full(full_size, dataset, header, nonce) > target:
        nonce = (nonce + 1) % 2**64
    return nonce

定義種子雜湊

為了計算將用於在給定區塊之上進行挖礦的種子雜湊,我們使用以下演算法:

 def get_seedhash(block):
     s = '\x00' * 32
     for i in range(block.number // EPOCH_LENGTH):
         s = serialize_hash(sha3_256(s))
     return s

請注意,為了順利進行挖礦和驗證,我們建議在單獨的執行緒中預先計算未來的種子雜湊和資料集。

延伸閱讀

知道有什麼社群資源對您有幫助嗎?編輯此頁面並加入它!

附錄

如果您有興趣將上述 Python 規範作為程式碼執行,則應在前面加上以下程式碼。

資料大小

以下查找表提供了大約 2048 個紀元的資料大小和快取大小的列表。