Blockchain en Python
Blockchain en Python
Serás capaz de crear puntos de acceso para diferentes funciones del blockchain, tales como
añadir una transacción, usando el microframework Flask, y luego ejecutar los programas en
múltiples máquinas para crear una red descentralizada. También verás cómo desarrollar una
interfaz de usuario simple que interactúe con el blockchain y almacene la información para
cualquier uso, como pagos peer-to-peer, conversaciones o comercio electrónico.
Obtener el código
Requisitos
Un conocimiento básico de programación en Python [véase el tutorial de Python].
El microframework Flask (para crear puntos de acceso para el servidor blockchain).
Fundamentos
Una red blockchain pública, como la red Bitcoin, está completamente abierta al público, y
cualquiera puede unirse y participar.
En la otra mano, negocios que requieren de mayor privacidad, seguridad y velocidad en las
transacciones optan por una red blockchain privada, donde los participantes necesitan una
invitación para unirse. Saber más.
En 2008, un artículo titulado Bitcoin: un sistema de dinero electrónico peer-to-peer fue
publicado por un individuo (o tal vez un grupo) llamado Satoshi Nakamoto. El artículo
combinaba técnicas criptográficas y una red peer-to-peer sin la necesidad de confiar en una
autoridad centralizada (como los bancos) para realizar pagos de una persona a otra. Nació
Bitcoin. Además de Bitcoin, el mismo artículo introdujo un sistema distribuido para
almacenar información (ahora conocido popularmente como «blockchain»), que tiene un
campo de aplicación mucho más amplio que únicamente pagos o criptomonedas.
¿Qué es «blockchain»?
Inmutabilidad de la historia.
Invulnerabilidad del sistema.
Persistencia de la información.
No hay un solo punto de falla.
Ahora bien, ¿cómo es que blockchain obtiene estas características? Nos iremos acercando a
ello a medida que las vayamos implementando. Empecemos.
Acerca de la aplicación
Definamos qué es lo que hará la aplicación que estamos desarrollando. Nuestro objetivo es
construir un sitio web simple que permita a los usuarios compartir información. Puesto que
el contenido estará almacenado en el blockchain, será inmutable y permanente.
1. Contenido
2. Autor
3. Fecha de publicación [timestamp]
{
"author": "nombre_del_autor",
"content": "Algunos pensamientos que el autor quiere compartir",
"timestamp": "El tiempo en el que el contenido fue creado"
}
Las transacciones están empaquetadas en bloques. Además, un bloque puede contener una
o más transacciones. Los bloques que contienen las transacciones son generados
frecuentemente y añadidos al blockchain. Dado que puede haber múltiples bloques, cada
uno tendría que tener un identificador único:
1. class Block:
2. def __init__(self, index, transactions, timestamp):
3. self.index = []
4. self.transactions = transactions
5. self.timestamp = timestamp
Una función hash es una función que toma información de cualquier tamaño y a partir de
ella produce otra información de un tamaño fijo, que generalmente sirve para identificar la
entrada [input]. Aquí hay un ejemplo en Python usando la función hash sha256.
Necesitamos una solución para asegurarnos que cualquier cambio en los bloques anteriores
invalide la cadena entera. Una forma de hacer esto es encadenar los bloques por su hash. Al
encadenarlos de esta forma, queremos decir incluir el hash del bloque anterior en el actual.
Así, si el contenido de cualquiera de los bloques anteriores cambia, el hash del bloque va a
cambiar, llevando a una discrepancia con el campo previos_hash en el próximo bloque.
Perfecto, cada bloque está enlazado al anterior por el campo previous_hash, ¿pero qué
sucede con el primer bloque de todos? El primer bloque es llamado el bloque génesis y es
generado manualmente o por alguna lógica única, en la mayoría de los casos. Agreguemos
el campo previous_hash a la clase Block e implementemos la estructura inicial de
nuestra clase Blockchain (ver Listado 1).
1. class Blockchain:
2.
3. def __init__(self):
4. self.unconfirmed_transactions = [] # información para insertar en el blockchain
5. self.chain = []
6. self.create_genesis_block()
7.
8. def create_genesis_block(self):
9. """
10. Una función para generar el bloque génesis y añadirlo a la
11. cadena. El bloque tiene index 0, previous_hash 0 y un hash
12. válido.
13. """
14. genesis_block = Block(0, [], time.time(), "0")
15. genesis_block.hash = genesis_block.compute_hash()
16. self.chain.append(genesis_block)
17.
18. @property
19. def last_block(self):
20. return self.chain[-1]
1. class Blockchain:
2. # Dificultad del algoritmo de prueba de trabajo.
3. difficulty = 2
4.
5. """
6. Código anterior...
7. """
8.
9. def proof_of_work(self, block):
10. """
11. Función que intenta distintos valores de nonce hasta obtener
12. un hash que satisfaga nuestro criterio de dificultad.
13. """
14. block.nonce = 0
15.
16. computed_hash = block.compute_hash()
17. while not computed_hash.startswith('0' * Blockchain.difficulty):
18. block.nonce += 1
19. computed_hash = block.compute_hash()
20.
21. return computed_hash
1. class Blockchain:
2. """
3. Código anterior...
4. """
5. def add_block(self, block, proof):
6. """
7. Una función que agrega el bloque a la cadena luego de la verificación.
8. """
9. previous_hash = self.last_block.hash
10.
11. if previous_hash != block.previous_hash:
12. return False
13.
14. if not self.is_valid_proof(block, proof):
15. return False
16.
17. block.hash = proof
18. self.chain.append(block)
19. return True
20.
21. def is_valid_proof(self, block, block_hash):
22. """
23. Chquear si block_hash es un hash válido y satisface nuestro
24. criterio de dificultad.
25. """
26. return (block_hash.startswith('0' * Blockchain.difficulty) and
27. block_hash == block.compute_hash())
Minado
1. class Blockchain:
2. """
3. Código anterior...
4. """
5.
6. def add_new_transaction(self, transaction):
7. self.unconfirmed_transactions.append(transaction)
8.
9. def mine(self):
10. """
11. Esta función sirve como una interfaz para añadir las transacciones
12. pendientes al blockchain añadiéndolas al bloque y calculando la
13. prueba de trabajo.
14. """
15. if not self.unconfirmed_transactions:
16. return False
17.
18. last_block = self.last_block
19.
20. new_block = Block(index=last_block.index + 1,
21. transactions=self.unconfirmed_transactions,
22. timestamp=time.time(),
23. previous_hash=last_block.hash)
24.
25. proof = self.proof_of_work(new_block)
26. self.add_block(new_block, proof)
27. self.unconfirmed_transactions = []
28. return new_block.index
Muy bien, ya casi estamos. Puedes ver el código completo hasta este momento en GitHub.
6. Crear interfaces
Bien, ahora es momento de crear interfaces para nuestro nodo para interactuar con otros
compañeros así como con la aplicación que vamos a crear. Estaremos utilizando Flask para
crear una REST-API que interactúe con nuestro nodo. Aquí está el código para ello:
Necesitamos un punto de acceso para nuestra aplicación para enviar una nueva transacción.
Éste será utilizado por nuestra aplicación para añadir nueva información (publicaciones)
al blockchain:
1. @app.route('/new_transaction', methods=['POST'])
2. def new_transaction():
3. tx_data = request.get_json()
4. required_fields = ["author", "content"]
5.
6. for field in required_fields:
7. if not tx_data.get(field):
8. return "Invlaid transaction data", 404
9.
10. tx_data["timestamp"] = time.time()
11.
12. blockchain.add_new_transaction(tx_data)
13.
14. return "Success", 201
Aquí hay un punto de acceso para retornar la copia del blockchain que tiene el nodo.
Nuestra aplicación estará usando este punto de acceso para solicitar todas las publicaciones
para mostrar.
1. @app.route('/chain', methods=['GET'])
2. def get_chain():
3. chain_data = []
4. for block in blockchain.chain:
5. chain_data.append(block.__dict__)
6. return json.dumps({"length": len(chain_data),
7. "chain": chain_data})
Y aquí hay otro para solicitar al nodo que mine las transacciones sin confirmar (si es que
hay alguna).
1. @app.route('/mine', methods=['GET'])
2. def mine_unconfirmed_transactions():
3. result = blockchain.mine()
4. if not result:
5. return "No transactions to mine"
6. return "Block #{} is mined.".format(result)
7.
8.
9. # punto de acceso para obtener las transacciones
10. # no confirmadas
11. @app.route('/pending_tx')
12. def get_pending_tx():
13. return json.dumps(blockchain.unconfirmed_transactions)
14.
15.
16. app.run(debug=True, port=8000)
Te habrás dado cuenta que hay un problema con los múltiples nodos. Debido a la
manipulación intencional o por razones inintencionales, la copia de cadenas de algunos
nodos puede diferir. En ese caso, necesitamos ponernos de acuerdo respecto de alguna
versión de la cadena para mantener la integridad de todo el sistema. Necesitamos
consensuar.
1. def consensus():
2. """
3. Nuestro simple algoritmo de consenso. Si una cadena válida más larga es
4. encontrada, la nuestra es reemplazada por ella.
5. """
6. global blockchain
7.
8. longest_chain = None
9. current_len = len(blockchain)
10.
11. for node in peers:
12. response = requests.get('http://{}/chain'.format(node))
13. length = response.json()['length']
14. chain = response.json()['chain']
15. if length > current_len and blockchain.check_chain_validity(chain):
16. current_len = length
17. longest_chain = chain
18.
19. if longest_chain:
20. blockchain = longest_chain
21. return True
22.
23. return False
Y ahora finalmente, necesitamos desarrollar una forma para que cada nodo pueda anunciar
a la red que ha minado un bloque para que todos puedan actualizar su blockchain y seguir
minando otras transacciones. Otros nodos pueden simplemente verificar la prueba de
trabajo y añadirla a sus respectivas cadenas:
1. # punto de acceso para añadir un bloque minado por alguien más a la cadena del nodo.
2. @app.route('/add_block', methods=['POST'])
3. def validate_and_add_block():
4. block_data = request.get_json()
5. block = Block(block_data["index"], block_data["transactions"],
6. block_data["timestamp", block_data["previous_hash"]])
7.
8. proof = block_data['hash']
9. added = blockchain.add_block(block, proof)
10.
11. if not added:
12. return "The block was discarded by the node", 400
13.
14. return "Block added to the chain", 201
15.
16. def announce_new_block(block):
17. for peer in peers:
18. url = "http://{}/add_block".format(peer)
19. requests.post(url, data=json.dumps(block.__dict__, sort_keys=True))
8. Crear la aplicación
Bien, el backend está listo. Puedes ver el código hasta este momento en GitHub.
1. import datetime
2. import json
3.
4. import requests
5. from flask import render_template, redirect, request
6.
7. from app import app
8.
9.
10. CONNECTED_NODE_ADDRESS = "http://127.0.0.1:8000"
11.
12. posts = []
1. def fetch_posts():
2. get_chain_address = "{}/chain".format(CONNECTED_NODE_ADDRESS)
3. response = requests.get(get_chain_address)
4. if response.status_code == 200:
5. content = []
6. chain = json.loads(response.content)
7. for block in chain["chain"]:
8. for tx in block["transactions"]:
9. tx["index"] = block["index"]
10. tx["hash"] = block["previous_hash"]
11. content.append(tx)
12.
13. global posts
14. posts = sorted(content, key=lambda k: k['timestamp'],
15. reverse=True)
La aplicación tiene un formulario HTML para tomar la entrada del usuario y luego realiza
una petición POST a un nodo conectado para añadir la transacción al conjunto de
transacciones sin confirmar. La transacción luego es minada por la red, y finalmente será
obtenida una vez que recarguemos nuestro sitio web:
1. @app.route('/submit', methods=['POST'])
2. def submit_textarea():
3. """
4. Punto de acceso para crear una nueva transacción vía nuestra
5. aplicación.
6. """
7. post_content = request.form["content"]
8. author = request.form["author"]
9.
10. post_object = {
11. 'author': author,
12. 'content': post_content,
13. }
14.
15. # Submit a transaction
16. new_tx_address = "{}/new_transaction".format(CONNECTED_NODE_ADDRESS)
17.
18. requests.post(new_tx_address,
19. json=post_object,
20. headers={'Content-type': 'application/json'})
21.
22. return redirect('/')
9. Ejecutar la aplicación
¡Está lista! Puedes encontrar el código final en GitHub.
Verificar transacciones
Tal vez hayas notado una falla en la aplicación: cualquiera puede cambiar cualquier nombre
y publicar cualquier contenido. Una forma de resolver esto es creando los usuarios
usando criptografía de clave pública-privada. Cada usuario nuevo necesita una clave
pública (análoga a un nombre de usuario) y una clave privada para ser capaces de publicar
en nuestra aplicación. Las claves actúan como una firma digital. La clave pública solo
puede decodificar el contenido encriptado por la correspondiente clave privada. Las
transacciones serán verificadas usando la clave pública del autor antes de añadir cualquier
bloque. De esta manera, sabremos exactamente quién escribió el mensaje.
Conclusión
Este tutorial cubrió los fundamentos de un blockchain público. Si lo seguiste
completamente, has implementado un blockchain desde cero y creado una simple
aplicación permitiendo a los usuarios compartir información en él.