Przejdź do głównej treści
Change page

Ethash

Ethash był algorytmem kopania opartym na dowodzie pracy (PoW) w Ethereum. Dowód pracy został teraz całkowicie wyłączony, a Ethereum jest obecnie zabezpieczane za pomocą dowodu stawki (PoS). Przeczytaj więcej o The Merge, dowodzie stawki i stakingu. Ta strona ma charakter historyczny!

Ethash to zmodyfikowana wersja algorytmu Dagger-Hashimoto. Dowód pracy Ethash jest trudny pamięciowo (memory hard) (opens in a new tab), co miało uczynić algorytm odpornym na układy ASIC. Ostatecznie opracowano układy ASIC dla Ethash, ale kopanie za pomocą GPU nadal było opłacalną opcją, dopóki dowód pracy nie został wyłączony. Ethash jest nadal używany do kopania innych kryptowalut w innych sieciach opartych na dowodzie pracy, niezwiązanych z Ethereum.

Jak działa Ethash?

Trudność pamięciowa jest osiągana dzięki algorytmowi dowodu pracy, który wymaga wybierania podzbiorów stałego zasobu zależnego od nonce i nagłówka bloku. Ten zasób (o rozmiarze kilku gigabajtów) nazywa się DAG. DAG zmienia się co 30000 bloków, czyli w oknie czasowym wynoszącym około 125 godzin zwanym epoką (około 5,2 dnia), a jego wygenerowanie zajmuje trochę czasu. Ponieważ DAG zależy tylko od wysokości bloku, może zostać wygenerowany z wyprzedzeniem, ale jeśli tak się nie stanie, klient musi poczekać do końca tego procesu, aby wyprodukować blok. Jeśli klienci nie wygenerują i nie zbuforują DAG-ów z wyprzedzeniem, sieć może doświadczyć ogromnych opóźnień bloków przy każdym przejściu epoki. Należy zauważyć, że DAG nie musi być generowany do weryfikacji dowodu pracy, co w zasadzie pozwala na weryfikację przy użyciu zarówno małej mocy procesora, jak i niewielkiej ilości pamięci.

Ogólny przebieg algorytmu jest następujący:

  1. Istnieje ziarno (seed), które można obliczyć dla każdego bloku, skanując nagłówki bloków aż do tego momentu.
  2. Z ziarna można obliczyć 16 MB pseudolosowej pamięci podręcznej (cache). Lekcy klienci przechowują tę pamięć podręczną.
  3. Z pamięci podręcznej możemy wygenerować 1 GB zbiór danych (dataset), z tą właściwością, że każdy element w zbiorze danych zależy tylko od niewielkiej liczby elementów z pamięci podręcznej. Pełni klienci i górnicy przechowują ten zbiór danych. Zbiór danych rośnie liniowo w czasie.
  4. Kopanie polega na pobieraniu losowych fragmentów zbioru danych i ich wspólnym haszowaniu. Weryfikację można przeprowadzić przy użyciu małej ilości pamięci, wykorzystując pamięć podręczną do ponownego wygenerowania potrzebnych fragmentów zbioru danych, więc wystarczy przechowywać tylko pamięć podręczną.

Duży zbiór danych jest aktualizowany raz na 30000 bloków, więc zdecydowana większość wysiłku górnika będzie polegać na odczytywaniu zbioru danych, a nie na wprowadzaniu w nim zmian.

Definicje

Stosujemy następujące definicje:

Użycie 'SHA3'

Rozwój Ethereum zbiegł się w czasie z rozwojem standardu SHA3, a proces standaryzacji wprowadził późną zmianę w dopełnieniu (padding) sfinalizowanego algorytmu haszującego, przez co hashe "sha3_256" i "sha3_512" w Ethereum nie są standardowymi haszami sha3, ale wariantem często określanym w innych kontekstach jako "Keccak-256" i "Keccak-512". Zobacz dyskusję, np. tutaj (opens in a new tab), tutaj (opens in a new tab) lub tutaj (opens in a new tab).

Prosimy o tym pamiętać, ponieważ w poniższym opisie algorytmu pojawiają się odniesienia do haszy "sha3".

Parametry

Parametry pamięci podręcznej i zbioru danych Ethash zależą od numeru bloku. Rozmiar pamięci podręcznej i rozmiar zbioru danych rosną liniowo; jednak zawsze bierzemy najwyższą liczbę pierwszą poniżej liniowo rosnącego progu, aby zmniejszyć ryzyko przypadkowych prawidłowości prowadzących do zachowań cyklicznych.

Tabele wartości rozmiarów zbioru danych i pamięci podręcznej znajdują się w załączniku.

Generowanie pamięci podręcznej

Teraz określimy funkcję do tworzenia pamięci podręcznej:

Proces tworzenia pamięci podręcznej polega najpierw na sekwencyjnym zapełnieniu 32 MB pamięci, a następnie wykonaniu dwóch przebiegów algorytmu RandMemoHash autorstwa Sergio Demiana Lernera z publikacji Strict Memory Hard Hashing Functions (2014) (opens in a new tab). Wynikiem jest zestaw 524288 64-bajtowych wartości.

Funkcja agregacji danych

W niektórych przypadkach używamy algorytmu inspirowanego haszem FNV (opens in a new tab) jako niełącznego zamiennika dla XOR. Należy zauważyć, że mnożymy liczbę pierwszą przez pełne 32-bitowe wejście, w przeciwieństwie do specyfikacji FNV-1, która mnoży liczbę pierwszą kolejno przez jeden bajt (oktet).

FNV_PRIME = 0x01000193

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

Należy pamiętać, że chociaż żółta księga określa fnv jako v1*(FNV_PRIME ^ v2), wszystkie obecne implementacje konsekwentnie używają powyższej definicji.

Obliczanie pełnego zbioru danych

Każdy 64-bajtowy element w pełnym 1 GB zbiorze danych jest obliczany w następujący sposób:

Zasadniczo łączymy dane z 256 pseudolosowo wybranych węzłów pamięci podręcznej i haszujemy je, aby obliczyć węzeł zbioru danych. Cały zbiór danych jest następnie generowany przez:

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

Główna pętla

Teraz określimy główną pętlę w stylu "hashimoto", w której agregujemy dane z pełnego zbioru danych w celu wyprodukowania naszej końcowej wartości dla konkretnego nagłówka i nonce. W poniższym kodzie header reprezentuje hash SHA3-256 reprezentacji RLP obciętego nagłówka bloku, to znaczy nagłówka z wyłączeniem pól mixHash i nonce. nonce to osiem bajtów 64-bitowej liczby całkowitej bez znaku w kolejności big-endian. Zatem nonce[::-1] to ośmiobajtowa reprezentacja tej wartości w kolejności little-endian:

Zasadniczo utrzymujemy "miks" o szerokości 128 bajtów i wielokrotnie sekwencyjnie pobieramy 128 bajtów z pełnego zbioru danych, a następnie używamy funkcji fnv, aby połączyć je z miksem. Używa się 128 bajtów dostępu sekwencyjnego, aby każda runda algorytmu zawsze pobierała pełną stronę z pamięci RAM, minimalizując chybienia w buforze TLB (Translation Lookaside Buffer), których układy ASIC teoretycznie mogłyby uniknąć.

Jeśli wynik tego algorytmu jest poniżej pożądanego celu, to nonce jest prawidłowy. Należy zauważyć, że dodatkowe zastosowanie sha3_256 na końcu zapewnia istnienie pośredniego nonce, który można dostarczyć, aby udowodnić, że wykonano przynajmniej niewielką ilość pracy; ta szybka zewnętrzna weryfikacja PoW może być używana do celów anty-DDoS. Służy to również zapewnieniu statystycznej pewności, że wynik jest nieobciążoną, 256-bitową liczbą.

Kopanie

Algorytm kopania jest zdefiniowany następująco:

def mine(full_size, dataset, header, difficulty):
    # uzupełnij cel zerami, aby porównać z hashem na tej samej cyfrze
    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

Definiowanie hasha ziarna

W celu obliczenia hasha ziarna, który zostałby użyty do kopania na danym bloku, używamy następującego algorytmu:

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

Należy pamiętać, że w celu płynnego kopania i weryfikacji zalecamy wstępne obliczanie przyszłych haszy ziaren i zbiorów danych w osobnym wątku.

Dalsza lektura

Znasz zasób społeczności, który Ci pomógł? Edytuj tę stronę i dodaj go!

Załącznik

Poniższy kod powinien zostać dodany na początku, jeśli jesteś zainteresowany uruchomieniem powyższej specyfikacji w języku Python jako kodu.

Rozmiary danych

Poniższe tabele przeglądowe zawierają około 2048 stabelaryzowanych epok rozmiarów danych i rozmiarów pamięci podręcznej.