<jupyter_start><jupyter_text>Cryptocurrency with Python**Ángel C.** Libraries required:* **datetime*** **hashlib*** **json*** **flask*** **flask-ngrok*** **requests*** **uuid*** **urllib.parse**  Install<jupyter_code>!pip install flask==1.1.2
!pip install requests==2.25.1
!pip install flask-ngrok==0.0.25<jupyter_output>Collecting flask-ngrok==0.0.25
  Downloading flask_ngrok-0.0.25-py3-none-any.whl (3.1 kB)
Requirement already satisfied: Flask>=0.8 in /usr/local/lib/python3.7/dist-packages (from flask-ngrok==0.0.25) (1.1.2)
Requirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from flask-ngrok==0.0.25) (2.25.1)
Requirement already satisfied: Jinja2>=2.10.1 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.8->flask-ngrok==0.0.25) (2.11.3)
Requirement already satisfied: click>=5.1 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.8->flask-ngrok==0.0.25) (7.1.2)
Requirement already satisfied: itsdangerous>=0.24 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.8->flask-ngrok==0.0.25) (1.1.0)
Requirement already satisfied: Werkzeug>=0.15 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.8->flask-ngrok==0.0.25) (1.0.1)
Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from Jinja2>=2.10.1->Flask>=0.8->flask-ng[...]<jupyter_text>Cryptocurrency code<jupyter_code># Importación de las librerías
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<jupyter_output><empty_output><jupyter_text>Essential methods included:* New block creation* Obtaining hash for a block* Consensus protocol Proof of Work (PoW)* Block hash generation* Verifying Blockchain validity* Adding transaction to blockchain* Adding new node to blockchain* Replace blockchain for the correct one in a node<jupyter_code>class Blockchain:
    
  def __init__(self):
    """ Class constructor. """

    self.chain = []
    self.transactions = []
    self.create_block(proof = 1, previous_hash = '0')
    self.nodes = set()
      
  def create_block(self, proof, previous_hash):
    """ New block creation. 

      Arguments:
        - proof: Nonce of current block.
        - previous_hash: Hash of previous block.

      Returns: 
        - block: New block created. 
      """

    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):
    """ Obtaining previous block.
    
      Returns:
        - Obtaining last block. """

    return self.chain[-1]
  
  def proof_of_work(self, previous_proof):     
    """ Consensus protocol Proof of Work (PoW).
    
      Arguments:
        - previous_proof: Nonce of previous block.

      Returns:
        - new_proof: New nonce obtained with PoW. """

    new_proof = 1
    check_proof = False
    while check_proof is False:
        hash_operation = hashlib.sha256(str(new_proof**2 - previous_proof**2).encode()).hexdigest()
        if hash_operation[:4] == '0000':
            check_proof = True
        else: 
            new_proof += 1
    return new_proof
  
  def hash(self, block):
    """ Calculation of hash for a block
    Arguments:
        - block: ID of a block in the blockchain.
    
    Returns:
        - hash_block: Returns hash of the block """

    encoded_block = json.dumps(block, sort_keys = True).encode()
    hash_block = hashlib.sha256(encoded_block).hexdigest()
    return hash_block
  
  def is_chain_valid(self, chain):
    """ Determines if the blockchain is valid. 
    
    Arguments:
        - chain: Blockchain including transactions information.
    
    Returns:
        - True/False: Boolean representing blockchain validity """
                      
    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()).hexdigest()
        if hash_operation[:4] != '0000':
            return False
        previous_block = block
        block_index += 1
    return True
  
  def add_transaction(self, sender, receiver, amount):
    """ Transactions.
    
    Arguments:
        - sender: Who makes the transaction
        - receiver: Who receives the transaction
        - amount: Amount of coins sent

    Returns: 
        - Index greater than last block
    """

    self.transactions.append({'sender'  : sender,
                              'receiver': receiver, 
                              'amount'  : amount})
    previous_block = self.get_previous_block()
    return previous_block['index'] + 1

  def add_node(self, address):
    """ New node in the blockchain.
    
      Arguments:
        - address: Address of the new node
    """

    parsed_url = urlparse(address)
    self.nodes.add(parsed_url.netloc)
  
  def replace_chain(self):
    """ Replacing the chain for the longest, valid one. """
    
    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
# Blocks mining

# Creation of web app
app = Flask(__name__)
run_with_ngrok(app)  

# If it returns 500, update flask, reload spider and run the next line
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False

# Creation of address on port 5000
node_address = str(uuid4()).replace('-', '')

# Creation of blockchain
blockchain = Blockchain()


@app.route('/mine_block', methods=['GET'])
def mine_block():
  """ Mining of a new block """ 

  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 = "Miner #2", amount = 10)
  block = blockchain.create_block(proof, previous_hash)
  response = {'message'       : 'Woohoo! new block 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():
  """ Obtaining the complete chain """

  response = {'chain'   : blockchain.chain, 
              'length'  : len(blockchain.chain)}
  return jsonify(response), 200

@app.route('/is_valid', methods = ['GET'])
def is_valid():
  """ Checking validity of the chain """

  is_valid = blockchain.is_chain_valid(blockchain.chain)
  if is_valid:
      response = {'message' : 'All right! blockchain is valid.'}
  else:
      response = {'message' : 'Whoops! blockchain is NOT valid.'}
  return jsonify(response), 200  

@app.route('/add_transaction', methods = ['POST'])
def add_transaction():
  """ Adding transaction to blockchain """

  json = request.get_json()
  transaction_keys = ['sender', 'receiver', 'amount']
  if not all(key in json for key in transaction_keys):
      return 'Some arguments are missing in the transaction', 400
  index = blockchain.add_transaction(json['sender'], json['receiver'], json['amount'])
  response = {'message': f'Transaction added to blockchain {index}'}
  return jsonify(response), 201
    
# Decentralization of the blockchain

# Connecting new nodes
@app.route('/connect_node', methods = ['POST'])
def connect_node():
  json = request.get_json()
  nodes = json.get('nodes')
  if nodes is None: 
      return 'No nodes to add', 400
  for node in nodes:
      blockchain.add_node(node)
  response = {'message'     : 'All nodes have been connected. AGCcoin Blockchain includes now the following nodes: ',
              'total_nodes' : list(blockchain.nodes)}
  return jsonify(response), 201

@app.route('/replace_chain', methods = ['GET'])
def replace_chain():
  """ Replacing the chain for the longest one (if necessary) """

  is_chain_replaced = blockchain.replace_chain()
  if is_chain_replaced:
      response = {'message' : 'The nodes contained different chains and have been updated.',
                  'new_chain': blockchain.chain}
  else:
      response = {'message'       : 'All set. The blockchain included in all nodes is the current one.',
                  'actual_chain'  : blockchain.chain}
  return jsonify(response), 200  
# Ejecución de la app con Google Colab
app.run()

# Ejecución externa a Google colab
#app.run(host = '0.0.0.0', port = 5001)<jupyter_output>* Serving Flask app "__main__" (lazy loading)
 * Environment: production
[31m   WARNING: This is a development server. Do not use it in a production deployment.[0m
[2m   Use a production WSGI server instead.[0m
 * Debug mode: off
{'fraction_non_alphanumeric': 0.12129408093207115, 'fraction_numerical': 0.020597108082804537, 'mean_word_length': 3.0125208681135227, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '15253', 'license': 'MIT', 'n_tokens_mistral': 3045, 'n_tokens_neox': 2872, 'n_words': 782, 'path': 'AGCcoin_5001.ipynb', 'repo_name': 'angelcatanzariti1/cryptocurrency_with_python', 'size': '9613'}