Blog
Blockchain: Implementando otras funciones a nuestra API Blockchain
- Publicado por: Hebert
- Categoría: Blog
En este capítulo nos centraremos a crear las interfaces de una API con ayuda del framework Flask, además de agregar otras funcionalidades a nuestra red blockchain.
Agregando bloques a la cadena
Vamos a comenzar agregando bloques a la cadena pero antes para que esto pueda ser posible debemos verificar un par de cosas:
- Los datos no han sido alterados (la prueba de trabajo proporcionada es correcta).
- Se conserva el orden de las transacciones (el campo
hash_bloque_anterior
del bloque que se agregará señala al hash del último bloque de nuestra cadena).
Lo cierto es que todo esto no se cumple hasta este punto, para solucionarlo debemos escribir un poco más de código dentro de nuestra clase ContenedorAmarillo . Lo principal es crear dos nuevos métodos en la clase uno llamado agregar_bloque
y otro llamado prueba_valida
al agregar estos dos métodos nuestra clase ContenedorAmarillo debe quedar de la siguiente manera:
class Bloque(): def __init__(self, id, transacciones, fecha, hash_bloque_anterior): self.id = id self.transacciones = transacciones self.fecha = fecha self.hash_bloque_anterior = hash_bloque_anterior def calcular_hash(self): hash_bloque = json.dumps(self.__dict__, sort_keys=True) return sha256(hash_bloque.encode()).hexdigest()st() class ContenedorAmarillo(): dificultad = 2 def __init__(self): self.cadena_bloques = [] def crear_bloque_genesis(self): bloque_genesis = Bloque(0, [], 0, "0") bloque_genesis.hash = bloque_genesis.calcular_hash() self.cadena.append(bloque_genesis) @property def ultimo_bloque(self): return self.cadena[-1] def prueba_de_trabajo(self, bloque): bloque.nonce = 0 calcular_hash = bloque.calcular_hash() while not calcular_hash.startswith('0' * ContenedorAmarillo.dificultad): bloque.nonce += 1 calcular_hash = bloque.calcular_hash() return calcular_hash def agregar_bloque(self, bloque, prueba): hash_bloque_anterior = self.ultimo_bloque.hash if hash_bloque_anterior != bloque.hash_bloque_anterior: return False if not ContenedorAmarillo.prueba_valida(bloque, prueba): return False bloque.hash = prueba self.cadena.append(bloque) return True def prueba_valida(self, cls, bloque, hash_bloque): return (hash_bloque.startswith('0' * ContenedorAmarillo.dificultad) and hash_bloque == bloque.calcular_hash())
Agregando la funcionalidad Minar
Las transacciones se almacenarán inicialmente como un conjunto de transacciones no confirmadas. El proceso de colocar las transacciones no confirmadas en un bloque y calcular la prueba de trabajo se conoce como la extracción de bloques. Una vez que se descubre el nonce que satisface nuestras limitaciones, podemos decir que se ha extraído un bloque y se puede colocar en la cadena de bloques.
En la mayoría de las criptomonedas (incluido Bitcoin), los mineros pueden recibir algunas criptomonedas como recompensa por gastar su poder de cómputo para calcular una prueba de trabajo. Ahora lo que resta es agregar esta funcionalidad, pero para logar esto debemos introducir los siguientes cambios en nuestra clase ContenedorAmarillo:
class ContenedorAmarillo(): dificultad = 2 def __init__(self): self.transacciones_no_confirmadas = [] self.cadena_bloques = [] self.crear_bloque_genesis() def crear_bloque_genesis(self): bloque_genesis = Bloque(0, [], time.time(), "0") bloque_genesis.hash = bloque_genesis.calcular_hash() self.cadena_bloques.append(bloque_genesis) @property def utlimo_bloque(self): return self.cadena_bloques[-1] def prueba_de_trabajo(self, bloque): bloque.nonce = 0 calcular_hash = bloque.calcular_hash() while not calcular_hash.startswith('0' * ContenedorAmarillo.dificultad): bloque.nonce += 1 calcular_hash = bloque.calcular_hash() return calcular_hash def agregar_bloque(self, bloque, prueba_de_trabajo): hash_bloque_anterior = self.utlimo_bloque.hash if hash_bloque_anterior != block.hash_bloque_anterior: return False if not ContenedorAmarillo.prueba_valida(bloque, prueba_de_trabajo): return False bloque.hash = prueba_de_trabajo self.chain.append(bloque) return True @classmethod def prueba_valida(cls, bloque, hash_bloque): return (hash_bloque.startswith('0' * ContenedorAmarillo.dificultad) and hash_bloque == bloque.calcular_hash()) def agregar_nueva_transaccion(self, transaccion): self.transacciones_no_confirmadas.append(transaccion) def minar(self): if not self.transacciones_no_confirmadas: return False ultimo_bloque = self.ultimo_bloque nuevo_bloque = Bloque(id=ultimo_bloque.id + 1, transacciones=self.transacciones_no_confirmadas, fecha=time.time(), hash_bloque_anterior=ultimo_bloque.hash) prueba = self.prueba_de_trabajo(nuevo_bloque) self.agregar_bloque(nuevo_bloque, prueba) self.transacciones_no_confirmadas = [] return True
Hemos agregado algunos cambios como por ejemplo, agregar una variable de instancia en el constructor llamada transacciones_no_confirmadas
además agregamos la función minar que no mantiene un código tan complicado y lo hace es servir como una interfaz para agregar las transacciones pendientes a la cadena de bloques al agregarlas al bloque y averiguar la prueba de trabajo.
Creando interfaces para nuestra API
Bien, ahora es el momento de crear interfaces para que nuestro nodo blockchain interactúe con la aplicación que vamos a construir. Utilizaremos un microframework de Python popular llamado Flask para crear una API REST que interactúa e invoca varias operaciones en nuestro nodo blockchain.
Vamos a comenzar escribiendo un código sencillo de Flask, el código es bastante simple ya que solo debemos inicializar el objeto Flask y nuestro propio objeto ContenedorAmarillo:
from flask import Flask, request import requests app = Flask(__name__) ContenedorAmarillo = ContenedorAmarillo()
Pero ahora necesitamos una ruta para que nuestra aplicación envíe una nueva transacción. Esta será utilizada por nuestra aplicación para agregar nuevos datos (publicaciones) a la cadena de bloques:
@app.route('/nueva_transaccion_amarilla', methods=['POST']) def nueva_transaccion_amarilla(): datos_transaccion = request.get_json() campos_requeridos = ["nombre", "codigo"] for campo in campos_requeridos: if not datos_transaccion.get(campo): return "Datos de transacción inválidos", 404 datos_transaccion["fecha"] = time.time() ContenedorAmarillo.agregar_nueva_transaccion(datos_transaccion) return "Transacción agregar exitosamente", 201
Aquí hay una ruta para devolver la copia del nodo de la cadena. Nuestra aplicación utilizará esta ruta para consultar todos los datos que se mostrarán:
@app.route('/cadena_amarilla', methods=['GET']) def obtener_cadena_amarilla(): datos_cadena = [] for bloque in ContenedorAmarillo.cadena: datos_cadena.append(bloque.__dict__) return json.dumps({"longitud": len(datos_cadena), "cadena": datos_cadena, "compañeros": list(compañeros)})
Aquí programamos una nueva ruta para solicitar al nodo que extraiga las transacciones no confirmadas (si las hay). Lo usaremos para iniciar un comando para minar desde nuestra propia aplicación:
@app.route('/minar_contenedor_amarillo', methods=['GET']) def minar_transacciones_no_confirmadas_amarillas(): resultado = ContenedorAmarillo.minar() if not resultado: print('Sin transacciones para minar') return redirect('http://127.0.0.1:5000/contenedor_amarillo') else: longitud_cadena = len(ContenedorAmarillo.cadena) consenso_amarillo() if longitud_cadena == len(ContenedorAmarillo.cadena): anunciar_nuevo_bloque_amarillo(ContenedorAmarillo.ultimo_bloque) print("Bloque #{} ha sido minado.".format(ContenedorAmarillo.ultimo_bloque.id)) return redirect('http://127.0.0.1:5000/contenedor_amarillo') @app.route('/transacciones_pendientes_amarillas') def obtener_transacciones_pendientes_amarillas(): return json.dumps(ContenedorAmarillo.transacciones_no_confirmadas)
Este es el código de nuestro archivo Blockchain_server.py
hasta el momento:
from hashlib import sha256 import time import json class Bloque(): def __init__(self, id, transacciones, fecha, hash_bloque_anterior): self.id = id self.transacciones = transacciones self.fecha = fecha self.hash_bloque_anterior = hash_bloque_anterior def calcular_hash(self): bloque_string = json.dumps(self.__dict__, sort_keys=True) return sha256(bloque_string.encode()).hexdigest() class ContenedorAmarillo(): dificultad = 2 def __init__(self): self.transacciones_no_confirmadas = [] self.cadena_bloques = [] self.crear_bloque_genesis() def crear_bloque_genesis(self): bloque_genesis = Bloque(0, [], time.time(), "0") bloque_genesis.hash = bloque_genesis.calcular_hash() self.cadena_bloques.append(bloque_genesis) @property def utlimo_bloque(self): return self.cadena_bloques[-1] def prueba_de_trabajo(self, bloque): bloque.nonce = 0 calcular_hash = bloque.calcular_hash() while not calcular_hash.startswith('0' * ContenedorAmarillo.dificultad): bloque.nonce += 1 calcular_hash = bloque.calcular_hash() return calcular_hash def agregar_bloque(self, bloque, prueba_de_trabajo): hash_bloque_anterior = self.utlimo_bloque.hash if hash_bloque_anterior != block.hash_bloque_anterior: return False if not ContenedorAmarillo.prueba_valida(bloque, prueba_de_trabajo): return False bloque.hash = prueba_de_trabajo self.chain.append(bloque) return True def prueba_valida(self, bloque, hash_bloque): return (hash_bloque.startswith('0' * ContenedorAmarillo.dificultad) and hash_bloque == bloque.calcular_hash()) def agregar_nueva_transaccion(self, transaccion): self.transacciones_no_confirmadas.append(transaccion) def minar(self): if not self.transacciones_no_confirmadas: return False utlimo_bloque = self.utlimo_bloque nuevo_bloque = Bloque(id=utlimo_bloque.id + 1, transacciones=self.transacciones_no_confirmadas, fecha=time.time(), hash_bloque_anterior=utlimo_bloque.hash) prueba_de_trabajo = self.prueba_de_trabajo(nuevo_bloque) self.agregar_bloque(nuevo_bloque, prueba_de_trabajo) self.transacciones_no_confirmadas = [] return nuevo_bloque.id from flask import Flask, request import requests app = Flask(__name__) ContenedorAmarillo= ContenedorAmarillo() @app.route('/nueva_transaccion_amarilla', methods=['POST']) datos_transaccion = request.get_json() campos_requeridos = ["nombre", "codigo"] for campo in campos_requeridos: if not datos_transaccion.get(campo): return "Datos de transacción inválidos", 404 datos_transaccion["fecha"] = time.time() ContenedorAmarillo.agregar_nueva_transaccion(datos_transaccion) return "Transacción agregar exitosamente", 201 @app.route('/cadena_amarilla', methods=['GET']) def obtener_cadena_amarilla(): datos_cadena = [] for bloque in ContenedorAmarillo.cadena: datos_cadena.append(bloque.__dict__) return json.dumps({"longitud": len(datos_cadena), "cadena": datos_cadena, "compañeros": list(compañeros)}) @app.route('/minar_contenedor_amarillo', methods=['GET']) def minar_transacciones_no_confirmadas_amarillas(): resultado = ContenedorAmarillo.minar() if not resultado: print('Sin transacciones para minar') return redirect('http://127.0.0.1:5000/contenedor_amarillo') else: longitud_cadena = len(ContenedorAmarillo.cadena) consenso_amarillo() if longitud_cadena == len(ContenedorAmarillo.cadena): anunciar_nuevo_bloque_amarillo(ContenedorAmarillo.ultimo_bloque) print("Bloque #{} ha sido minado.".format(ContenedorAmarillo.ultimo_bloque.id)) return redirect('http://127.0.0.1:5000/contenedor_amarillo') @app.route('/transacciones_pendientes_amarillas') def obtener_transacciones_pendientes_amarillas(): return json.dumps(ContenedorAmarillo.transacciones_no_confirmadas)
➡ ¡Enhorabuena si has llegado hasta aquí! Continúa aprendiendo con nuestro Curso de Blockchain: