Blog
Llegados a este punto del curso, ya deberíamos de comprender que funciona la red blockchain desde el punto de vista de la programación, especificamente con python, bien; a partir de aquí comenzaremos a programar una API con Flask que va a interactuar con una aplicación que también vamos a programar usando Flask, la aplicación que vamos a desarrollar estará basada en una red descentralizada de reciclaje, en la cual utilizaremos 3 contenedores, el contenedor amarillo, azul y verde.
Estaremos haciendo uso de formatos JSON, por lo cual quiero presentarte la manera en como van a estar estructurados nuestros datos en un formato JSON. Por ejemplo, los textos que estarán protegidos por la red blockchain tendrán el siguiente formato JSON:
{ "Nombre": "Nombre del objeto a reciclar", "codigo": "código de barras del objeto", "fecha": "Fecha y hora cuando el objeto fue reciclado" }
El término que comúnmente utilizamos es internet es el termino “datos”, pero en un blockchain este termino cambia bastante ya que el termino correcto es “transacciones”. Por lo tanto, solo para evitar confusiones y mantener la coherencia, utilizaremos el término “transacción” para referirnos a los datos en nuestra API.
Almacenar transacciones en bloques
Como vimos anteriormente, ya sabemos que las transacciones se almacenan en bloques y que un bloque puede contener una o varias transacciones y los bloques que contienen las transacciones se generan con frecuencia y se agregan a la cadena, por otro lado también sabemos que cada bloque mantiene un id único (hash del bloque) debido a que pueden haber múltiples bloques.
Para comenzar a desarrollar nuestra API vamos a crear una carpeta bajo el nombre de API Blockchain
y dentro de esta crearemos un archivo llamado Blockchain_server.py
y dentro de este archivo crearemos nuestra primera clase, la cual será la clase Bloque:
class Bloque(): def __init__(self, id, transacciones, fecha): self.id = id self.transacciones = transacciones self.fecha = fecha
- id es el id único del bloque.
- transacciones se encarga de tomar las transacciones de la lista de transacciones.
- fecha es la fecha y hora en que fue generado el bloque.
Agregando huellas digitales a nuestro bloque
Nos gustaría evitar cualquier tipo de alteración en los datos almacenados dentro del bloque, y la detección es el primer paso para eso. Para detectar si los datos en el bloque han sido alterados, podemos usar funciones hash criptográficas como las que anteriormente vimos.
Es importante recordar que una función hash es una función que toma datos de cualquier tamaño y produce datos de un tamaño fijo (un hash), que generalmente se usa para identificar la entrada. Las características de una función hash ideal son:
- Debería ser fácil de calcular.
- Debe ser determinista, lo que significa que los mismos datos siempre darán como resultado el mismo hash.
- Debería ser uniformemente aleatorio, lo que significa que incluso un solo cambio de bit en los datos debería cambiar el hash significativamente.
La consecuencia de esto es:
- Es prácticamente imposible adivinar los datos de entrada dado el hash. (La única forma es probar todas las combinaciones de entrada posibles).
- Si conocemos los datos de entrada como el hash, simplemente podemos pasar la entrada a través de la función hash para verificar el hash proporcionado.
Esta asimetría de esfuerzos que se requiere para calcular el hash a partir de una entrada (fácil) versus averiguar la entrada de un hash (casi imposible) es lo que aprovecha blockchain para obtener las características deseadas en las que destaca la seguridad.
Vamos a ejecutar un ejemplo con la librería hashlib, vamos a abrir una consola de python y probaremos lo siguiente:
λ python Python 3.7.5 (tags/v3.7.5:5c02a39a0b, Oct 15 2019, 00:11:34) [MSC v.1916 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. from hashlib import sha256 >>> datos = "Esta variable es de prueba" >>> sha256(datos.encode()).hexdigest() 'acb405a8b98ca9d83b146db7bcea10a53a10161e6223602b070d8e8f45f5ee8f' >>> datos = b"Esta variable es de pruebas" >>> sha256(datos.encode()).hexdigest() '5d989c8f5b02624f0945966b6c80ae8555b2c50af5381bcbc8fcf67dc2fb2baa'
El hash que obtenemos, siempre será el mismo resultado para la cadena de bytes “Esta variable es de prueba”. Pero si por ejemplo, modificamos su contenido agregándole por ejemplo una letra “s” al final el hash cambia totalmente como podemos ver y he aquí la seguridad, ya que esto es lo que permite saber si el contenido fue cambiado o alterado y en una red blockchain es lo que permite saber si el bloque fue alterado y por lo tanto quedaría invalidado.
Lo siguiente por hacer es crear nuestro primer método para la clase Bloque al que llamaremos calcular_hash
:
def calcular_hash(self): hash_bloque = json.dumps(self.__dict__, sort_keys=True) return sha256(hash_bloque.encode()).hexdigest()
Este método almacenas el hash del bloque en un campo dentro de nuestro objeto Bloque, y actuará como una huella digital (o firma) de los datos contenidos en él, también es importante destacar que la instancia del bloque hash la devolverá como un JSON string.
Encadenar los bloques
Bien, ahora hemos configurado los bloques. Se supone que la cadena de bloques es una colección de bloques. Podemos almacenar todos los bloques en la lista de Python. Pero esto no es suficiente, porque ¿qué pasa si alguien reemplaza intencionalmente un bloque antiguo con un bloque nuevo en la colección? Crear un nuevo bloque con transacciones alteradas, calcular el hash y reemplazarlo con cualquier bloque anterior no es gran cosa en nuestra implementación actual.
Necesitamos una manera de asegurarnos de que cualquier cambio en los bloques anteriores invalide toda la cadena. La forma de Bitcoin de hacer esto es crear dependencia entre bloques consecutivos encadenándolos con el hash del bloque inmediatamente anterior a ellos. Al encadenar aquí, queremos incluir el hash del bloque anterior en el bloque actual en un nuevo campo que llamaremos hash_bloque_anterior
tal cual como lo hicimos en el script de las lecciones pasadas.
Si cada bloque está vinculado al bloque anterior a través del campo hash_bloque_anterior
, ¿qué pasa con el primer bloque? Ese bloque se llama bloque génesis como lo vimos anteriormente en el script y se puede generar manualmente o mediante alguna lógica única. Agreguemos el campo hash_bloque_anterior
a la clase Bloque e implementemos la estructura inicial de nuestra clase Blockchain, pero para esta ocasión esta ocasión la clase Blockchain recibirá el nombre de ContenedorAmarillo, es decir; cada contenedor de reciclaje hará las funciones de un Blockchain.
Introduciendo todos estos cambios, ahora nuestro archivo Blockchain_server.py tiene el siguiente aspecto:
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(): def __init__(self): self.cadena = [] 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]
Ahora, si el contenido de cualquiera de los bloques anteriores cambia:
- El hash de ese bloque anterior cambiaría.
- Esto conducirá a una falta de coincidencia con el campo hash_bloque_anterior en el siguiente bloque.
- Dado que los datos de entrada para calcular el hash de cualquier bloque también consisten en el campo hash_bloque_anterior, el hash del siguiente bloque también cambiará.
En última instancia, toda la cadena que sigue al bloque reemplazado queda invalidada, y la única forma de solucionarlo es volver a calcular toda la cadena, es importante destacar que en este punto aún nos falta implementar otras funcionalidades de seguridad que mantiene una red blockchain.
Ahora, lo que sigue es implementar la dificultad del bloque. ¿Porqué hacemos esto? la respuesta es que ci cambiamos el bloque anterior, los hash de todos los bloques que siguen se pueden volver a calcular con bastante facilidad para crear una cadena de bloques válida diferente y esto representa un problema, ya que hace que nuestra red blockchain no sea segura.
Para evitar esto, podemos hacer uso de la asimetría de las funciones hash que discutimos anteriormente para hacer que la tarea de calcular el hash sea difícil y aleatoria. Así es como hacemos esto: en lugar de aceptar cualquier hash para el bloque, le agregamos alguna restricción. Vamos a proceder a a gregar una restricción de que nuestro hash debe comenzar con un número “n” donde “n” puede ser cualquier número entero positivo.
Vamos a introducir un nuevo campo en nuestro bloque llamado nonce . El nonce que satisface la restricción sirve como prueba de que se ha realizado algún cálculo. Esta técnica es una versión simplificada del algoritmo Hashcash utilizado en Bitcoin. El número de ceros especificado en la restricción determina la dificultad de nuestro algoritmo de prueba de trabajo (cuanto mayor es el número de ceros, más difícil es descubrir el nonce).
Además, debido a la asimetría, la prueba de trabajo es difícil de calcular pero muy fácil de verificar una vez que descubres el nonce (solo tienes que ejecutar la función hash nuevamente), vamos a agregar un poco más de código a nuestra clase ContenedorAmarillo y debería de verse ahora así:
class ContenedorAmarillo(): dificultad = 2 def __init__(self): self.cadena_bloques = [] 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
Debemos tener en cuenta que no existe una lógica específica para resolver el nonce rápidamente. La única mejora definitiva que podemos hacer es utilizar chips de hardware especialmente diseñados para calcular la función hash en un número menor de instrucciones de la CPU, pero eso ya es otra historia pero que vale la pena destacar aquí.
Quizás hasta este punto no tengas tan claro lo del nonce, pero esto es debido a que aún no hacemos uso de él como tal ya que eso se vera en las próximas lecciones.
➡ ¡Enhorabuena! Continúa aprendiendo con nuestro Curso de Blockchain: