Pools 18
Mineradores n/d
Workers n/d

< Todos os artigos

Bug de consenso KawPow da Ravencoin

Um bloco da Ravencoin minerado na altura 4.487.776 escreveu uma altura menor no próprio cabeçalho. Como esse número ficou abaixo do último checkpoint, em 2.383.550, a validação confiou no mix_hash que já estava no cabeçalho em vez de executar a verificação completa, que exige muita memória.

A Ravencoin passou a segunda semana de agosto revertendo sua cadeia em cerca de três dias. A causa não foi um cartel de mineração, uma falha de carteira ou uma função de hash quebrada. Foi um único campo no cabeçalho do bloco em que o nó confiava para saber qual de duas verificações de prova de trabalho executar. Cada trecho de código citado abaixo foi lido no próprio código-fonte de consenso da Ravencoin, e não em um anúncio, e onde os anúncios dizem algo que o código não mostra, este artigo diz isso em vez de adivinhar.

O que um cabeçalho KAWPOW carrega

KAWPOW é a prova de trabalho da Ravencoin, uma variante do ProgPoW que a cadeia adotou em maio de 2020 para manter a moeda em GPUs comuns. Essa mudança alterou o cabeçalho do bloco. Antes do momento de ativação, o cabeçalho termina com o nonce comum de 32 bits que toda cadeia derivada do Bitcoin usa. Depois dele, o serializador escreve três campos nesse espaço, nesta ordem: nHeight, depois nNonce64, depois mix_hash.

Dois deles não têm nada de especial. nNonce64 é apenas um nonce mais largo. mix_hash é o resultado intermediário da parte do algoritmo que exige muita memória, levado no cabeçalho para que quem verifica tenha um valor com que comparar, em vez de um cálculo a refazer do zero. O terceiro é o interessante. nHeight é o bloco dizendo, dentro do próprio cabeçalho, em que altura acredita estar.

A posição real de um bloco na cadeia não é questão de opinião. Ela é fixada pelo hashPrevBlock: o bloco sobre o qual você constrói decide onde você cai, e não dá para mentir sobre isso e ainda se ligar à ponta da cadeia. nHeight é uma segunda cópia, editável, desse mesmo fato, e a cópia de um fato só vale tanto quanto a verificação que a compara com o original.

Duas funções de hash, e uma delas não verifica nada

O hash.cpp da Ravencoin define dois pontos de entrada KAWPOW, e as assinaturas deles contam quase toda a história antes de você ler uma linha de qualquer um dos corpos. O primeiro é uint256 KAWPOWHash(const CBlockHeader& blockHeader, uint256& mix_hash). O segundo é uint256 KAWPOWHash_OnlyMix(const CBlockHeader& blockHeader).

O primeiro recebe mix_hash como parâmetro de saída, porque produz um. Ele escolhe o dataset com ethash::get_epoch_number(blockHeader.nHeight), monta o contexto dessa época se ainda não o tiver e então executa progpow::hash sobre ele. O mix que devolve é um subproduto de ter feito o trabalho. Este é o caminho que exige muita memória, e essa exigência de memória é toda a razão de o KAWPOW existir em vez de um hash simples.

O segundo não recebe esse parâmetro, porque não produz nada. Ele consome o mix que já está no cabeçalho, via progpow::hash_no_verify(blockHeader.nHeight, header_hash, to_hash256(blockHeader.mix_hash.GetHex()), blockHeader.nNonce64). A biblioteca de origem chamou essa função de hash_no_verify, e o nome é exato. Ela faz a etapa final de mistura sobre um valor que recebeu pronto e devolve a resposta. Nunca monta o dataset, então nunca comprova que o valor recebido só poderia ter vindo de fazer o trabalho.

Nenhuma das duas funções é um erro. A barata existe por um bom motivo: um nó sincronizando milhões de blocos antigos, já resolvidos por um checkpoint fixo no código, não deveria ter de remontar um dataset de vários gigabytes por época para reconfirmar um histórico que ninguém contesta. A única pergunta que um projeto assim precisa responder é quais blocos podem usá-la.

O portão, e no que ele confiava

Essa pergunta é respondida pelo CheckBlockHeader em validation.cpp, e o comentário logo acima da resposta diz a intenção com clareza: “If we are checking a KAWPOW block below a know checkpoint height. We can validate the proof of work using the mix_hash”. A intenção está correta. A implementação decide com block.nHeight <= (uint32_t)pcheckpoint->nHeight, e block.nHeight é o número que o bloco escreveu sobre si mesmo.

A linha de baixo chama a função genérica CheckProofOfWork(uint256 hash, unsigned int nBits, const Consensus::Params&). Repare no que essa assinatura não contém. Não há mix, nem nonce, nem cabeçalho. Ela recebe um hash pronto e um alvo e compara os dois, e não tem como saber qual das duas funções produziu o número recebido. Para um bloco KAWPOW, CBlockHeader::GetHash() devolve KAWPOWHash_OnlyMix(*this), então o número que ela recebe é o barato.

Juntando tudo, um bloco que declarava um nHeight baixo o bastante era validado perguntando apenas se o valor derivado do seu próprio mix hash ficava abaixo do alvo. Em nenhum ponto desse caminho algo perguntava de onde vinha o mix, e em nenhum ponto algo comparava a altura declarada com a posição que o bloco realmente ocupava.

Quão baixo é baixo o bastante não tem nada de sutil. O checkpoint mais alto nos parâmetros da mainnet lançados é 2.383.550. O primeiro bloco forjado estava em 4.487.776. O cabeçalho dele precisou declarar uma posição cerca de 2,1 milhões de blocos atrás de onde realmente estava, e o caminho que isso escolheu nunca olhou para a diferença.

O que o atalho comprou e o que não comprou

Vale ser preciso aqui, porque a maior parte da cobertura foi vaga nesse ponto, e a versão vaga faz o bug parecer ao mesmo tempo mais simples e mais estranho do que é.

O atalho não reduziu a dificuldade. O nBits é validado contra o que o bloco anterior real exige, e o bloco anterior real é escolhido pelo hashPrevBlock, que um atacante não consegue falsificar e ainda cair na ponta da cadeia. Um bloco forjado tinha de superar exatamente o mesmo alvo que um bloco honesto minerado no mesmo minuto.

O que mudou foi o preço de uma única tentativa. Um minerador honesto paga cada tentativa com uma passada completa por um dataset medido em gigabytes, que é todo o sentido de um algoritmo que exige muita memória e a razão de o KAWPOW ter mantido ASICs fora da Ravencoin por seis anos. O atalho custa um hash do cabeçalho mais uma etapa final de mistura sobre um valor que o atacante escolheu livremente. Mesma loteria, mesmas chances por bilhete, bilhetes mais baratos em ordens de grandeza. A 2Miners descreveu os resultados como blocos de trabalho quase zero, e comparado com o que o KAWPOW deveria custar, a descrição é justa.

O que aconteceu na rede

O primeiro bloco inválido conhecido é o da altura 4.487.776, com data e hora 2026-08-07 15:44:01 UTC. A exploração continuou até a versão de emergência de 10 de agosto. Em um trecho examinado de 2.089 blocos, a 2Miners contou 96 afetados, então os blocos forjados estão espalhados pelo ramo em vez de juntos numa única sequência, e por isso a solução teve de ser uma reorganização e não uma remoção cirúrgica.

Para quem opera nós, o sintoma visível foi pior do que um bloco inválido aceito em silêncio. Os nós travaram nesses blocos e, depois de reiniciar, pararam de sincronizar. Tron Black descreveu esse mecanismo depois, e ele é um belo exemplo de dano colateral. O índice de blocos guarda a altura em que o bloco realmente está, não a altura declarada no cabeçalho. Ao reiniciar, o nó reconstrói cada cabeçalho a partir do índice e recalcula o hash, então, para um bloco forjado, ele faz o hash com a altura real onde o cabeçalho tinha uma falsa. O hash deixa de bater, a verificação de prova de trabalho falha e o LoadBlockIndexGuts aborta. O bloco foi aceito uma vez e depois ficou impossível de carregar, e por isso o segundo sintoma durou mais que o primeiro.

Várias exchanges suspenderam depósitos e saques de RVN enquanto o ramo não estava resolvido. É a resposta comum e correta para uma cadeia cujo histórico recente está prestes a ser reescrito, e vale esperar por ela em vez de lê-la como um veredito sobre a moeda.

A correção, e de onde ela veio

A primeira correção saiu em 10 de agosto como versão 4.6.1.1-hf1, e veio da 2Miners, um pool de mineração, e não do projeto Ravencoin. Ela estancou o problema para quem a instalou. Foi substituída um dia depois.

A versão 4.8.0, publicada em 11 de agosto no repositório da Ravencoin, é a que você deve rodar. Não se contente com nada anterior. Ela rejeita, a partir de 4.487.776, blocos cuja altura declarada no cabeçalho não corresponde à posição real, define 4.487.775 como checkpoint do último bloco limpo, reconstrói automaticamente o estado corrompido da cadeia em vez de abortar e inclui uma correção de overflow em transferências de ativos que o hotfix de emergência não trazia, junto com outros trabalhos da base de código.

Só a primeira dessas é o conserto. Rejeite um bloco cuja altura declarada não corresponde a onde ele realmente está, e o portão se fecha. O checkpoint e a reconstrução automática são recuperação, e um checkpoint em especial é um instrumento grosseiro: é uma afirmação fixa no código de que um bloco específico é o correto, o tipo de coisa que uma cadeia quer ter o mínimo possível. Ele está ali porque o ramo honesto precisava se firmar mais rápido do que o hashrate sozinho conseguiria.

O próprio ponto de recuperação foi uma decisão de julgamento, não algo óbvio. Tron Black escreveu que pediu aos pools que considerassem um corte mais recente, para tornar a reorganização mais rasa e tratar os blocos forjados como exceção. Eles recusaram e escolheram a linha mais limpa possível. Num sistema de prova de trabalho, essa discussão se resolve sozinha: quando a maioria do hashrate honesto recusa um histórico, esse histórico perde, e os dois maiores pools juntos tinham a maioria.

O que uma reorg de três dias realmente significa

Como nós vulneráveis aceitaram os blocos forjados, o ramo que os continha acumulou cadeia legítima por cima. Descartá-lo significa uma reorganização, e com o alvo de um minuto por bloco da Ravencoin, o trecho de 4.487.776 até a correção equivale a cerca de três dias de blocos.

Se você tem RVN, o efeito prático é que transações confirmadas no ramo descartado deixam de estar confirmadas. A maioria volta para a mempool e é minerada de novo na cadeia que sobrevive, mas isso não é garantido, e um tipo delas não pode voltar de jeito nenhum. Uma transação que gastou a coinbase de um bloco descartado está gastando moedas que não existem mais na cadeia que sobrevive, e o mesmo vale para tudo o que foi construído em cima dela. Essas são simplesmente inválidas, e não pendentes. Por isso as suspensões das exchanges foram a decisão certa e não um exagero, e por isso a atividade depois de 4.487.775 pedia cautela até uma única ponta da cadeia se firmar.

Um detalhe vale a pena se você viu um nó se recusar a avançar naquela semana. Os nós da Ravencoin têm uma regra contra reorgs longas, que existe para impedir que um ataque surpresa de maioria reescreva o histórico profundo. A maioria dos nós antigos rodando sem parar acabou ficando abaixo do número de pares de que essa regra depende e então aceitou a cadeia mais longa, como previsto. Alguns não, porque nunca perderam pares suficientes e porque, pelas regras como estão escritas, nada tinha sido violado. É uma proteção funcionando exatamente como especificada num problema para o qual nunca foi feita. A divisão veio de uma falha de validação, não de um ataque de maioria, e a regra não consegue distinguir os dois.

O que fazer se você minera RVN

Se você roda seu próprio nó Ravencoin, rode a 4.8.0, e não uma das versões anteriores que trataram só parte disso. Pare o nó, troque o binário e inicie de novo. Não há reindex a rodar nem passo manual: o software reconstrói sozinho o que precisa. Um nó vindo do hotfix de emergência reinicia rápido. Um nó vindo de software sem correção precisa reprocessar e reconstruir, o que leva várias horas em hardware comum, e erros bad-blk-height durante esse período são a correção funcionando, não uma falha dela.

Se você minera por um pool, não há nada a mudar do seu lado. Nenhum endereço novo, nenhuma porta nova, nenhuma atualização do programa de mineração. O KAWPOW em si nunca foi o problema: o algoritmo fez exatamente o que foi projetado para fazer, e o defeito estava no código que decidia quando executá-lo. O hashrate que você apontou para o ramo antigo foi trabalho real. Ele só caiu em blocos que foram descartados junto com os forjados espalhados entre eles.

Há um jeito de uma linha para confirmar que você terminou na cadeia certa, e vale fazer em vez de supor. Confira o hash do bloco 4.494.143. Na cadeia canônica ele termina em 4c67d6a4, e todos os exploradores independentes da Ravencoin mostravam essa mesma cadeia quando o projeto declarou a rede recuperada.

O formato do bug

Tire os detalhes e o que sobra é um formato que se repete o tempo todo, em blockchains e bem fora delas. Um valor vindo de fora foi usado para decidir com quanto cuidado esse mesmo valor seria examinado. O cabeçalho declarou a própria altura, e o nó usou essa declaração para escolher qual verificação de prova de trabalho o cabeçalho teria de passar.

A versão correta não é complicada, e a correção é basicamente uma linha dela: comparar a altura declarada com a posição que o bloco realmente ocupa antes de deixá-la decidir qualquer coisa. Por que isso faltava é a metade mais interessante. O atalho foi escrito para blocos já confiáveis, e dentro dessa premissa o portão parece a afirmação de um fato, e não uma pergunta que alguém poderia responder de forma desonesta. Ele deixa de parecer isso no instante em que um bloco não confiável pode preencher o campo, e nada no código marcou o momento em que isso mudou.

Duas coisas sobre como ele foi encontrado merecem registro. A Ravencoin já passou por auditorias de segurança profissionais do seu código, e elas não pegaram isto nem o overflow de ativos corrigido junto. Tron Black escreveu que suspeita fortemente que os dois foram revelados por uma revisão de segurança feita com um modelo de IA de fronteira. Seja qual for a ferramenta, combina com o formato: a falha é invisível enquanto você lê o atalho nos termos dele, e só aparece quando você pergunta quem pode preencher o campo em que o atalho confia.

A última coisa que vale notar é quem agiu, e em que ordem. Um pool de mineração leu o caminho de validação, entendeu e lançou um binário de emergência em menos de três dias. Os dois maiores pools então concordaram em minerar uma cadeia que jogava fora toda a janela do ataque em vez de aceitar o corte conveniente, e quando pediram a eles o corte mais raso, recusaram. O projeto veio um dia depois com a versão que substitui o hotfix e corrige mais do que ele. Quem veio primeiro importa menos do que o fato de as duas coisas terem acontecido, e de que o que de fato resolveu a questão não foi nenhum dos anúncios, e sim o peso acumulado de trabalho real de um lado da divisão. É assim que a descentralização se parece num dia em que precisa aguentar peso, em vez de ficar num whitepaper.

De onde vêm estes números

Para que qualquer pessoa possa conferir em vez de acreditar na nossa palavra. O layout do cabeçalho, as duas funções de hash e suas assinaturas, a escolha da época e o ramo de validação com seu comentário foram lidos no próprio código-fonte da Ravencoin, em src/primitives/block.h, src/primitives/block.cpp, src/hash.cpp, src/hash.h, src/pow.h e src/validation.cpp. A lista de checkpoints, incluindo 2.383.550 como o mais alto da mainnet, foi lida em src/chainparams.cpp. A primeira altura inválida e sua data e hora vêm do aviso de rede do próprio projeto Ravencoin. O caminho do travamento no índice de blocos, as transações dependentes da coinbase que não podem voltar, o comportamento contra reorgs longas, o ponto de recuperação mais raso recusado, as observações sobre a auditoria e a revisão por IA, o conteúdo da 4.8.0 e a verificação do bloco 4.494.143 vêm do relato de Tron Black pelo projeto Ravencoin. A versão do hotfix de emergência e a contagem de 96 blocos afetados em 2.089 vêm do lançamento da 2Miners e da cobertura em torno dele. O que as fontes não dizem ficou fora deste artigo em vez de ser preenchido.