paint-brush
Aprenda Blockchains construyendo unopor@dvf
724,448 lecturas
724,448 lecturas

Aprenda Blockchains construyendo uno

por Daniel van Flymen20m2017/09/22
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Aprenda Blockchains construyendo uno: la forma más rápida de aprender cómo funcionan es construir uno. Python 3.6+ (junto con pip) está instalado, Flask y la maravillosa biblioteca Requests. También necesitará un cliente HTTP, como Postman o cURL, pero cualquier cosa servirá. Debería sentirse cómodo leyendo y escribiendo Python básico, además de comprender cómo funcionan las solicitudes HTTP, ya que hablaremos con nuestra cadena de bloques a través de HTTP.

Coin Mentioned

Mention Thumbnail
featured image - Aprenda Blockchains construyendo uno
Daniel van Flymen HackerNoon profile picture

La forma más rápida de aprender cómo funcionan las Blockchains es construir una

Estás aquí porque, como yo, estás entusiasmado con el auge de las criptomonedas. Y quiere saber cómo funcionan las Blockchains, la tecnología fundamental detrás de ellas.

Pero entender Blockchains no es fácil, o al menos no lo fue para mí. Caminé a través de videos densos, seguí tutoriales porosos y lidié con la frustración amplificada de muy pocos ejemplos.

Me gusta aprender haciendo. Me obliga a tratar el tema a nivel de código, lo que hace que se pegue. Si hace lo mismo, al final de esta guía tendrá una cadena de bloques en funcionamiento con una sólida comprensión de cómo funcionan.

Antes de empezar...

Recuerde que una cadena de bloques es una cadena secuencial e inmutable de registros llamada Bloques. Pueden contener transacciones, archivos o cualquier dato que desee, de verdad. Pero lo importante es que están encadenados mediante hashes .

Si no está seguro de qué es un hash, aquí hay una explicación .

¿A quién va dirigida esta guía? Debería sentirse cómodo leyendo y escribiendo algo básico de Python, así como también comprender cómo funcionan las solicitudes HTTP, ya que hablaremos con nuestra cadena de bloques a través de HTTP.

¿Qué necesito? Asegúrese de que Python 3.6 + (junto con pip) esté instalado. También necesitarás instalar Flask y la maravillosa biblioteca de Solicitudes:

 pip install Flask== 0.12 .2 requests== 2.18 .4

Ah, también necesitará un cliente HTTP, como Postman o cURL. Pero cualquier cosa servirá.

¿Dónde está el código final? El código fuente está disponible aquí .

Paso 1: construir una cadena de bloques

Abre tu editor de texto favorito o IDE, personalmente yo ❤️ PyCharm . Cree un nuevo archivo, llamado

 blockchain.py
. Solo usaremos un único archivo, pero si se pierde, siempre puede consultar el código fuente .

Representando una cadena de bloques

Crearemos un

 Blockchain
clase cuyo constructor crea una lista vacía inicial (para almacenar nuestra cadena de bloques) y otra para almacenar transacciones. Aquí está el plano para nuestra clase:

 class Blockchain ( object ): def __init__ ( self ): self . chain = [] self.current_transactions = [] def new_block(self): # Creates a new Block and adds it to the chain pass def new_transaction(self): # Adds a new transaction to the list of transactions pass @staticmethod def hash(block): # Hashes a Block pass @property def last_block(self): # Returns the last Block in the chain pass

(Modelo de nuestra Clase Blockchain)

Nuestro

 Blockchain
La clase es responsable de administrar la cadena. Almacenará transacciones y tendrá algunos métodos auxiliares para agregar nuevos bloques a la cadena. Comencemos a desarrollar algunos métodos.

¿Cómo se ve un bloque?

Cada Bloque tiene un índice , una marca de tiempo (en tiempo de Unix), una lista de transacciones , una prueba (más sobre eso más adelante) y el hash del Bloque anterior .

Aquí hay un ejemplo de cómo se ve un solo bloque:

 block = { 'index' : 1 , 'timestamp' : 1506057125.900785 , 'transactions' : [ { 'sender' : "8527147fe1f5426f9dd545de4b27ee00" , 'recipient' : "a77f5cdfa2934df3954a5c7c7da5df1f" , 'amount' : 5 , } ], 'proof' : 324984774000 , 'previous_hash' : "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" }

( Ejemplo de un Bloque en nuestra Blockchain)

En este punto, la idea de una cadena debería ser evidente: cada bloque nuevo contiene dentro de sí mismo, el hash del bloque anterior. Esto es crucial porque es lo que le da inmutabilidad a las cadenas de bloques: si un atacante corrompió un bloque anterior en la cadena, todos los bloques posteriores contendrán hashes incorrectos.

¿Esto tiene sentido? Si no es así, tómese un tiempo para asimilarlo: es la idea central detrás de las cadenas de bloques.

Adición de transacciones a un bloque

Necesitaremos una forma de agregar transacciones a un Bloque. Nuestro

 new_transaction()
El método es responsable de esto, y es bastante sencillo:

 class Blockchain ( object ): ... def new_transaction ( self , sender , recipient , amount ): """ Creates a new transaction to go into the next mined Block : param sender : < str > Address of the Sender : param recipient : < str > Address of the Recipient : param amount : < int > Amount : return : < int > The index of the Block that will hold this transaction """ self . current_transactions . append ( { 'sender' : sender, 'recipient' : recipient, 'amount' : amount, }) return self.last_block[ 'index' ] + 1

Después

 new_transaction()
agrega una transacción a la lista, devuelve el índice del bloque al que se agregará la transacción, el siguiente que se extraerá. Esto será útil más adelante, para el usuario que envía la transacción.

Crear nuevos bloques

Cuando nuestro

 Blockchain
está instanciado, necesitaremos sembrarlo con un bloque de génesis , un bloque sin predecesores. También necesitaremos agregar una "prueba" a nuestro bloque de génesis que es el resultado de la minería (o prueba de trabajo). Hablaremos más sobre la minería más adelante.

Además de crear el bloque de génesis en nuestro constructor, también desarrollaremos los métodos para

 new_block()
,
 new_transaction()
y
 hash()
:

 import hashlib import json from time import time class Blockchain ( object ): def __init__ ( self ): self . current_transactions = [] self.chain = [] # Create the genesis block self.new_block(previous_hash= 1 , proof= 100 ) def new_block(self, proof, previous_hash=None): "" " Create a new Block in the Blockchain :param proof: <int> The proof given by the Proof of Work algorithm :param previous_hash: (Optional) <str> Hash of previous Block :return: <dict> New Block " "" block = { 'index' : len(self.chain) + 1 , 'timestamp' : time(), 'transactions' : self.current_transactions, 'proof' : proof, 'previous_hash' : previous_hash or self.hash(self.chain[ -1 ]), } # Reset the current list of transactions self.current_transactions = [] self.chain.append(block) return block def new_transaction(self, sender, recipient, amount): "" " Creates a new transaction to go into the next mined Block :param sender: <str> Address of the Sender :param recipient: <str> Address of the Recipient :param amount: <int> Amount :return: <int> The index of the Block that will hold this transaction " "" self.current_transactions.append({ 'sender' : sender, 'recipient' : recipient, 'amount' : amount, }) return self.last_block[ 'index' ] + 1 @property def last_block(self): return self.chain[ -1 ] @staticmethod def hash(block): "" " Creates a SHA-256 hash of a Block :param block: <dict> Block :return: <str> " "" # We must make sure that the Dictionary is Ordered, or we 'll have inconsistent hashes block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest()

Lo anterior debería ser sencillo: he agregado algunos comentarios y cadenas de documentación para ayudar a mantenerlo claro. Casi hemos terminado de representar nuestra cadena de bloques. Pero en este punto, debe preguntarse cómo se crean, forjan o extraen nuevos bloques.

Comprender la prueba de trabajo

Un algoritmo de prueba de trabajo (PoW) es cómo se crean o extraen nuevos bloques en la cadena de bloques . El objetivo de PoW es descubrir un número que resuelva un problema. El número debe ser difícil de encontrar pero fácil de verificar —computacionalmente hablando— por cualquiera en la red. Esta es la idea central detrás de la Prueba de trabajo.

Veremos un ejemplo muy simple para ayudar a que esto se asiente.

Decidamos que el hash de algún entero x multiplicado por otro y debe terminar en 0. Entonces,

 hash(x * y) = ac23dc...0
. Y para este ejemplo simplificado, arreglemos
 x = 5
. Implementando esto en Python:

 from hashlib import sha256 x = 5 y = 0 # We don 't know what y should be yet... while sha256(f' {x*y} '.encode()).hexdigest()[-1] != "0": y += 1 print(f' The solution is y = {y} ')

La solución aquí es

 y = 21
. Dado que el hash producido termina en 0:

 hash( 5 * 21 ) = 1253e9373 e.. .5e3600155 e860

En Bitcoin, el algoritmo de Prueba de trabajo se llama Hashcash . Y no es muy diferente de nuestro ejemplo básico anterior. Es el algoritmo que los mineros se apresuran a resolver para crear un nuevo bloque. En general, la dificultad está determinada por el número de caracteres buscados en una cadena. Luego, los mineros son recompensados por su solución al recibir una moneda, en una transacción.

La red puede verificar fácilmente su solución.

Implementación de Prueba de trabajo básica

Implementemos un algoritmo similar para nuestra cadena de bloques. Nuestra regla será similar al ejemplo anterior:

Encuentre un número p que cuando se haga hash con la solución del bloque anterior sea un hash con 4 a la izquierda
 0s
es producido.
 import hashlib import json from time import time from uuid import uuid4 class Blockchain ( object ): ... def proof_of_work ( self , last_proof ): """ Simple Proof of Work Algorithm : - Find a number p ' such that hash ( pp ') contains leading 4 zeroes , where p is the previous p ' - p is the previous proof , and p ' is the new proof : param last_proof : < int > : return : < int > """ proof = 0 while self.valid_proof(last_proof, proof) is False: proof += 1 return proof @staticmethod def valid_proof(last_proof, proof): "" " Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes? :param last_proof: <int> Previous Proof :param proof: <int> Current Proof :return: <bool> True if correct, False if not. " "" guess = f '{last_proof}{proof}' .encode() guess_hash = hashlib.sha256(guess).hexdigest() return guess_hash[: 4 ] == "0000"

Para ajustar la dificultad del algoritmo, podríamos modificar el número de ceros iniciales. Pero 4 es suficiente. Descubrirá que la adición de un solo cero inicial hace una gran diferencia en el tiempo requerido para encontrar una solución.

Nuestra clase está casi completa y estamos listos para comenzar a interactuar con ella mediante solicitudes HTTP.

Paso 2: Nuestra Blockchain como API

Vamos a utilizar Python Flask Framework. Es un micromarco y facilita la asignación de puntos finales a las funciones de Python. Esto nos permite hablar con nuestra cadena de bloques a través de la web mediante solicitudes HTTP.

Crearemos tres métodos:

  •  /transactions/new
    para crear una nueva transacción a un bloque
  •  /mine
    para decirle a nuestro servidor que extraiga un nuevo bloque.
  •  /chain
    para devolver la Blockchain completa

Configuración de matraz

Nuestro "servidor" formará un solo nodo en nuestra red blockchain. Vamos a crear un código repetitivo:

 import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask class Blockchain ( object ): ... # Instantiate our Node app = Flask(__name__) # Generate a globally unique address for this node node_identifier = str(uuid4()).replace( '-' , '' ) # Instantiate the Blockchain blockchain = Blockchain() @app.route( '/mine' , methods=[ 'GET' ]) def mine(): return "We'll mine a new Block" @app.route( '/transactions/new' , methods=[ 'POST' ]) def new_transaction(): return "We'll add a new transaction" @app.route( '/chain' , methods=[ 'GET' ]) def full_chain(): response = { 'chain' : blockchain.chain, 'length' : len(blockchain.chain), } return jsonify(response), 200 if __name__ == '__main__' : app.run(host= '0.0.0.0' , port= 5000 )

Una breve explicación de lo que hemos añadido anteriormente:

  • Línea 15: Instancia nuestro Node. Lea más sobre Flask aquí .
  • Línea 18: Cree un nombre aleatorio para nuestro nodo.
  • Línea 21: instanciar nuestro
     Blockchain
    clase.
  • Línea 24–26: Cree el
     /mine
    punto final, que es un
     GET
    solicitud.
  • Línea 28–30: Cree el
     /transactions/new
    punto final, que es un
     POST
    solicitud, ya que le enviaremos datos.
  • Línea 32–38: Cree el
     /chain
    punto final, que devuelve el Blockchain completo.
  • Línea 40–41: Ejecuta el servidor en el puerto 5000.

El punto final de transacciones

Así es como se verá la solicitud de una transacción. Es lo que el usuario envía al servidor:

 { "sender" : "my address" , "recipient" : "someone else's address" , "amount" : 5 }

Como ya tenemos nuestro método de clase para agregar transacciones a un bloque, el resto es fácil. Escribamos la función para sumar transacciones:

 import hashlib import json from textwrap import dedent from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @app.route( '/transactions/new' , methods=[ 'POST' ]) def new_transaction(): values = request.get_json() # Check that the required fields are in the POST 'ed data required = [' sender ', ' recipient ', ' amount '] if not all(k in values for k in required): return ' Missing values ', 400 # Create a new Transaction index = blockchain.new_transaction(values[' sender '], values[' recipient '], values[' amount ']) response = {' message ': f' Transaction will be added to Block {index} '} return jsonify(response), 201

( Un método para crear Transacciones)

El punto final de la minería

Nuestro punto final de minería es donde ocurre la magia, y es fácil. Tiene que hacer tres cosas:

  • Calcular la prueba de trabajo
  • Recompense al minero (nosotros) agregando una transacción que nos otorgue 1 moneda
  • Forja el nuevo Bloque añadiéndolo a la cadena.
  •  import hashlib import json from time import time from uuid import uuid4 from flask import Flask, jsonify, request ... @app.route( '/mine' , methods=[ 'GET' ]) def mine(): # We run the proof of work algorithm to get the next proof... last_block = blockchain.last_block last_proof = last_block[ 'proof' ] proof = blockchain.proof_of_work(last_proof) # We must receive a reward for finding the proof. # The sender is "0" to signify that this node has mined a new coin. blockchain.new_transaction( sender= "0" , recipient=node_identifier, amount= 1 , ) # Forge the new Block by adding it to the chain previous_hash = blockchain.hash(last_block) block = blockchain.new_block(proof, previous_hash) response = { 'message' : "New Block Forged" , 'index' : block[ 'index' ], 'transactions' : block[ 'transactions' ], 'proof' : block[ 'proof' ], 'previous_hash' : block[ 'previous_hash' ], } return jsonify(response), 200


Tenga en cuenta que el destinatario del bloque extraído es la dirección de nuestro nodo. Y la mayor parte de lo que hemos hecho aquí es simplemente interactuar con los métodos de nuestra clase Blockchain. En este punto, hemos terminado y podemos comenzar a interactuar con nuestra cadena de bloques.

Paso 3: Interactuando con nuestra Blockchain

Puede usar cURL o Postman para interactuar con nuestra API a través de una red.

Enciende el servidor:

 $ python blockchain.py * Running on http: //127.0.0.1:5000/ (Press CTRL+C to quit)

Intentemos minar un bloque haciendo un

 GET
solicitud de
 http://localhost:5000/mine
:

( Usando Postman para hacer una solicitud GET)

Vamos a crear una nueva transacción haciendo un

 POST
solicitud de
 http://localhost:5000/transactions/new
con un cuerpo que contiene nuestra estructura de transacción:

( Uso de Postman para hacer una solicitud POST)

Si no está utilizando Postman, puede realizar la solicitud equivalente utilizando cURL:

 $ curl -X POST -H "Content-Type: application/json" -d '{ "sender": "d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address", "amount": 5 }' "http://localhost:5000/transactions/new"

Reinicié mi servidor y extraje dos bloques, para dar 3 en total. Inspeccionemos la cadena completa solicitando

 http://localhost:5000/chain

 { "chain" : [ { "index" : 1 , "previous_hash" : 1 , "proof" : 100 , "timestamp" : 1506280650.770839 , "transactions" : [] }, { "index" : 2 , "previous_hash" : "c099bc...bfb7" , "proof" : 35293 , "timestamp" : 1506280664.717925 , "transactions" : [ { "amount" : 1 , "recipient" : "8bbcb347e0634905b0cac7955bae152b" , "sender" : "0" } ] }, { "index" : 3 , "previous_hash" : "eff91a...10f2" , "proof" : 35089 , "timestamp" : 1506280666.1086972 , "transactions" : [ { "amount" : 1 , "recipient" : "8bbcb347e0634905b0cac7955bae152b" , "sender" : "0" } ] } ], "length" : 3 }

Paso 4: Consenso

Esto es muy genial. Tenemos una cadena de bloques básica que acepta transacciones y nos permite extraer nuevos bloques. Pero el objetivo de Blockchains es que deberían estar descentralizados . Y si están descentralizados, ¿cómo diablos nos aseguramos de que todos reflejen la misma cadena? Esto se llama el problema del Consenso , y tendremos que implementar un Algoritmo de Consenso si queremos más de un nodo en nuestra red.

Registro de nuevos nodos

Antes de que podamos implementar un algoritmo de consenso, necesitamos una forma de informar a un nodo sobre los nodos vecinos en la red. Cada nodo de nuestra red debe mantener un registro de otros nodos de la red. Por lo tanto, necesitaremos algunos puntos finales más:

  •  /nodes/register
    para aceptar una lista de nuevos nodos en forma de URL.
  •  /nodes/resolve
    para implementar nuestro algoritmo de consenso, que resuelve cualquier conflicto, para garantizar que un nodo tenga la cadena correcta.

Tendremos que modificar el constructor de nuestra Blockchain y proporcionar un método para registrar nodos:

 ... from urllib.parse import urlparse ... class Blockchain(object): def __init__(self): ... self.nodes = set() ... def register_node(self, address): "" " Add a new node to the list of nodes :param address: <str> Address of node. Eg. 'http://192.168.0.5:5000' :return: None " "" parsed_url = urlparse(address) self.nodes.add(parsed_url.netloc)

    ( Un método para agregar nodos vecinos a nuestra Red)

Tenga en cuenta que hemos utilizado un

 set()
para contener la lista de nodos. Esta es una forma económica de garantizar que la adición de nuevos nodos sea idempotente, lo que significa que no importa cuántas veces agreguemos un nodo específico, aparecerá exactamente una vez.

Implementando el Algoritmo de Consenso

Como se mencionó, un conflicto es cuando un nodo tiene una cadena diferente a otro nodo. Para resolver esto, estableceremos la regla de que la cadena válida más larga tiene autoridad. En otras palabras, la cadena más larga de la red es la de facto . Usando este algoritmo, llegamos a un consenso entre los nodos de nuestra red.

 ... import requests class Blockchain ( object ) ... def valid_chain ( self , chain ): """ Determine if a given blockchain is valid : param chain : < list > A blockchain : return : < bool > True if valid , False if not """ last_block = chain[ 0 ] current_index = 1 while current_index < len(chain): block = chain[current_index] print(f '{last_block}' ) print(f '{block}' ) print( "\n-----------\n" ) # Check that the hash of the block is correct if block[ 'previous_hash' ] != self.hash(last_block): return False # Check that the Proof of Work is correct if not self.valid_proof(last_block[ 'proof' ], block[ 'proof' ]): return False last_block = block current_index += 1 return True def resolve_conflicts(self): "" " This is our Consensus Algorithm, it resolves conflicts by replacing our chain with the longest one in the network. :return: <bool> True if our chain was replaced, False if not " "" neighbours = self.nodes new_chain = None # We 're only looking for chains longer than ours max_length = len(self.chain) # Grab and verify the chains from all the nodes in our network for node in neighbours: response = requests.get(f' http: //{node}/chain') if response.status_code == 200 : length = response.json()[ 'length' ] chain = response.json()[ 'chain' ] # Check if the length is longer and the chain is valid if length > max_length and self.valid_chain(chain): max_length = length new_chain = chain # Replace our chain if we discovered a new , valid chain longer than ours if new_chain: self.chain = new_chain return True return False

el primer metodo

 valid_chain()
es responsable de verificar si una cadena es válida recorriendo cada bloque y verificando tanto el hash como la prueba.

 resolve_conflicts()
es un método que recorre todos nuestros nodos vecinos, descarga sus cadenas y las verifica utilizando el método anterior. Si se encuentra una cadena válida, cuya longitud es mayor que la nuestra, reemplazamos la nuestra.

Registremos los dos puntos finales en nuestra API, uno para agregar nodos vecinos y otro para resolver conflictos:

 @app.route( '/nodes/register' , methods=[ 'POST' ]) def register_nodes(): values = request.get_json() nodes = values.get( 'nodes' ) if nodes is None: return "Error: Please supply a valid list of nodes" , 400 for node in nodes: blockchain.register_node(node) response = { 'message' : 'New nodes have been added' , 'total_nodes' : list(blockchain.nodes), } return jsonify(response), 201 @app.route( '/nodes/resolve' , methods=[ 'GET' ]) def consensus(): replaced = blockchain.resolve_conflicts() if replaced: response = { 'message' : 'Our chain was replaced' , 'new_chain' : blockchain.chain } else : response = { 'message' : 'Our chain is authoritative' , 'chain' : blockchain.chain } return jsonify(response), 200

En este punto, puede tomar una máquina diferente si lo desea y activar diferentes nodos en su red. O active procesos usando diferentes puertos en la misma máquina. Hice girar otro nodo en mi máquina, en un puerto diferente, y lo registré con mi nodo actual. Por lo tanto, tengo dos nodos:

 http://localhost:5000
y
 http://localhost:5001
.

( Registro de un nuevo Nodo)

Luego extraje algunos bloques nuevos en el nodo 2 para asegurarme de que la cadena fuera más larga. Después, llamé

 GET /nodes/resolve
en el nodo 1, donde la cadena fue reemplazada por el algoritmo de consenso:

( Algoritmo de consenso en el trabajo)

Y eso es un resumen... Reúna a algunos amigos para que lo ayuden a probar su Blockchain.

Espero que esto te haya inspirado para crear algo nuevo. Estoy entusiasmado con las criptomonedas porque creo que Blockchains cambiará rápidamente la forma en que pensamos sobre las economías, los gobiernos y el mantenimiento de registros.

Actualización: planeo continuar con la Parte 2, en la que ampliaremos nuestra cadena de bloques para tener un mecanismo de validación de transacciones, así como también discutiremos algunas formas en las que puede producir su cadena de bloques.

Si disfrutó de esta guía, o tiene alguna sugerencia o pregunta, hágamelo saber en los comentarios. Y si ha detectado algún error, no dude en contribuir con el código aquí .

¿Está de acuerdo o en desacuerdo con algunas de las ideas presentadas en este artículo?Dejar
nosotros sabemos sus pensamientos sobre Digg.

¿ Busca más artículos sobre Blockchain ? ¡Suscríbase a nuestro boletín en el pie de página a continuación!

Echa un vistazo a nuestro podcast sobre blockchain.