Como a Ethereum funciona afinal? (PTBR ver.)

READ STORY
Special thanks to Jeff Prestes (@jeffprestes) for translating this post into Brazilian Portuguese.

Introdução

Provavelmente você já ouviu sobre o blockchain da Ethereum, independentemente de você saber ou não o que é. Ele está nas notícias ultimamente, incluindo a capa de algumas revistas, mas ler esses artigos pode ser ruim se você não tem um fundamento para o que é exatamente a Ethereum. Então, o que ela é? Em essência, um banco de dados público que mantém permanentemente registros de transacções digitais. É importante que este banco de dados não exija nenhuma autoridade central para mantê-la e protegê-la. Em vez disso, funciona como um sistema transacional "sem intermediários" - um ambiente em que os indivíduos podem fazer transações peer-to-peer sem necessidade de confiar uns nos outros ou em um terceiro.

Ainda está confuso? É aí que entra esta publicação. O meu objectivo é explicar como o Ethereum funciona a um nível técnico, sem fórmulas matemáticas ou de aparência complicada. Mesmo que você não seja um programador, espero que você vá embora com pelo menos melhor compreensão da tecnologia. Se algumas partes são muito técnicas e difíceis de compreender, isso é completamente OK! Não há realmente necessidade de entender cada pequeno detalhe. Recomendo que nos concentremos apenas na compreensão das coisas a um nível amplo.

Muitos dos tópicos abordados nesta postagem são um detalhamento dos conceitos discutidos na definições técnicas da Ethereum. Eu adicionei minhas próprias explicações e diagramas para facilitar a compreensão da Ethereum. Os corajosos o suficiente para enfrentar o desafio técnico também podem ler as definições técnicas da Ethereum. Vamos começar!

Definição de Blockchain

Um blockchain é "uma máquina única criptograficamente segura com estado compartilhado." [1] Isso é lindo de dizer, não é? Vamos dividir em partes.

  • "Criptograficamente seguro"  significa que a criação de moeda digital é garantida por algoritmos matemáticos complexos que são obscenamente difíceis de quebrar. Pense em um firewall de tipos. Eles tornam quase impossível trapacear o sistema (por exemplo, criar transações falsas, apagar transações etc.)
  • "Máquina única transacional"  significa que há uma única instância canônica responsável por todas as transações sendo criadas no sistema. Em outras palavras, há uma única verdade global em que todos acreditam.
  • "Com estado compartilhado" significa que o estado armazenado nesta máquina é compartilhado e aberto a todos.


A Ethereum implementa este paradigma blockchain.

O paradigma do blockchain Ethereum explicado

O blockchain da Ethereum é essencialmente uma máquina de estado baseado em transações. Na ciência da computação, uma máquina de estado refere-se a algo que irá ler uma série de entradas e, com base nessas entradas, irá mudar para um novo estado.

Com a máquina de estado da Ethereum, começamos com um "estado gênesis". Isto é análogo a uma folha em branco, antes que quaisquer transações tenham acontecido na rede. Quando transações são executadas, esta transição do estado gênesis entra em algum estado final. A qualquer altura, este estado final representa o estado atual da Ethereum.

O estado da Ethereum tem milhões de transações. Estas transações são agrupadas em "blocos". Um bloco contém uma série de transações, e cada bloco está encadeado junto com seu bloco anterior.

Para fazer uma transição de um estado para o outro, uma transação deve ser válida. Para uma transação ser considerada válida, ela deve passar por um processo de validação conhecido como mineração. A mineração é quando um grupo de nós (ou seja, computadores) gasta seus recursos de computação para criar um bloco de transações válidas.

Qualquer nó na rede que se declara a si mesmo como um minerador pode tentar criar e validar um bloco. Muitos mineradores de todo o mundo tentam criar e validar blocos ao mesmo tempo. Cada minerador fornece uma "prova" matemática ao submeter um bloco à cadeia de blocos, e esta prova age como garantia: se a prova existe, o bloco deve ser válido.

Para que um bloco seja adicionado ao blockchain principal, o minerador deve prová-lo mais rápido do que qualquer outro minerador concorrente. O processo de validação de cada bloco por um minerador que fornece uma prova matemática, é conhecido como "prova de trabalho".  

Um minerador que valida um novo bloco é recompensado com uma certa quantidade de valor por fazer este trabalho. Qual é esse valor? O blockchain do Ethereum usa um token digital intrínseco chamado "Ether". Toda vez que um minerador prova um bloco, novos tokens do Ether são gerados e premiados.

Podemos perguntar-nos: qual a garantia de que todos ficam associados a uma cadeia de blocos? Como podemos ter certeza de que não existe um subconjunto de mineradores que decidirão criar sua própria cadeia de blocos?

Anteriormente, definimos um blockchain como uma máquina única transacional com estado compartilhado. Utilizando esta definição, podemos compreender que o estado atual correto é uma verdade global única, que todos devem aceitar. Ter múltiplos estados (ou cadeias) arruinaria todo o sistema, porque seria impossível chegar a acordo sobre qual estado era o correto. Se as cadeias fossem divergentes, poderiam existir 10 moedas numa cadeia, 20 em outra, e 40 em outra.

Neste cenário não haveria nenhuma maneira de determinar qual cadeia era a mais "válida". Sempre que vários caminhos são gerados um "fork" ocorre. Nós normalmente queremos evitar forks, porque eles perturbam o sistema e obrigam as pessoas a escolher em que cadeia elas "acreditam".

Para determinar qual caminho é mais válido e evitar várias cadeias, a Ethereum usa um mecanismo chamado "Protocolo GHOST.  

"GHOST" = "Greedy Heaviest Observed Subtree"

Em termos simples, o protocolo GHOST diz que devemos escolher o caminho que mais computação fez sobre ele. Uma maneira de determinar que caminho é usar o número de bloco do bloco mais recente (o "bloco de folha"), que representa o número total de blocos no caminho atual (não contando o bloco Gênesis). Quanto maior o número do bloco, maior o caminho e maior o esforço de mineração que deve ter chegado à folha. Utilizar este raciocínio permite-nos chegar a acordo sobre a versão canónica do estado actual.

Agora que você tem a visão geral do que é um blockchain vamos nos aprofundar nos detalhes dos componentes principais dos quais o sistema Ethereum é composto:


  • contas
  • estado
  • gás e taxas
  • transações
  • blocos
  • execução de transação
  • mineração
  • prova de trabalho


Uma nota antes de começar: sempre que digo "hash" de X, estou me referindo a KECCAK-256 o algoritmo de hash que a Ethereum usa.

Contas

O "estado compartilhado" global da Ethereum é composto por muitos objetos pequenos (“contas”) que são capazes de interagir uns com os outros através de um framework de passagem de mensagens. Cada conta tem um estado associado a ela e um endereço de 20 bytes de comprimento. Um endereço na Ethereum é um identificador de 160-bit que é usado para identificar qualquer conta.

Existem dois tipos de contas:

  • Contas de propriedade externa, que são controladas por chaves privadas e têm nenhum código associado a elas.
  • Contas de contrato, que são controladas pelo seu código contratual e têm código associado a elas.

Contas de propriedade externa vs. contas de contrato

É importante entender uma diferença fundamental entre contas de propriedade externa e contas de contratos. Uma conta de propriedade externa pode enviar mensagens para outras contas de propriedade externa OU para outras contas de contrato, criando e assinando uma transação usando sua chave privada. Uma mensagem entre duas contas de propriedade externa é simplesmente uma transferência de valor. Mas uma mensagem de uma conta de propriedade externa para uma conta de contrato ativa o código da conta de contrato, permitindo-lhe executar várias ações (por exemplo tokens de transferência, gravação no armazenamento interno, mineração de novos tokens, efetuar alguns cálculos, criar novos contratos, etc.).

Ao contrário das contas de propriedade externa, as contas de contrato não podem iniciar novas transações por conta própria. Em vez disso contas de contrato só podem disparar transações em resposta a outras transações recebidas (de uma conta de propriedade externa ou de outra conta de contrato). Vamos aprender mais sobre chamadas contrato-para-contrato na seção “Transações e mensagens”.

Portanto, qualquer ação que ocorra no blockchain da Ethereum é sempre iniciada por transações disparadas de contas controladas externamente.

Estado da conta

O estado da conta consiste em quatro componentes que estão presentes independentemente do tipo de conta:

  • nonce: Se a conta é uma conta de propriedade externa, este número representa o número de transações enviadas pelo endereço da conta. Se a conta é uma conta de contrato, o nonce é o número de contratos criados pela conta.
  • Saldo: O número de Wei que pertence a este endereço. Há 1e+18 Wei por Ether.
  • storageRoot: Um hash do nó raiz de uma árvore Patricia Merkle (explicaremos árvores Merkle mais tarde). Esta árvore codifica o hash do conteúdo de armazenamento desta conta e está vazia por padrão.
  • codeHash: O código de hash da EVM (Ethereum Virtual Machine — mais sobre esse tópico adiante) desta conta. Para contas de contrato, este é o código que é hashado e armazenado como codeHash. Para contas de propriedade externa, o campo codeHash é o hash de uma string (texto) vazia.

Estado do mundo

Ok, então nós sabemos que o estado global da Ethereum consiste em um mapeamento entre os endereços de conta e os estados da conta. Este mapeamento é armazenado em uma estrutura de dados conhecida como árvore Patricia Merkle.

Uma árvore de Merkle (ou também referida como “triagem de Merkle”) é um tipo de árvore binária composta por um conjunto de nós com:

  • um grande número de folhas no fundo da árvore que contêm os dados subjacentes
  • um conjunto de nós intermediários, onde cada nó é o hash de seus dois nós filhos
  • um único nó raiz, também formado a partir do hash de seus dois nós filhos, representando o topo da árvore

Os dados na parte inferior da árvore são gerados dividindo os dados em que queremos armazenar pedaços, depois dividir os pedaços em baldes, e, em seguida, pegar o hash de cada balde e repetir o mesmo processo até que o número total de hashes restantes se torne apenas um: o hash raiz.

Esta árvore é necessária para ter uma chave para cada valor armazenado dentro dela. Começando pelo nó raiz da árvore, a chave deve dizer a você qual nó filho seguir para obter o valor correspondente, que é armazenado nos nós de folha. No caso do Ethereum, o mapeamento chave/valor para a árvore de estado está entre endereços e suas contas associadas, incluindo o saldo, nonce, codeHash e storageRoot para cada conta (onde o armazenamento é ele mesmo uma árvore).

Fonte: Ethereum whitepaper Esta mesma estrutura também é usada para armazenar transações e recibos. Mais especificamente, cada bloco tem um "cabeçalho" que armazena o hash do nó raiz de três estruturas diferentes de Merkle, incluindo:

  1. Estado da árvore (trie)
  2. Transações na árvore (trie)
  3. Recibos na árvore (trie)

A capacidade de armazenar toda essa informação eficientemente em tentativas de Merkle é incrivelmente útil na Ethereum para o que chamamos de "clientes leves" ou "nós leves". Lembre-se de que um blockchain é mantido por um monte de nós. Em termos gerais, há dois tipos de nós: nós completos e nós leves.

Um nó completo sincroniza o blockchain baixando a cadeia completa, do bloco Gênesis para o último bloco atual, executando todas as transações contidas nele.  Normalmente, os mineradores armazenam o nó completo do arquivo, porque são necessários para o processo de mineração. Também é possível baixar um nó completo sem executar cada transação. Independentemente disso, qualquer nó completo contém toda a cadeia.

Mas a menos que um nó precise executar todas as transações ou facilmente consultar dados históricos, não há realmente necessidade de armazenar toda a cadeia. É aqui que entra o conceito de um nó leve. Em vez de baixar e armazenar a cadeia completa e executar todas as transações, nós leves baixam apenas a cadeia de cabeçalhos, do bloco Gênesis para o bloco atual, sem executar nenhuma transação ou recuperar qualquer estado associado. Como os nós leves têm acesso aos cabeçalhos de bloco, que contêm hashes de três tries, ainda podem facilmente gerar e receber respostas verificáveis sobre transações, eventos, saldos, etc.

A razão pela qual isto funciona é porque os hashes na árvore Merkel propagam-se para cima — se um usuário mal-intencionado tentar trocar uma transação falsa para o fundo de uma árvore Merkle, esta alteração causará uma mudança no hash do nó acima, que irá alterar o hash do nó acima disso, e assim por diante, até que finalmente mude a raiz da árvore.

Qualquer nó que queira verificar dados pode usar algo chamado de "prova de Merkle" para fazê-lo. Uma prova de Merkle consiste de:

  1. Um pedaço de dados para ser verificado e seu hash
  2. O hash raiz da árvore
  3. O "galho" (todos os hashes do parceiro subindo no caminho do pedaço para a raiz)

Qualquer pessoa que leia a prova pode verificar que o hash para aquele galho é consistente todo o alto da árvore, e, portanto, que o pedaço em análise está realmente nessa posição na árvore.

Em resumo, o benefício de usar uma árvore Merkle Patricia é que o nó raiz desta estrutura é criptograficamente dependente dos dados armazenados na árvore, e assim o hash do nó raiz pode ser usado como uma identidade segura para estes dados. Uma vez que o cabeçalho do bloco inclui o hash raiz do estado, transações e recibos de árvores, qualquer nó pode validar uma pequena parte do estado da Ethereum sem necessidade de armazenar todo o estado, que pode ser potencialmente sem limites em tamanho.

Gás e pagamento

Um conceito muito importante na Ethereum é o conceito de taxas. Toda computação que ocorre como resultado de uma transação na rede Ethereum incorre numa taxa — não há almoço grátis! Esta taxa é paga em uma denominação chamada "gás".

Gas é a unidade usada para medir as taxas necessárias para uma determinada tarefa de computação. **Preço de Gás ** é a quantidade de Ether que você está disposto a gastar em cada unidade de gás, e é medido em "gwei". “Wei” é a unidade mais pequena de Ether, onde 100000000000000000 Wei representa 1 Ether.

Um gwei é 1.000.000.000 Wei. Com cada transação, um remetente define um limite de gás que pode ser usado na transação e o preço do gás. A multiplicação entre preço do gás e o limite de gás representa a quantidade máxima de Wei que o remetente está disposto a pagar para executar uma transação.

Por exemplo, digamos que o remetente define o limite de gás para 50.000 e um preço de gás para 20 gwei. Isso implica que o remetente está disposto a gastar no máximo 50.000 x 20 gwei = 1.000,000,000,000 Wei = 0.001 Ether para executar essa transação.

Lembrem-se de que o limite de gás representa o gás máximo em que o remetente está disposto a gastar dinheiro. Se eles têm o suficiente Ether em seu saldo de conta para cobrir esse máximo, eles estão prontos para ir. O remetente é reembolsado por qualquer gás não utilizado no final da transação, trocado na taxa original.

No caso de o remetente não fornecer o gás necessário para executar a transação, a transação é executada "sem gás" e considerada inválida. Neste caso, o processamento de transações abortadas e quaisquer alterações que ocorreram no estado são revertidas, de maneira que acabamos no estado da Ethereum antes da transação. Além disso, um registro da transação falhada é registrado, mostrando qual transação foi tentada e onde ela falhou. E uma vez que a máquina já gastou esforços para executar os cálculos antes de ficar sem gás, logicamente, nenhum do gás é reembolsado ao remetente.

Para onde é que esse dinheiro (ether) do gás vai exatamente? Todo o dinheiro gasto no gás pelo remetente é enviado para o endereço "beneficiário", que normalmente é o endereço do minerador. Uma vez que os mineradores estão gastando esforço para executar computações e validar transações, eles recebem a taxa de gás como uma recompensa.

Normalmente, quanto maior o preço do gás que o remetente está disposto a pagar, maior o valor que o minerador recebe da transação. Assim, provavelmente os mineradores serão mais propensos a selecionar transações que pagam mais wei por gás. De qualquer forma, os mineradores são livres para escolher quais transações querem validar ou ignorar. A fim de guiar os remetentes sobre que preço de gás definir, os mineradores têm a opção de publicitar o preço mínimo de gás pelo qual realizarão transações.

Existem taxas para armazenamento, também

**O gás não é usado apenas para pagar etapas de computação, também é usado para pagar o uso do armazenamento. ** A taxa total de armazenamento é proporcional ao menor múltiplo de 32 bytes usado.

As taxas de armazenamento têm alguns aspectos diferenciados. Por exemplo, como um armazenamento grande aumenta o estado do banco de dados de todos os nós da Ethereum, existe um incentivo para manter a quantidade de dados armazenados pequenos. Por este motivo, se uma transação tem um passo que limpa uma informação no armazenamento, a taxa para executar essa operação é renunciada, e um reembolso é dado para liberar espaço de armazenamento.

Qual é o propósito das taxas?

Um aspecto importante da forma como a Ethereum funciona é que **todas as operações executadas pela rede são realizadas simultaneamente por todos os nós completos. ** No entanto, as etapas computacionais na Máquina Virtual da Ethereum são muito caras. Portanto, contratos inteligentes da Ethereum são mais usados para tarefas simples, como executar lógica simples de negócios ou verificar assinaturas e outros objetos criptográficos, ao invés de usos mais complexos, como armazenamento de arquivos, e-mail ou aprendizagem por máquina, que podem colocar uma pressão na rede. Impor taxas impede que os usuários sobrecarreguem a rede.

Ethereum é um idioma de Turing completo. (Em suma, uma máquina de Turing é uma máquina que pode simular qualquer algoritmo de computador (para aqueles que não estão familiarizados com as máquinas de Turing, confira este link e este link). Isso permite laços e torna a Ethereum suscetível ao problema de interrupção, um problema no qual você não pode determinar se um programa será executado infinitamente. Se não houvesse taxas, um ator malicioso poderia facilmente tentar interromper a rede executando um laço infinito dentro de uma transação, sem repercussões.

Assim, as taxas protegem a rede de ataques deliberados. Você pode estar pensando: "por que temos de pagar o armazenamento?" Bem, assim como computação, o armazenamento na rede Ethereum é um custo do qual toda a rede tem que assumir o encargo.

Transação e mensagens

Notamos anteriormente que a Ethereum é uma máquina de estado baseada em transações. Em outras palavras, transações que ocorrem entre diferentes contas é o que movimenta o estado global da Ethereum de um Estado para o outro.

No sentido mais básico, uma transação é um pedaço de instrução com assinatura criptográfica que é gerado por uma conta de propriedade externa, serializado, e então enviado para o blockchain. Existem dois tipos de transações: chamadas de mensagem e criações de contratos (ou seja, transações que criam novos contratos Ethereum).

Todas as transações contêm os seguintes componentes, independentemente do seu tipo:

  • nonce: uma contagem do número de transações enviadas pelo remetente.
  • Preço de Gás: o número de Wei que o remetente está disposto a pagar por unidade de gás necessária para executar a transação.
  • Limite de gás: a quantidade máxima de gás que o remetente está disposto a pagar para executar esta transação. Este valor é definido e pago antecipadamente, antes que qualquer execução computacional seja feita.
  • para: o endereço do destinatário. Em uma transação criadora de contrato, o endereço da conta de contrato ainda não existe, portanto um valor vazio é utilizado.
  • valor: a quantidade de Wei a ser transferido do remetente para o destinatário. Em uma transação geradora de contrato, este valor serve como o saldo inicial dentro da conta de contrato recém-criada.
  • v, r, s: usado para gerar a assinatura que identifica o remetente da transação.
  • init (só existe para transações criadoras de contratos): Um fragmento de código EVM que é usado para inicializar a nova conta de contrato. init é executado apenas uma vez, e depois é descartado. Quando init é executado pela primeira vez, ele retorna o corpo do código da conta, que um é o pedaço de código que está permanentemente associado com a conta de contrato.
  • dados (campo opcional que só existe para chamadas de mensagem): os dados de entrada (ou seja, parâmetros) da chamada de mensagem. Por exemplo, se um contrato inteligente serve como serviço de registo de domínios, uma chamada para esse contrato pode esperar campos de entrada como domínio e endereço IP.

Aprendemos na seção "Contas" que transações — tanto chamadas de mensagem como transações que criam contrato — são sempre iniciadas por contas de propriedade externa e enviadas ao blockchain. Outra maneira de pensar sobre isso é que as transações são o que faz a ponte entre o mundo externo e o estado interno da Ethereum.

Mas isso não significa que os contratos não podem conversar com outros contratos. Contratos que existem no âmbito global do estado da Ethereum podem falar com outros contratos dentro desse mesmo escopo. A maneira que eles fazem isso é através de "mensagens" ou "transações internas" para outros contratos. Podemos pensar em mensagens ou transações internas como sendo semelhantes a transações, com a grande diferença de que elas NÃO são geradas por contas de propriedade externa. Em vez disso são geradas por contratos. Elas são objetos virtuais que, ao contrário das transações, não são serializados e existem apenas no ambiente de execução da Ethereum.

Quando um contrato envia uma transação interna para outro contrato, o código associado que existe na conta do contrato de destino é executado.

Uma coisa importante é que as transações internas ou mensagens não contêm um limite de gás. Isto é porque o limite de gás é determinado pelo criador externo da transação original (ou seja, alguma conta de propriedade externa). O limite de gás que a conta de propriedade externa define deve ser alto o suficiente para realizar a transação, incluindo quaisquer sub-execuções que ocorram como resultado dessa transação, tais como mensagens de contrato-contrato. Se, na cadeia de transações e mensagens, a execução de uma determinada mensagem fica sem gás, então a execução dessa mensagem será revertida, junto com quaisquer mensagens subsequentes acionadas pela execução. No entanto, a execução-mãe não precisa de regredir.

Blocos

Todas as transações são agrupadas em "blocos". Um blockchain contém uma série de tais blocos que estão encadeados juntos. Na Ethereum, um bloco consiste de:

  • o cabeçalho de bloco
  • informações sobre o conjunto de transações incluído nesse bloco
  • um conjunto de outros cabeçalhos de bloco para os ommers do bloco atual.

Ommers explicados

Que raios é um "ommer"? Um ommer é um bloco no qual o pai é igual ao avô do bloco atual. Vamos mergulhar rapidamente no que os ommers são usados e por que um bloco contém os cabeçalhos dos blocos para ommers.

Devido à forma como a Ethereum é construída, os tempos de blocos são muito menores (~15 segundos) do que os de outras cadeias de blocos, como Bitcoin (~10 minutos). Isto permite um processamento de transações mais rápido. No entanto, um dos lados negativos dos tempos mais curtos dos blocos é o fato de mais soluções de blocos concorrentes serem encontradas por mineradores. Estes blocos concorrentes também são referidos como "blocos órfãos" (ou seja, blocos minerados não incorporados na cadeia principal).

O objectivo dos ommers é ajudar os mineradores a incluir estes blocos órfãos. Os ommers que os mineradores incluem devem ser "válidos", significando dentro da sexta geração ou menor do bloco atual. Após seis crianças, blocos órfãos obsoletos não podem mais ser referenciados (porque incluindo transações antigas complicaria as coisas por um pouco).

Blocos de Ommer recebem uma recompensa menor do que um bloco completo. No entanto, ainda há algum incentivo para os mineradores incluírem estes blocos órfãos e colherem uma recompensa.

Cabeçalho do bloco

Vamos voltar aos blocos por um momento. Nós mencionamos anteriormente que cada bloco tem um "cabeçalho", mas o que é exatamente isto?

Um cabeçalho de bloco é uma parte do bloco que consiste de:

  • Hash: um hash do cabeçalho do bloco pai (isto é o que faz o bloco definir uma "cadeia")
  • ommersHash: um hash da lista de ommers do bloco atual
  • beneficiário: o endereço da conta que recebe as taxas para minerar este bloco
  • stateRoot: o hash do nó raiz da árvore de estado (lembre-se como aprendemos que o estado da árvore é armazenado no cabeçalho e torna fácil para clientes leves verificar qualquer coisa sobre o estado)
  • transaçõesRoot: o hash do nó raiz da árvore que contém todas as transações listadas neste bloco
  • recibos: o hash do nó raiz da árvore que contém os recibos de todas as transações listadas neste bloco
  • logsBloom: um filtro Bloom (estrutura de dados) que consiste em informações de log
  • Dificuldade: o nível de dificuldade deste bloco
  • número: o contador de blocos atuais (o bloco Gênesis tem um número de bloco zero; o número do bloco aumenta em 1 para cada bloco subsequente)
  • Limite de gás: o limite de gás atual por bloco
  • gás utilizados: a soma do total de gás usado pelas transações neste bloco
  • timestamp: O carimbo de tempo do começo deste bloco
  • extraData: dados extras relacionados a este bloco
  • mixHash: um hash que, quando combinado com o nonce, prova que este bloco realizou computação suficiente
  • nonce: um hash que, quando combinado com o mixHash, prova que este bloco realizou computação suficiente

Observe como cada cabeçalho de bloco contém três estruturas de árvores para:

  • estado (stateRoot)
  • transações (transactionsRoot)
  • recibos (receiptsRoot)


Estas estruturas de árvore não são mais do que as tentativas de Merkle Patricia que discutimos anteriormente. Além disso, há alguns termos da descrição acima que merecem ser esclarecidos. Vamos dar uma olhada.

Logs

A Ethereum permite logs para que seja possível rastrear várias transações e mensagens. Um contrato pode gerar um log explicitamente definindo "eventos" que quer registrar. Uma entrada de log contém:

  • o endereço da conta do loger,
  • uma série de tópicos que representam vários eventos realizados por esta transação, e
  • todos os dados associados a estes eventos.

Os logs são armazenados em um filtro de bloom, que armazena os dados de log intermináveis de uma forma eficiente.

Recibo da transação

Logs armazenados no cabeçalho vêm das informações de log contidas no recibo da transação. Assim como você recebe um recibo quando você compra algo em uma loja, a Ethereum gera um recibo para cada transação. Como você esperava, cada recibo contém certas informações sobre a transação. Este recibo inclui itens como:

  • o número de bloco
  • hash do bloco
  • hash da transação
  • gás usado pela transação atual
  • gás cumulativo usado no bloco atual depois que a transação atual foi executada
  • logs criados quando a transação atual foi executada
  • ... e assim por diante

Dificuldade do bloco

A "dificuldade" de um bloco é usada para impor a consistência no tempo que leva para validar os blocos. O bloco Gênesis tem uma dificuldade de 131.072, e uma fórmula especial é usada para calcular a dificuldade de cada bloco depois. Se um determinado bloco for validado mais rapidamente do que o bloco anterior, o protocolo Ethereum aumenta a dificuldade desse bloco.

A dificuldade do bloco afeta o nonce, que é um hash que deve ser calculado quando minerar um bloco, usando o algoritmo de prova de trabalho.

A relação entre dificuldade do bloco e nonce é representada matematicamente como:

onde Hd é a dificuldade.

A única maneira de encontrar um nonce que atenda a um limite de dificuldade é usar o algoritmo da prova de trabalho para enumerar todas as possibilidades. O tempo esperado para encontrar uma solução é proporcional à dificuldade — quanto maior a dificuldade, mais difícil se torna encontrar o nonce, e, assim, quanto mais difícil será validar o bloco, o que, por sua vez, aumenta o tempo que leva para validar um novo bloco **Então, ajustando a dificuldade de um bloco, o protocolo pode ajustar o tempo que leva para validar um bloco. **

Se, por outro lado, o tempo de validação está ficando mais lento, o protocolo diminui a dificuldade. Desta forma, o tempo de validação se auto-ajusta para manter uma taxa constante — em média, um bloco a cada 15 segundos.

Execução de transação

Nós chegamos a uma das partes mais complexas do protocolo Ethereum: a execução de uma transação. O que acontece com a transição do estado da Ethereum para incluir sua transação? O que acontece com a transição do estado da Ethereum para incluir sua transação?

Primeiro, todas as transações devem cumprir um conjunto inicial de requisitos para serem executadas. Estas incluem:

  • A transação deve estar formatada corretamente em RLP. “RLP” significa “Prefixo de Comprimento recursivo” e é um formato de dados usado para codificar matrizes aninhadas de dados binários. RLP é o formato Ethereum usado para serializar objetos.
  • Assinatura de transação válida.
  • Nonce de transação válida. Lembre-se de que o nonce de uma conta é a contagem de transações enviadas por essa conta. Para ser válido, um nonce de transação deve ser igual ao nonce da conta remetente.
  • O limite de gás da transação deve ser igual ou maior que o gás intrínseco usado pela transação. O gás intrínseco inclui:
  1. um custo predefinido de 21.000 gases para executar a transação
  2. uma taxa de gás para dados enviados com a transação (4 gás para cada byte de dados ou código que seja igual a zero, e 68 gases para cada byte não-zero de dados ou código)
  3. se a transação for uma transação geradora de contrato, mais 32.000 gás
  • O saldo da conta do remetente deve ter o Ether suficiente para cobrir os custos de gás "antecipados" que o remetente deve pagar. O cálculo para o custo do gás inicial é simples: em primeiro lugar, o limite de gás da transação é multiplicado pelo preço de gás da transação para determinar o custo máximo de gás. Depois, este custo máximo é adicionado ao valor total que foi transferido do remetente para o destinatário.

Se a transação cumpre todos os requisitos acima para validade, então nós movemos para o próximo passo.

Primeiro, nós deduzimos o custo de execução do saldo do remetente e aumentamos o nonce da conta do remetente em 1 para dar conta da transação atual. Neste ponto, podemos calcular o gás restante como o limite de gás total para a transação menos o gás intrínseco.

Em seguida, a transação começa a ser executada. Durante a execução de uma transação, a Ethereum mantém o controle do "subestado". Este subestado é uma maneira de registrar informações acumuladas durante a transação que serão necessárias imediatamente após a conclusão da transação. Concretamente, ele contém:

  • Conjunto de autodestruição: um conjunto de contas (se houver) que será descartado após a conclusão da transação.
  • Série de logs: pontos de verificação arquivados e indexáveis da execução de código da máquina virtual.
  • Valor do reembolso: o valor a ser reembolsado para a conta do remetente após a transação. Lembra-se de como mencionamos que o armazenamento na Ethereum custa dinheiro, e que um remetente é reembolsado por limpar o armazenamento? A Ethereum acompanha isto usando um contador de reembolso. O contador de reembolso começa em zero e incrementa toda vez que o contrato exclui algo no armazenamento.

Em seguida, os vários cálculos exigidos pela transação são processados.

Uma vez que todos os passos necessários para a transação foram processados e assumindo que não há nenhum estado inválido o estado é finalizado determinando a quantidade de gás não utilizada a ser devolvido ao remetente. Além do gás não utilizado, o remetente também reembolsa algum subsídio do "saldo de reembolso" que descrevemos acima.

Uma vez que o remetente é reembolsado:

  • o Ether pelo gás é dado ao minerador
  • o gás usado pela transação é adicionado ao contador de gás do bloco (que acompanha o total de gás usado por todas as transações no bloco, e é útil ao validar um bloco)
  • todas as contas no conjunto de autodestruição (se houver) serão excluídas

Finalmente, temos o novo estado e um conjunto de logs criados pela transação.

Agora que cobrimos o básico da execução de transações, vamos ver algumas das diferenças entre transações geradoras de contrato e chamadas de mensagem.

Criação de contrato

Lembre-se que, na Ethereum, existem dois tipos de contas: contas de contrato e contas de propriedade externa. Quando dizemos que uma transação é "criadora de contrato", queremos dizer que o propósito da transação é criar uma nova conta de contrato.

Para criar uma nova conta de contrato, primeiro declaramos o endereço da nova conta usando uma fórmula especial. Então inicializamos a nova conta:

  • Definindo o nonce para zero
  • Se o remetente enviou alguma quantidade de Ether como valor com a transação, definindo o saldo da conta para esse valor
  • Dedução do valor adicionado a esta nova conta a partir do saldo do remetente
  • Definindo o armazenamento como vazio
  • Definindo o codeHash do contrato como o hash de uma string vazia


Uma vez que inicializamos a conta, podemos realmente criar a conta usando o **código init ** enviado com a transação (consulte a seção "Transação e mensagens" para um atualizador no código init). O que acontece durante a execução deste código de entrada é variado. Dependendo do construtor do contrato, ele pode atualizar o armazenamento da conta, criar outras contas de contrato, fazer outras chamadas de mensagem, etc.

Como o código para inicializar um contrato é executado, ele usa gás. A transação não tem permissão para usar mais gás do que o gás restante. Se o fizer, a execução atingirá a exceção gás insuficiente (out-of-gas ou OOG) e sairá. Se a transação sai devido a uma exceção de gás insuficiente, então o estado é revertido para o ponto imediatamente antes da transação. O remetente não é reembolsado com o gás que foi gasto antes de se esgotar.

Boo hoo.

No entanto, se o remetente enviou algum valor Ether com a transação, o valor do Ether será reembolsado mesmo se a criação do contrato falhar. Ufa!

Se o código de inicialização for executado com sucesso, um custo final de criação de contrato é pago. Este é um custo de armazenamento e é proporcional ao tamanho do código do contrato criado (novamente, sem almoço grátis!). Se não houver gás suficiente restante para pagar esse custo final, então a transação declarará novamente uma exceção gás insuficiente e aborta.

Se tudo correr bem e sem exceções, qualquer gás restante não utilizado é devolvido ao remetente original da transação, e o estado alterado agora é permitido ser salvo! Aeee!

Chamadas de mensagem

A execução de uma chamada de mensagem é semelhante à de um contrato, com algumas diferenças.

A execução de chamada de mensagem não inclui nenhum código de entrada, uma vez que nenhuma nova conta está sendo criada. No entanto, ela pode conter dados de entrada, se esses dados foram fornecidos pelo remetente da transação.

Uma vez executado, as chamadas de mensagem também têm um componente extra contendo os dados de saída, que é usado se uma execução subsequente precisar destes dados. Da mesma maneira que para a criação de contratos, se uma chamada de mensagem falhar porque está sem gás ou porque a transação é inválida (por exemplo: stack overflow, destino de salto inválido ou instrução inválida), nenhum do gás usado é reembolsado para o remetente original. Em vez disso, todo o restante gás não utilizado é consumido, e o estado é reposto ao ponto imediatamente antes da transferência de saldo.

Até a atualização mais recente da Ethereum, não havia maneira de parar ou reverter a execução de uma transação sem que o sistema consumisse todo o gás que você forneceu. Por exemplo, digamos que você criou um contrato que lançou um erro quando um chamador não estava autorizado a realizar algumas transações. Nas versões anteriores da Ethereum, o restante gás continuaria a ser consumido e nenhum gás seria devolvido ao remetente. Mas a atualização Byzantium inclui um novo código de "reverter" que permite que um contrato interrompa a execução e reverta as mudanças de estado sem consumir o gás restante e com a capacidade de retornar um motivo para a transação falhada. Se uma transação sair devido a uma reversão, então o gás não utilizado é devolvido ao remetente.

Modelo de execução

Até agora, aprendemos sobre a série de passos que têm que acontecer para que uma transação seja executada do início ao fim. Agora, vamos ver como a transação é realmente executada dentro da VM.

A parte do protocolo que realmente lida com o processamento das transações é a própria máquina virtual da Ethereum, conhecida como a Máquina Virtual Ethereum (EVM).  

A EVM é uma máquina virtual Turing complete, como foi abordado anteriormente. A única limitação que a EVM tem, que uma típica máquina completa de Turing não tem, é o fato da EVM estar intrinsecamente vinculada ao gás. Assim, a quantidade total de computação que pode ser feita é intrinsecamente limitada pela quantidade de gás fornecido.

Source: CMU

Fonte: CMU Além disso, o EVM tem uma arquitetura baseada em pilhas. Uma máquina de pilha é um computador que usa uma pilha final para manter valores temporários.

O tamanho de cada item de pilha na EVM é de 256 bits, e a pilha tem um tamanho máximo de 1024.

A EVM tem memória, onde os itens são armazenados como arrays de bytes abordados por palavras.

A memória é volátil, o que significa que não é permanente. A EVM também tem armazenamento. Ao contrário da memória, o armazenamento não é volátil e é mantido como parte do estado do sistema. A EVM armazena o código do programa separadamente, em uma ROM virtual que só pode ser acessado através de instruções especiais. Desta forma, a EVM difere do típico von Neumann arquiteture, em que o código do programa é armazenado na memória ou no armazenamento.

A EVM também tem o seu próprio idioma: “EVM bytecode". Quando um programador como você ou eu escreve contratos inteligentes que operam na Ethereum, normalmente escrevemos código em uma língua de nível mais alto como o Solidity. Podemos então compilar isso em EVM bytecode que a EVM pode entender. Tudo bem, agora vamos a execução. Antes de executar uma determinada computação, o processador certifica-se de que as seguintes informações estejam disponíveis e válidas:

  • Estado do sistema
  • Gás restante para computação
  • Endereço da conta que possui o código que está executando
  • Endereço do remetente da transação que originou esta execução
  • Endereço da conta que causou a execução do código (pode ser diferente do remetente original)
  • Preço gás da transação que originou esta execução
  • Dados informados para essa execução
  • Valor (em Wei) passado para esta conta como parte da execução atual
  • Código de máquina a ser executado
  • Cabeçalho de bloco do bloco atual
  • Profundidade da presente chamada de mensagem ou pilha de criação de contrato


No início da execução, memória e pilha estão vazias e o contador do programa é zero.

PC: 0 STACK: [] MEM: [], STORAGE: {}

A EVM executa a transação recursivamente, computando o estado do sistema e o estado da máquina para cada loop. O estado do sistema é simplesmente o estado global da Ethereum. O estado da máquina é composto de:

  • gás disponível
  • contador do programa
  • conteúdo de memória
  • número ativo de palavras na memória
  • pilha de conteúdos.

Itens de pilha são adicionados ou removidos da parte mais esquerda da série. Em cada ciclo, a quantidade de gás apropriada é reduzida do gás restante e o contador do programa aumenta. No final de cada loop há três possibilidades:

  1. A máquina atinge um estado excepcional (por exemplo, gás insuficiente, instruções inválidas, itens de pilha insuficientes e itens de pilha excederiam acima de 1024, destino JUMP/JUMPI inválido, etc.) e então deve ser interrompido, com quaisquer alterações descartadas
  2. A sequência continua processando no próximo loop
  3. A máquina atinge uma parada controlada (o fim do processo de execução)

Assumindo que a execução não atinge um estado excepcional e atinge uma parada "controlada" ou normal, a máquina gera o estado resultante, o gás restante após esta execução, o subestado acumulado e a saída resultante.

Ufa. Nós passamos por uma das partes mais complexas da Ethereum. Mesmo que você não tenha entendido completamente esta parte, tudo bem. Você não precisa realmente entender os detalhes da execução, a menos que esteja trabalhando em um nível muito profundo.

Como um bloco é finalizado

Finalmente, vamos ver como um bloco de muitas transações é finalizado. Quando dizemos "finalizado", isso pode significar duas coisas diferentes, dependendo se o bloco é novo ou existente. Se for um novo bloco, estamos nos referindo ao processo necessário para minerar este bloco. Se é um bloco existente, então estamos falando sobre o processo de validação do bloco. Em ambos os casos, há quatro requisitos para um bloco ser "finalizado":

1) Validar (ou, se minerar, determinar) os ommers Cada bloco de ommer dentro do cabeçalho do bloco deve ser um cabeçalho válido e estar dentro da sexta geração do bloco atual.

2) Validar (ou, se minerar, determinar) as transações O número de gás utilizado no bloco deve ser igual ao gás cumulativo usado pelas transações listadas no bloco. (Lembre-se de que, ao executar uma transação, nós acompanhamos o contador de gás do bloco, que mantém o controle do gás total usado por todas as transações no bloco).

3) Aplicar recompensas (somente se estiver minerando) O endereço do beneficiário é remunerado com 2 Ether por minerar o bloco. (Proposta de Ethereum EIP-649, reduziu essa recompensa de 5 ETH para 2 ETH). Além disso, para cada ommer, o beneficiário do bloco atual é premiado com uma recompensa adicional de 1/32 da atual recompensa do bloco. Por último, o beneficiário do bloco ommer também recebe uma certa quantidade (há uma fórmula especial para como isto é calculado).

4) Verificar o estado e o nonce (ou, se minerar, computar um válido) Certifique-se de que todas as transações e alterações de estado resultantes sejam aplicadas, e, em seguida, defina o novo bloco como o estado depois que a recompensa por bloco foi aplicada ao estado resultante da transação final. A verificação ocorre verificando este estado final na tentativa de estado armazenada no cabeçalho.

Minerando com prova de trabalho

A seção “Blocos” abordou brevemente o conceito de dificuldade do bloco. O algoritmo que dá significado para a dificuldade do bloco é chamado Prova de Trabalho (PoW). O algoritmo de prova de trabalho do Ethereum é chamado de "Ethash" (anteriormente conhecido como Dagger-Hashimoto). O algoritmo é formalmente definido como:

onde m é o mixHash, n é o nonce, Hn *é o cabeçalho do novo bloco (excluindo os componentes nonce e o mixHash, que devem ser computados), Hn é o nonce do cabeçalho do bloco, e d é o DAG, que é um conjunto de dados grande. Na seção "Blocos", falávamos sobre os vários itens que existem em um cabeçalho de bloco. Dois desses componentes foram chamados de mixHash e de nonce. Como você deve lembrar:


  • mixHash é **um hash que, quando combinado com o nonce, prova que este bloco realizou uma computação suficiente
  • nonce é um hash que, quando combinado com o mixHash, prova que este bloco realizou computação suficiente

A função PoW é usada para avaliar estes dois itens.

Como exatamente são calculados os mixHash e nonce usando a função PoW é algo complexo, e algo que podemos aprofundar em uma publicação separada. Mas em um nível alto, funciona dessa maneira:

uma "semente" é calculada para cada bloco. Esta semente é diferente para cada "época", onde cada período tem 30.000 blocos de comprimento. Para a primeira época, a semente é o hash de uma série de 32 bytes de zeros. Para cada época subsequente, é o hash do hash da semente anterior. Usando esta semente, um nó pode calcular um "cache" pseudo-aleatório.

Este cache é incrivelmente útil porque permite o conceito de "nós leves", que discutimos anteriormente nesta publicação. O propósito dos nós leves é permitir a certos nós a capacidade de verificar eficientemente uma transação sem o fardo de armazenar todo o conjunto de dados do blockchain. Um nó leve pode verificar a validade de uma transação baseada apenas neste cache, porque o cache pode regenerar o bloqueio específico que precisa para verificar.

Usando o cache, um nó pode gerar o conjunto de dados DAG onde cada item no conjunto de dados depende de um pequeno número de itens pseudo-aleatoriamente selecionados do cache. Para ser um minerador, você deve gerar este conjunto de dados completo; todos os clientes e mineradores completos armazenam este conjunto de dados, e o conjunto de dados cresce linearmente com o tempo. Os mineradores podem então pegar fatias aleatórias do conjunto de dados e colocá-los através de uma função matemática para colocá-los juntos em um "Hash".

Um minerador irá gerar repetidamente mixHash até que a saída esteja abaixo do alvo desejado nonce. Quando a saída atende a este requisito, este nonce é considerado válido e o bloco pode ser adicionado à cadeia.

Minerando como mecanismo de segurança

Em geral, o propósito do PoW é demonstrar, de uma forma criptograficamente segura, que uma determinada quantidade de computação foi gasta para gerar alguma saída (por exemplo o nonce). Isto é porque não há melhor maneira de encontrar um nonce que esteja abaixo do limite requerido a não ser enumerar todas as possibilidades. Os resultados da aplicação repetida da função hash têm uma distribuição uniforme para que possamos ter a certeza disso. Em média, o tempo necessário para encontrar tal nonce depende do limite de dificuldade. Quanto maior a dificuldade, mais tempo demora para chegar-se ao nonce. Desta forma, o algoritmo PoW dá significado ao conceito de dificuldade, que é usado para impor a segurança do blockchain.

O que queremos dizer com segurança do blockchain? É simples: queremos criar um blockchain em que TODOS confiam. Como discutimos anteriormente nesta publicação, se mais de uma cadeia existisse, os usuários perderiam a confiança, porque eles seriam incapazes de determinar razoavelmente qual cadeia era a cadeia de blocos "válida". Para que um grupo de usuários aceite o estado subjacente que é armazenado em um blockchain, precisamos de um único blockchain canônico em que um grupo de pessoas acredite.

Isso é exatamente o que o algoritmo PoW faz: garante que um blockchain em particular permanecerá canônico no futuro, tornando incrivelmente difícil para um atacante criar novos blocos que substituam uma determinada parte da história (por exemplo apagando transações ou criando transações falsas) ou mantendo um fork. Para ter seu bloco validado primeiro, um atacante precisaria resolver o nonce de forma consistentemente mais rápido do que qualquer outra pessoa na rede, de tal maneira que a rede acreditasse que sua cadeia é a cadeia de blocos mais pesada (baseada nos princípios do protocolo GHOST que nos referimos anteriormente). Isso seria impossível a menos que o atacante tivesse mais de metade do poder de mineração da rede, um cenário conhecido como ataque maioritário de 51%.

Mineração como um mecanismo de distribuição de riqueza

Além de fornecer um blockchain seguro, o PoW também é uma maneira de distribuir riqueza àqueles que gastaram sua computação para fornecer esta segurança. Lembre que um minerador recebe uma recompensa por minerar um bloco, incluindo:

  • uma recompensa de bloco estático de 2 ether para o bloco "vencedor"
  • o custo do gás gasto dentro do bloco pelas transações incluídas no bloco
  • uma recompensa extra para a inclusão de ommers como parte do bloco

A fim de garantir que a utilização do mecanismo de consenso PoW para a segurança e a distribuição da riqueza é sustentável a longo prazo, a Ethereum se esforça para manter estas duas propriedades:

  • Torná-lo acessível ao maior número possível de pessoas. Por outras palavras, as pessoas não devem precisar de hardware especializado ou incomum para executar o algoritmo. O objetivo é tornar o modelo de distribuição de riqueza tão aberto quanto possível, para que qualquer pessoa possa fornecer qualquer quantidade de poder de computação em troca do Ether.
  • Reduz a possibilidade de qualquer nó (ou conjunto pequeno) fazer uma quantidade desproporcionada de lucro. Qualquer nó que pode fazer uma quantidade desproporcionada de lucro significa que o nó tem grande influência na determinação do blockchain canônico. Isto é problemático porque reduz a segurança da rede.

Na rede Bitcoin um problema que se coloca em relação às duas propriedades acima é que o algoritmo PoW é uma função hash SHA256. A fraqueza deste tipo de função é que ela pode ser resolvida de forma muito mais eficiente usando hardware especializado, também conhecido como ASICs.

A fim de mitigar este problema, a Ethereum escolheu tornar seu algoritmo de PoW (Ethhash) sequencialmente difícil em memória. Isto significa que o algoritmo que é projetado para calcular o nonce requer muita memória e largura de banda. Os requisitos de memória grandes tornam difícil para um computador usar sua memória em paralelo para descobrir vários nonces simultâneos e os requisitos de largura de banda alta dificultam que até um computador super-rápido descubra múltiplos paralelamente. Isto reduz o risco de centralização e cria condições de concorrência mais equitativas para os nós que estão fazendo a verificação.

Uma coisa a notar é que a Ethereum está a passar de um mecanismo de consenso PoW para algo chamado "prova de depósito". Esta é uma questão muito própria que esperamos poder explorar numa futura publicação.

Conclusão

... ufa! Você conseguiu até o fim. Eu espero que bem.

Há muito para digerir nesta publicação, eu sei. Se você tiver que ler várias vezes para entender completamente, tudo bem. Li pessoalmente as definições técnicas da Ethereum, o whitepaper e várias partes do código-fonte muitas vezes antes de compreender o que se passava.

Ainda assim, espero que tenha achado útil esta visão geral. Se você encontrar algum erro ou engano, por favor escreva uma nota privada ou publique diretamente nos comentários. Eu olho para todos eles, prometo ;)

E lembre-se, sou humano (sim, é verdade) e eu cometo erros. Levei tempo para escrever esta publicação gratuitamente em benefício da comunidade. Por isso, peço-vos que sejam construtivos nos seus comentários, sem usurpações desnecessárias.

[1] https://github.com/ethereum/yellowpaper


Story tags:

Why am I sharing my travel stories?

Founder & CEO of TruStory. I have a passion for understanding things at a fundamental level and sharing it as clearly as possible.

Preethi Kasireddy
LEARN MORE