Pular para o conteúdo principal
Change page

Ethash

O Ethash era o algoritmo de mineração de Prova de Trabalho (PoW) do Ethereum. A Prova de Trabalho (PoW) foi agora totalmente desativada e o Ethereum agora é protegido usando a Prova de Participação (PoS) em vez disso. Leia mais sobre o The Merge, a Prova de Participação (PoS) e o staking. Esta página é para interesse histórico!

O Ethash é uma versão modificada do algoritmo Dagger-Hashimoto. A Prova de Trabalho (PoW) do Ethash é difícil em termos de memória (memory hard) (opens in a new tab), o que se pensava tornar o algoritmo resistente a ASIC. ASICs para Ethash foram eventualmente desenvolvidos, mas a mineração por GPU ainda era uma opção viável até que a Prova de Trabalho (PoW) fosse desativada. O Ethash ainda é usado para minerar outras moedas em outras redes de Prova de Trabalho (PoW) que não são do Ethereum.

Como o Ethash funciona?

A dificuldade de memória é alcançada com um algoritmo de Prova de Trabalho (PoW) que requer a escolha de subconjuntos de um recurso fixo dependente do nonce e do cabeçalho do bloco. Esse recurso (com alguns gigabytes de tamanho) é chamado de DAG. O DAG é alterado a cada 30.000 blocos, uma janela de ~125 horas chamada de época (aproximadamente 5,2 dias) e leva um tempo para ser gerado. Como o DAG depende apenas da altura do bloco, ele pode ser pré-gerado, mas se não for, o cliente precisa esperar até o final desse processo para produzir um bloco. Se os clientes não pré-gerarem e armazenarem em cache os DAGs com antecedência, a rede poderá sofrer um atraso massivo de blocos em cada transição de época. Observe que o DAG não precisa ser gerado para verificar a Prova de Trabalho (PoW), permitindo essencialmente a verificação com baixo uso de CPU e pouca memória.

A rota geral que o algoritmo segue é a seguinte:

  1. Existe uma semente (seed) que pode ser calculada para cada bloco, verificando os cabeçalhos do bloco até aquele ponto.
  2. A partir da semente, pode-se calcular um cache pseudoaleatório de 16 MB. Clientes leves armazenam o cache.
  3. A partir do cache, podemos gerar um conjunto de dados de 1 GB, com a propriedade de que cada item no conjunto de dados depende de apenas um pequeno número de itens do cache. Clientes completos e mineradores armazenam o conjunto de dados. O conjunto de dados cresce linearmente com o tempo.
  4. A mineração envolve pegar fatias aleatórias do conjunto de dados e fazer a geração de hash delas juntas. A verificação pode ser feita com pouca memória usando o cache para regenerar as partes específicas do conjunto de dados de que você precisa, de modo que você só precisa armazenar o cache.

O grande conjunto de dados é atualizado uma vez a cada 30.000 blocos, portanto, a grande maioria do esforço de um minerador será ler o conjunto de dados, e não fazer alterações nele.

Definições

Empregamos as seguintes definições:

O uso do 'SHA3'

O desenvolvimento do Ethereum coincidiu com o desenvolvimento do padrão SHA3, e o processo de padronização fez uma alteração tardia no preenchimento (padding) do algoritmo de hash finalizado, de modo que os hashes "sha3_256" e "sha3_512" do Ethereum não são hashes sha3 padrão, mas uma variante frequentemente chamada de "Keccak-256" e "Keccak-512" em outros contextos. Veja a discussão, por exemplo, aqui (opens in a new tab), aqui (opens in a new tab) ou aqui (opens in a new tab).

Tenha isso em mente, pois os hashes "sha3" são mencionados na descrição do algoritmo abaixo.

Parâmetros

Os parâmetros para o cache e o conjunto de dados do Ethash dependem do número do bloco. O tamanho do cache e o tamanho do conjunto de dados crescem linearmente; no entanto, sempre pegamos o maior número primo abaixo do limite de crescimento linear para reduzir o risco de regularidades acidentais que levam a um comportamento cíclico.

Tabelas de valores de tamanho de cache e conjunto de dados são fornecidas no apêndice.

Geração de cache

Agora, especificamos a função para produzir um cache:

O processo de produção de cache envolve primeiro preencher sequencialmente 32 MB de memória e, em seguida, realizar duas passagens do algoritmo RandMemoHash de Sergio Demian Lerner de Strict Memory Hard Hashing Functions (2014) (opens in a new tab). A saída é um conjunto de 524.288 valores de 64 bytes.

Função de agregação de dados

Usamos um algoritmo inspirado no hash FNV (opens in a new tab) em alguns casos como um substituto não associativo para XOR. Observe que multiplicamos o número primo com a entrada completa de 32 bits, em contraste com a especificação FNV-1, que multiplica o número primo com um byte (octeto) por vez.

FNV_PRIME = 0x01000193

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

Observe que, embora o yellow paper especifique fnv como v1*(FNV_PRIME ^ v2), todas as implementações atuais usam consistentemente a definição acima.

Cálculo do conjunto de dados completo

Cada item de 64 bytes no conjunto de dados completo de 1 GB é calculado da seguinte forma:

Essencialmente, combinamos dados de 256 nós de cache selecionados de forma pseudoaleatória e fazemos a geração de hash disso para calcular o nó do conjunto de dados. Todo o conjunto de dados é então gerado por:

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

Loop principal

Agora, especificamos o loop principal semelhante ao "hashimoto", onde agregamos dados do conjunto de dados completo para produzir nosso valor final para um cabeçalho e nonce específicos. No código abaixo, header representa o hash SHA3-256 da representação RLP de um cabeçalho do bloco truncado, ou seja, de um cabeçalho excluindo os campos mixHash e nonce. nonce são os oito bytes de um número inteiro sem sinal de 64 bits em ordem big-endian. Portanto, nonce[::-1] é a representação little-endian de oito bytes desse valor:

Essencialmente, mantemos uma "mistura" (mix) de 128 bytes de largura e buscamos repetidamente e sequencialmente 128 bytes do conjunto de dados completo e usamos a função fnv para combiná-la com a mistura. 128 bytes de acesso sequencial são usados para que cada rodada do algoritmo sempre busque uma página inteira da RAM, minimizando as falhas do buffer de tradução (translation lookaside buffer misses) que os ASICs teoricamente seriam capazes de evitar.

Se a saída desse algoritmo estiver abaixo do alvo desejado, então o nonce é válido. Observe que a aplicação extra de sha3_256 no final garante que exista um nonce intermediário que pode ser fornecido para provar que pelo menos uma pequena quantidade de trabalho foi feita; essa verificação rápida externa de PoW pode ser usada para fins anti-DDoS. Também serve para fornecer garantia estatística de que o resultado é um número imparcial de 256 bits.

Mineração

O algoritmo de mineração é definido da seguinte forma:

def mine(full_size, dataset, header, difficulty):
    # preenche o alvo com zeros para comparar com o hash no mesmo dígito
    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

Definindo o hash semente

Para calcular o hash semente que seria usado para minerar em cima de um determinado bloco, usamos o seguinte algoritmo:

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

Observe que, para uma mineração e verificação tranquilas, recomendamos pré-calcular hashes semente e conjuntos de dados futuros em uma thread separada.

Leitura adicional

Conhece um recurso da comunidade que o ajudou? Edite esta página e adicione-o!

Apêndice

O código a seguir deve ser adicionado no início se você estiver interessado em executar a especificação Python acima como código.

Tamanhos de dados

As tabelas de pesquisa a seguir fornecem aproximadamente 2048 épocas tabuladas de tamanhos de dados e tamanhos de cache.