<jupyter_start><jupyter_text><jupyter_code>pip install flask-ngrok
import datetime
import hashlib
import json
import requests
from uuid import uuid4
from flask  import Flask, jsonify, request
from urllib.parse import urlparse
from flask_ngrok import run_with_ngrok
class Blockchain:

  def __init__(self) :
      """Constructor del Blockchain"""
      self.chain = []
      self.transactions = []
      self.create_block(proof=1, previous_hash= '0')
      self.nodes = set()

  def create_block(self, proof, previous_hash):
    """Cracion de un nuevo Bloque:

    Arguments:
      -proof:Nounce del Bloque actual
      -previous_hash: Hash del bloque previo
      
    Returns:
      -Block: Devolucion Nuevo bloque creado.
      """
    block={
            'index'         : len(self.chain)+1,
            'Timestamp'     : str(datetime.datetime.now()),
            'proof'         : proof,
            'previous_hash' : previous_hash,
            'transactions'  : self.transactions
          }
    self.transactions = []
    self.chain.append(block)
    return block
  
  def get_previous_block(self):
    """Obtencion del bloque anterior
    Return:
      -Devolucion del ultimo bloque de la blockchain.
    """
    return self.chain[-1]

  def proof_of_work(self, previous_proof):
    """Protocolo de concenco (PoW)

    Arguments:
      -previous_proof: Hash del bloque previo

    Return:
      -new_proof: Devolucion del nuevo hash obtenido del PoW
    """
    new_proof=1

    chech_proof=False

    while chech_proof == False:
      hash_operation = hashlib.SHA256(str(new_proof**2-previous_proof**2).encode()).hexidiges()

      if hash_operation[:4] == '0000':

        chech_proof == True

      else:
         
        new_proof += 1

    return new_proof
  
  def hash(self, block):
    """Calculo del Hash de un bloque
    
    Arguments:
      -block : Identifica a un bloque de Blockchain

    Returns:
      -hash_block: Devuelve el hash del bloque
    """

    encoded_block = json.dumps(block, sort_keys=True).encode()
    hash_block = hashlib.SHA256(encoded_block).hexidiges()

    return hash_block
  
  def is_chain_valid(self, chain):
    """Verifica si la Blockchain es valida
    
    Arguments:
      -chain : cadena de bloques que contiene toda la informacion de las transacciones

    Returns:
      -True/False: Devuelve un booleano en funcion de la validez de la blockchain
                  (True: valido , False:Invalido)
    """
    previous_block = chain[0]
    block_index=1

    while block_index < len(chain):

      block = chain[block_index]
      
      if block['previous_hash'] != self.hash(previous_block):

        return False
      
      previous_proof = previous_block['proof']
      proof= block['proof']
      hash_operation = hashlib.SHA256(str(proof**2-previous_proof**2).encode()).hexidiges()

      if hash_operation[:4] != '0000':
        
        return False

      previous_block = block
      block_index += 1
    
    return True
  
  def add_transaction(self, sender, receiver, amount):
    """ Realizacion de transaccion
      
      Arguments:
        - sender: Persona que hace la Transaccion
        - receiver : Persona que resive la Transaccion
        -amount : Monto de la Transaccion

      Returns:
        -Devolucion del indice superior al ultimo bloque
    """
    self.trnsactions.append({'sender'   :sender,
                             'receiver' : receiver,
                             'amount'   :amount})
    previous_block = self.get_previous_block()
    return previous_block['index']+1

  def add_node(self, address):
    """ Nuevo nodo en la Blockchain
      
      Arguments:
        -address: Direccion del nuevo nodo
    """
    parsed_url=urlparse(address)
    self.nodes.add(parsed_url.netloc)

  def replace_chain(self):
    """Reemplazo de la cadena por la cadena mas larga,
    siempre y cuando sea valida"""

    network= self.nodes
    longest_chain = None
    max_length = len(self.chain)
    for node in network:
      response=requests.get(f'http://{node}/get_chain')
      if response.status_code == 200:
        length = response.json()['length']
        chain = response.json()['chain']
      if length > max_length and self.is_chain_valid(chain):
        max_length = length
        longest_chain = chain
    if longest_chain:
      self.chain = longest_chain
      return True
    return False



#Crear una app web
#Ejecucion de la app con Flask
app = Flask(__name__)
run_with_ngrok(app)

#Si se obtiene un error 500, actualizar flask y ejecutar la siguiente linea
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False

# Crear la direccion del nodo en el puerto 500
node_address = str(uuid4()).replace('-','')

#Creacion de la Blockchain
blockchain = Blockchain()

@app.route('/mine_block', methods =['GET'])
def mine_block():
  """Minado de un bloque"""

  previous_block = blockchain.get_previous_block()
  previous_proof = previous_block['proof']
  proof = blockchain.proof_of_work(previous_proof)
  previous_hash = blockchain.hash(previous_block)
  blockchain.add_transaction(sender = node_address, receiver= 'JuanAmengual' , amount =10)
  block = blockchain.create_block(proof, previous_hash)
  response = {'message'       :'Good work, a block was mined!',
              'index'         : block['index'],
              'Timestamp'     : block['Timestamp'],
              'proof'         : block['proof'],
              'previous_hash' : block['previous_hash'],  
              'transactions' : block['transactions'],                
             }
  return jsonify(response), 200


@app.route('/get_chain', methods = ['GET'])
def get_chain():
  """ Obtencion de Blockchain """
  response = {'(chain'    : blockchain.chain,
              'length'   : len(blockchain.chain)       
             }
  return jsonify(response), 200

@app.route('/is_valid', methods = ['GET'])
def is_valid():
  """ Comprobasion si la Blockchain es valida """

  is_valid = blockchain.is_chain_valid(blockchain.chain)
  if is_valid:
    response = {'message' : 'The Blockchain es valida'}
  else:
    response = {'message' : 'The Blockchain NO es valida'}
  return jsonify(response), 200

@app.route('/add_transaction', method = ['POST'])
def add_transaction():
  json=request.get_json()
  transaction_keys=['sender', 'receiver', 'amount']
  if not all(key in json for key in transaction_keys):
    return 'Faltan algunos elementos de la transaccion'
  
  index = blockchain.add_transaction(json['sender'],json['receiver'],json['amount'])
  response = {'message' : f'La transaccion sera añadida al bloque {index}'}

  return jsonify(response), 201

# Descentalizacion de la CAdena de Bloques

# Conectar nuevos nodos
@app.route('/conect_node', method=['POST'])
def connect_node():
  json = request.get_json()
  nodes = json.get('nodes')
  if nodes is None:
    return 'No hay nodos para añadir', 400
  for node in nodes:
    blockchain.add_node(node)

  response = {'message'     : 'Todos los nodos han sido conectados. La Blockchain contiene ahora los nodos siguientes: ',
              'total_nodes' : list(blockchain.nodes)}

  return jsonify(response), 201


@app.route('/replace_chain', methods = ['GET'])
def replace_chain():
  """Reemplaza la cadena por la mas larga (si es necesario)"""

  is_chain_replaced = blockchain.replace_chain()
  if is_chain_replaced:
    response = {'message'     : 'Los nodos tenian diferentes cadenas, se ha reemplazado por la Blockchain mas larga.',
              'new_chain' : blockchain.chain}
  else:
    response = {'message'     : 'Todo Correcto. La Blockchain ya es la mas larga en todos los nodos',
              'actual_chain' : blockchain.chain}
  return jsonify(response), 201 
app.run()<jupyter_output><empty_output>
StarcoderdataJupyterStructuredCleanDedup