text: import hashlib
  import json
  from time import time
  from time import sleep
  from urllib.parse import urlparse
  from uuid import uuid4
  import random
  import threading
  import datetime
  
  import requests
  from flask import Flask, jsonify, request
  
  class Blockchain:
      def __init__(self):
          self.current_transactions = []
          self.chain = []
          self.nodes = set()
          self.BLOCKCHAIN_ADDRESS = 'http://127.0.0.1:2000'
  
          # Create the genesis block
          self.new_genesis_block(previous_hash='1', proof=100, block_transactions=[])
  
          # Add first neighbor node
          self.register_node("http://127.0.0.1:5000")
  
      def register_node(self, address):
          """
          Add a new node to the list of nodes
  
          :param address: Address of node. Eg. 'http://192.168.0.5:5000'
          """
  
          parsed_url = urlparse(address)
          if parsed_url.netloc:
              self.nodes.add(parsed_url.netloc)
          elif parsed_url.path:
              # Accepts an URL without scheme like '192.168.0.5:5000'.
              self.nodes.add(parsed_url.path)
          else:
              raise ValueError('Invalid URL')
  
      def resolve_nodes(self, node_address):
          """
          Spread node list to neighbor nodes
          """
          # TODO: Ability to remove adress completely from network
          neighbors = self.nodes
          if len(neighbors) > 1:
              payload = {'nodes': list(neighbors)}
              headers = {'content-type': 'application/json'}
              for node in neighbors:
                  # Do not request node list from itself
                  if node != node_address:
                      response = requests.post(url=f'http://{node}/nodes/register', json=payload, headers=headers)
                      requests.get(url='http://127.0.0.1:4000/report_traffic')
  
      def valid_chain(self, chain):
          """
          Determine if a given blockchain is valid
  
          :param chain: A blockchain
          :return: True if valid, False if not
          """
  
          last_block = chain[0]
          current_index = 1
  
          while current_index < len(chain):
              block = chain[current_index]
  
              # Check that the hash of the block is correct
              last_block_hash = self.hash(last_block)
              if block['previous_hash'] != last_block_hash:
                  return False
  
              # Check that the Proof of Work is correct
              if not self.valid_proof(last_block['proof'], block['proof'], last_block_hash):
                  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: 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')
              requests.get(url='http://127.0.0.1:4000/report_traffic')
  
              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
  
  
      def compose_block_transactions(self):
          # Max size of block in "kilobytes"
          max_size = 2000
          block_size = 0
          block_transactions = []
  
          for transaction in self.current_transactions:
              transaction_size = transaction['size']
              if (transaction_size + block_size) > max_size:
                  return block_transactions
              else:
                  if transaction:
                      block_transactions.append(transaction)
                      block_size += transaction_size
          return block_transactions
  
          # if self.chain:
          #     while True:
          #         if self.current_transactions:
          #             transaction_size = self.current_transactions[0]['size']
          #             if (transaction_size + block_size) <= max_size:
          #                 block_transactions.append(self.current_transactions.pop(0))
          #                 #del self.current_transactions[0]
          #                 block_size += transaction_size
          #             else:
          #                 break
          #         else:
          #             # Put transactions back in front of queue
          #             for e in reversed(block_transactions):
          #                 self.current_transactions.insert(0, e)
          #             return []
          # return block_transactions
  
  
  
      def new_block(self, proof, previous_hash, block_transactions, node_identifier):
          """
          Create a new Block in the Blockchain
  
          :param proof: The proof given by the Proof of Work algorithm
          :param previous_hash: Hash of previous Block
          :return: New Block
          """
          # Ensure we are the longest chain
          #self.resolve_conflicts()
          block_size = 0
          for t in block_transactions:
              block_size += t['size']
  
          block = {
              'index': self.last_block()['index'] + 1,
              'timestamp': str(datetime.datetime.now()),
              'transactions': block_transactions,
              'proof': proof,
              'previous_hash': previous_hash or self.hash(self.chain[-1]),
              'size': block_size,   # 2MB max size
              'node': node_identifier
          }
  
          #self.chain.append(block)
          payload = {
              'block': block, 
              'node_id': node_identifier, 
              'node_address': node_address,
              'current_transactions': self.current_transactions
          }
  
          for tx in block_transactions:
              if tx in self.current_transactions:
                  index = self.current_transactions.index(tx)
                  self.current_transactions.pop(index)
  
          for node in self.nodes:
              if node != node_address:
                  r = requests.post(url=f'http://{node}/validate', json=block)
                  requests.get(url='http://127.0.0.1:4000/report_traffic')
                  if r.status_code == 200:
                      requests.get(url=f'http://{node}/mine/stop')
                      requests.get(url='http://127.0.0.1:4000/report_traffic')
                      blockchain.sync_transactions(node)
                  else:
                      # Restart nodes that were shut off if validation fails
                      for n in self.nodes:
                          requests.get(url=f'http://{n}/mine')
                          requests.get(url='http://127.0.0.1:4000/report_traffic')
                          return False
  
          stop_mining()
          r = requests.post(url=f'{self.BLOCKCHAIN_ADDRESS}/append_block_old', json=payload)
          requests.get(url='http://127.0.0.1:4000/report_traffic')
          if r.status_code == 200:
              # DEBUGGING
              #self.generate_log(f'Current transactions: {len(self.current_transactions)}')
              # tx = block_transactions[0]
              # if tx in self.current_transactions:
              #     self.generate_log(f'Transaction found in current_transactions')
              # else:
              #     self.generate_log(f'Rogue transaction found!')
              # ----------
  
              # for tx in block_transactions:
              #     if tx in self.current_transactions:
              #         index = self.current_transactions.index(tx)
              #         self.current_transactions.pop(index)
              self.generate_log(f'Current transactions: {len(self.current_transactions)}')
  
              for node in self.nodes:
                  self.sync_transactions(node)
              # Resume mining
              mine()
              for node in self.nodes:
                  requests.get(url=f'http://{node}/mine')
                  requests.get(url='http://127.0.0.1:4000/report_traffic')
              return True
          else:
              for node in self.nodes:
                  requests.get(url=f'http://{node}/mine')
                  requests.get(url='http://127.0.0.1:4000/report_traffic')
              return False
  
  
      def new_genesis_block(self, proof, previous_hash, block_transactions):
          if not self.chain:
              block_size = 0
              for t in block_transactions:
                  block_size += t['size']
  
              block = {
                  'index': len(self.chain) + 1,
                  'timestamp': time(),
                  'transactions': block_transactions,
                  'proof': proof,
                  'previous_hash': previous_hash or self.hash(self.chain[-1]),
                  'size': block_size,   # 2MB max size
                  'node': node_identifier
              }
  
              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: Address of the Sender
          :param recipient: Address of the Recipient
          :param amount: Amount
          :return: The index of the Block that will hold this transaction
          """
          self.current_transactions.append({
              'sender': sender,
              'recipient': recipient,
              'amount': amount,
              'size': random.randint(10,100),         # Simulated size in kilobytes
              'id': str(uuid4()).replace('-', '')     # Unique ID
          })
  
          return self.last_block()['index'] + 1
  
  
      #@property
      def last_block(self):
          r = requests.get(url=f'{self.BLOCKCHAIN_ADDRESS}/get_chain')
          requests.get(url='http://127.0.0.1:4000/report_traffic')
          while r.status_code != 200:
              r = requests.get(url=f'{self.BLOCKCHAIN_ADDRESS}/get_chain')
              requests.get(url='http://127.0.0.1:4000/report_traffic')
              sleep(0.5)
          return r.json()['chain'][-1]
  
  
      @staticmethod
      def hash(block):
          """
          Creates a SHA-256 hash of a Block
  
          :param block: Block
          """
  
          # 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()
  
  
      def proof_of_work(self, last_block):
          """
          Simple Proof of Work Algorithm:
  
           - Find a number p' such that hash(pp') contains leading 4 zeroes
           - Where p is the previous proof, and p' is the new proof
           
          :param last_block: <dict> last Block
          :return: <int>
          """
  
          last_proof = last_block['proof']
          last_hash = self.hash(last_block)
  
          proof = random.randint(0, 100000)
          while self.valid_proof(last_proof, proof, last_hash) is False:
              proof += 1
          # Simulated mining
          #sleep(random.randint(1,4))
          return proof
  
  
      @staticmethod
      def valid_proof(last_proof, proof, last_hash):
          """
          Validates the Proof
  
          :param last_proof: <int> Previous Proof
          :param proof: <int> Current Proof
          :param last_hash: <str> The hash of the Previous Block
          :return: <bool> True if correct, False if not.
          """
  
          guess = f'{last_proof}{proof}{last_hash}'.encode()
          guess_hash = hashlib.sha256(guess).hexdigest()
          return guess_hash[:5] == "00000"         # Hash made easy to simulate mining
      
      def sync_transactions(self, node):
          requests.get(url='http://127.0.0.1:4000/report_traffic')
          return requests.post(url=f'http://{node}/transactions/update', json=self.current_transactions)
      
      def generate_log(self, event):
          payload = {
              'miner_id': node_identifier,
              'manager_id': node_address,
              'event': event,
              'time': str(datetime.datetime.now())
          }
          # Send data to logging node
          requests.post(url='http://127.0.0.1:3000/report', json=payload)
      
  
  # Instantiate the Node
  app = Flask(__name__)
  
  # Generate a globally unique address for this node
  node_identifier = str(uuid4()).replace('-', '')
  node_address = ""
  
  # Instantiate the Blockchain
  blockchain = Blockchain()
  
  # Activates / Deactivates mining process
  is_mining = True
  mine_loop_done = True
  
  # Activates / Deactivates node list syncing process
  is_syncing = True
  
  # Asynchronous mining
  class Mine(threading.Thread):
      def __init__(self, task_id):
          threading.Thread.__init__(self)
          self.task_id = task_id
  
      def run(self):
          while True:
              while is_mining:
                  #mine_loop_done = False
  
                  # Compose list of transactions of block
                  block_transactions = blockchain.compose_block_transactions()
                  if block_transactions:
                      # We run the proof of work algorithm to get the next proof...
                      last_block = blockchain.last_block()
                      proof = blockchain.proof_of_work(last_block)
  
                      # Forge the new Block by adding it to the chain
                      previous_hash = blockchain.hash(last_block)
                      blockchain.new_block(proof, previous_hash, block_transactions, node_identifier)
                  sleep(0.1)
  
              #mine_loop_done = True
              sleep(0.2)
  
  
  class Sync(threading.Thread):
      def __init__(self, task_id):
          threading.Thread.__init__(self)
          self.task_id = task_id
  
      def run(self):
          while is_syncing:
              blockchain.resolve_nodes(node_address)
              sleep(8)
  
  
  @app.route('/mine', methods=['GET'])
  def mine():
      # while not mine_loop_done:
      #     sleep(0.1)
      global is_mining
      if is_mining:
          return 'Node is already mining', 400
      else:
          is_mining = True
          return 'Started mining process', 200
  
      # async_task = Mine(task_id=1)
      # try:
      #     with app.test_request_context():
      #         async_task.start()
      #     return 'Started mining process', 200
      # except RuntimeError:
      #     return 'Node is already mining', 400
  
  
  @app.route('/mine/stop', methods=['GET'])
  def stop_mining():
      global is_mining
      is_mining = False
      # while not mine_loop_done:
      #     sleep(0.1)
      return 'Mining process stopped', 200
  
  
  @app.route('/sync', methods=['GET'])
  def sync_nodes():
      global is_syncing
      is_syncing = True
      async_task = Sync(task_id=2)
      try:
          with app.test_request_context():
              async_task.start()
          return 'Started syncing node lists', 200
      except RuntimeError:
          return 'Node is already syncing', 400
  
  
  @app.route('/sync/stop', methods=['GET'])
  def stop_syncing():
      global is_syncing
      is_syncing = False
      return 'Syncing process stopped', 400
  
  
  @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
  
  
  @app.route('/transactions', methods=['GET'])
  def get_transactions():
      response = {
          'transactions': blockchain.current_transactions,
          'size': len(blockchain.current_transactions)
      }
      return jsonify(response), 200
  
  
  @app.route('/chain', methods=['GET'])
  def full_chain():
      response = {
          'chain': blockchain.chain,
          'length': len(blockchain.chain),
      }
      return jsonify(response), 200
  
  
  @app.route('/nodes', methods=['GET'])
  def get_nodes():
      response = {
          'nodes': list(blockchain.nodes)
      }
      return jsonify(response), 200
  
  
  @app.route('/nodes/register', methods=['POST'])
  def register_nodes():
      values = request.get_json()
  
      nodes = values['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
  
  
  # Generate transactions for testing
  @app.route('/transactions/generate', methods=['POST'])
  def generate_transactions():
      values = request.get_json()
      number = values.get('number')
      new_transactions = []
  
      for i in range(0, number):
          amount = random.randint(1,1000)
          sender = random.randint(1,100)
          recipient = random.randint(1,100)
          while recipient == sender:
              recipient = random.randint(1,100)
          
          new_transactions.append({
              'sender': sender,
              'recipient': recipient,
              'amount': amount,
              'size': random.randint(10,100),         # Simulated size in kilobytes
              'id': str(uuid4()).replace('-', '')     # Unique ID
          })
          #blockchain.new_transaction(sender, recipient, amount)
  
      blockchain.current_transactions.extend(new_transactions)
  
      return f'{number} transactions generated!'
  
  
  @app.route('/transactions/update', methods=['POST'])
  def update_transactions():
      new_transactions = request.get_json()
      blockchain.current_transactions = new_transactions
      return 'Transactions updated!', 200
  
  
  @app.route('/validate', methods=['POST'])
  def validate_block():
      block = request.get_json()
      if int(block['index']) == int(blockchain.last_block()['index'])+1:
          #manager.generate_log('If 1 korrekt block index')
          last_block_hash = blockchain.hash(blockchain.last_block())
          if block['previous_hash'] != last_block_hash:
              #manager.generate_log('If 1.1 invalid block hash')
              return 'Invalid block hash', 400
          if not blockchain.valid_proof(blockchain.last_block()['proof'], block['proof'], last_block_hash):
              #manager.generate_log('If 1.2 invalid block proof')
              return 'Invalid block proof', 400
          else:
              #manager.generate_log('If 1.3 Correct block')
              return 'Block correct, add to chain', 200
      else:
          #manager.generate_log('Else 2 invalid block index')
          return "Invalid index", 400
  
  
  # Initialization --------------------
  # Activate syncing of node lists
  sync_nodes()
  
  # Activate mining
  #mine()
  async_task = Mine(task_id=1)
  try:
      with app.test_request_context():
          async_task.start()
  except:
      raise Exception
  
  
  if __name__ == '__main__':
      from argparse import ArgumentParser
  
      parser = ArgumentParser()
      parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
      args = parser.parse_args()
      port = args.port
  
      # Add own address to node list
      address = 'http://127.0.0.1:{}'.format(port)
      blockchain.register_node(address)
      node_address = address
  
      # Start flask app
      app.run(host='127.0.0.1', port=port, threaded=False)