Crea tu blockchain en un solo post con python

Si ya sabemos los conceptos básicos de blockchain y las motivaciones detrás de la blockchain, cómo se agregan las transacciones a los bloques y cómo se agregan los bloques a los bloques anteriores para formar una blockchain, el siguiente paso lógico sería construir una blockchain a mano. Al implementar tu propia blockchain, tendrás una visión más detallada de cómo conceptos como transacciones, minería y prueba de trabajo para lograr consenso. Obviamente, implementar una blockchain completa no es para los débiles de corazón, y eso definitivamente no es el objetivo de este post. Lo que intentaremos hacer es implementar una cadena de bloques conceptual usando Python y usarla para ilustrar conceptos claves.

nuestra implementacion de blockchain

Para este post, construiremos una cadena de bloques muy simple como se muestra en la imagen:

Nuestra Blockchain conceptual

Para simplificar las cosas, cada bloque de nuestra cadena de bloques contendrá lo siguientes componentes:

  • timestamp: Que será el tiempo en el que el bloque fué añadido a la blockchain
  • index: Indicando el numero de bloque
  • hash del bloque previo: El resultado del hash del bloque anterior
  • nonce: Un numero usado por el proceso de mineria
  • Transaccion(es): Cada bloque contendrá un número variable de transacciones.

Por simplicidad, no nos vamos a preocupar por representar las transacciones en un árbol Merkle, ni vamos a separar un bloque en encabezado y contenido de bloque.

El nivel de dificultad de la red también se fijará en cuatro ceros, es decir, para obtener el nonce, el resultado del hash del bloque debe comenzar con cuatro ceros. Esto permitirá la creacion de bloques más rápidamente.

obteniendo el nonce

Para nuestra implementación de blockchain, el nonce se combiná con el índice del bloque, el hash del bloque anterior y todas las transacciones y comprobando si el hash resultante coincide con el nivel de dificultad de la red:

Como el nonce es derivado

Una vez que se encuentra el nonce, el bloque se agregará al último bloque de la
blockchain, con el timestamp agregado:

Una vez que el bloque es minado, será añadido a la blockchain con el timestamp añadido al bloque

instalando flask

Nuestra blockchain la ejecutaremos como una API REST, para que se pueda interactuar a través de llamadas REST. Para ello usaremos el microframework Flask. Para instalar Flask,
escriba los siguientes comandos en la Terminal:

$ pip install flask
$ pip install requests

El comando anterior instala el microframework de Flask.

Flask es un framework web que hace que la creación de aplicaciones web sea fácil y rápida.

importando las librerias y modulos que necesitamos

Para comenzar, creemos un archivo de texto llamado blockchain.py. En la parte superior de este archivo, vamos a importar todas las bibliotecas y módulos necesarios:

import sys
import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
import requests
from urllib.parse import urlparse

declarando la clase en python

Para representar la blockchain, declaremos una clase llamada blockchain, con lo siguientes dos métodos iniciales:

class Blockchain(object):
 difficulty_target = "0000"
 def hash_block(self, block):
   block_encoded = json.dumps(block,
   sort_keys=True).encode()
   return hashlib.sha256(block_encoded).hexdigest()
 def __init__(self):
 # stores all the blocks in the entire blockchain
    self.chain = []
 # temporarily stores the transactions for the
 # current block
 self.current_transactions = []
# create the genesis block with a specific fixed hash
 # of previous block genesis block starts with index 0
     genesis_hash = self.hash_block("genesis_block")
     self.append_block(
     hash_of_previous_block = genesis_hash,
     nonce = self.proof_of_work(0, genesis_hash, [])
 )

Lo anterior crea una clase llamada blockchain con dos métodos:

  • El metodo hash_block() codifica un bloque en un array de bytes y luego lo hashea; debes asegurarte de que el diccionario esté ordenado, si no, encontraras inconsistencias mas tarde.
  • La función __init__() es el constructor de la clase. Tu aquí almacenas toda la cadena de bloques como una lista. Ya que cada blockchain tiene un bloque génesis, es necesario inicializar el bloque génesis con el hash del bloque anterior, y en este ejemplo, simplemente usamos un cadena llamada “genesis_block” para obtener su hash. Una vez que el hash del bloque anterior se encuentra, necesitamos encontrar el nonce para el bloque usando el método llamado prrof_of_work() (que definiremos en despues).

El método proof_of_work () (detallado a continuación) devolverá un nonce que resultará en un hash que coincide con el objetivo de dificultad cuando el contenido del bloque actual es hasheado. Por simplicidad, estamos fijando el valor de dificultad_target en un resultado hash que comienza con cuatro ceros (“0000”).

El código fuente de nuestra cadena de bloques se muestra al final de este post, en un enlace de github

encontrando el nonce

Ahora definimos el método proof_of_work () para encontrar el nonce para el bloque:

# use PoW to find the nonce for the current block
 
def proof_of_work(self, index, hash_of_previous_block,
 transactions):
 # try with nonce = 0
    nonce = 0
 # try hashing the nonce together with the hash of the
 # previous block until it is valid
    while self.valid_proof(index, hash_of_previous_block,
       transactions, nonce) is False:
       nonce += 1
    return nonce

La función proof_of_work () primero comienza con cero para el nonce y verifica si el nonce junto con el contenido del bloque produce un hash que coincide con la dificultad objetivo. De lo contrario, incrementa el nonce en uno y luego vuelve a intentarlo hasta que encuentre el valor correcto. Mientras tanto, el siguiente método, valid_proof (), codifica el contenido de un bloque y comprueba si
el hash del bloque cumple con el objetivo de dificultad:

def valid_proof(self, index, hash_of_previous_block, transactions, nonce):
 # create a string containing the hash of the previous
 # block and the block content, including the nonce
     content =
 f'{index}{hash_of_previous_block}{transactions}{nonce}'.encode()
 # hash using sha256
     content_hash = hashlib.sha256(content).hexdigest()
 # check if the hash meets the difficulty target
     return content_hash[:len(self.difficulty_target)] ==
        self.difficulty_target

añadiendo un bloque a la blockchain

Una vez que se ha encontrado el nonce para un bloque, ahora puede escribir el método para agregar el bloque a la cadena de bloques existente. Esta es la función del método append_block ():

# creates a new block and adds it to the blockchain
 def append_block(self, nonce, hash_of_previous_block):
    block = {
      'index': len(self.chain),
      'timestamp': time(),
      'transactions': self.current_transactions,
      'nonce': nonce,
      'hash_of_previous_block': hash_of_previous_block
 }
 # reset the current list of transactions
    self.current_transactions = []
 # add the new block to the blockchain
    self.chain.append(block)
    return block

Cuando el bloque se agrega a la cadena de bloques, lel timestampo actual también se agrega al bloque:

añadiendo transacciones

El siguiente método que agregaremos a la clase Blockchain es el método add_transaction ():

def add_transaction(self, sender, recipient, amount):
     self.current_transactions.append({
     'amount': amount,
     'recipient': recipient,
     'sender': sender,
 })
     return self.last_block['index'] + 1

Este método agrega una nueva transacción a la lista actual de transacciones. Entonces se pone el índice del último bloque de la cadena de bloques y le agrega uno. Este nuevo índice será el bloque al que se agregará la transacción actual.
Para obtener el último bloque en la cadena de bloques, definimos una propiedad llamada last_block en la clase Blockchain:

@property
 def last_block(self):
 # returns the last block in the blockchain
 return self.chain[-1]

exponiendo la clase blockchain como una api rest

Nuestra clase Blockchain ahora está completa, por lo que ahora la expongamos como una API REST usando Flask. Agregue las siguientes declaraciones al final del archivo blockchain.py:

app = Flask(__name__)
# generate a globally unique address for this node
   node_identifier = str(uuid4()).replace('-', ")
# instantiate the Blockchain
   blockchain = Blockchain()

obteniendo una blockchain entera

Para la API REST, queremos crear una ruta para que los usuarios obtengan la cadena de bloques actual, por lo que agregamos las siguientes declaraciones al final de blockchain.py:

# return the entire blockchain
@app.route('/blockchain', methods=['GET'])
def full_chain():
 response = {
 'chain': blockchain.chain,
 'length': len(blockchain.chain),
 }
 return jsonify(response), 200

creando minado

También necesitamos crear una ruta para permitir que los mineros extraigan un bloque para poder agregarlo a la cadena de bloques:

@app.route('/mine', methods=['GET'])
def mine_block():
 blockchain.add_transaction(
 sender="0",
 recipient=node_identifier,
 amount=1,
 )
 # obtain the hash of last block in the blockchain
 last_block_hash =
 blockchain.hash_block(blockchain.last_block)
 # using PoW, get the nonce for the new block to be added
 # to the blockchain
 index = len(blockchain.chain)
 nonce = blockchain.proof_of_work(index, last_block_hash,
 blockchain.current_transactions)
 # add the new block to the blockchain using the last block
 # hash and the current nonce
 block = blockchain.append_block(nonce, last_block_hash)
 response = {
 'message': "New Block Mined",
 'index': block['index'],
 'hash_of_previous_block':
 block['hash_of_previous_block'],
 'nonce': block['nonce'],
 'transactions': block['transactions'],
 }
 return jsonify(response), 200

Cuando un minero logra extraer un bloque, debe recibir una recompensa por encontrar la prueba de trabajo. Aquí, agregamos una transacción para enviar unas unidades de recompensa al minero por minar con éxito el bloque. Al extraer un bloque, debe encontrar el hash del bloque anterior y luego usarlo junto con el contenido del bloque actual para encontrar el nonce del bloque. Una vez el
nonce es encontrado, lo agregaremos a la cadena de bloques.

añadiendo transacciones

Otra ruta que deseas agregar a la API es la capacidad de agregar transacciones al
bloque actual:

@app.route('/transactions/new', methods=['POST'])
def new_transaction():
 # get the value passed in from the client
 values = request.get_json()
 # check that the required fields are in the POST'ed data
 required_fields = ['sender', 'recipient', 'amount']
 if not all(k in values for k in required_fields):
 return ('Missing fields', 400)
 # create a new transaction
 index = blockchain.add_transaction(
 values['sender'],
 values['recipient'],
 values['amount']
 )
 response = {'message':
 f'Transaction will be added to Block {index}'}
 return (jsonify(response), 201)

Ejemplos de transacciones son las que los usuarios envían criptomonedas desde una cuenta a otra.

testeando nuestra blockchain

Ahora estamos listos para probar la cadena de bloques. En este paso final, agregamos las siguientes declaraciones hasta el final de blockchain.py:

if __name__ == '__main__':
 app.run(host='0.0.0.0', port=int(sys.argv[1]))

En esta implementación, permitimos que el usuario ejecute la API en función del numero de puerto especificado. Para iniciar el primer nodo, escriba el siguiente comando en Terminal:

> python blockchain.py 5000

Veras el siguiente resultado:

* Serving Flask app "blockchain" (lazy loading)
* Environment: production
 WARNING: Do not use the development server in a production environment.
 Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)


Nuestra primera cadena de bloques que se ejecuta en el primer nodo ahora se está ejecutando. También está escuchando en el puerto 5000, donde puedes agregar transacciones y extraer un bloque. En otra ventana de Terminal, escriba el siguiente comando para ver el contenido de la blockchain ejecutándose en el nodo:

> curl http://localhost:5000/blockchain

Verá la siguiente salida:

{
 "chain": [{
 "hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6
d7461792413aab3f97bd3da95",
 "index": 0,
 "nonce": 61093,
 "timestamp": 1560757569.810427,
 "transactions": []
 }],
 "length": 1
}

El primer bloque (índice 0) es el bloque génesis.

Intentemos extraer un bloque para ver cómo afectará a la cadena de bloques. Escriba el siguiente comando en la Terminal:

> curl http://localhost:5000/mine
{
 "hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142bcfc319
43e4857b7ba4df48bd98",
 "index": 2,
 "message": "New Block Mined",
 "nonce": 224075,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
}

El primer nodo ahora debería tener tres bloques:

> curl http://localhost:5000/blockchain
{
 "chain": [{
 "hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
 "index": 0,
 "nonce": 61093,
 "timestamp": 1560823108.2946198,
 "transactions": []
 }, {
 "hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217aae
89e5c0efd56959d87127b4d3",
 "index": 1,
 "nonce": 92305,
 "timestamp": 1560823210.26095,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }, {
 "hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142bcf
c31943e4857b7ba4df48bd98",
 "index": 2,
 "nonce": 224075,
 "timestamp": 1560823212.887074,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }],
 "length": 3
}

Como no hemos realizado ninguna minería en el segundo nodo (5001), solo hay un bloque en este nodo:

> curl http://localhost:5001/blockchain
{
 "chain": [{
 "hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
 "index": 0,
 "nonce": 61093,
 "timestamp": 1560823126.898498,
 "transactions": []
 }],
 "length": 1
}

Para decirle al segundo nodo que hay un nodo vecino, use el siguiente comando:

> curl -H "Content-type: application/json" -d '{"nodes" :
["http://127.0.0.1:5000"]}' -X POST http://localhost:5001/nodes/add_nodes

{
 "message": "New nodes added",
 "nodes": ["127.0.0.1:5000"]
}

El comando anterior registra un nuevo nodo con el nodo en el puerto 5001 que hay un nodo vecino escuchando en el puerto 5000.

Para decirle al primer nodo que hay un nodo vecino, use el siguiente comando:

> curl -H "Content-type: application/json" -d '{"nodes" :
["http://127.0.0.1:5001"]}' -X POST http://localhost:5000/nodes/add_nodes
{
 "message": "New nodes added",
 "nodes": ["127.0.0.1:5001"]
}

El comando anterior registra un nuevo nodo con el nodo en el puerto 5000 que hay un nodo vecino escuchando en el puerto 5001.

La figura de abajo muestra los dos nodos conscientes de la existencia del otro.

Los dos nodos en la blockchain

Con el primer nodo consciente de la existencia del segundo nodo (y viceversa), vamos a
intentar sincronizar la cadena de bloques a partir del primer nodo:

> curl http://localhost:5000/nodes/sync
{
 "blockchain": [{
 "hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
 "index": 0,
 "nonce": 61093,
 "timestamp": 1560823108.2946198,
 "transactions": []
 }, {
 "hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217aae
89e5c0efd56959d87127b4d3",
 "index": 1,
 "nonce": 92305,
 "timestamp": 1560823210.26095,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }, {
 "hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142bcf
c31943e4857b7ba4df48bd98",
 "index": 2,
 "nonce": 224075,
 "timestamp": 1560823212.887074,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }],
 "message": "Our blockchain is the latest"
}

Como muestra el resultado, el primer nodo tiene la cadena más larga (tres bloques) y, por lo tanto, es la cadena de bloques valida y permanece intacta. Ahora sincronizamos desde el segundo nodo:

> curl http://localhost:5001/nodes/sync
{
 "blockchain": [{
 "hash_of_previous_block": "181cfa3e85f3c2a7aa9fb74f992d0d061d3e4a6d
7461792413aab3f97bd3da95",
 "index": 0,
 "nonce": 61093,
 "timestamp": 1560823108.2946198,
 "transactions": []
 }, {
 "hash_of_previous_block": "ac46b1f492997e27612a8b5750e0fe340a217aae
89e5c0efd56959d87127b4d3",
 "index": 1,
 "nonce": 92305,
 "timestamp": 1560823210.26095,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }, {
 "hash_of_previous_block": "790ed48f5d52b3eacd2f419e6fdfb2f6b3142bcf
c31943e4857b7ba4df48bd98",
 "index": 2,
 "nonce": 224075,
 "timestamp": 1560823212.887074,
 "transactions": [{
 "amount": 1,
 "recipient": "db9ef69db7764331a6f4f23dbb8acd68",
 "sender": "0"
 }]
 }],
 "message": "The blockchain has been updated to the latest"
}

Como la cadena de bloques del segundo nodo solo tiene un bloque, se considera obsoleta. Ahora reemplazamos su cadena de bloques de la del primer nodo.

El código completo en github

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Salir /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Salir /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s