Obrigado Google: Como minerar o Bitcoin no BigQuery do Google

Aproveitando o poder do SQL para minar criptomoeda na nuvem

Eu amo o Google BigQuery . É uma plataforma de dados altamente dimensionável e gerenciada, o que significa que você pode consultar grandes quantidades de dados muito rapidamente (por exemplo, 4 Terabytes em menos de 30 segundos! ). Eu uso regularmente meus projetos (como a Ação de Lição em Espanhol para o Assistente do Google ), e estou sempre impressionado com o alto desempenho.

Na semana passada, organizamos um Meetup Dev.IL sobre o Blockchain, a tecnologia por trás do Bitcoin, e fiquei impressionado com uma das palestras de Asaf Nadler, explicando os mecanismos por trás da tecnologia que faz o Bitcoin funcionar ( fique à vontade para assistir a palestra aqui) , mas aviso justo, é em hebraico :-). Quando fui para casa depois do encontro, meu console do BigQuery foi aberto com alguma consulta de análise que escrevi no dia anterior e tive a seguinte ideia: e se eu pudesse usar o BigQuery para minerar o Bitcoin? É mesmo possível? Isso pode ser rentável? Dada a impressionante escalabilidade do BigQuery, parecia que poderia ser uma boa combinação.

( Spoiler: foi! Até 25 Giga-hashes / segundo, grátis e muito divertido! Continue lendo para descobrir como…)

Fiquei muito intrigado e decidi experimentá-lo. Eu estava interessado em experimentar a tecnologia Blockchain por um tempo, e essa foi uma ótima oportunidade para fazê-lo. Foi preciso ler muitos artigos e algumas horas de trabalho, mas tive algum sucesso! (Ou melhor, sucesso de prova de conceito 😉

Achei que seria interessante compartilhar minha jornada com você ao transformar uma máquina de análise de dados em uma máquina de mineração Bitcoin. Fiquei bastante surpreso com os resultados. e aposto que você também será!

Mas primeiro as primeiras coisas: vamos fazer uma visão geral super rápida de como funciona a mineração Bitcoin.

Mineração de Bitcoins em um Nutshell

Você provavelmente já ouviu falar que o Bitcoin de mineração envolve a solução de um problema matemático computacionalmente difícil, e isso é verdade: o sistema Bitcoin consiste em transações (isto é, transferência de dinheiro entre usuários) e essas transações são registradas em um ledger público, chamado blockchain. O blockchain, como o nome sugere, é uma lista encadeada de blocos de dados de transação.

Mineração Bitcoin envolve essencialmente encontrar um próximo bloco válido, que por sua vez, dá a você, o mineiro, um prêmio – atualmente, 12.5BTC para cada bloco que você encontrar.

Cada bloco é composto por duas partes: um cabeçalho de 80 bytes, seguido por uma lista de transações. O cabeçalho inclui o identificador do bloco anterior (ou seja, o hash do cabeçalho do bloco anterior), bem como um hash SHA256 da lista de transações, bem como algumas outras informações. Como minerador, você basicamente tem que se certificar de que o cabeçalho do bloco, quando hash duas vezes usando a função hash SHA256, seja menor que um determinado número, também chamado de "dificuldade" – ou quão difícil é encontrar o alvo número (ou seja, o próximo bloco válido).

Blocos de Bitcoin são extraídos a uma taxa de cerca de 1 bloco a cada 10 minutos. A fim de garantir que a taxa permaneça constante, a dificuldade é automaticamente ajustada a cada bloco de 2016 (aproximadamente a cada duas semanas), portanto é mais ou menos proporcional ao total de mineiros de energia de computação colocados no processo.

Mineração envolve basicamente tentar diferentes variações para o cabeçalho, principalmente no campo nonce (os últimos 4 bytes do cabeçalho) até você eventualmente encontrar um cabeçalho cujo hash começa com um determinado número de zeros (ou, para colocá-lo diferentemente – menor que alguns número, como afirmei antes).

Se você quiser uma explicação mais detalhada, pode conferir a postagem no blog de Ken Shirriff sobre a mineração Bitcoin , ou simplesmente acompanhar e coletar as informações que menciono em todo o post.

Mineração com o BigQuery

Você está convidado a seguir meus passos e executar os exemplos neste blog. Algumas das perguntas aqui apresentadas podem assemelhar-se a partes do processo de mineração de Bitcoin. Se você tem uma conta de avaliação gratuita do Google Cloud Platform, os termos deles proíbem você de participar de uma criptomoeda de mineração. Embora nenhum dos exemplos apresentados neste post realmente mine qualquer criptomoeda, ainda aconselho que você faça um backup e tenha uma conta paga do Google Cloud Platform, que, pelo que sei, não proíbe de qualquer forma a criptografia de mineração.

Primeiras coisas primeiro: olhando para o cabeçalho de um bloco

Vamos começar observando como a mineração é feita na prática. Vamos dar uma olhada no cabeçalho de algum bloco do blockchain Bitcoin e tentar calcular o hash para ver como é feito (e também verificar se podemos fazer a parte de hashing com o BigQuery).

Mas onde encontramos um bloco?

Acontece que você pode encontrar todo o Blockchain no BigQuery . Mas para os nossos propósitos, vamos usar uma fonte diferente, que também fornece uma maneira de obtermos os dados binários brutos do bloco: um site chamado blockchain.info . Eu escolhi aleatoriamente um dos blocos recentes, número 514868:

Blocos de Bitcoin na altura 514868
Blocos de Bitcoin na altura {0} na cadeia de blocos do Bitcoin blockchain.info

Você pode obter os dados binários para este bloco (hex codificado), clicando no hash do bloco e, em seguida, anexando ?format=hex na URL, resultando neste link . A visão de bloco também mostra a lista completa de transações e outros dados interessantes, e eu convido você a explorar por conta própria.

Por enquanto, porém, vamos nos concentrar no cabeçalho. Vamos copiar os primeiros 160 caracteres dos dados do bloco (os primeiros 80 bytes):

 000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117ac997500 

Esta página Wiki do Bitcoin explica como funciona o algoritmo de hashing: basicamente, precisamos pegar este cabeçalho e executar a função SHA256 nele, depois executá-lo novamente no resultado da primeira execução. Isso deve resultar no hash do bloco.

Então, primeiro, se quiséssemos fazer isso no BigQuery, precisaríamos de uma função SHA256. Felizmente, foi introduzido na versão 2 do BigQuery (também conhecido como Standard SQ). Também precisamos de algum método para converter esses valores hexadecimais em bytes reais. Felizmente, uma função chamada FROM_HEX nos cobriu. Por enquanto, tudo bem.

Agora podemos tentar escrever a consulta real (como uma única linha):

 SELECIONE TO_HEX (SHA256 (SHA256 (FROM_HEX ( 
«000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117ac997500 '))))

(Se você quiser acompanhar, pode tentar executar essa consulta no console do BigQuery . Também será necessário desmarcar as opções de consulta "Usar legado SQL". Se você receber um erro que diz: "Função não reconhecida to_hex", significa que você não desmarcou essa caixa.

Ao executar a consulta acima, obtemos o seguinte resultado:

Eu esperava ver " 000000000000000000082fac02f1bf91ad4e024e6a5a1537936e9d518f827a53 "

Quando fiz isso pela primeira vez, fiquei bastante desapontado, porque parecia que o hash era diferente do hash original do bloco . Eu percebi rapidamente, no entanto, que era apenas invertido! (Aparentemente, hashes de Bitcoin são armazenados little-endian ).

Adicionando uma função REVERSE antes de chamar TO_HEX fez o truque:

 SELECIONE TO_HEX (REVERSE (SHA256 (SHA256 (FROM_HEX) 
«000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117ac997500 ')))))

Agora parece legítimo!

Então, isso já é uma grande conquista: agora sabemos que podemos verificar os hashes dos blocos de Bitcoin no BigQuery. Se quiséssemos usar o BigQuery no meu, gostaríamos de usar o mesmo conjunto de funções, exceto que não teríamos o cabeçalho completo: teríamos que procurar um cabeçalho válido com um valor de hash pequeno o suficiente.

Um cabeçalho válido consiste em 6 campos:

  1. version – Valores de 2, 3 e 4 são comuns (novos valores estão sendo adicionados )
  2. hashPrevBlock – O hash do bloco anterior na cadeia
  3. hashMerkleRoot – Hash de todas as transações no bloco ( explainer )
  4. time – Timestamp de quando o bloco foi criado
  5. difficulty – A dificuldade do bloco armazenado como um número de ponto flutuante
  6. nonce – 4 bytes que podemos variar para afetar o valor do cabeçalho

Mineração é basicamente tentar todas as combinações possíveis para nonce e, em seguida, verificar o hash para cada até que o hash esteja abaixo do valor de destino. O valor alvo pode ser facilmente derivado da dificuldade do bloco:

 target = (0xffff * 2 ** (256-48)) / dificuldade 

A dificuldade mínima para qualquer bloco é 1. Portanto, qualquer alvo terá pelo menos 48 zeros à esquerda. Assim, como o número da dificuldade do nosso bloco era 3462542391191.563, o alvo era: 000000000000000000514a490000000000000000000000000000000000000000

Você pode encontrar a dificuldade atual e o valor alvo aqui (o link também lhe dará um pouco mais de explicação sobre a relação entre a dificuldade e o valor alvo).

Então, basicamente, se quiséssemos reproduzir o processo de mineração para este bloco, teríamos que tentar todas as combinações diferentes para o nonce , os últimos 4 bytes do cabeçalho, até encontrarmos um cujo hash é menor que o acima número.

Re-Minerando o Bloco

Eu decidi começar pequeno apenas procurando pelo último byte, já que já temos a resposta neste caso. Inicialmente, pensei em fazer o upload de uma pequena tabela com todos os números entre 0 e 255 (os valores válidos para esse byte), mas descobri que essa tabela pode ser imitada pela combinação de duas funções SQL: UNNEST() e GENERATE_ARRAY() :

 SELECT * FROM UNNEST (GENERATE_ARRAY (0, 255)) num; 

Com esse conhecimento, criei minha primeira consulta que tentava reproduzir o processo de mineração no BigQuery:

 SELECIONAR 
TO_HEX (CODE_POINTS_TO_BYTES ([0xac, 0x99, 0x75, num])) como nonce
A PARTIR DE
UNNEST (GENERATE_ARRAY (0, 255)) num
ONDE
TO_HEX (REVERSE (SHA256 (SHA256) CONCAT (FROM_HEX (
'000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117'), CODE_POINTS_TO_BYTES ([0xac, 0x99, 0x75, num])))))) CURTIR '000000000000000000%'

Ok, vamos provocar isso um pouco!

Basicamente, já que estamos apenas procurando corrigir o nonce , eu removi essa parte do cabeçalho (você pode verificar – a cadeia hex longa na consulta tem apenas 152 caracteres, o que representa 76 bytes, ou seja, o cabeçalho sem o cabeçalho). nonce). O que nossa consulta está procurando é um valor de 4 bytes que, uma vez anexado ao cabeçalho, resultará em um hash menor que o número de destino.

Como esse foi meu teste inicial, usei os valores que já conhecia do bloco para os primeiros 3 bytes no nonce, e só fiz o BigQuery procurar o byte final. Isso funcionou como um encanto e rapidamente encontrou o valor correto do nonce :

Você pode estar se perguntando por que usei LIKE dentro da cláusula WHERE para filtrar o resultado correto. Eu faço isso porque estamos simplesmente procurando por hashes menores que o valor alvo. Como o valor alvo começa com 18 zeros, simplesmente filtramos qualquer número que não comece com 18 zeros (pois é obviamente maior que o valor alvo). Isso significa que podemos obter alguns falsos positivos (por exemplo, número que começa com 18 zeros e, em seguida, qualquer dígito maior que 5), mas podemos filtrá-los rapidamente manualmente.

Então, agora, quando sabemos como procurar o nonce sneaky, podemos expandir rapidamente a pesquisa para mais bytes:

 SELECIONAR 
TO_HEX (CODE_POINTS_TO_BYTES ([0xac, num2, num3, num4])) como nonce
A PARTIR DE
UNNEST (GENERATE_ARRAY (0, 255)) num2,
UNNEST (GENERATE_ARRAY (0, 255)) num3,
UNNEST (GENERATE_ARRAY (0, 255)) num4
ONDE
TO_HEX (REVERSE (SHA256 (SHA256) CONCAT (FROM_HEX (
'000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117'), CODE_POINTS_TO_BYTES ([0xac, num2, num3, num4])))))) LIKE '000000000000000000%'

Enquanto esta consulta faz o truque e encontra os 3 bytes, houve um problema: corre devagar! Essa consulta levou cerca de 30 segundos para ser concluída. Se fôssemos procurar por todos os 4 bytes, levaria cerca de duas horas a essa velocidade. Eu mencionei acima que um novo bloco é extraído a cada 10 minutos, então no momento em que encontramos um resultado, já estaríamos atrás e isso não seria relevante.

Isso foi um pouco decepcionante (o que aconteceu com a promessa “altamente escalonável” do BigQuery?), Então decidi explorar mais e tentar ver se poderia fazer algo para melhorar isso.

Da minha experiência anterior com o BigQuery, lembrei-me de que ingressar em diferentes tabelas era uma operação dispendiosa que fazia com que as consultas fossem executadas por muito mais tempo. Tentei remover a etapa de junção gerando uma tabela maior (ou seja, informando GENERATE_ARRAY para gerar mais números), mas após algumas tentativas e erros, parecia que o tamanho máximo da tabela ainda seria de cerca de 1 milhão de linhas, o que significa que eu Eu ainda tenho que juntar tabelas ou apenas procurar por 1 milhão de hashes todas as vezes (o que é um pouco inútil, já que a CPU do meu computador pode fazer isso em menos de um segundo).

Então, parecia que seria possível minerar o Bitcoin com o BigQuery, mas dificilmente prático.

Ainda assim, não gosto muito de desistir, então tentei mais algumas abordagens. E um deles realmente funcionou!

Mineração mais rápida

Lembrei que o BigQuery tinha alguns conjuntos de dados públicos muito grandes contendo tabelas enormes. Após a digitalização de alguns deles, encontrei um que continha 5,3 bilhões de linhas , o que é um pouco acima de 4.294.967.296, o número do número diferente de combinações para nonce . Se eu pudesse construir uma consulta que fosse sobre essa tabela e tentasse uma combinação diferente para cada linha da tabela, eu conseguiria cobrir todas as combinações possíveis.

Eu tentei usar o ROW_NUMBER() OVER() função SQL para isso, que deve retornar o número da linha atual (então eu poderia derivar um conjunto diferente de bytes para cada linha, de acordo com o número da linha), mas morreu rapidamente com o seguinte erro:

Recursos excedidos durante a execução da consulta: A consulta não pôde ser executada na memória alocada.

Vadio! Mas então comecei a pensar – e se eu apenas tentasse números aleatórios? Eventualmente, há uma boa chance de que, se houver um hash correspondente, eu o encontre, já que o número de tentativas é maior que o número de combinações.

Então, eu criei essa consulta:

 SELECIONAR TO_HEX (nonce) 
A PARTIR DE (
SELECIONAR
CODE_POINTS_TO_BYTES ([
CAST (TRUNC (256 * RAND ()) como INT64),
CAST (TRUNC (256 * RAND ()) como INT64),
CAST (TRUNC (256 * RAND ()) como INT64),
CAST (TRUNC (256 * RAND ()) COMO INT64)
]) AS nonce
A PARTIR DE
`fh-bigquery.wikipedia.wikipedia_views_201308`
)
ONDE
TO_HEX (REVERSE (SHA256 (SHA256) CONCAT (FROM_HEX (
«000000204a4ef98461ee26898076e6a2cfc7c764d02b5f8d670832000000000000000000f99f5c4d5025979fcb33d245536a55b628d4564c075c0210cbbc941ad79fdbc5e491b55a494a5117 '), nonce))))) COMO' 000000000000000000% '

CAST(TRUNC(256*RAND()) AS INT64) gera um número aleatório entre 0 e 255, e então a parte SELECT interna simplesmente gera uma tabela com ~ 5.3 bilhões de linhas de quatro valores aleatórios. Em seguida, a consulta externa certifica-se de que só recebemos valores que resultem em um hash baixo o suficiente – que é o que estamos procurando!

Para minha agradável surpresa, esta consulta retornou em meros 20,9 segundos – e de fato encontrou o valor correto do nonce (ele até encontrou duas vezes!):

Isso significa que consegui verificar quase 4 bilhões de hashes em 20 segundos, ou cerca de 200 megawash por segundo. Isso não é tão ruim – considerando que as GPUs só levarão cerca de 30-60 mega-hashes / segundo (CPUs ainda menos , sem mencionar mineração com lápis e papel ).

Então, obviamente, isso não nos leva nem perto de um hardware de mineração dedicado , mas ainda havia mais um truque que eu tinha na manga…

Concorrência

Se você é um especialista em combinatória, você deve ter notado que verificar todos os valores possíveis para nonce não garante uma boa chance de encontrar um hash pequeno o suficiente (como eu disse no começo, é um problema difícil!).

No momento da escrita, o alvo era de aproximadamente 2¹?² (veja como calculamos o alvo da dificuldade acima), o que significa que existem 2¹?² combinações hash válidas de um total de 2???? possíveis, ou em outras palavras – a chance de um hash arbitrário ser menor que o alvo é 1 em 2??.

Isso basicamente significa que se apenas variarmos nonce , vamos checar 2³² possibilidades, e nossa chance de realmente encontrar um hash desejado é super pequena: então precisamos de mais bytes para brincar.

Se dermos outra olhada na estrutura do cabeçalho , você notará que podemos alterar ligeiramente o campo de time (outros nós ainda aceitarão o bloco se o tempo estiver um pouco errado), ou podemos alterar algo na lista de transações do nosso bloco, o que resultará em um valor completamente diferente para o campo hash da raiz de merkle .

Uma maneira de variar a lista de transações (e, assim, gerar um hash de merkle diferente) é adicionando uma carga extra à primeira transação, chamada extraNonce , que pode ter até 100 bytes. Outra maneira seria escolher um conjunto diferente de transações para ser incluído no bloco.

O ponto é que, para ser um minerador bem-sucedido, é preciso tentar combinações diferentes de lista de transações e / ou variar o campo de time , além do nonce . Isso significa que podemos executar a consulta que encontramos várias vezes, em paralelo, cada vez com um cabeçalho de bloco diferente. Mas quantas consultas simultâneas o BigQuery permite?

De acordo com a página de cotas , você pode ler até 50 consultas simultâneas, o que pode, teoricamente, resultar em hash até 50 vezes mais rápido. Isso realmente funciona? Eu não sei, eu não tentei. Atualmente, meu melhor resultado com uma única consulta foi de 500 Mega-hashes por segundo (usando este conjunto de dados de wikipedia de 106 bilhões de linhas como uma tabela de origem), o que significa que com um limite de 50 consultas simultâneas, podemos obter até 25 Giga- hashes / segundo. Como um hardware de mineração dedicado moderado – o que não é tão ruim, considerando que é essencialmente gratuito .

Então, quanto custa?

O BigQuery afirma ser uma solução econômica . A essa altura, eu já estava convencido de sua reivindicação de escalabilidade, mas a estrutura de preços deles também foi uma surpresa para mim. Uma surpresa muito boa.

O modelo de preços do BigQuery baseia-se unicamente na quantidade de dados que você consulta: basicamente, você é cobrado pelo byte. O problema é que você não está faturando pelo poder de processamento nem pelos dados intermediários que sua consulta gerou – apenas para bytes que você leu na tabela de origem.

No nosso caso, usamos tabelas de origem enormes, mas apenas as usamos para gerar um grande número de combinações de números aleatórios – na verdade, não lemos nenhum dado para as tabelas. O BigQuery tem um bom recurso onde mostra o preço estimado de uma consulta (como o número de bytes pelos quais você será cobrado). O primeiro 1 Terabyte é grátis todo mês, e você paga 5 $ por cada Tetabyte adicional que você consultar depois disso. Não é ruim!

Então, se verificarmos o que BigQuery estima para nossa grande consulta, isso é o que encontramos:

Então, basicamente, podemos executar quantas consultas quiser, de graça! Eles certamente estão à altura de sua reivindicação de custo-efetividade (neste caso, pelo menos).

Sumário e conclusões

Isso começou como um exercício divertido, com o objetivo de aprender melhor como funciona o blockchain. Quando eu comecei, eu tinha certeza que iria atingir algum limite rígido (talvez uma cota de tempo de CPU ou algo assim), e devo dizer que ainda estou muito surpreso que eu poderia ter todo esse poder de computação de graça.

Embora minha pesquisa mostre que deveria ser possível transformar o BigQuery em uma máquina de mineração em teoria , ainda há uma quantidade considerável de trabalho que teria que ser feito se eu quisesse automatizar o processo de mineração. Um cálculo bruto mostra que, mesmo que tal minerador fosse construído, ao competir com todo o hardware de mineração dedicado em todo o mundo, isso me custaria cerca de US $ 5 por ano . No entanto, agora eu sei que Bitcoin pode ser extraído com SQL, que é inestimável 😉

Mas o mais importante, esse experimento me deu uma nova perspectiva sobre o enorme poder de computação que está sendo gasto na mineração de Bitcoin. Estamos falando de 26 bilhões de giga-hashes por segundo – isso é 2,6 * 10¹?, o que é mais do que o número de grãos de areia na Terra. Todo segundo. Você deve imaginar o que conseguiríamos se usássemos uma fração desse poder de computação para pesquisa médica …

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *