Blockchain

Python - criando uma blockchain

By lingy | Lingy | 26 Nov 2021


Me siga também na Odysee.


Provavelmente você já deve ter se deparado com o termo blockchain como sendo "a tecnologia por trás das criptomoedas". Mas o que é uma blockchain? E como em construo uma?

O que é uma blockchain?

Uma blockchain é, em resumo, uma lista encadeada de blocos, onde cada bloco adicionado atesta a veracidade do bloco imediatamente anterior a ele, garantindo a legitimidade dos dados. Apesar de ser uma explicação simplista, em resumo, é esse o funcionamento de uma blockchain.

Como o conceito é um pouco confuso, acredito que seja melhor ver como uma blockchain funciona na prática, ou seja, criando uma do zero.

Iniciando o desenvolvimento

Primeiro vamos adicionar as seguintes dependências no arquivo:

import re
import json
from datetime import datetime as dt
from hashlib import sha256

Cada uma dessas dependências vão ser explicadas no decorrer do código, mas adicione-as porque serão necessárias. Agora, vamos ao código em si.

O bloco

Um bloco é, em resumo, um objeto que possui:

  • Um ID
  • Um hash de identificação
  • O hash do bloco anterior (para garantir a integridade)
  • Conteúdo (dados do bloco)
  • Data e hora da transação

Claro que um bloco pode ter mais ou menos atributos, mas essa é a estrutura mais comum de uma blockchain que temos. Os blocos, como eles são encadeados, acabam se comportando como uma "corrente de blocos", onde você consegue puxar os dados de todos os blocos anteriores a partir do último registrado.

Agora, vamos criar uma classe Block com esses dados:

class Block:
    def __init__(self, index, previous_hash, data):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()

Percebeu algo de estranho? Exatamente, não estamos gerando o hash do bloco atual:

class Block:
    # código do construtor
    
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()

Essa função vai converter os dados do bloco em um JSON, concatena com todos os outros dados e cria um hash criptográfico sha256 a partir dessa string. Esse é o hash do bloco, que valida a sua integridade. O sha256 é apenas um dos algoritmos, mas você pode usar diversos outros, como o md5 (embora eu não recomende) ou outros algoritmos de hash criptográfico. Você atribuirá a propriedade à classe como self.hash = self.generate_hash().

Então temos isso como nosso bloco inicial:

class Block:
    def __init__(self, index, previous_hash, data):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.hash = self.generate_hash()
        
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()

A blockchain

Uma blockchain funciona como se fosse uma lista encadeada de blocos, onde o último bloco anexado é relacionado ao penúltimo pelo hash do mesmo. Isso é o que vai garantir a validação dele e garantir que cada bloco seja legítimo. A estrutura inicial vai ficar assim:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
        self.index = 1

Claro que o usuário não vai ficar colocando manualmente o ID e o hash dos blocos, ele apenas vai te entregar um JSON contendo os dados do bloco e quer que ele esteja lá da forma que deveria. Então vamos adicionar o bloco correspondente:

class Blockchain:
    # código do construtor
    
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data)

        self.index += 1
        self.blocks.append(block)

Podemos testar se tudo está funcionando corretamente da seguinte forma:

chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.blocks[1].data)

Esse código retornará um dicionário com a seguinte estrutura:

{
    'content': {
        'transaction_01': 1, 'transaction_02': 2
    }
}

Podemos também ver o hash do bloco se quisermos:

print(chain.blocks[1].hash)

Isso retornará uma string aleatória, que nem a string abaixo:

2d76eeac710e20749fb1d594b9caf552ec93733d1205593f4c27e1488509e7f4

Validando blocos

Claro que, com o código que temos, qualquer um pode alterar algum dado da nossa chain e seria difícil de validar. Observe a minha alteração:

chain.blocks[1].data = {'content': {'transaction_01': 3000}}

Isso não parece certo, não é mesmo? Afinal, se eu fiz uma transação, eu quero que ela seja única, e que, caso alguém altere, toda a corrente se torne inválida. Então vamos adicionar uma função de validação à blockchain:

class Blockchain:
    # demais funções
    
    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

Essa função executa as seguintes operações:

  • Percorre a chain
  • Verifica se os valores estão na ordem correta
  • Verifica se o hash do bloco corresponde ao hash de seu sucessor
  • Verifica se o hash é válido

Podemos agora testar nossa função:

chain = Blockchain()
chain.add_new_block({'content': {'transaction_01': 1, 'transaction_02': 2}})
print(chain.is_valid()) # True
chain.blocks[1].data = {'content': {'transaction_01': 3000}}
print(chain.is_valid()) # False

Se você seguiu todos os passos até aqui, provavelmente tem algo como isso:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'})]
        self.index = 1
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data)

        self.index += 1
        self.blocks.append(block)

    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

Mineração

Agora conseguimos mais um problema: eu posso simplesmente fazer um loop e ficar adicionando lixo à chain. Claro que não queremos isso, até porque se ficarmos adicionando lixo à nossa blockchain, ela se tornará gigante e inútil. Observe abaixo:

chain = Blockchain()
i = 0
while(True):
    i += 1
    chain.add_new_block({'content': i})

Isso vai simplesmente lotar nossa blockchain com blocos inúteis e sem sentido. Precisamos adicionar um certo "trabalho", uma dificuldade extra, para termos certeza que a pessoa que está adicionando aquele bloco REALMENTE quer adicionar ele. Uma forma de fazermos isso é a mineração.

Os hashes em sha256 são gerados a partir de um algoritmo, por isso não são randômicos de verdade, mas é preciso um certo poder computacional para obter um hash válido para um determinado bloco. A mineração é exatamente isso. Durante o processo de mineração, você tem uma dificuldade que varia de 0 até a largura do hash do algoritmo (no nosso caso, 64). Se a dificuldade for 1, por exemplo, o primeiro caractere do hash deve ser 0, se a dificuldade for 2 o primeiro e o segundo caractere devem ser 0, se for 3 o primeiro, o segundo e o terceiro caractere devem ser zero, e assim por diante. Para obter esses hashes, o computador ficará criando dezenas, centenas, milhares ou até milhões de hashes, até que encontre algum que corresponda à dificuldade selecionada. Então, vamos começar criando uma função mine para o nosso bloco:

class Block:
    def __init__(self, index, previous_hash, data, difficulty):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.difficulty = difficulty
        
        self.mine()
    
    def mine(self):
        self.hash = self.generate_hash()

        while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
            self.nonce += 1
            self.hash = self.generate_hash()

E adicionaremos a dificuldade à nossa blockchain:

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
        self.index = 1
        self.difficulty
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data, self.difficulty)

        self.index += 1
        self.blocks.append(block)

Agora levará um certo tempo para que cada bloco seja minerado (um tempo aleatório, claro, mas ainda será algum tempo), o que evitará que preencham sua chain com coisas inúteis.

O código completo, então, pode ser resumido em:

import re
import json
from datetime import datetime as dt
from hashlib import sha256

class Block:
    def __init__(self, index, previous_hash, data, difficulty):
        self.index = index
        self.previous_hash = previous_hash
        self.data = data
        self.timestamp = dt.now().timestamp()
        self.difficulty = difficulty
        
        self.mine()
    
    def mine(self):
        self.hash = self.generate_hash()

        while re.match("^[0]+$", self.hash[:self.difficulty]) == None:
            self.nonce += 1
            self.hash = self.generate_hash()
        
    def generate_hash(self):
        return sha256(str( \
                          str(self.index) + \
                          str(self.previous_hash) + \
                          json.dumps(self.data) + \
                          str(self.timestamp)).encode('utf-8') \
                     ).hexdigest()
        

class Blockchain:
    def __init__(self):
        self.blocks = [Block(0, None, {'content': 'Bloco Inicial'}), 1]
        self.index = 1
        self.difficulty
    
    def get_last_block(self):
        return self.blocks[-1]
    
    def add_new_block(self, data):
        previous_hash = self.get_last_block().hash

        block = Block(self.index, previous_hash, data, self.difficulty)

        self.index += 1
        self.blocks.append(block)
        
    def is_valid(self):
        for i in range(len(self.blocks)):
            current_block = self.blocks[i + 1]
            previous_block = self.blocks[i]

            if current_block.hash != current_block.generate_hash():
                return False

            if current_block.index != (previous_block.index + 1):
                return False

            if current_block.previous_hash != previous_block.hash:
                return False

            return True

E pronto, você tem a sua primeira blockchain desenvolvida em Python.

How do you rate this article?

2



Lingy
Lingy

My personal blog about technology

Publish0x

Send a $0.01 microtip in crypto to the author, and earn yourself as you read!

20% to author / 80% to me.
We pay the tips from our rewards pool.