diff --git a/.history/account_db_20241012145317.py b/.history/account_db_20241012145317.py new file mode 100644 index 0000000..227de1a --- /dev/null +++ b/.history/account_db_20241012145317.py @@ -0,0 +1,23 @@ +import json +import os +from ellipticcurve.privateKey import PrivateKey + +class AccountReader: + def __init__(self, filename="accounts.json"): + self.filename = filename + self.account_data = self.load_accounts() + + def load_accounts(self): + """Load account information from a JSON file.""" + if os.path.exists(self.filename): + try: + with open(self.filename, 'r') as f: + return json.load(f) + except IOError as e: + print(f"Error loading account data: {e}") + except json.JSONDecodeError: + print("Error decoding account data file") + else: + print(f"No accounts file found at {self.filename}") + return [] + diff --git a/.history/account_db_20241016123601.py b/.history/account_db_20241016123601.py new file mode 100644 index 0000000..227de1a --- /dev/null +++ b/.history/account_db_20241016123601.py @@ -0,0 +1,23 @@ +import json +import os +from ellipticcurve.privateKey import PrivateKey + +class AccountReader: + def __init__(self, filename="accounts.json"): + self.filename = filename + self.account_data = self.load_accounts() + + def load_accounts(self): + """Load account information from a JSON file.""" + if os.path.exists(self.filename): + try: + with open(self.filename, 'r') as f: + return json.load(f) + except IOError as e: + print(f"Error loading account data: {e}") + except json.JSONDecodeError: + print("Error decoding account data file") + else: + print(f"No accounts file found at {self.filename}") + return [] + diff --git a/.history/app_20241017101423.py b/.history/app_20241017101423.py new file mode 100644 index 0000000..4db7581 --- /dev/null +++ b/.history/app_20241017101423.py @@ -0,0 +1,230 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + blockchain.register(f'simplicity_server.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017101438.py b/.history/app_20241017101438.py new file mode 100644 index 0000000..c2d3a3f --- /dev/null +++ b/.history/app_20241017101438.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + print("chain" ,blockchain.chain) + print("chain type" ,type(blockchain.chain)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017101516.py b/.history/app_20241017101516.py new file mode 100644 index 0000000..c2d3a3f --- /dev/null +++ b/.history/app_20241017101516.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + print("chain" ,blockchain.chain) + print("chain type" ,type(blockchain.chain)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017111434.py b/.history/app_20241017111434.py new file mode 100644 index 0000000..fa872d7 --- /dev/null +++ b/.history/app_20241017111434.py @@ -0,0 +1,232 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017111435.py b/.history/app_20241017111435.py new file mode 100644 index 0000000..fa872d7 --- /dev/null +++ b/.history/app_20241017111435.py @@ -0,0 +1,232 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017111441.py b/.history/app_20241017111441.py new file mode 100644 index 0000000..c2d3a3f --- /dev/null +++ b/.history/app_20241017111441.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + print("chain" ,blockchain.chain) + print("chain type" ,type(blockchain.chain)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017111442.py b/.history/app_20241017111442.py new file mode 100644 index 0000000..c2d3a3f --- /dev/null +++ b/.history/app_20241017111442.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +def register_node(port): + print(f"Registering node with port {port}...") + print("nodes" ,blockchain.nodes) + print("nodes type" ,type(blockchain.nodes)) + print("chain" ,blockchain.chain) + print("chain type" ,type(blockchain.chain)) + blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017112341.py b/.history/app_20241017112341.py new file mode 100644 index 0000000..5c856f1 --- /dev/null +++ b/.history/app_20241017112341.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017112351.py b/.history/app_20241017112351.py new file mode 100644 index 0000000..0f024c1 --- /dev/null +++ b/.history/app_20241017112351.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017113300.py b/.history/app_20241017113300.py new file mode 100644 index 0000000..db3776e --- /dev/null +++ b/.history/app_20241017113300.py @@ -0,0 +1,241 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from g.blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +# Use app.before_request to ensure g.blockchain is initialized before each request +@app.before_request +def before_request(): + g.blockchain = blockchain + + +@app.route('/hello', methods=['GET']) +def hello(): + + return flask.jsonify({ + 'nodes': list(g.blockchain.nodes), + 'length': len(list(g.blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': g.blockchain.chain, + 'length': len(g.blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = g.blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + g.blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in g.blockchain.nodes: + g.blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + g.blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/resolve', methods=['GET']) +def consensus(): + replaced = g.blockchain.resolve_conflicts() + + if replaced: + response = { + 'message': 'Our chain was replaced', + 'new_chain': g.blockchain.chain + } + else: + response = { + 'message': 'Our chain is authoritative', + 'chain': g.blockchain.chain + } + + return flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if g.blockchain.hash(block) in g.blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in g.blockchain.current_transactions: + g.blockchain.current_transactions.remove(transaction) + + g.blockchain.chain.append(block) + g.blockchain.hash_list.add(g.blockchain.hash(block)) + + # send data to the known nodes in the network + for node in g.blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(g.blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in g.blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + g.blockchain.current_transactions.append(transaction) + g.blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in g.blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + g.blockchain.chain = [] + parent_node = response[1] + g.blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + g.blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in g.blockchain.chain: + g.blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {g.blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + g.blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(g.blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in g.blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,g.blockchain.nodes) +# print("nodes type" ,type(g.blockchain.nodes)) +# print("chain" ,g.blockchain.chain) +# print("chain type" ,type(g.blockchain.chain)) +# g.blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017113311.py b/.history/app_20241017113311.py new file mode 100644 index 0000000..db3776e --- /dev/null +++ b/.history/app_20241017113311.py @@ -0,0 +1,241 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from g.blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +# Use app.before_request to ensure g.blockchain is initialized before each request +@app.before_request +def before_request(): + g.blockchain = blockchain + + +@app.route('/hello', methods=['GET']) +def hello(): + + return flask.jsonify({ + 'nodes': list(g.blockchain.nodes), + 'length': len(list(g.blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': g.blockchain.chain, + 'length': len(g.blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = g.blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + g.blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in g.blockchain.nodes: + g.blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + g.blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/resolve', methods=['GET']) +def consensus(): + replaced = g.blockchain.resolve_conflicts() + + if replaced: + response = { + 'message': 'Our chain was replaced', + 'new_chain': g.blockchain.chain + } + else: + response = { + 'message': 'Our chain is authoritative', + 'chain': g.blockchain.chain + } + + return flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if g.blockchain.hash(block) in g.blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in g.blockchain.current_transactions: + g.blockchain.current_transactions.remove(transaction) + + g.blockchain.chain.append(block) + g.blockchain.hash_list.add(g.blockchain.hash(block)) + + # send data to the known nodes in the network + for node in g.blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(g.blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in g.blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + g.blockchain.current_transactions.append(transaction) + g.blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in g.blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + g.blockchain.chain = [] + parent_node = response[1] + g.blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + g.blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in g.blockchain.chain: + g.blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {g.blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + g.blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(g.blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in g.blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,g.blockchain.nodes) +# print("nodes type" ,type(g.blockchain.nodes)) +# print("chain" ,g.blockchain.chain) +# print("chain type" ,type(g.blockchain.chain)) +# g.blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017113605.py b/.history/app_20241017113605.py new file mode 100644 index 0000000..dc8a572 --- /dev/null +++ b/.history/app_20241017113605.py @@ -0,0 +1,241 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +# Use app.before_request to ensure g.blockchain is initialized before each request +@app.before_request +def before_request(): + g.blockchain = blockchain + + +@app.route('/hello', methods=['GET']) +def hello(): + + return flask.jsonify({ + 'nodes': list(g.blockchain.nodes), + 'length': len(list(g.blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': g.blockchain.chain, + 'length': len(g.blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = g.blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + g.blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in g.blockchain.nodes: + g.blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + g.blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(g.blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/resolve', methods=['GET']) +def consensus(): + replaced = g.blockchain.resolve_conflicts() + + if replaced: + response = { + 'message': 'Our chain was replaced', + 'new_chain': g.blockchain.chain + } + else: + response = { + 'message': 'Our chain is authoritative', + 'chain': g.blockchain.chain + } + + return flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if g.blockchain.hash(block) in g.blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in g.blockchain.current_transactions: + g.blockchain.current_transactions.remove(transaction) + + g.blockchain.chain.append(block) + g.blockchain.hash_list.add(g.blockchain.hash(block)) + + # send data to the known nodes in the network + for node in g.blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(g.blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in g.blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + g.blockchain.current_transactions.append(transaction) + g.blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in g.blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + g.blockchain.chain = [] + parent_node = response[1] + g.blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + g.blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in g.blockchain.chain: + g.blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {g.blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + g.blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(g.blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in g.blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,g.blockchain.nodes) +# print("nodes type" ,type(g.blockchain.nodes)) +# print("chain" ,g.blockchain.chain) +# print("chain type" ,type(g.blockchain.chain)) +# g.blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017113646.py b/.history/app_20241017113646.py new file mode 100644 index 0000000..0f024c1 --- /dev/null +++ b/.history/app_20241017113646.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +@app.teardown_appcontext +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115251.py b/.history/app_20241017115251.py new file mode 100644 index 0000000..f780a91 --- /dev/null +++ b/.history/app_20241017115251.py @@ -0,0 +1,238 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115252.py b/.history/app_20241017115252.py new file mode 100644 index 0000000..f780a91 --- /dev/null +++ b/.history/app_20241017115252.py @@ -0,0 +1,238 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + host_url = getattr(g, 'host_url', None) # Get the host URL safely + if host_url: + for node in blockchain.nodes: + try: + requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) + except requests.exceptions.RequestException as e: + print(f"Error notifying node {node}: {e}") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115410.py b/.history/app_20241017115410.py new file mode 100644 index 0000000..52d410d --- /dev/null +++ b/.history/app_20241017115410.py @@ -0,0 +1,231 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115438.py b/.history/app_20241017115438.py new file mode 100644 index 0000000..9591d64 --- /dev/null +++ b/.history/app_20241017115438.py @@ -0,0 +1,231 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115447.py b/.history/app_20241017115447.py new file mode 100644 index 0000000..9591d64 --- /dev/null +++ b/.history/app_20241017115447.py @@ -0,0 +1,231 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115551.py b/.history/app_20241017115551.py new file mode 100644 index 0000000..fc49872 --- /dev/null +++ b/.history/app_20241017115551.py @@ -0,0 +1,232 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115656.py b/.history/app_20241017115656.py new file mode 100644 index 0000000..c4bd8b3 --- /dev/null +++ b/.history/app_20241017115656.py @@ -0,0 +1,232 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + # database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115807.py b/.history/app_20241017115807.py new file mode 100644 index 0000000..fc49872 --- /dev/null +++ b/.history/app_20241017115807.py @@ -0,0 +1,232 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115843.py b/.history/app_20241017115843.py new file mode 100644 index 0000000..75bbc30 --- /dev/null +++ b/.history/app_20241017115843.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017115846.py b/.history/app_20241017115846.py new file mode 100644 index 0000000..75bbc30 --- /dev/null +++ b/.history/app_20241017115846.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017120108.py b/.history/app_20241017120108.py new file mode 100644 index 0000000..75bbc30 --- /dev/null +++ b/.history/app_20241017120108.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017122022.py b/.history/app_20241017122022.py new file mode 100644 index 0000000..75bbc30 --- /dev/null +++ b/.history/app_20241017122022.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017122030.py b/.history/app_20241017122030.py new file mode 100644 index 0000000..8653cfc --- /dev/null +++ b/.history/app_20241017122030.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server1.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017122034.py b/.history/app_20241017122034.py new file mode 100644 index 0000000..8785a8b --- /dev/null +++ b/.history/app_20241017122034.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017122053.py b/.history/app_20241017122053.py new file mode 100644 index 0000000..8785a8b --- /dev/null +++ b/.history/app_20241017122053.py @@ -0,0 +1,233 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017123229.py b/.history/app_20241017123229.py new file mode 100644 index 0000000..70b7a96 --- /dev/null +++ b/.history/app_20241017123229.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + blockchain.resolve_conflicts() + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017124342.py b/.history/app_20241017124342.py new file mode 100644 index 0000000..e4c0dc0 --- /dev/null +++ b/.history/app_20241017124342.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + blockchain.resolve_conflicts() + database = BlockchainDb() + database.save_blockchain(blockchain) + # database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017124401.py b/.history/app_20241017124401.py new file mode 100644 index 0000000..70b7a96 --- /dev/null +++ b/.history/app_20241017124401.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + blockchain.resolve_conflicts() + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/app_20241017124720.py b/.history/app_20241017124720.py new file mode 100644 index 0000000..70b7a96 --- /dev/null +++ b/.history/app_20241017124720.py @@ -0,0 +1,234 @@ +import threading +import time +from urllib.parse import urlparse +from uuid import uuid4 +import flask +import requests +from blockchain import Blockchain +from database import BlockchainDb +from flask_cors import CORS # Import CORS +import atexit + +app = flask.Flask(__name__) +from flask import Flask, copy_current_request_context, g, request, jsonify + +# Enable CORS for the entire Flask app +CORS(app) + +blockchain = Blockchain() + +@app.route('/hello', methods=['GET']) +def hello(): + return flask.jsonify({ + 'nodes': list(blockchain.nodes), + 'length': len(list(blockchain.nodes)) + }) + + +@app.route('/chain', methods=['GET']) +def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) + return flask.jsonify({ + 'chain': blockchain.chain, + 'length': len(blockchain.chain) + }) + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = flask.request.get_json() + + # Check that the required fields are in the POST'ed data + required = ['transaction', 'digital_signature', 'public_key'] + if not all(k in values for k in required): + return 'Missing values', 400 + + # Create a new Transaction + index, error = blockchain.new_transaction(values['transaction'], values['public_key'], values['digital_signature']) + if index is not None: + response = {'message': f'Transaction will be added to Block {index}'} + else: + response = {'message': error} + return flask.jsonify(response), 201 + + +@app.route('/nodes/register', methods=['POST']) +def register_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + + +@app.route('/nodes/update_nodes', methods=['POST']) +def update_nodes(): + values = flask.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: + print("this is parent node", "simplicity_server.onrender.com") + if node not in blockchain.nodes: + blockchain.nodes.add(node) + + response = { + 'message': 'New nodes have been added', + 'total_nodes': list(blockchain.nodes), + } + return flask.jsonify(response), 201 + +@app.route('/nodes/update_ttl', methods=['POST']) +def update_ttl(): + values = flask.request.get_json() + print(values) + update_nodes = values.get('updated_nodes') + print("this is the updated nodes in the request", update_nodes) + node = values.get('node') + if update_nodes is None: + return "Error: Please supply a valid list of nodes", 400 + + blockchain.updateTTL(update_nodes , node ) + response = { + 'message': 'The TTL of nodes have been updated', + 'total_nodes': list(blockchain.nodes), + } + return flask.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 flask.jsonify(response), 200 + + +@app.route('/nodes/update_block', methods=['POST']) +def update_block(): + block = flask.request.get_json() + print("this is block", block) + if blockchain.hash(block) in blockchain.hash_list: + return flask.jsonify(f"Already added Block in the network {block}"), 200 + else: + for transaction in block['transactions']: + if transaction in blockchain.current_transactions: + blockchain.current_transactions.remove(transaction) + + blockchain.chain.append(block) + blockchain.hash_list.add(blockchain.hash(block)) + + # send data to the known nodes in the network + for node in blockchain.nodes: + requests.post(f'http://{node}/nodes/update_block', json=block, timeout=5) + requests.post(f'http://{node}/nodes/update_nodes', json={ + "nodes": list(blockchain.nodes) + }) + + return flask.jsonify(f"Added Block to the network {block}"), 200 + + +@app.route('/nodes/update_transaction', methods=['POST']) +def update_transaction(): + transaction = flask.request.get_json() + + if transaction.get('id') in [t.get('id') for t in blockchain.current_transactions]: + return flask.jsonify({"message": f"Transaction already in the network", "transaction": transaction}), 200 + + blockchain.current_transactions.append(transaction) + blockchain.miner() + + # Send data to the known nodes in the network + failed_nodes = [] + for node in blockchain.nodes: + try: + response = requests.post(f'http://{node}/nodes/update_transaction', json=transaction, timeout=5) + if response.status_code != 200: + failed_nodes.append({"node": node, "reason": f"Non-200 status code: {response.status_code}"}) + except requests.exceptions.RequestException as e: + failed_nodes.append({"node": node, "reason": str(e)}) + + if failed_nodes: + app.logger.warning(f"Failed to send transaction to some nodes: {failed_nodes}") + + return flask.jsonify({ + "message": "Added transaction to the network", + "transaction": transaction, + "failed_nodes": failed_nodes + }), 200 + + +@app.route('/nodes/update_chain', methods=['POST']) +def update_chain(): + response = flask.request.get_json() + blockchain.chain = [] + parent_node = response[1] + blockchain.nodes.add(parent_node) + chain_list = response[0] + hash_list = response[2] + blockchain.hash_list = set(hash_list) + for chain in chain_list: + if chain not in blockchain.chain: + blockchain.chain.append(chain) + + return flask.jsonify(f"Added Chain to the network {chain_list} and nodes are {blockchain.nodes}"), 200 + + +@app.route('/delete_node', methods=['POST']) +def delete_chain(): + response = flask.request.get_json() + blockchain.nodes.remove(response.get("node")) + + return flask.jsonify(f"removed Node from the network"), 200 + + +def shutdown_session(exception=None): + blockchain.resolve_conflicts() + database = BlockchainDb() + database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + + + + +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') + + +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 + # threading.Thread(target=register_node, args=[port], daemon=True).start() + app.run(host='0.0.0.0', port=port) diff --git a/.history/blockchain_20241017103342.py b/.history/blockchain_20241017103342.py new file mode 100644 index 0000000..d46c55a --- /dev/null +++ b/.history/blockchain_20241017103342.py @@ -0,0 +1,679 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain(object): + def __init__(self): + """ + Initialize the Blockchain + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl : dict= {} + self.public_address= "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block( proof=100 , prev_hash =1 ) + self.error = "" + database = BlockchainDb() + db_chain = database.load_blockchain(self ) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + + self.publoc_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + # print("Loaded chain is invalid. Starting with a new blockchain.") + + # #getting the longest chain in the network + # self.resolve_conflicts() + # #resetting the blockchain + # # self.hash_list = set() + # # self.chain = [] + # # self.nodes = set() + # # self.current_transactions = [] + # # self.new_block( proof=100 , prev_hash =1 ) + + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017103343.py b/.history/blockchain_20241017103343.py new file mode 100644 index 0000000..d46c55a --- /dev/null +++ b/.history/blockchain_20241017103343.py @@ -0,0 +1,679 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain(object): + def __init__(self): + """ + Initialize the Blockchain + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl : dict= {} + self.public_address= "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block( proof=100 , prev_hash =1 ) + self.error = "" + database = BlockchainDb() + db_chain = database.load_blockchain(self ) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + + self.publoc_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + # print("Loaded chain is invalid. Starting with a new blockchain.") + + # #getting the longest chain in the network + # self.resolve_conflicts() + # #resetting the blockchain + # # self.hash_list = set() + # # self.chain = [] + # # self.nodes = set() + # # self.current_transactions = [] + # # self.new_block( proof=100 , prev_hash =1 ) + + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017103630.py b/.history/blockchain_20241017103630.py new file mode 100644 index 0000000..7b9761b --- /dev/null +++ b/.history/blockchain_20241017103630.py @@ -0,0 +1,684 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain(object): + def __init__(self): + """ + Initialize the Blockchain + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl : dict= {} + self.public_address= "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block( proof=100 , prev_hash =1 ) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self ) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + + self.publoc_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + # print("Loaded chain is invalid. Starting with a new blockchain.") + + # #getting the longest chain in the network + # self.resolve_conflicts() + # #resetting the blockchain + # # self.hash_list = set() + # # self.chain = [] + # # self.nodes = set() + # # self.current_transactions = [] + # # self.new_block( proof=100 , prev_hash =1 ) + + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017111211.json b/.history/blockchain_20241017111211.json new file mode 100644 index 0000000..e69de29 diff --git a/.history/blockchain_20241017111314.py b/.history/blockchain_20241017111314.py new file mode 100644 index 0000000..546e92c --- /dev/null +++ b/.history/blockchain_20241017111314.py @@ -0,0 +1,684 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain(object): + def __init__(self): + """ + Initialize the Blockchain + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl : dict= {} + self.public_address= "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block( proof=100 , prev_hash =1 ) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self ) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + + self.publoc_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + # print("Loaded chain is invalid. Starting with a new blockchain.") + + # #getting the longest chain in the network + # self.resolve_conflicts() + # #resetting the blockchain + # # self.hash_list = set() + # # self.chain = [] + # # self.nodes = set() + # # self.current_transactions = [] + # # self.new_block( proof=100 , prev_hash =1 ) + + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017113520.py b/.history/blockchain_20241017113520.py new file mode 100644 index 0000000..ef14eae --- /dev/null +++ b/.history/blockchain_20241017113520.py @@ -0,0 +1,680 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super(Blockchain, cls).__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self): + if self._initialized: + return + self._initialized = True + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017113526.py b/.history/blockchain_20241017113526.py new file mode 100644 index 0000000..ef14eae --- /dev/null +++ b/.history/blockchain_20241017113526.py @@ -0,0 +1,680 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + _instance = None + _lock = threading.Lock() + + def __new__(cls): + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super(Blockchain, cls).__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self): + if self._initialized: + return + self._initialized = True + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017115926.py b/.history/blockchain_20241017115926.py new file mode 100644 index 0000000..262f13f --- /dev/null +++ b/.history/blockchain_20241017115926.py @@ -0,0 +1,671 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + if self._initialized: + return + self._initialized = True + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017115931.py b/.history/blockchain_20241017115931.py new file mode 100644 index 0000000..83cc4b4 --- /dev/null +++ b/.history/blockchain_20241017115931.py @@ -0,0 +1,668 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120105.py b/.history/blockchain_20241017120105.py new file mode 100644 index 0000000..83cc4b4 --- /dev/null +++ b/.history/blockchain_20241017120105.py @@ -0,0 +1,668 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120106.py b/.history/blockchain_20241017120106.py new file mode 100644 index 0000000..83cc4b4 --- /dev/null +++ b/.history/blockchain_20241017120106.py @@ -0,0 +1,668 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120203.py b/.history/blockchain_20241017120203.py new file mode 100644 index 0000000..83cc4b4 --- /dev/null +++ b/.history/blockchain_20241017120203.py @@ -0,0 +1,668 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120357.py b/.history/blockchain_20241017120357.py new file mode 100644 index 0000000..ea4cdab --- /dev/null +++ b/.history/blockchain_20241017120357.py @@ -0,0 +1,669 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120416.py b/.history/blockchain_20241017120416.py new file mode 100644 index 0000000..2f5ef63 --- /dev/null +++ b/.history/blockchain_20241017120416.py @@ -0,0 +1,670 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120432.py b/.history/blockchain_20241017120432.py new file mode 100644 index 0000000..de87003 --- /dev/null +++ b/.history/blockchain_20241017120432.py @@ -0,0 +1,672 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120523.py b/.history/blockchain_20241017120523.py new file mode 100644 index 0000000..de87003 --- /dev/null +++ b/.history/blockchain_20241017120523.py @@ -0,0 +1,672 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120541.py b/.history/blockchain_20241017120541.py new file mode 100644 index 0000000..4ef9b69 --- /dev/null +++ b/.history/blockchain_20241017120541.py @@ -0,0 +1,673 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120717.py b/.history/blockchain_20241017120717.py new file mode 100644 index 0000000..51357e1 --- /dev/null +++ b/.history/blockchain_20241017120717.py @@ -0,0 +1,674 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + print("the loaded chain is : ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120759.py b/.history/blockchain_20241017120759.py new file mode 100644 index 0000000..b1671f3 --- /dev/null +++ b/.history/blockchain_20241017120759.py @@ -0,0 +1,674 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + print("the loaded chain is : ", self.validate_loaded_chain()) + print("the loaded chain is : ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120806.py b/.history/blockchain_20241017120806.py new file mode 100644 index 0000000..0083561 --- /dev/null +++ b/.history/blockchain_20241017120806.py @@ -0,0 +1,674 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + print("the loaded returned chain is : ", self.validate_loaded_chain()) + print("the loaded chain is : ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017120829.py b/.history/blockchain_20241017120829.py new file mode 100644 index 0000000..7ce1210 --- /dev/null +++ b/.history/blockchain_20241017120829.py @@ -0,0 +1,674 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self .chain = list(self.validate_loaded_chain()) + print("the loaded chain is : ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121104.py b/.history/blockchain_20241017121104.py new file mode 100644 index 0000000..38a8559 --- /dev/null +++ b/.history/blockchain_20241017121104.py @@ -0,0 +1,675 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + validated_chain = list(self.validate_loaded_chain()) + print("Validated chain: ", validated_chain) # Check the output here + self.chain = validated_chain + print("Assigned chain: ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121117.py b/.history/blockchain_20241017121117.py new file mode 100644 index 0000000..2e28e82 --- /dev/null +++ b/.history/blockchain_20241017121117.py @@ -0,0 +1,675 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + validated_chain = self.validate_loaded_chain() + print("Validated chain: ", validated_chain) # Check the output here + self.chain = validated_chain + print("Assigned chain: ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121138.py b/.history/blockchain_20241017121138.py new file mode 100644 index 0000000..17fea59 --- /dev/null +++ b/.history/blockchain_20241017121138.py @@ -0,0 +1,675 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + print( self.validate_loaded_chain() ) + print("Validated chain: ", validated_chain) # Check the output here + self.chain = validated_chain + print("Assigned chain: ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121144.py b/.history/blockchain_20241017121144.py new file mode 100644 index 0000000..b096ffd --- /dev/null +++ b/.history/blockchain_20241017121144.py @@ -0,0 +1,675 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + print( self.validate_loaded_chain() ) + # print("Validated chain: ", validated_chain) # Check the output here + # self.chain = validated_chain + print("Assigned chain: ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121147.py b/.history/blockchain_20241017121147.py new file mode 100644 index 0000000..b096ffd --- /dev/null +++ b/.history/blockchain_20241017121147.py @@ -0,0 +1,675 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + if db_chain: + print( self.validate_loaded_chain() ) + # print("Validated chain: ", validated_chain) # Check the output here + # self.chain = validated_chain + print("Assigned chain: ", self.chain) + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121549.py b/.history/blockchain_20241017121549.py new file mode 100644 index 0000000..4ef9b69 --- /dev/null +++ b/.history/blockchain_20241017121549.py @@ -0,0 +1,673 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + self.chain = self.validate_loaded_chain() + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017121653.py b/.history/blockchain_20241017121653.py new file mode 100644 index 0000000..8b06b43 --- /dev/null +++ b/.history/blockchain_20241017121653.py @@ -0,0 +1,676 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123254.py b/.history/blockchain_20241017123254.py new file mode 100644 index 0000000..8b06b43 --- /dev/null +++ b/.history/blockchain_20241017123254.py @@ -0,0 +1,676 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123329.py b/.history/blockchain_20241017123329.py new file mode 100644 index 0000000..8b06b43 --- /dev/null +++ b/.history/blockchain_20241017123329.py @@ -0,0 +1,676 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123330.py b/.history/blockchain_20241017123330.py new file mode 100644 index 0000000..8b06b43 --- /dev/null +++ b/.history/blockchain_20241017123330.py @@ -0,0 +1,676 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123410.py b/.history/blockchain_20241017123410.py new file mode 100644 index 0000000..5e6d620 --- /dev/null +++ b/.history/blockchain_20241017123410.py @@ -0,0 +1,678 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + print( + "Length of the chain is " + str(len(self.chain)) + ) + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123413.py b/.history/blockchain_20241017123413.py new file mode 100644 index 0000000..5e6d620 --- /dev/null +++ b/.history/blockchain_20241017123413.py @@ -0,0 +1,678 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) == 0: + print("No chain found. Starting with a new chain.") + return self.chain + print( + "Length of the chain is " + str(len(self.chain)) + ) + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123827.py b/.history/blockchain_20241017123827.py new file mode 100644 index 0000000..901cd57 --- /dev/null +++ b/.history/blockchain_20241017123827.py @@ -0,0 +1,678 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) <= 2 : + print("No chain found. Starting with a new chain.") + return self.chain + print( + "Length of the chain is " + str(len(self.chain)) + ) + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017123837.py b/.history/blockchain_20241017123837.py new file mode 100644 index 0000000..901cd57 --- /dev/null +++ b/.history/blockchain_20241017123837.py @@ -0,0 +1,678 @@ +import base64 +import logging +from time import time +import threading +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve import PublicKey , Signature +from flask import request +from ellipticcurve.ecdsa import Ecdsa +from ellipticcurve.privateKey import PrivateKey , PublicKey +import hashlib +import json +import time as t +from typing import Dict +from urllib.parse import urlparse +import schedule + +import ecdsa +import flask +import requests + +from account_db import AccountReader +from nodeManager import NodeManager +from database import BlockchainDb + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" + +class Blockchain: + + + def __init__(self): + + self.chain = [] + self.current_transactions = [] + self.hash_list = set() + self.nodes = set() + self.ttl = {} + self.public_address = "" + self.private_address = "" + self.ip_address = "" + self.target = 4 # Easy target value + self.max_block_size = 1000000 + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + + database = BlockchainDb() + db_chain = database.load_blockchain(self) + + self.mining_thread = None + self.should_mine = False + + accountDb = AccountReader() + accountDb.load_accounts() + accounts_data = accountDb.account_data + for account in accounts_data: + if account['publicKey']: + self.public_key = account['publicKey'] + if account['privateKey']: + self.private_address = account['privateKey'] + + print("the db chain is : ", db_chain) + if db_chain: + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + + self.start_scheduled_mining() + def Blockchain(self , public_address): + self.public_address = public_address + + def create_coinbase_transaction(self, miner_address: str, reward: int = 50): + """ + Creates a coinbase transaction for the miner. + + :param miner_address: Address of the miner receiving the reward + :param reward: Amount of coins to reward the miner + :return: The coinbase transaction + """ + # Create the coinbase transaction structure + coinbase_tx = { + + 'sender': '0', # Indicates it's a coinbase transaction + 'recipient': miner_address, + 'amount': reward, + 'timestamp': time(), + + } + + # Generate transaction ID + coinbase_tx['transaction_id'] = self.generate_transaction_id(coinbase_tx) + + + # Optionally set the public address and digital signature if needed + # For the coinbase transaction, you may want to sign it with the miner's public key + public_address = self.public_address # This should be set to the miner's public key + + + digital_signature = self.sign_transaction(coinbase_tx) + coinbase_tx["public_address"] = public_address + + transaction = { + "transaction": coinbase_tx, + "public_address": public_address, + "digital_signature": digital_signature + } + + return transaction + def generate_transaction_id(self , coinbase_tx): + transaction_data = json.dumps(coinbase_tx, sort_keys=True) + return hashlib.sha256(transaction_data.encode()).hexdigest() + + def validate_loaded_chain(self): + """Validate the loaded chain for integrity.""" + + if len(self.chain) <= 2 : + print("No chain found. Starting with a new chain.") + return self.chain + print( + "Length of the chain is " + str(len(self.chain)) + ) + for i in range(1, len(self.chain)): + current_block = self.chain[i] + previous_block = self.chain[i-1] + if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain[:i-1] + print("Loaded chain is valid. lenght is " + str(len(self.chain))) + return self.chain + def create_mining_reward(self, miners_address, block_height): + # Calculate the reward based on block height + base_reward = 50 # Starting reward + halving_interval = 210000 # Number of blocks between reward halvings + halvings = block_height // halving_interval + current_reward = base_reward / (2 ** halvings) + + # Add a transaction fee reward + transaction_fees = sum(tx['transaction']['amount'] for tx in self.current_transactions if tx['transaction']['sender'] != "0") + total_reward = current_reward + transaction_fees + + # Create the coinbase transaction + coinbase_tx = self.create_coinbase_transaction( + miner_address=miners_address, + reward=total_reward + ) + + # The coinbase transaction will be added as the first transaction in the new block + return total_reward, coinbase_tx + + def register(self , ip_address): + # Create a NodeManager instance + node_manager = NodeManager() + self.ip_address = ip_address + # Get a random node + random_node = node_manager.get_random_node() + nodes = node_manager.load_nodes() + print("the nodes are : ", nodes) + print("the random node is : ", random_node) + self.remove_expired_nodes() + print("the ip address is : ", self.ip_address) + print("nodes after removing expired nodes : ", nodes) + + if self.ip_address not in nodes: + data = { + "nodes": [self.ip_address] + } + print("Registering node : {}".format(ip_address) ) + requests.post(f'http://{random_node}/nodes/register' , json=data) + if self.ttl: + requests.post(f'http://{random_node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : self.ip_address + }) + + + + + def register_node(self , address , current_address): + """ + Adds a new node to the list of nodes + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :return: None + """ + + #What is netloc? + """ + `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + + `netloc` contains the network location part of the URL, which includes: + + * The hostname or domain name + * The port number (if specified) + + For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + + In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. + """ + self.remove_expired_nodes() + + parsed_url = urlparse(address) + if parsed_url not in self.nodes: + self.nodes.add(parsed_url) + current_url = urlparse(current_address) + requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) + requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ + "nodes": list(self.nodes) + }) + if self.ttl: + requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : current_url + }) + + def remove_expired_nodes(self): + if self.ttl: + # Iterate over a copy of the set to avoid modifying it while iterating + for node in list(self.nodes): + if node not in self.ttl: + self.nodes.remove(node) + continue + if int(self.ttl[node]) < int(time()): + self.nodes.remove(node) + + + def verify_block(self , block: Dict, previous_block: Dict, target: int, max_block_size: int , isCoinbase) -> bool: + """ + Verify the validity of a block. + + :param block: The block to verify + :param previous_block: The previous block in the chain + :param target: The current mining difficulty target + :param max_block_size: The maximum allowed block size in bytes + :return: True if the block is valid, False otherwise + """ + # Check block structure + required_keys = ['index', 'timestamp', 'transactions', 'proof', 'previous_hash'] + if not all(key in block for key in required_keys): + print("Invalid block structure") + return False + + # Verify block header hash + if self.valid_proof(previous_block['proof'], block['proof'], target) is False: + print("Block hash does not meet the target difficulty") + return False + + # Check timestamp + current_time = int(time()) + if block['timestamp'] > current_time + 7200: # 2 hours in the future + print("Block timestamp is too far in the future") + return False + + # Check block size + block_size = len(str(block).encode()) + if block_size > max_block_size: + print(f"Block size ({block_size} bytes) exceeds maximum allowed size ({max_block_size} bytes)") + return False + + # Verify previous block hash + if block['previous_hash'] != self.hash(previous_block): + print("Previous block hash is incorrect") + return False + + # Check that the first transaction is a coinbase transaction + if not block['transactions'] or block['transactions'][0]['transaction']['sender'] != "0": + print("First transaction is not a coinbase transaction") + return False + + # Verify all transactions in the block + if not isCoinbase: + for tx in block['transactions'][1:]: # Skip the coinbase transaction + if not self.valid_transaction(tx): + print(f"Invalid transaction found: {tx}") + return False + + return True + + def new_block(self , proof , prev_hash , isCoinbase = False ,coinbase_transaction=None , miner_address=None ): + + # Creates a new Block in the Blockchain + + # :param proof: The proof given by the Proof of Work algorithm + # :param previous_hash: (Optional) Hash of previous Block + # :return: New Block + + + block = { + "index" : len(self.chain) + 1 , + "timestamp" : time(), + "transactions" : [coinbase_transaction] + self.current_transactions , + "proof" : proof, + "previous_hash" : prev_hash or self.chain[len(self.chain) - 1]["hash"] + } + + if self.chain and not self.verify_block(block , self.chain[-1] , self.target , self.max_block_size , isCoinbase): + print("Invalid block") + return False + + + + self.chain.append(block) + hashed_block = self.hash(block) + self.hash_list.add(hashed_block) + # Reset the current list of transactions + self.remove_expired_nodes() + + #send data to the konwn nodes in the network + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_block' , json=block) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : miner_address + }) + + + self.current_transactions = [] + return block + + + + + def updateTTL(self, updated_nodes: dict, neighbor_node: str): + """ + Remove nodes from ttl that have timed out and update TTLs for nodes. + + :param updated_nodes: A dictionary of nodes and their corresponding TTLs + :type updated_nodes: dict + :param neighbor_node: The node that transmitted the block + :type neighbor_node: str + """ + try: + # Remove any protocol (http, https) from neighbor_node if it exists + parsed_neighbor = urlparse(neighbor_node) + neighbor_node_cleaned = parsed_neighbor or neighbor_node # Use netloc if available, otherwise raw string + + print("Updating TTL for neighbor node...", neighbor_node_cleaned) + if neighbor_node_cleaned in self.ttl: + self.ttl[neighbor_node_cleaned] = self.ttl[neighbor_node_cleaned] + 600 + print(f"Updated TTL for neighbor_node '{neighbor_node_cleaned}' to {self.ttl[neighbor_node_cleaned]}") + else: + self.ttl[neighbor_node_cleaned] = time() + 600 + + # Remove nodes with expired TTLs + current_time = time() + old_ttl_count = len(self.ttl) + self.ttl = {node: ttl for node, ttl in self.ttl.items() if ttl >= current_time} + print(f"Removed {old_ttl_count - len(self.ttl)} timed-out nodes.") + + # Update TTLs for nodes in updated_nodes + for node, ttl in updated_nodes.items(): + parsed_node = urlparse(node) + node_cleaned = parsed_node or node # Remove protocol if present + + if node_cleaned in self.ttl: + old_ttl = self.ttl[node_cleaned] + self.ttl[node_cleaned] = max(self.ttl[node_cleaned], ttl) + print(f"Updated TTL for node '{node_cleaned}' from {old_ttl} to {self.ttl[node_cleaned]}") + else: + self.ttl[node_cleaned] = ttl + print(f"Added node '{node_cleaned}' with TTL {ttl}") + + print(f"TTL update completed. Current TTL count: {len(self.ttl)}") + + except Exception as e: + print(f"Error in updateTTL: {str(e)}") + + + def new_transaction(self, transaction , public_address , digital_signature): + try: + print("senders key" , transaction["sender"]) + sender = PublicKey.fromCompressed(transaction["sender"]) + except: + self.error = "Transaction will not be added to Block due to invalid sender address" + return None, self.error + try: + recipient = PublicKey.fromCompressed(transaction["recipient"]) + except: + self.error = "Transaction will not be added to Block due to invalid recipient address" + return None, self.error + + if self.valid_transaction(transaction , public_address , digital_signature) or sender == "0": + self.current_transactions.append({ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + self.miner() + # send transactions to the known nodes in the network + self.remove_expired_nodes() + for node in self.nodes: + requests.post(f'http://{node}/nodes/update_transaction', json={ + "transaction": transaction, + "public_address": public_address, + "digital_signature": digital_signature + }) + if self.ttl: + requests.post(f'http://{node}/nodes/update_ttl' , json={ + "updated_nodes": self.ttl, + "node" : request.host_url + }) + return self.last_block['index'] + 1, "Successful Transaction" + else: + return None, self.error + + + def start_scheduled_mining(self): + print("the chain is " , self.chain) + schedule.every(10).minutes.do(self.scheduled_mine) + threading.Thread(target=self.run_schedule, daemon=True).start() + + def run_schedule(self): + while True: + schedule.run_pending() + t.sleep(1) + + def scheduled_mine(self): + if not self.mining_thread or not self.mining_thread.is_alive(): + self.should_mine = True + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + def mine(self): + if not self.should_mine: + return + miners_address = "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a" + last_block = self.last_block + last_proof = last_block['proof'] + proof = self.proof_of_work(last_proof) + block_height = len(self.chain) + + total_reward, coinbase_tx = self.create_mining_reward(miners_address, block_height) + previous_hash = self.hash(last_block) + self.new_block(proof, previous_hash, True, coinbase_tx) + + def mine_with_timer(self): + start_time = time() + self.mine() + end_time = time() + print(f"Mining took {end_time - start_time} seconds") + self.should_mine = False + + + def miner(self): + if len(self.current_transactions) >= self.max_mempool or len(self.current_transactions) >= self.max_block_size: + self.should_mine = True + if not self.mining_thread or not self.mining_thread.is_alive(): + self.mining_thread = threading.Thread(target=self.mine_with_timer) + self.mining_thread.start() + + def valid_transaction(self, transaction , public_address , digital_signature): + # Verify the transaction signature + if not self.verify_digital_signature(transaction , public_address , digital_signature): + self.error = "Transaction will not be added to Block due to invalid signature" + return False + + # Check if the sender has enough coins + sender_balance = self.check_balance(transaction) + if sender_balance: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + @staticmethod + def hash(block): + + # Creates a SHA-256 hash of a Block + + # :param block: Block + # :return: + + block_string = json.dumps(block, sort_keys=True).encode() + return hashlib.sha256(block_string).hexdigest() + # def verify_signature(self, transaction , public_address , digital_signature): + # """ + # Verify the digital signature of the transaction. + # """ + # try: + # public_address = ecdsa.VerifyingKey.from_string(bytes.fromhex(public_address), curve=ecdsa.SECP256k1) + # transaction = transaction + # signature = bytes.fromhex(digital_signature) + + # # Recreate the transaction data string that was signed + # transaction_string = json.dumps(transaction, sort_keys=True) + + # public_address.verify(signature, transaction_string.encode()) + # return True + # except (ecdsa.BadSignatureError, ValueError): + # return False + + + + + + def verify_digital_signature(self, transaction, compressed_public_key, digital_signature_base64): + try: + # Validate input types + if not isinstance(transaction, dict): + raise ValueError("Transaction must be a dictionary") + if not isinstance(compressed_public_key, str): + raise ValueError("Compressed public key must be a string") + if not isinstance(digital_signature_base64, str): + raise ValueError("Digital signature must be a base64-encoded string") + + # Validate transaction structure + required_keys = ['sender', 'recipient', 'amount', 'timestamp'] + if not all(key in transaction for key in required_keys): + raise ValueError("Transaction is missing required fields") + + # Convert transaction to JSON with sorted keys + transaction_json = json.dumps(transaction, sort_keys=True) + + # Create PublicKey object + try: + print("Compressed public key: ", compressed_public_key) + public_address = PublicKey.fromCompressed(compressed_public_key) + print("public key: ", compressed_public_key) + except ValueError as e: + print("Invalid compressed public key: ", e) + raise ValueError(f"Invalid compressed public key: {e}") + + # Create Signature object + try: + digital_signature = Signature.fromBase64(digital_signature_base64) + except (ValueError, base64.binascii.Error) as e: + raise ValueError(f"Invalid digital signature: {e}") + print( + f"Transaction: {transaction_json}\n" + f"Public key: {public_address}\n" + f"Digital signature: {digital_signature}" + ) + # Verify the signature + is_valid = Ecdsa.verify(transaction_json, digital_signature, public_address) + + if not is_valid: + raise SignatureVerificationError("Signature verification failed") + + return True + + except ValueError as e: + logging.error(f"Input validation error: {e}") + return False + except SignatureVerificationError as e: + logging.error(f"Signature verification failed: {e}") + return False + except Exception as e: + logging.error(f"Unexpected error in verify_digital_signature: {e}") + return False + + def sign_transaction(self, transaction): + message = json.dumps(transaction, sort_keys=True) + private_key = PrivateKey.fromString(self.private_address) + signature = Ecdsa.sign(message, private_key) + return signature.toBase64() + + @property + def last_block(self): + + """ + Returns the last block in the blockchain + :return: The last block in the blockchain + """ + + return self.chain[-1] + + + def proof_of_work(self , last_proof): + + # Finds a number p' such that hash(pp') contains 4 leading zeroes + + # :param last_proof: + # :return: A number p' + proof = 0 + while self.valid_proof(last_proof , proof , self.target) is False: + proof += 1 + return proof + + @staticmethod + def valid_proof(last_proof, proof, target): + """ + Validates the Proof: Checks if hash(last_proof, proof) meets the target difficulty. + + :param last_proof: Previous proof value + :param proof: Current proof value + :param target: The difficulty target (number of leading zeros required in the hash) + :return: True if valid, False otherwise + """ + guess = f'{last_proof}{proof}'.encode() + guess_hash = hashlib.sha256(guess).hexdigest() + + # Check if the hash is valid by comparing to the target difficulty + if guess_hash[:target] == '0' * target: + return True # The proof is valid (meets difficulty) + return False # The proof does not meet the difficulty + + + + def valid_chain(self , chain): + 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'] , self.target): + return False + last_block = block + current_index += 1 + return True + + def check_balance(self , transaction): + + # Check if the sender has enough coins + sender_balance = 0 + sender_address = transaction['sender'] + sender_amount = transaction['amount'] + + for block in self.chain: + for transaction in block['transactions']: + if transaction['transaction']['recipient'] == sender_address: + sender_balance += transaction['transaction']['amount'] + if transaction['transaction']['sender'] == sender_address: + sender_balance -= transaction['transaction']['amount'] + + for tx in self.current_transactions: + if tx['transaction']['recipient'] == sender_address: + sender_balance += tx['amount'] + if tx['transaction']['sender'] == sender_address: + sender_balance -= tx['transaction']['amount'] + if sender_balance >= sender_amount: + return True + else: + self.error = "Transaction will not be added to Block due to insufficient funds" + return False + + + 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') + + 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 + +class SignatureVerificationError(Exception): + pass diff --git a/.history/blockchain_20241017124143.json b/.history/blockchain_20241017124143.json new file mode 100644 index 0000000..42aadd5 --- /dev/null +++ b/.history/blockchain_20241017124143.json @@ -0,0 +1,668 @@ +{ + "chain": [ + { + "index": 2, + "previous_hash": "7ebe3f2f48fd01a145b056519aa646585048b3ca562ad9833ce31a44fb29e4f3", + "proof": 35293, + "timestamp": 1728974524.0400372, + "transactions": [ + { + "digital_signature": "MEYCIQDTm3LkbQh980Gxt/6YpHd43mWc/L739PIjtjUW11NTEgIhANtyw1ucq81tdJfpX81ZlVP1w1HbnaUwzO/E9ApxqXVx", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974524.0336194, + "transaction_id": "9c5d65021bc50fe93a19af30b71b8fbaaf920e43d45e6c4fb26ac1a8fddeaad9" + } + } + ] + }, + { + "index": 3, + "previous_hash": "ea9fcdb805d1519b46ff274e27122028683f00e441a7bba0ef24570d1deb80a6", + "proof": 35089, + "timestamp": 1728974584.1404045, + "transactions": [ + { + "digital_signature": "MEUCIAaaLt/hwWHiqcNzz14SWISNqr2aR5+VAlOwkIC3ZGmRAiEA+W93lJHno0ePkyIKZvIp2eE6eNW4rwJYhHdZLwsuyik=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974584.1352344, + "transaction_id": "1f9eaeb43dad5f58b6f69964c38f1c263c7de41fa04d1421b980997875932363" + } + } + ] + }, + { + "index": 4, + "previous_hash": "aad438bdcdfd5e93cbe8b0a5a917da56784bb78e91161bc0a3ed0eea56b94ed1", + "proof": 119678, + "timestamp": 1728974644.3219702, + "transactions": [ + { + "digital_signature": "MEYCIQD4IJr2FIaN0d80wPjRs32UhUiJWpgjvI9zDdOktRstpQIhAJeM1XZdYFONgJFLIWzoDzBh4PVImq+fe5sthngjE16c", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974644.3169801, + "transaction_id": "fac86976024f9567eac8a9d0c913b9fff662d2e54740922d9153fe978a656aed" + } + } + ] + }, + { + "index": 5, + "previous_hash": "c09881935ad064d13bd87e0250958497fdad66358760d5c97bafb9efd4e45bc6", + "proof": 146502, + "timestamp": 1728974704.4888444, + "transactions": [ + { + "digital_signature": "MEYCIQCZzROmPVnwo87lC5httLXhT4ZR5UcyzECdk8Z6ghXs6gIhAPCSlpR2P7Z6Wi1pfvMqtg07RcUquJNLeWVNZtQQUTsK", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974704.4858475, + "transaction_id": "0dc93f2066989fc1795d42e76f89a6d025080a9f6e280c132cb6d291bfc00ea1" + } + } + ] + }, + { + "index": 6, + "previous_hash": "9577d88fe2fe44b85079ee07c9bd2ec77d17bae9bc2370e0ebcb6f535c48349d", + "proof": 43538, + "timestamp": 1728974764.3581252, + "transactions": [ + { + "digital_signature": "MEYCIQDOAPFftH8RQxeSgvXf8cS3LQzZybbouZa/xgzITIH3vAIhAKxLxSr/mv9/bkMZw2O7+P1fDbNtxEVEqWC+NfRPzaAL", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974764.352396, + "transaction_id": "23916989d5984b126a57db5818181a14c8bbb4246b5884d273b477a0b4e0fd00" + } + } + ] + }, + { + "index": 7, + "previous_hash": "1c44c6fabac81569a096d27232a969a266b75dd2dcda84d63df25cde8616ab6d", + "proof": 85724, + "timestamp": 1728974824.4366474, + "transactions": [ + { + "digital_signature": "MEUCIQD+zWEisJqmKJ5z8SXwIqgqPXAKNakTBWz0QbsX3cJeLAIgEK55LEP86zKaFNWA6C2gPUcuQuH+pmlXpuuY8c9LC4A=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974824.4333982, + "transaction_id": "bf0910788fd53f021d55feba6a1c56a686414411bbb31998991d5de20f100681" + } + } + ] + }, + { + "index": 8, + "previous_hash": "8ac34c7968dbd9c65bacdf7616dc530c2a10741940daca0c54efdb0100bd5a6a", + "proof": 51178, + "timestamp": 1728974884.3970501, + "transactions": [ + { + "digital_signature": "MEYCIQCWIOHbCPiG1sSo6yOI/7eIocb1Xss5IXc+2HW5zbIKxgIhAPmA9+9Sde/TGtwk3u8+Cb+QQSdeOFjli2QX9S/lNYBe", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974884.395058, + "transaction_id": "07e783e78e1d4cba0d6ac7fe28115489f7a4b909a10accee55d186837cf7db64" + } + } + ] + }, + { + "index": 9, + "previous_hash": "778b4cba347e2e70b9d8c47d070a001ea99e96434d4daeeb943763388eb046f9", + "proof": 71730, + "timestamp": 1728974944.4548056, + "transactions": [ + { + "digital_signature": "MEYCIQCrh0eQCAcGidTtQkbBDsx600H02d9ZNQ708JfwY7+VQQIhALyPxkf6BeNCzzere+eMq98IKXPHd0a0E9oWV7f/9x7r", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974944.45279, + "transaction_id": "cc578313a9c526ffe96b0fe4600bb93b39332868ff496babe62b4346e007974f" + } + } + ] + }, + { + "index": 10, + "previous_hash": "a96ac9eee81499d1264dd115cfca31b6cea266ed12ed217d1ef5213cf15b6a9f", + "proof": 55589, + "timestamp": 1728975004.4618313, + "transactions": [ + { + "digital_signature": "MEQCIEngRwU14Jl3+ymWczqRJAvMk1qlOM5XvsXxvIkw+3KgAiAlCBncpektqKapPlu+p+kNhTu6U7obGwF57Nuc9PQZGA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975004.4600081, + "transaction_id": "12d3e76b4cab5591b2ee02f4a91324b6ef0eeee96e8adbc4bf965f5ed3e142fa" + } + } + ] + }, + { + "index": 11, + "previous_hash": "76d0b6e10e61ae1e75bc580b83a0be2d830d0b3166f62ef8f09cfb8ecf4eb693", + "proof": 35704, + "timestamp": 1728975064.4856203, + "transactions": [ + { + "digital_signature": "MEUCIAXISgaX+bU/DQ91NyaH+S1yRDClLURY5ZQRdYrDyixUAiEAySkoszgdHBU9bdIKCkRVU35TrS98DXvbeZoE2mXZUMs=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975064.4836202, + "transaction_id": "884db8e2299d319785795019582f8ad0c0723629f27c8f3b39e442e79de8aa44" + } + } + ] + }, + { + "index": 12, + "previous_hash": "32f12ca6cdb5be144ee46cee125bdced266194b97414624dc1fd210907b170ae", + "proof": 57342, + "timestamp": 1728975124.6048725, + "transactions": [ + { + "digital_signature": "MEYCIQC7fsDsJ3wQoY9QL/PlWs9ZjKERbxgWMh/63FTn1h7scAIhALF+GmxpNgq5uqhOgoUbVrmzwWAbDVqM5QGmRaC8Srku", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975124.6011765, + "transaction_id": "0834dc176b09859569aad7b4597893f2de845357b78af6d784abc528b69807e4" + } + } + ] + }, + { + "index": 13, + "previous_hash": "d377ab16295533389908fea265491cc7bed4e82bf0b547336fcff1c62bedd7dc", + "proof": 68975, + "timestamp": 1728975184.6077056, + "transactions": [ + { + "digital_signature": "MEYCIQDpt6olYojDgXBkvjn2sKIP6iw9nQc9cLZsOQO+ih6chQIhAL3rAnkYdGTpZDgEHUKuHcTZ3P9SfORIBVSott5cHeH0", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975184.604074, + "transaction_id": "39b5a25bd9e666c6b4057a57867ac80f68d545015e739c216bb0161aae06d0c5" + } + } + ] + }, + { + "index": 14, + "previous_hash": "cdb6a7ed569e50024e3a3077e8c8bb4ad09b17078bde559e5263bd93ce5dd1ec", + "proof": 153122, + "timestamp": 1728975244.689295, + "transactions": [ + { + "digital_signature": "MEQCICCoFk2hCTOqwcm5ij0MOuMV/hr9vgfg9IdA+AQW729gAiA4ZM7mH83nVDvDXOkxHl8pENKeEMjzQ6VdMu6Ske69mA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975244.6872911, + "transaction_id": "8ff23a738fce7cdf9f74b38987b6891f884503d90132a970fc236a824ab6bb0a" + } + } + ] + }, + { + "index": 15, + "previous_hash": "de7f3f590c5976a155a573957e8a2c2da2658a360a3ae21f78d6c504f06f2201", + "proof": 20760, + "timestamp": 1728975304.6244986, + "transactions": [ + { + "digital_signature": "MEQCIG4DXGXrCPYbtzRG+500+8tvDIBVR6aSMcei1k1NQcHsAiAaYmjiEUsH2k0WjLUuSC2vCh8YlAxIrfLLJl1vA6lZhA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975304.622504, + "transaction_id": "06143f8e221f1f7ec20d39e2bd904c3cbeed2259f013eaf3dae4a6f15a6dbd6c" + } + } + ] + }, + { + "index": 15, + "previous_hash": "2aabc645f76685ffb8e587f37d423c1396b2a2104489546d8ed2ff97966c123a", + "proof": 29341, + "timestamp": 1728975756.5164192, + "transactions": [ + { + "digital_signature": "MEQCIFLy8ImmWUnI2nIauS3XCG+sq96U/Fq/uPyt/HP0qP7eAiB2tKovBOYt/Xs2HZfXPfP6/c1CL0QaT1nvOevkdOZ5lg==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975756.5133958, + "transaction_id": "142270daba595b613abba390576fd2bb72767da33ce4b79de512ffc5a7f921a5" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + } + ] + }, + { + "index": 16, + "previous_hash": "53e80a715d78a4e1d1682f609df04cd326fc389df00c214d385284afb68b1418", + "proof": 15889, + "timestamp": 1728975971.0139954, + "transactions": [ + { + "digital_signature": "MEQCIDoekApOmyc9UzeZ4YhR0f0bWD+/3Lolpmn/0S30tFqEAiBsGFGh7M8USbUG1vAXBmq3GDMKuKamjAWP/lcP7cEUNA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 350, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975971.0119958, + "transaction_id": "b18f0804a34377d5c251a98767ef4c9d56e346169a27697a93b8c37cceb0d68c" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + } + ] + }, + { + "index": 17, + "previous_hash": "e470f9d6a55cdd925aea82dc391a8ff7c0fcc9bb9a2394d0857af6bc4d8896b7", + "proof": 209765, + "timestamp": 1728976024.9810693, + "transactions": [ + { + "digital_signature": "MEYCIQDrZ7q9xHVJ7lh/3gT46y1adZlph4fN294IbkfDyVnBsQIhALPq8Fv4ZwSuwWvbLdy/eZgYVsaqSBFkETQps7/p2S2g", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 450, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976024.979543, + "transaction_id": "da8eea2ec16240110a5d913ecbae1cd9ca2fcc80cf13f031bfa23fb65e364403" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + }, + { + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } + } + ] + }, + { + "index": 18, + "previous_hash": "8189da9ee091cdbe6fa9d0db1092cfcaf99f50aa5c0300c66b107686d6f580c5", + "proof": 3748, + "timestamp": 1728976103.733967, + "transactions": [ + { + "digital_signature": "MEYCIQDr+8jpV3tvLrVPcTadPNhxreM2THaFwKJ8vXZdsjEX6QIhAMACHbHEu7+5UJeMTX0YnkP25jOzy0+PPAjzqJ6bW5nQ", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 550, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976103.732963, + "transaction_id": "0dd94ebb25df84f6684c2caa0a849d5d1310924e43700a71ca1a763de36a89d7" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + }, + { + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } + }, + { + "digital_signature": "MEUCIQDTWXwJ9UOjJqjVgcd1hQiuTOa3DTzrGTwVGxl31FbU7gIgZ2BRMQPi8GHtdLr64NmmzLPlQaryFjBPG2Ral31jRxI=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976103.7194138, + "transaction_id": "134eea2fcf5c87cc930df55c430e67230bb892bce3a28ddba6ed0346105610d1" + } + } + ] + }, + { + "index": 19, + "previous_hash": "0b4fe1a9f1692b542f4a9943d37863403bc5a9bc71b356c6dfc1e86777c5a329", + "proof": 28807, + "timestamp": 1728976122.9694924, + "transactions": [ + { + "digital_signature": "MEYCIQDjI7PnA3J3c1HGl9xZdTPAVLo7uDkkvEzvrJOLt6rdUQIhAKo5EFOj5xyWhTTgZ55Feroc5ZPzc7opBwni7HeLRwr1", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976122.9679887, + "transaction_id": "4ee9a79f736d808bbf0a689433353669687b3090cfa2444e0215d16cc019f95d" + } + }, + { + "digital_signature": "MEQCIBVU0sWBlx4mmyyDu9NxzUyJ1KHbuq2CyV7QbmEZAb0XAiBhrihf5gdZXaHNdpuckfITpqQ3odMCOj6BNKxKxH2/0w==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976110.323565, + "transaction_id": "63575decc2edbfe96737b7bedcc2d4ba9d230fb1eecb8310c5ec97b73376d646" + } + }, + { + "digital_signature": "MEYCIQCtaVYVEmovF9Q4fgXAtfGM9/1OLhmm2ydsceYS+kGZ1QIhAJQk+lYygkWBZ7VUsyQfMK1UUZIDDsy+dZQAyDZcHTGp", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976122.9341776, + "transaction_id": "d05338d826bed327e000c0dadfe6dbf6696ee8c0c6b2f0b1c73d2d885ce4fe5b" + } + } + ] + }, + { + "index": 20, + "previous_hash": "a16c2898879799c3dc1988c84a3eed4c16b0f3a8705993576d11f4b0fa31e4af", + "proof": 70832, + "timestamp": 1728982795.977029, + "transactions": [ + { + "digital_signature": "MEUCIQCbU4ZVg0fMROF21D7aEaN9xxHjwpEPxcG33CdRK4bLmwIgTCqmFMyVYmtGJbxm/mCbka6TXggZjLdgBonB8Xj6zK4=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982795.9745123, + "transaction_id": "07fd3b0e1a4c95f2c24d7b4f4c5279a5b877c28e9004a47881a1389536ede68c" + } + } + ] + }, + { + "index": 21, + "previous_hash": "4989e8b8021887d35737e9bd201eebb2e5e611a5c3c1d2b2455f1361a7472b33", + "proof": 47031, + "timestamp": 1728982796.253764, + "transactions": [ + { + "digital_signature": "MEYCIQCa6nS5/CQMb6xoPPaysXuANZMSy8W5EjVGgq4a+ETAgwIhANDKMlul8q6pf/OjmJRqkYpwJK/gBRVX11hzsvAsVfld", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982796.2499352, + "transaction_id": "423069ba8b1487f5c7394605b6628395fdf571fe56c86d95ee882ffd705d945e" + } + } + ] + }, + { + "index": 22, + "previous_hash": "19f4fa00986014243d97fa5539db5a2dbda69a522c4d2f155dc203f6f3c5eda4", + "proof": 48098, + "timestamp": 1728983396.2543488, + "transactions": [ + { + "digital_signature": "MEQCIDa7z0tfh1xCbh7DtutvqeZPzKUDnhPU/efllO6CTJxtAiAw7XI4eA3g+RAP83feJaQvRPubamYnSrMATsbUxfgfDw==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983396.248372, + "transaction_id": "0820117dececa39333cb2b17973f9f40327ae5177f45374bd79d64f3b9005fa0" + } + } + ] + }, + { + "index": 23, + "previous_hash": "f8d4e2cfcccaa25855f70902140bf9fda0b3cb70ad6d6f1545b3badbb4c71c6e", + "proof": 30743, + "timestamp": 1728983397.2447324, + "transactions": [ + { + "digital_signature": "MEUCIQDoYhvL1y5B13z2+hCMD8WfzyOKVt4Rb/l0tdnGeEjrZQIgFpGSGRxgLlnI3A9D02BD1B0cgu6boecY17yONJnfksw=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983397.2373633, + "transaction_id": "fb410938d213ed553a9c0886e6001598b5986fa6defa6cbcb7a2b9b2f132ff93" + } + } + ] + }, + { + "index": 24, + "previous_hash": "25f25ddc2e3fa0e775e511bc94410080c86493649b598b65cb94363a9eef5a6f", + "proof": 41003, + "timestamp": 1728998883.4670477, + "transactions": [ + { + "digital_signature": "MEUCIQDKLP510ilNznCAND1x6bn2L6U7yNFOtTiQUoiJ0HLSHgIgA+YT8q3XbHCbGOiuLxiabcMjFB9/omh2uGSxstJ/yc0=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728998883.4660475, + "transaction_id": "b54a2b509f074d94500a935d6573cc5bd7b322a9540371a445acc851a0422e5c" + } + } + ] + } + ], + "current_transactions": [], + "nodes": [ + "simplicity-server1.onrender.com", + "simplicity-server.onrender.com" + ], + "ttl": { + "simplicity-server1": 1729137155.8219357 + } +} \ No newline at end of file diff --git a/.history/blockchain_20241017124413.json b/.history/blockchain_20241017124413.json new file mode 100644 index 0000000..084b6db --- /dev/null +++ b/.history/blockchain_20241017124413.json @@ -0,0 +1,668 @@ +{ + "chain": [ + { + "index": 2, + "previous_hash": "7ebe3f2f48fd01a145b056519aa646585048b3ca562ad9833ce31a44fb29e4f3", + "proof": 35293, + "timestamp": 1728974524.0400372, + "transactions": [ + { + "digital_signature": "MEYCIQDTm3LkbQh980Gxt/6YpHd43mWc/L739PIjtjUW11NTEgIhANtyw1ucq81tdJfpX81ZlVP1w1HbnaUwzO/E9ApxqXVx", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974524.0336194, + "transaction_id": "9c5d65021bc50fe93a19af30b71b8fbaaf920e43d45e6c4fb26ac1a8fddeaad9" + } + } + ] + }, + { + "index": 3, + "previous_hash": "ea9fcdb805d1519b46ff274e27122028683f00e441a7bba0ef24570d1deb80a6", + "proof": 35089, + "timestamp": 1728974584.1404045, + "transactions": [ + { + "digital_signature": "MEUCIAaaLt/hwWHiqcNzz14SWISNqr2aR5+VAlOwkIC3ZGmRAiEA+W93lJHno0ePkyIKZvIp2eE6eNW4rwJYhHdZLwsuyik=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974584.1352344, + "transaction_id": "1f9eaeb43dad5f58b6f69964c38f1c263c7de41fa04d1421b980997875932363" + } + } + ] + }, + { + "index": 4, + "previous_hash": "aad438bdcdfd5e93cbe8b0a5a917da56784bb78e91161bc0a3ed0eea56b94ed1", + "proof": 119678, + "timestamp": 1728974644.3219702, + "transactions": [ + { + "digital_signature": "MEYCIQD4IJr2FIaN0d80wPjRs32UhUiJWpgjvI9zDdOktRstpQIhAJeM1XZdYFONgJFLIWzoDzBh4PVImq+fe5sthngjE16c", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974644.3169801, + "transaction_id": "fac86976024f9567eac8a9d0c913b9fff662d2e54740922d9153fe978a656aed" + } + } + ] + }, + { + "index": 5, + "previous_hash": "c09881935ad064d13bd87e0250958497fdad66358760d5c97bafb9efd4e45bc6", + "proof": 146502, + "timestamp": 1728974704.4888444, + "transactions": [ + { + "digital_signature": "MEYCIQCZzROmPVnwo87lC5httLXhT4ZR5UcyzECdk8Z6ghXs6gIhAPCSlpR2P7Z6Wi1pfvMqtg07RcUquJNLeWVNZtQQUTsK", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974704.4858475, + "transaction_id": "0dc93f2066989fc1795d42e76f89a6d025080a9f6e280c132cb6d291bfc00ea1" + } + } + ] + }, + { + "index": 6, + "previous_hash": "9577d88fe2fe44b85079ee07c9bd2ec77d17bae9bc2370e0ebcb6f535c48349d", + "proof": 43538, + "timestamp": 1728974764.3581252, + "transactions": [ + { + "digital_signature": "MEYCIQDOAPFftH8RQxeSgvXf8cS3LQzZybbouZa/xgzITIH3vAIhAKxLxSr/mv9/bkMZw2O7+P1fDbNtxEVEqWC+NfRPzaAL", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974764.352396, + "transaction_id": "23916989d5984b126a57db5818181a14c8bbb4246b5884d273b477a0b4e0fd00" + } + } + ] + }, + { + "index": 7, + "previous_hash": "1c44c6fabac81569a096d27232a969a266b75dd2dcda84d63df25cde8616ab6d", + "proof": 85724, + "timestamp": 1728974824.4366474, + "transactions": [ + { + "digital_signature": "MEUCIQD+zWEisJqmKJ5z8SXwIqgqPXAKNakTBWz0QbsX3cJeLAIgEK55LEP86zKaFNWA6C2gPUcuQuH+pmlXpuuY8c9LC4A=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974824.4333982, + "transaction_id": "bf0910788fd53f021d55feba6a1c56a686414411bbb31998991d5de20f100681" + } + } + ] + }, + { + "index": 8, + "previous_hash": "8ac34c7968dbd9c65bacdf7616dc530c2a10741940daca0c54efdb0100bd5a6a", + "proof": 51178, + "timestamp": 1728974884.3970501, + "transactions": [ + { + "digital_signature": "MEYCIQCWIOHbCPiG1sSo6yOI/7eIocb1Xss5IXc+2HW5zbIKxgIhAPmA9+9Sde/TGtwk3u8+Cb+QQSdeOFjli2QX9S/lNYBe", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974884.395058, + "transaction_id": "07e783e78e1d4cba0d6ac7fe28115489f7a4b909a10accee55d186837cf7db64" + } + } + ] + }, + { + "index": 9, + "previous_hash": "778b4cba347e2e70b9d8c47d070a001ea99e96434d4daeeb943763388eb046f9", + "proof": 71730, + "timestamp": 1728974944.4548056, + "transactions": [ + { + "digital_signature": "MEYCIQCrh0eQCAcGidTtQkbBDsx600H02d9ZNQ708JfwY7+VQQIhALyPxkf6BeNCzzere+eMq98IKXPHd0a0E9oWV7f/9x7r", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974944.45279, + "transaction_id": "cc578313a9c526ffe96b0fe4600bb93b39332868ff496babe62b4346e007974f" + } + } + ] + }, + { + "index": 10, + "previous_hash": "a96ac9eee81499d1264dd115cfca31b6cea266ed12ed217d1ef5213cf15b6a9f", + "proof": 55589, + "timestamp": 1728975004.4618313, + "transactions": [ + { + "digital_signature": "MEQCIEngRwU14Jl3+ymWczqRJAvMk1qlOM5XvsXxvIkw+3KgAiAlCBncpektqKapPlu+p+kNhTu6U7obGwF57Nuc9PQZGA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975004.4600081, + "transaction_id": "12d3e76b4cab5591b2ee02f4a91324b6ef0eeee96e8adbc4bf965f5ed3e142fa" + } + } + ] + }, + { + "index": 11, + "previous_hash": "76d0b6e10e61ae1e75bc580b83a0be2d830d0b3166f62ef8f09cfb8ecf4eb693", + "proof": 35704, + "timestamp": 1728975064.4856203, + "transactions": [ + { + "digital_signature": "MEUCIAXISgaX+bU/DQ91NyaH+S1yRDClLURY5ZQRdYrDyixUAiEAySkoszgdHBU9bdIKCkRVU35TrS98DXvbeZoE2mXZUMs=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975064.4836202, + "transaction_id": "884db8e2299d319785795019582f8ad0c0723629f27c8f3b39e442e79de8aa44" + } + } + ] + }, + { + "index": 12, + "previous_hash": "32f12ca6cdb5be144ee46cee125bdced266194b97414624dc1fd210907b170ae", + "proof": 57342, + "timestamp": 1728975124.6048725, + "transactions": [ + { + "digital_signature": "MEYCIQC7fsDsJ3wQoY9QL/PlWs9ZjKERbxgWMh/63FTn1h7scAIhALF+GmxpNgq5uqhOgoUbVrmzwWAbDVqM5QGmRaC8Srku", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975124.6011765, + "transaction_id": "0834dc176b09859569aad7b4597893f2de845357b78af6d784abc528b69807e4" + } + } + ] + }, + { + "index": 13, + "previous_hash": "d377ab16295533389908fea265491cc7bed4e82bf0b547336fcff1c62bedd7dc", + "proof": 68975, + "timestamp": 1728975184.6077056, + "transactions": [ + { + "digital_signature": "MEYCIQDpt6olYojDgXBkvjn2sKIP6iw9nQc9cLZsOQO+ih6chQIhAL3rAnkYdGTpZDgEHUKuHcTZ3P9SfORIBVSott5cHeH0", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975184.604074, + "transaction_id": "39b5a25bd9e666c6b4057a57867ac80f68d545015e739c216bb0161aae06d0c5" + } + } + ] + }, + { + "index": 14, + "previous_hash": "cdb6a7ed569e50024e3a3077e8c8bb4ad09b17078bde559e5263bd93ce5dd1ec", + "proof": 153122, + "timestamp": 1728975244.689295, + "transactions": [ + { + "digital_signature": "MEQCICCoFk2hCTOqwcm5ij0MOuMV/hr9vgfg9IdA+AQW729gAiA4ZM7mH83nVDvDXOkxHl8pENKeEMjzQ6VdMu6Ske69mA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975244.6872911, + "transaction_id": "8ff23a738fce7cdf9f74b38987b6891f884503d90132a970fc236a824ab6bb0a" + } + } + ] + }, + { + "index": 15, + "previous_hash": "de7f3f590c5976a155a573957e8a2c2da2658a360a3ae21f78d6c504f06f2201", + "proof": 20760, + "timestamp": 1728975304.6244986, + "transactions": [ + { + "digital_signature": "MEQCIG4DXGXrCPYbtzRG+500+8tvDIBVR6aSMcei1k1NQcHsAiAaYmjiEUsH2k0WjLUuSC2vCh8YlAxIrfLLJl1vA6lZhA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975304.622504, + "transaction_id": "06143f8e221f1f7ec20d39e2bd904c3cbeed2259f013eaf3dae4a6f15a6dbd6c" + } + } + ] + }, + { + "index": 15, + "previous_hash": "2aabc645f76685ffb8e587f37d423c1396b2a2104489546d8ed2ff97966c123a", + "proof": 29341, + "timestamp": 1728975756.5164192, + "transactions": [ + { + "digital_signature": "MEQCIFLy8ImmWUnI2nIauS3XCG+sq96U/Fq/uPyt/HP0qP7eAiB2tKovBOYt/Xs2HZfXPfP6/c1CL0QaT1nvOevkdOZ5lg==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975756.5133958, + "transaction_id": "142270daba595b613abba390576fd2bb72767da33ce4b79de512ffc5a7f921a5" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + } + ] + }, + { + "index": 16, + "previous_hash": "53e80a715d78a4e1d1682f609df04cd326fc389df00c214d385284afb68b1418", + "proof": 15889, + "timestamp": 1728975971.0139954, + "transactions": [ + { + "digital_signature": "MEQCIDoekApOmyc9UzeZ4YhR0f0bWD+/3Lolpmn/0S30tFqEAiBsGFGh7M8USbUG1vAXBmq3GDMKuKamjAWP/lcP7cEUNA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 350, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975971.0119958, + "transaction_id": "b18f0804a34377d5c251a98767ef4c9d56e346169a27697a93b8c37cceb0d68c" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + } + ] + }, + { + "index": 17, + "previous_hash": "e470f9d6a55cdd925aea82dc391a8ff7c0fcc9bb9a2394d0857af6bc4d8896b7", + "proof": 209765, + "timestamp": 1728976024.9810693, + "transactions": [ + { + "digital_signature": "MEYCIQDrZ7q9xHVJ7lh/3gT46y1adZlph4fN294IbkfDyVnBsQIhALPq8Fv4ZwSuwWvbLdy/eZgYVsaqSBFkETQps7/p2S2g", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 450, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976024.979543, + "transaction_id": "da8eea2ec16240110a5d913ecbae1cd9ca2fcc80cf13f031bfa23fb65e364403" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + }, + { + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } + } + ] + }, + { + "index": 18, + "previous_hash": "8189da9ee091cdbe6fa9d0db1092cfcaf99f50aa5c0300c66b107686d6f580c5", + "proof": 3748, + "timestamp": 1728976103.733967, + "transactions": [ + { + "digital_signature": "MEYCIQDr+8jpV3tvLrVPcTadPNhxreM2THaFwKJ8vXZdsjEX6QIhAMACHbHEu7+5UJeMTX0YnkP25jOzy0+PPAjzqJ6bW5nQ", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 550, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976103.732963, + "transaction_id": "0dd94ebb25df84f6684c2caa0a849d5d1310924e43700a71ca1a763de36a89d7" + } + }, + { + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } + }, + { + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + }, + { + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + }, + { + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } + }, + { + "digital_signature": "MEUCIQDTWXwJ9UOjJqjVgcd1hQiuTOa3DTzrGTwVGxl31FbU7gIgZ2BRMQPi8GHtdLr64NmmzLPlQaryFjBPG2Ral31jRxI=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976103.7194138, + "transaction_id": "134eea2fcf5c87cc930df55c430e67230bb892bce3a28ddba6ed0346105610d1" + } + } + ] + }, + { + "index": 19, + "previous_hash": "0b4fe1a9f1692b542f4a9943d37863403bc5a9bc71b356c6dfc1e86777c5a329", + "proof": 28807, + "timestamp": 1728976122.9694924, + "transactions": [ + { + "digital_signature": "MEYCIQDjI7PnA3J3c1HGl9xZdTPAVLo7uDkkvEzvrJOLt6rdUQIhAKo5EFOj5xyWhTTgZ55Feroc5ZPzc7opBwni7HeLRwr1", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976122.9679887, + "transaction_id": "4ee9a79f736d808bbf0a689433353669687b3090cfa2444e0215d16cc019f95d" + } + }, + { + "digital_signature": "MEQCIBVU0sWBlx4mmyyDu9NxzUyJ1KHbuq2CyV7QbmEZAb0XAiBhrihf5gdZXaHNdpuckfITpqQ3odMCOj6BNKxKxH2/0w==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976110.323565, + "transaction_id": "63575decc2edbfe96737b7bedcc2d4ba9d230fb1eecb8310c5ec97b73376d646" + } + }, + { + "digital_signature": "MEYCIQCtaVYVEmovF9Q4fgXAtfGM9/1OLhmm2ydsceYS+kGZ1QIhAJQk+lYygkWBZ7VUsyQfMK1UUZIDDsy+dZQAyDZcHTGp", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976122.9341776, + "transaction_id": "d05338d826bed327e000c0dadfe6dbf6696ee8c0c6b2f0b1c73d2d885ce4fe5b" + } + } + ] + }, + { + "index": 20, + "previous_hash": "a16c2898879799c3dc1988c84a3eed4c16b0f3a8705993576d11f4b0fa31e4af", + "proof": 70832, + "timestamp": 1728982795.977029, + "transactions": [ + { + "digital_signature": "MEUCIQCbU4ZVg0fMROF21D7aEaN9xxHjwpEPxcG33CdRK4bLmwIgTCqmFMyVYmtGJbxm/mCbka6TXggZjLdgBonB8Xj6zK4=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982795.9745123, + "transaction_id": "07fd3b0e1a4c95f2c24d7b4f4c5279a5b877c28e9004a47881a1389536ede68c" + } + } + ] + }, + { + "index": 21, + "previous_hash": "4989e8b8021887d35737e9bd201eebb2e5e611a5c3c1d2b2455f1361a7472b33", + "proof": 47031, + "timestamp": 1728982796.253764, + "transactions": [ + { + "digital_signature": "MEYCIQCa6nS5/CQMb6xoPPaysXuANZMSy8W5EjVGgq4a+ETAgwIhANDKMlul8q6pf/OjmJRqkYpwJK/gBRVX11hzsvAsVfld", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982796.2499352, + "transaction_id": "423069ba8b1487f5c7394605b6628395fdf571fe56c86d95ee882ffd705d945e" + } + } + ] + }, + { + "index": 22, + "previous_hash": "19f4fa00986014243d97fa5539db5a2dbda69a522c4d2f155dc203f6f3c5eda4", + "proof": 48098, + "timestamp": 1728983396.2543488, + "transactions": [ + { + "digital_signature": "MEQCIDa7z0tfh1xCbh7DtutvqeZPzKUDnhPU/efllO6CTJxtAiAw7XI4eA3g+RAP83feJaQvRPubamYnSrMATsbUxfgfDw==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983396.248372, + "transaction_id": "0820117dececa39333cb2b17973f9f40327ae5177f45374bd79d64f3b9005fa0" + } + } + ] + }, + { + "index": 23, + "previous_hash": "f8d4e2cfcccaa25855f70902140bf9fda0b3cb70ad6d6f1545b3badbb4c71c6e", + "proof": 30743, + "timestamp": 1728983397.2447324, + "transactions": [ + { + "digital_signature": "MEUCIQDoYhvL1y5B13z2+hCMD8WfzyOKVt4Rb/l0tdnGeEjrZQIgFpGSGRxgLlnI3A9D02BD1B0cgu6boecY17yONJnfksw=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983397.2373633, + "transaction_id": "fb410938d213ed553a9c0886e6001598b5986fa6defa6cbcb7a2b9b2f132ff93" + } + } + ] + }, + { + "index": 24, + "previous_hash": "25f25ddc2e3fa0e775e511bc94410080c86493649b598b65cb94363a9eef5a6f", + "proof": 41003, + "timestamp": 1728998883.4670477, + "transactions": [ + { + "digital_signature": "MEUCIQDKLP510ilNznCAND1x6bn2L6U7yNFOtTiQUoiJ0HLSHgIgA+YT8q3XbHCbGOiuLxiabcMjFB9/omh2uGSxstJ/yc0=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728998883.4660475, + "transaction_id": "b54a2b509f074d94500a935d6573cc5bd7b322a9540371a445acc851a0422e5c" + } + } + ] + } + ], + "current_transactions": [], + "nodes": [ + "simplicity-server.onrender.com", + "simplicity-server1.onrender.com" + ], + "ttl": { + "simplicity-server1": 1729137155.8219357 + } +} \ No newline at end of file diff --git a/.history/database_20241017101423.py b/.history/database_20241017101423.py new file mode 100644 index 0000000..77b0e8f --- /dev/null +++ b/.history/database_20241017101423.py @@ -0,0 +1,96 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to Firebase. + + :param blockchain: The Blockchain instance to save + + """ + + ref = db.reference('blockchain') + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save to Firebase. Starting with a new blockchain.") + return + + # Ensure nodes are stored as hashable types (e.g., converting lists to tuples if necessary) + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + print( + "the data is : ", data + ) + + ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + ref = self.ref + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + print("retriving data from firebase") + blockchain.chain = ref.get('chain', []) + print("retrived data from firebase" , ref.get('chain', [])) + + blockchain.current_transactions = ref.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes_list = [] + ref = db.reference('blockchain') + for node in ref.get('nodes', []): + available_node = ref.get(node, "") + nodes_list.append(available_node) + + ref = db.reference('blockchain') + + print("nodes" ,nodes_list) + + blockchain.nodes = set(nodes_list) + blockchain.ttl = ref.get('ttl', blockchain.ttl) + print("ttl" , blockchain.ttl ) + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain: {e}") + return False diff --git a/.history/database_20241017102653.py b/.history/database_20241017102653.py new file mode 100644 index 0000000..57f5d66 --- /dev/null +++ b/.history/database_20241017102653.py @@ -0,0 +1,96 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017102950.py b/.history/database_20241017102950.py new file mode 100644 index 0000000..57f5d66 --- /dev/null +++ b/.history/database_20241017102950.py @@ -0,0 +1,96 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017103339.py b/.history/database_20241017103339.py new file mode 100644 index 0000000..57f5d66 --- /dev/null +++ b/.history/database_20241017103339.py @@ -0,0 +1,96 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017104033.py b/.history/database_20241017104033.py new file mode 100644 index 0000000..5041628 --- /dev/null +++ b/.history/database_20241017104033.py @@ -0,0 +1,98 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017105036.py b/.history/database_20241017105036.py new file mode 100644 index 0000000..3cd9cb3 --- /dev/null +++ b/.history/database_20241017105036.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110649.py b/.history/database_20241017110649.py new file mode 100644 index 0000000..2b84c26 --- /dev/null +++ b/.history/database_20241017110649.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110658.py b/.history/database_20241017110658.py new file mode 100644 index 0000000..c4bf1db --- /dev/null +++ b/.history/database_20241017110658.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110659.py b/.history/database_20241017110659.py new file mode 100644 index 0000000..c4bf1db --- /dev/null +++ b/.history/database_20241017110659.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110743.py b/.history/database_20241017110743.py new file mode 100644 index 0000000..fac8ec7 --- /dev/null +++ b/.history/database_20241017110743.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + ref = db.reference('blockchain') + data = ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110744.py b/.history/database_20241017110744.py new file mode 100644 index 0000000..fac8ec7 --- /dev/null +++ b/.history/database_20241017110744.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + ref = db.reference('blockchain') + data = ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110750.py b/.history/database_20241017110750.py new file mode 100644 index 0000000..fac8ec7 --- /dev/null +++ b/.history/database_20241017110750.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + ref = db.reference('blockchain') + data = ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110757.py b/.history/database_20241017110757.py new file mode 100644 index 0000000..dfa93a4 --- /dev/null +++ b/.history/database_20241017110757.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/blockchain" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('/') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + ref = db.reference('/') + data = ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110822.py b/.history/database_20241017110822.py new file mode 100644 index 0000000..4d69ca6 --- /dev/null +++ b/.history/database_20241017110822.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110828.py b/.history/database_20241017110828.py new file mode 100644 index 0000000..4d69ca6 --- /dev/null +++ b/.history/database_20241017110828.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110842.py b/.history/database_20241017110842.py new file mode 100644 index 0000000..2b5a32d --- /dev/null +++ b/.history/database_20241017110842.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110927.py b/.history/database_20241017110927.py new file mode 100644 index 0000000..2b5a32d --- /dev/null +++ b/.history/database_20241017110927.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110953.py b/.history/database_20241017110953.py new file mode 100644 index 0000000..2b5a32d --- /dev/null +++ b/.history/database_20241017110953.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110954.py b/.history/database_20241017110954.py new file mode 100644 index 0000000..2b5a32d --- /dev/null +++ b/.history/database_20241017110954.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017110956.py b/.history/database_20241017110956.py new file mode 100644 index 0000000..2b5a32d --- /dev/null +++ b/.history/database_20241017110956.py @@ -0,0 +1,100 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + self.save_blockchain(blockchain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111034.py b/.history/database_20241017111034.py new file mode 100644 index 0000000..7d77145 --- /dev/null +++ b/.history/database_20241017111034.py @@ -0,0 +1,99 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111203.py b/.history/database_20241017111203.py new file mode 100644 index 0000000..9e27574 --- /dev/null +++ b/.history/database_20241017111203.py @@ -0,0 +1,103 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111617.py b/.history/database_20241017111617.py new file mode 100644 index 0000000..4612edb --- /dev/null +++ b/.history/database_20241017111617.py @@ -0,0 +1,110 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111620.py b/.history/database_20241017111620.py new file mode 100644 index 0000000..044dccf --- /dev/null +++ b/.history/database_20241017111620.py @@ -0,0 +1,112 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111923.py b/.history/database_20241017111923.py new file mode 100644 index 0000000..044dccf --- /dev/null +++ b/.history/database_20241017111923.py @@ -0,0 +1,112 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain or not unique_transactions: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111940.py b/.history/database_20241017111940.py new file mode 100644 index 0000000..5f7ecad --- /dev/null +++ b/.history/database_20241017111940.py @@ -0,0 +1,112 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017111949.py b/.history/database_20241017111949.py new file mode 100644 index 0000000..5f7ecad --- /dev/null +++ b/.history/database_20241017111949.py @@ -0,0 +1,112 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112755.py b/.history/database_20241017112755.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017112755.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112756.py b/.history/database_20241017112756.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017112756.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112807.py b/.history/database_20241017112807.py new file mode 100644 index 0000000..341450a --- /dev/null +++ b/.history/database_20241017112807.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('https://simplicity-coin-default-rtdb.firebaseio.com/blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112808.py b/.history/database_20241017112808.py new file mode 100644 index 0000000..341450a --- /dev/null +++ b/.history/database_20241017112808.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('https://simplicity-coin-default-rtdb.firebaseio.com/blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112829.py b/.history/database_20241017112829.py new file mode 100644 index 0000000..2ef5079 --- /dev/null +++ b/.history/database_20241017112829.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('https://simplicity-coin-default-rtdb.firebaseio.com/') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112839.py b/.history/database_20241017112839.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017112839.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017112840.py b/.history/database_20241017112840.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017112840.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113825.py b/.history/database_20241017113825.py new file mode 100644 index 0000000..3e9b70c --- /dev/null +++ b/.history/database_20241017113825.py @@ -0,0 +1,119 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113827.py b/.history/database_20241017113827.py new file mode 100644 index 0000000..3e9b70c --- /dev/null +++ b/.history/database_20241017113827.py @@ -0,0 +1,119 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113828.py b/.history/database_20241017113828.py new file mode 100644 index 0000000..3e9b70c --- /dev/null +++ b/.history/database_20241017113828.py @@ -0,0 +1,119 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113841.py b/.history/database_20241017113841.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017113841.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113844.py b/.history/database_20241017113844.py new file mode 100644 index 0000000..5f7ecad --- /dev/null +++ b/.history/database_20241017113844.py @@ -0,0 +1,112 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017113849.py b/.history/database_20241017113849.py new file mode 100644 index 0000000..9b03d7c --- /dev/null +++ b/.history/database_20241017113849.py @@ -0,0 +1,113 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017114943.py b/.history/database_20241017114943.py new file mode 100644 index 0000000..8c71371 --- /dev/null +++ b/.history/database_20241017114943.py @@ -0,0 +1,107 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115417.py b/.history/database_20241017115417.py new file mode 100644 index 0000000..8c71371 --- /dev/null +++ b/.history/database_20241017115417.py @@ -0,0 +1,107 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115510.py b/.history/database_20241017115510.py new file mode 100644 index 0000000..81c6abe --- /dev/null +++ b/.history/database_20241017115510.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + if not os.path.exists(local_file_path): + print("No local data found. Starting with a new blockchain.") + return + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115511.py b/.history/database_20241017115511.py new file mode 100644 index 0000000..81c6abe --- /dev/null +++ b/.history/database_20241017115511.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + if not os.path.exists(local_file_path): + print("No local data found. Starting with a new blockchain.") + return + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115536.py b/.history/database_20241017115536.py new file mode 100644 index 0000000..9242770 --- /dev/null +++ b/.history/database_20241017115536.py @@ -0,0 +1,107 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115630.py b/.history/database_20241017115630.py new file mode 100644 index 0000000..b9c723b --- /dev/null +++ b/.history/database_20241017115630.py @@ -0,0 +1,107 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + # self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115704.py b/.history/database_20241017115704.py new file mode 100644 index 0000000..b9c723b --- /dev/null +++ b/.history/database_20241017115704.py @@ -0,0 +1,107 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + # self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017115750.py b/.history/database_20241017115750.py new file mode 100644 index 0000000..3a73fd7 --- /dev/null +++ b/.history/database_20241017115750.py @@ -0,0 +1,108 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + print("The length of the blockchain is " + str(len(blockchain.chain))) + print("The length of the current transactions is " + str(len(blockchain.current_transactions))) + print("The length of the nodes is " + str(len(blockchain.nodes))) + print("The length of the hash_list is " + str(len(blockchain.hash_list))) + print("Blockchain loaded from Firebase") + + # self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017120019.py b/.history/database_20241017120019.py new file mode 100644 index 0000000..d14cd18 --- /dev/null +++ b/.history/database_20241017120019.py @@ -0,0 +1,103 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + # self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017120022.py b/.history/database_20241017120022.py new file mode 100644 index 0000000..fbf0362 --- /dev/null +++ b/.history/database_20241017120022.py @@ -0,0 +1,103 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017120110.py b/.history/database_20241017120110.py new file mode 100644 index 0000000..fbf0362 --- /dev/null +++ b/.history/database_20241017120110.py @@ -0,0 +1,103 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + print("Retrieving data from Firebase" , data) + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017120139.py b/.history/database_20241017120139.py new file mode 100644 index 0000000..bba4e5b --- /dev/null +++ b/.history/database_20241017120139.py @@ -0,0 +1,102 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + blockchain.nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + blockchain.ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017121411.py b/.history/database_20241017121411.py new file mode 100644 index 0000000..8723c2a --- /dev/null +++ b/.history/database_20241017121411.py @@ -0,0 +1,110 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in data['chain']).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in data['current_transactions']).values()) + blockchain.nodes = set(data['nodes']) + blockchain.ttl = data['ttl'] + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017121424.py b/.history/database_20241017121424.py new file mode 100644 index 0000000..983fddb --- /dev/null +++ b/.history/database_20241017121424.py @@ -0,0 +1,110 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in data['current_transactions']).values()) + blockchain.nodes = set(data['nodes']) + blockchain.ttl = data['ttl'] + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017121440.py b/.history/database_20241017121440.py new file mode 100644 index 0000000..936f065 --- /dev/null +++ b/.history/database_20241017121440.py @@ -0,0 +1,110 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes = set(data['nodes']) + blockchain.ttl = data['ttl'] + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017121504.py b/.history/database_20241017121504.py new file mode 100644 index 0000000..6b405ce --- /dev/null +++ b/.history/database_20241017121504.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017122020.py b/.history/database_20241017122020.py new file mode 100644 index 0000000..6b405ce --- /dev/null +++ b/.history/database_20241017122020.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017123421.py b/.history/database_20241017123421.py new file mode 100644 index 0000000..6b405ce --- /dev/null +++ b/.history/database_20241017123421.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017124007.py b/.history/database_20241017124007.py new file mode 100644 index 0000000..6b405ce --- /dev/null +++ b/.history/database_20241017124007.py @@ -0,0 +1,109 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase. + """ + try: + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + with open(local_file_path, 'r') as f: + data = json.load(f) + self.ref.delete() + print("Deleting data from Firebase") + self.ref = db.reference('blockchain') + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017124110.py b/.history/database_20241017124110.py new file mode 100644 index 0000000..ec066cc --- /dev/null +++ b/.history/database_20241017124110.py @@ -0,0 +1,120 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + +def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase after deleting the existing data. + """ + try: + # Check if the local file exists + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + # Load the data from the local file + with open(local_file_path, 'r') as f: + data = json.load(f) + + # Check if the Firebase reference exists, if not initialize it + if not self.ref: + self.ref = db.reference('blockchain') + + # Delete existing data from Firebase + print("Deleting existing data from Firebase...") + self.ref.delete() # Deletes all data under 'blockchain' reference + + # Store the new data in Firebase + print("Saving new blockchain data to Firebase...") + self.ref.set(data) + print("Blockchain successfully saved to Firebase.") + + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017124111.py b/.history/database_20241017124111.py new file mode 100644 index 0000000..ec066cc --- /dev/null +++ b/.history/database_20241017124111.py @@ -0,0 +1,120 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + +def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase after deleting the existing data. + """ + try: + # Check if the local file exists + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + # Load the data from the local file + with open(local_file_path, 'r') as f: + data = json.load(f) + + # Check if the Firebase reference exists, if not initialize it + if not self.ref: + self.ref = db.reference('blockchain') + + # Delete existing data from Firebase + print("Deleting existing data from Firebase...") + self.ref.delete() # Deletes all data under 'blockchain' reference + + # Store the new data in Firebase + print("Saving new blockchain data to Firebase...") + self.ref.set(data) + print("Blockchain successfully saved to Firebase.") + + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/database_20241017124123.py b/.history/database_20241017124123.py new file mode 100644 index 0000000..89857f8 --- /dev/null +++ b/.history/database_20241017124123.py @@ -0,0 +1,120 @@ +import json +from collections import OrderedDict +import firebase_admin +from firebase_admin import credentials +from firebase_admin import db +import os + +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" + +class BlockchainDb: + def __init__(self): + if not firebase_admin._apps: + cred = credentials.Certificate(firebase_cred_path) + firebase_admin.initialize_app(cred, { + 'databaseURL': database_url + }) + self.ref = db.reference('blockchain') + + def save_blockchain(self, blockchain): + """ + Save the blockchain to a local JSON file. + + :param blockchain: The Blockchain instance to save + """ + try: + print("Saving blockchain to local file") + + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") + + def load_blockchain(self, blockchain): + """ + Load the blockchain from Firebase. + + :param blockchain: The Blockchain instance to update + :return: True if loaded successfully, False otherwise + """ + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") + return False + + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase after deleting the existing data. + """ + try: + # Check if the local file exists + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return + + # Load the data from the local file + with open(local_file_path, 'r') as f: + data = json.load(f) + + # Check if the Firebase reference exists, if not initialize it + if not self.ref: + self.ref = db.reference('blockchain') + + # Delete existing data from Firebase + print("Deleting existing data from Firebase...") + self.ref.delete() # Deletes all data under 'blockchain' reference + + # Store the new data in Firebase + print("Saving new blockchain data to Firebase...") + self.ref.set(data) + print("Blockchain successfully saved to Firebase.") + + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/.history/requirements_20241016123409.txt b/.history/requirements_20241016123409.txt new file mode 100644 index 0000000..e69de29 diff --git a/.history/requirements_20241016123604.txt b/.history/requirements_20241016123604.txt new file mode 100644 index 0000000..bbc83da --- /dev/null +++ b/.history/requirements_20241016123604.txt @@ -0,0 +1,8 @@ +base64 +Flask +Flask-Cors +requests +schedule +ecdsa +ellipticcurve +firebase-admin \ No newline at end of file diff --git a/.history/requirements_20241017123503.txt b/.history/requirements_20241017123503.txt new file mode 100644 index 0000000..2b0f954 --- /dev/null +++ b/.history/requirements_20241017123503.txt @@ -0,0 +1,12 @@ +pybase64 +Flask +Flask-Cors +requests +schedule +ecdsa +firebase-admin +base58 +starkbank-ecdsa +elliptic-curve +Flask-CORS +firebase-admin \ No newline at end of file diff --git a/README.md b/README.md index b0d8f50..d3f5a12 100644 Binary files a/README.md and b/README.md differ diff --git a/__pycache__/account_db.cpython-311.pyc b/__pycache__/account_db.cpython-311.pyc index 109b7d1..17daf11 100644 Binary files a/__pycache__/account_db.cpython-311.pyc and b/__pycache__/account_db.cpython-311.pyc differ diff --git a/__pycache__/blockchain.cpython-311.pyc b/__pycache__/blockchain.cpython-311.pyc index fb4b2fd..3a6411c 100644 Binary files a/__pycache__/blockchain.cpython-311.pyc and b/__pycache__/blockchain.cpython-311.pyc differ diff --git a/__pycache__/database.cpython-311.pyc b/__pycache__/database.cpython-311.pyc index b486a43..5325c0d 100644 Binary files a/__pycache__/database.cpython-311.pyc and b/__pycache__/database.cpython-311.pyc differ diff --git a/__pycache__/nodeManager.cpython-311.pyc b/__pycache__/nodeManager.cpython-311.pyc index ca1c86c..835b0cf 100644 Binary files a/__pycache__/nodeManager.cpython-311.pyc and b/__pycache__/nodeManager.cpython-311.pyc differ diff --git a/app.py b/app.py index 4db7581..78986ee 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ from blockchain import Blockchain from database import BlockchainDb from flask_cors import CORS # Import CORS +import atexit app = flask.Flask(__name__) from flask import Flask, copy_current_request_context, g, request, jsonify @@ -26,6 +27,7 @@ def hello(): @app.route('/chain', methods=['GET']) def chain(): + print("the length of the blockchain is " + str(len(blockchain.chain))) return flask.jsonify({ 'chain': blockchain.chain, 'length': len(blockchain.chain) @@ -60,7 +62,7 @@ def register_nodes(): for node in nodes: print("this is parent node", "simplicity_server.onrender.com") - blockchain.register_node(node, "simplicity_server.onrender.com") + blockchain.register_node(node, "https://simplicity_server.onrender.com") response = { 'message': 'New nodes have been added', @@ -201,23 +203,25 @@ def delete_chain(): return flask.jsonify(f"removed Node from the network"), 200 -@app.teardown_appcontext def shutdown_session(exception=None): + blockchain.resolve_conflicts() database = BlockchainDb() database.save_blockchain(blockchain) + database.save_to_firebase() + print("Blockchain saved to local file") + +atexit.register(shutdown_session) + - host_url = getattr(g, 'host_url', None) # Get the host URL safely - if host_url: - for node in blockchain.nodes: - try: - requests.post(f'http://{node}/delete_node', json={"node": host_url}, timeout=5) - except requests.exceptions.RequestException as e: - print(f"Error notifying node {node}: {e}") -def register_node(port): - print(f"Registering node with port {port}...") - blockchain.register(f'simplicity_server.onrender.com') +# def register_node(port): +# print(f"Registering node with port {port}...") +# print("nodes" ,blockchain.nodes) +# print("nodes type" ,type(blockchain.nodes)) +# print("chain" ,blockchain.chain) +# print("chain type" ,type(blockchain.chain)) +# blockchain.register('simplicity_server1.onrender.com') if __name__ == '__main__': @@ -226,5 +230,5 @@ def register_node(port): parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on') args = parser.parse_args() port = args.port - threading.Thread(target=register_node, args=[port], daemon=True).start() + # threading.Thread(target=register_node, args=[port], daemon=True).start() app.run(host='0.0.0.0', port=port) diff --git a/bloc.json b/bloc.json new file mode 100644 index 0000000..def35bb --- /dev/null +++ b/bloc.json @@ -0,0 +1,680 @@ +{ + "chain": [ + { + "index": 2, + "timestamp": 1728974524.0400372, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974524.0336194, + "transaction_id": "9c5d65021bc50fe93a19af30b71b8fbaaf920e43d45e6c4fb26ac1a8fddeaad9", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDTm3LkbQh980Gxt/6YpHd43mWc/L739PIjtjUW11NTEgIhANtyw1ucq81tdJfpX81ZlVP1w1HbnaUwzO/E9ApxqXVx" + } + ], + "proof": 35293, + "previous_hash": "7ebe3f2f48fd01a145b056519aa646585048b3ca562ad9833ce31a44fb29e4f3" + }, + { + "index": 3, + "timestamp": 1728974584.1404045, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974584.1352344, + "transaction_id": "1f9eaeb43dad5f58b6f69964c38f1c263c7de41fa04d1421b980997875932363", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIAaaLt/hwWHiqcNzz14SWISNqr2aR5+VAlOwkIC3ZGmRAiEA+W93lJHno0ePkyIKZvIp2eE6eNW4rwJYhHdZLwsuyik=" + } + ], + "proof": 35089, + "previous_hash": "ea9fcdb805d1519b46ff274e27122028683f00e441a7bba0ef24570d1deb80a6" + }, + { + "index": 4, + "timestamp": 1728974644.3219702, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974644.3169801, + "transaction_id": "fac86976024f9567eac8a9d0c913b9fff662d2e54740922d9153fe978a656aed", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQD4IJr2FIaN0d80wPjRs32UhUiJWpgjvI9zDdOktRstpQIhAJeM1XZdYFONgJFLIWzoDzBh4PVImq+fe5sthngjE16c" + } + ], + "proof": 119678, + "previous_hash": "aad438bdcdfd5e93cbe8b0a5a917da56784bb78e91161bc0a3ed0eea56b94ed1" + }, + { + "index": 5, + "timestamp": 1728974704.4888444, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974704.4858475, + "transaction_id": "0dc93f2066989fc1795d42e76f89a6d025080a9f6e280c132cb6d291bfc00ea1", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQCZzROmPVnwo87lC5httLXhT4ZR5UcyzECdk8Z6ghXs6gIhAPCSlpR2P7Z6Wi1pfvMqtg07RcUquJNLeWVNZtQQUTsK" + } + ], + "proof": 146502, + "previous_hash": "c09881935ad064d13bd87e0250958497fdad66358760d5c97bafb9efd4e45bc6" + }, + { + "index": 6, + "timestamp": 1728974764.3581252, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974764.352396, + "transaction_id": "23916989d5984b126a57db5818181a14c8bbb4246b5884d273b477a0b4e0fd00", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDOAPFftH8RQxeSgvXf8cS3LQzZybbouZa/xgzITIH3vAIhAKxLxSr/mv9/bkMZw2O7+P1fDbNtxEVEqWC+NfRPzaAL" + } + ], + "proof": 43538, + "previous_hash": "9577d88fe2fe44b85079ee07c9bd2ec77d17bae9bc2370e0ebcb6f535c48349d" + }, + { + "index": 7, + "timestamp": 1728974824.4366474, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974824.4333982, + "transaction_id": "bf0910788fd53f021d55feba6a1c56a686414411bbb31998991d5de20f100681", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIQD+zWEisJqmKJ5z8SXwIqgqPXAKNakTBWz0QbsX3cJeLAIgEK55LEP86zKaFNWA6C2gPUcuQuH+pmlXpuuY8c9LC4A=" + } + ], + "proof": 85724, + "previous_hash": "1c44c6fabac81569a096d27232a969a266b75dd2dcda84d63df25cde8616ab6d" + }, + { + "index": 8, + "timestamp": 1728974884.3970501, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974884.395058, + "transaction_id": "07e783e78e1d4cba0d6ac7fe28115489f7a4b909a10accee55d186837cf7db64", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQCWIOHbCPiG1sSo6yOI/7eIocb1Xss5IXc+2HW5zbIKxgIhAPmA9+9Sde/TGtwk3u8+Cb+QQSdeOFjli2QX9S/lNYBe" + } + ], + "proof": 51178, + "previous_hash": "8ac34c7968dbd9c65bacdf7616dc530c2a10741940daca0c54efdb0100bd5a6a" + }, + { + "index": 9, + "timestamp": 1728974944.4548056, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728974944.45279, + "transaction_id": "cc578313a9c526ffe96b0fe4600bb93b39332868ff496babe62b4346e007974f", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQCrh0eQCAcGidTtQkbBDsx600H02d9ZNQ708JfwY7+VQQIhALyPxkf6BeNCzzere+eMq98IKXPHd0a0E9oWV7f/9x7r" + } + ], + "proof": 71730, + "previous_hash": "778b4cba347e2e70b9d8c47d070a001ea99e96434d4daeeb943763388eb046f9" + }, + { + "index": 10, + "timestamp": 1728975004.4618313, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975004.4600081, + "transaction_id": "12d3e76b4cab5591b2ee02f4a91324b6ef0eeee96e8adbc4bf965f5ed3e142fa", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCIEngRwU14Jl3+ymWczqRJAvMk1qlOM5XvsXxvIkw+3KgAiAlCBncpektqKapPlu+p+kNhTu6U7obGwF57Nuc9PQZGA==" + } + ], + "proof": 55589, + "previous_hash": "a96ac9eee81499d1264dd115cfca31b6cea266ed12ed217d1ef5213cf15b6a9f" + }, + { + "index": 11, + "timestamp": 1728975064.4856203, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975064.4836202, + "transaction_id": "884db8e2299d319785795019582f8ad0c0723629f27c8f3b39e442e79de8aa44", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIAXISgaX+bU/DQ91NyaH+S1yRDClLURY5ZQRdYrDyixUAiEAySkoszgdHBU9bdIKCkRVU35TrS98DXvbeZoE2mXZUMs=" + } + ], + "proof": 35704, + "previous_hash": "76d0b6e10e61ae1e75bc580b83a0be2d830d0b3166f62ef8f09cfb8ecf4eb693" + }, + { + "index": 12, + "timestamp": 1728975124.6048725, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975124.6011765, + "transaction_id": "0834dc176b09859569aad7b4597893f2de845357b78af6d784abc528b69807e4", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQC7fsDsJ3wQoY9QL/PlWs9ZjKERbxgWMh/63FTn1h7scAIhALF+GmxpNgq5uqhOgoUbVrmzwWAbDVqM5QGmRaC8Srku" + } + ], + "proof": 57342, + "previous_hash": "32f12ca6cdb5be144ee46cee125bdced266194b97414624dc1fd210907b170ae" + }, + { + "index": 13, + "timestamp": 1728975184.6077056, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975184.604074, + "transaction_id": "39b5a25bd9e666c6b4057a57867ac80f68d545015e739c216bb0161aae06d0c5", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDpt6olYojDgXBkvjn2sKIP6iw9nQc9cLZsOQO+ih6chQIhAL3rAnkYdGTpZDgEHUKuHcTZ3P9SfORIBVSott5cHeH0" + } + ], + "proof": 68975, + "previous_hash": "d377ab16295533389908fea265491cc7bed4e82bf0b547336fcff1c62bedd7dc" + }, + { + "index": 14, + "timestamp": 1728975244.689295, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975244.6872911, + "transaction_id": "8ff23a738fce7cdf9f74b38987b6891f884503d90132a970fc236a824ab6bb0a", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCICCoFk2hCTOqwcm5ij0MOuMV/hr9vgfg9IdA+AQW729gAiA4ZM7mH83nVDvDXOkxHl8pENKeEMjzQ6VdMu6Ske69mA==" + } + ], + "proof": 153122, + "previous_hash": "cdb6a7ed569e50024e3a3077e8c8bb4ad09b17078bde559e5263bd93ce5dd1ec" + }, + { + "index": 15, + "timestamp": 1728975304.6244986, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728975304.622504, + "transaction_id": "06143f8e221f1f7ec20d39e2bd904c3cbeed2259f013eaf3dae4a6f15a6dbd6c", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCIG4DXGXrCPYbtzRG+500+8tvDIBVR6aSMcei1k1NQcHsAiAaYmjiEUsH2k0WjLUuSC2vCh8YlAxIrfLLJl1vA6lZhA==" + } + ], + "proof": 20760, + "previous_hash": "de7f3f590c5976a155a573957e8a2c2da2658a360a3ae21f78d6c504f06f2201" + }, + { + "index": 15, + "timestamp": 1728975756.5164192, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 250.0, + "timestamp": 1728975756.5133958, + "transaction_id": "142270daba595b613abba390576fd2bb72767da33ce4b79de512ffc5a7f921a5", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCIFLy8ImmWUnI2nIauS3XCG+sq96U/Fq/uPyt/HP0qP7eAiB2tKovBOYt/Xs2HZfXPfP6/c1CL0QaT1nvOevkdOZ5lg==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" + } + ], + "proof": 29341, + "previous_hash": "2aabc645f76685ffb8e587f37d423c1396b2a2104489546d8ed2ff97966c123a" + }, + { + "index": 16, + "timestamp": 1728975971.0139954, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 350.0, + "timestamp": 1728975971.0119958, + "transaction_id": "b18f0804a34377d5c251a98767ef4c9d56e346169a27697a93b8c37cceb0d68c", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCIDoekApOmyc9UzeZ4YhR0f0bWD+/3Lolpmn/0S30tFqEAiBsGFGh7M8USbUG1vAXBmq3GDMKuKamjAWP/lcP7cEUNA==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" + } + ], + "proof": 15889, + "previous_hash": "53e80a715d78a4e1d1682f609df04cd326fc389df00c214d385284afb68b1418" + }, + { + "index": 17, + "timestamp": 1728976024.9810693, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 450.0, + "timestamp": 1728976024.979543, + "transaction_id": "da8eea2ec16240110a5d913ecbae1cd9ca2fcc80cf13f031bfa23fb65e364403", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDrZ7q9xHVJ7lh/3gT46y1adZlph4fN294IbkfDyVnBsQIhALPq8Fv4ZwSuwWvbLdy/eZgYVsaqSBFkETQps7/p2S2g" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2" + } + ], + "proof": 209765, + "previous_hash": "e470f9d6a55cdd925aea82dc391a8ff7c0fcc9bb9a2394d0857af6bc4d8896b7" + }, + { + "index": 18, + "timestamp": 1728976103.733967, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 550.0, + "timestamp": 1728976103.732963, + "transaction_id": "0dd94ebb25df84f6684c2caa0a849d5d1310924e43700a71ca1a763de36a89d7", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDr+8jpV3tvLrVPcTadPNhxreM2THaFwKJ8vXZdsjEX6QIhAMACHbHEu7+5UJeMTX0YnkP25jOzy0+PPAjzqJ6bW5nQ" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728976103.7194138, + "transaction_id": "134eea2fcf5c87cc930df55c430e67230bb892bce3a28ddba6ed0346105610d1" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEUCIQDTWXwJ9UOjJqjVgcd1hQiuTOa3DTzrGTwVGxl31FbU7gIgZ2BRMQPi8GHtdLr64NmmzLPlQaryFjBPG2Ral31jRxI=" + } + ], + "proof": 3748, + "previous_hash": "8189da9ee091cdbe6fa9d0db1092cfcaf99f50aa5c0300c66b107686d6f580c5" + }, + { + "index": 19, + "timestamp": 1728976122.9694924, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 250.0, + "timestamp": 1728976122.9679887, + "transaction_id": "4ee9a79f736d808bbf0a689433353669687b3090cfa2444e0215d16cc019f95d", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQDjI7PnA3J3c1HGl9xZdTPAVLo7uDkkvEzvrJOLt6rdUQIhAKo5EFOj5xyWhTTgZ55Feroc5ZPzc7opBwni7HeLRwr1" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728976110.323565, + "transaction_id": "63575decc2edbfe96737b7bedcc2d4ba9d230fb1eecb8310c5ec97b73376d646" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEQCIBVU0sWBlx4mmyyDu9NxzUyJ1KHbuq2CyV7QbmEZAb0XAiBhrihf5gdZXaHNdpuckfITpqQ3odMCOj6BNKxKxH2/0w==" + }, + { + "transaction": { + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "amount": 100.0, + "timestamp": 1728976122.9341776, + "transaction_id": "d05338d826bed327e000c0dadfe6dbf6696ee8c0c6b2f0b1c73d2d885ce4fe5b" + }, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "digital_signature": "MEYCIQCtaVYVEmovF9Q4fgXAtfGM9/1OLhmm2ydsceYS+kGZ1QIhAJQk+lYygkWBZ7VUsyQfMK1UUZIDDsy+dZQAyDZcHTGp" + } + ], + "proof": 28807, + "previous_hash": "0b4fe1a9f1692b542f4a9943d37863403bc5a9bc71b356c6dfc1e86777c5a329" + }, + { + "index": 20, + "timestamp": 1728982795.977029, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728982795.9745123, + "transaction_id": "07fd3b0e1a4c95f2c24d7b4f4c5279a5b877c28e9004a47881a1389536ede68c", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIQCbU4ZVg0fMROF21D7aEaN9xxHjwpEPxcG33CdRK4bLmwIgTCqmFMyVYmtGJbxm/mCbka6TXggZjLdgBonB8Xj6zK4=" + } + ], + "proof": 70832, + "previous_hash": "a16c2898879799c3dc1988c84a3eed4c16b0f3a8705993576d11f4b0fa31e4af" + }, + { + "index": 21, + "timestamp": 1728982796.253764, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728982796.2499352, + "transaction_id": "423069ba8b1487f5c7394605b6628395fdf571fe56c86d95ee882ffd705d945e", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEYCIQCa6nS5/CQMb6xoPPaysXuANZMSy8W5EjVGgq4a+ETAgwIhANDKMlul8q6pf/OjmJRqkYpwJK/gBRVX11hzsvAsVfld" + } + ], + "proof": 47031, + "previous_hash": "4989e8b8021887d35737e9bd201eebb2e5e611a5c3c1d2b2455f1361a7472b33" + }, + { + "index": 22, + "timestamp": 1728983396.2543488, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728983396.248372, + "transaction_id": "0820117dececa39333cb2b17973f9f40327ae5177f45374bd79d64f3b9005fa0", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEQCIDa7z0tfh1xCbh7DtutvqeZPzKUDnhPU/efllO6CTJxtAiAw7XI4eA3g+RAP83feJaQvRPubamYnSrMATsbUxfgfDw==" + } + ], + "proof": 48098, + "previous_hash": "19f4fa00986014243d97fa5539db5a2dbda69a522c4d2f155dc203f6f3c5eda4" + }, + { + "index": 23, + "timestamp": 1728983397.2447324, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728983397.2373633, + "transaction_id": "fb410938d213ed553a9c0886e6001598b5986fa6defa6cbcb7a2b9b2f132ff93", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIQDoYhvL1y5B13z2+hCMD8WfzyOKVt4Rb/l0tdnGeEjrZQIgFpGSGRxgLlnI3A9D02BD1B0cgu6boecY17yONJnfksw=" + } + ], + "proof": 30743, + "previous_hash": "f8d4e2cfcccaa25855f70902140bf9fda0b3cb70ad6d6f1545b3badbb4c71c6e" + }, + { + "index": 24, + "timestamp": 1728998883.4670477, + "transactions": [ + { + "transaction": { + "sender": "0", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "amount": 50.0, + "timestamp": 1728998883.4660475, + "transaction_id": "b54a2b509f074d94500a935d6573cc5bd7b322a9540371a445acc851a0422e5c", + "public_address": "" + }, + "public_address": "", + "digital_signature": "MEUCIQDKLP510ilNznCAND1x6bn2L6U7yNFOtTiQUoiJ0HLSHgIgA+YT8q3XbHCbGOiuLxiabcMjFB9/omh2uGSxstJ/yc0=" + } + ], + "proof": 41003, + "previous_hash": "25f25ddc2e3fa0e775e511bc94410080c86493649b598b65cb94363a9eef5a6f" + } + ], + "current_transactions": [ + + + ], + "nodes": [ + "127.0.0.1:5001", + "192.168.29.49:5001", + "127.0.0.1:5002", + "192.168.29.49:5000", + "127.0.0.1:5000", + "127.0.0.1:5003" + ], + "ttl": { + "192.168.29.49:5001": 1729003678.6687841, + "127.0.0.1:5001": 1729000243.9717484, + "127.0.0.1:5003": 1729000547.3061314, + "192.168.29.49:5000": 1729000850.8906484, + "127.0.0.1:5000": 1729000250.9020162, + "127.0.0.1:5002": 1729000565.1822047 + } +} \ No newline at end of file diff --git a/blockchain.json b/blockchain.json index def35bb..42aadd5 100644 --- a/blockchain.json +++ b/blockchain.json @@ -1,680 +1,668 @@ { - "chain": [ - { - "index": 2, - "timestamp": 1728974524.0400372, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974524.0336194, - "transaction_id": "9c5d65021bc50fe93a19af30b71b8fbaaf920e43d45e6c4fb26ac1a8fddeaad9", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDTm3LkbQh980Gxt/6YpHd43mWc/L739PIjtjUW11NTEgIhANtyw1ucq81tdJfpX81ZlVP1w1HbnaUwzO/E9ApxqXVx" - } - ], - "proof": 35293, - "previous_hash": "7ebe3f2f48fd01a145b056519aa646585048b3ca562ad9833ce31a44fb29e4f3" - }, + "chain": [ + { + "index": 2, + "previous_hash": "7ebe3f2f48fd01a145b056519aa646585048b3ca562ad9833ce31a44fb29e4f3", + "proof": 35293, + "timestamp": 1728974524.0400372, + "transactions": [ { - "index": 3, - "timestamp": 1728974584.1404045, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974584.1352344, - "transaction_id": "1f9eaeb43dad5f58b6f69964c38f1c263c7de41fa04d1421b980997875932363", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIAaaLt/hwWHiqcNzz14SWISNqr2aR5+VAlOwkIC3ZGmRAiEA+W93lJHno0ePkyIKZvIp2eE6eNW4rwJYhHdZLwsuyik=" - } - ], - "proof": 35089, - "previous_hash": "ea9fcdb805d1519b46ff274e27122028683f00e441a7bba0ef24570d1deb80a6" - }, + "digital_signature": "MEYCIQDTm3LkbQh980Gxt/6YpHd43mWc/L739PIjtjUW11NTEgIhANtyw1ucq81tdJfpX81ZlVP1w1HbnaUwzO/E9ApxqXVx", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974524.0336194, + "transaction_id": "9c5d65021bc50fe93a19af30b71b8fbaaf920e43d45e6c4fb26ac1a8fddeaad9" + } + } + ] + }, + { + "index": 3, + "previous_hash": "ea9fcdb805d1519b46ff274e27122028683f00e441a7bba0ef24570d1deb80a6", + "proof": 35089, + "timestamp": 1728974584.1404045, + "transactions": [ { - "index": 4, - "timestamp": 1728974644.3219702, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974644.3169801, - "transaction_id": "fac86976024f9567eac8a9d0c913b9fff662d2e54740922d9153fe978a656aed", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQD4IJr2FIaN0d80wPjRs32UhUiJWpgjvI9zDdOktRstpQIhAJeM1XZdYFONgJFLIWzoDzBh4PVImq+fe5sthngjE16c" - } - ], - "proof": 119678, - "previous_hash": "aad438bdcdfd5e93cbe8b0a5a917da56784bb78e91161bc0a3ed0eea56b94ed1" - }, + "digital_signature": "MEUCIAaaLt/hwWHiqcNzz14SWISNqr2aR5+VAlOwkIC3ZGmRAiEA+W93lJHno0ePkyIKZvIp2eE6eNW4rwJYhHdZLwsuyik=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974584.1352344, + "transaction_id": "1f9eaeb43dad5f58b6f69964c38f1c263c7de41fa04d1421b980997875932363" + } + } + ] + }, + { + "index": 4, + "previous_hash": "aad438bdcdfd5e93cbe8b0a5a917da56784bb78e91161bc0a3ed0eea56b94ed1", + "proof": 119678, + "timestamp": 1728974644.3219702, + "transactions": [ { - "index": 5, - "timestamp": 1728974704.4888444, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974704.4858475, - "transaction_id": "0dc93f2066989fc1795d42e76f89a6d025080a9f6e280c132cb6d291bfc00ea1", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQCZzROmPVnwo87lC5httLXhT4ZR5UcyzECdk8Z6ghXs6gIhAPCSlpR2P7Z6Wi1pfvMqtg07RcUquJNLeWVNZtQQUTsK" - } - ], - "proof": 146502, - "previous_hash": "c09881935ad064d13bd87e0250958497fdad66358760d5c97bafb9efd4e45bc6" - }, + "digital_signature": "MEYCIQD4IJr2FIaN0d80wPjRs32UhUiJWpgjvI9zDdOktRstpQIhAJeM1XZdYFONgJFLIWzoDzBh4PVImq+fe5sthngjE16c", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974644.3169801, + "transaction_id": "fac86976024f9567eac8a9d0c913b9fff662d2e54740922d9153fe978a656aed" + } + } + ] + }, + { + "index": 5, + "previous_hash": "c09881935ad064d13bd87e0250958497fdad66358760d5c97bafb9efd4e45bc6", + "proof": 146502, + "timestamp": 1728974704.4888444, + "transactions": [ { - "index": 6, - "timestamp": 1728974764.3581252, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974764.352396, - "transaction_id": "23916989d5984b126a57db5818181a14c8bbb4246b5884d273b477a0b4e0fd00", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDOAPFftH8RQxeSgvXf8cS3LQzZybbouZa/xgzITIH3vAIhAKxLxSr/mv9/bkMZw2O7+P1fDbNtxEVEqWC+NfRPzaAL" - } - ], - "proof": 43538, - "previous_hash": "9577d88fe2fe44b85079ee07c9bd2ec77d17bae9bc2370e0ebcb6f535c48349d" - }, + "digital_signature": "MEYCIQCZzROmPVnwo87lC5httLXhT4ZR5UcyzECdk8Z6ghXs6gIhAPCSlpR2P7Z6Wi1pfvMqtg07RcUquJNLeWVNZtQQUTsK", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974704.4858475, + "transaction_id": "0dc93f2066989fc1795d42e76f89a6d025080a9f6e280c132cb6d291bfc00ea1" + } + } + ] + }, + { + "index": 6, + "previous_hash": "9577d88fe2fe44b85079ee07c9bd2ec77d17bae9bc2370e0ebcb6f535c48349d", + "proof": 43538, + "timestamp": 1728974764.3581252, + "transactions": [ { - "index": 7, - "timestamp": 1728974824.4366474, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974824.4333982, - "transaction_id": "bf0910788fd53f021d55feba6a1c56a686414411bbb31998991d5de20f100681", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIQD+zWEisJqmKJ5z8SXwIqgqPXAKNakTBWz0QbsX3cJeLAIgEK55LEP86zKaFNWA6C2gPUcuQuH+pmlXpuuY8c9LC4A=" - } - ], - "proof": 85724, - "previous_hash": "1c44c6fabac81569a096d27232a969a266b75dd2dcda84d63df25cde8616ab6d" - }, + "digital_signature": "MEYCIQDOAPFftH8RQxeSgvXf8cS3LQzZybbouZa/xgzITIH3vAIhAKxLxSr/mv9/bkMZw2O7+P1fDbNtxEVEqWC+NfRPzaAL", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974764.352396, + "transaction_id": "23916989d5984b126a57db5818181a14c8bbb4246b5884d273b477a0b4e0fd00" + } + } + ] + }, + { + "index": 7, + "previous_hash": "1c44c6fabac81569a096d27232a969a266b75dd2dcda84d63df25cde8616ab6d", + "proof": 85724, + "timestamp": 1728974824.4366474, + "transactions": [ { - "index": 8, - "timestamp": 1728974884.3970501, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974884.395058, - "transaction_id": "07e783e78e1d4cba0d6ac7fe28115489f7a4b909a10accee55d186837cf7db64", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQCWIOHbCPiG1sSo6yOI/7eIocb1Xss5IXc+2HW5zbIKxgIhAPmA9+9Sde/TGtwk3u8+Cb+QQSdeOFjli2QX9S/lNYBe" - } - ], - "proof": 51178, - "previous_hash": "8ac34c7968dbd9c65bacdf7616dc530c2a10741940daca0c54efdb0100bd5a6a" - }, + "digital_signature": "MEUCIQD+zWEisJqmKJ5z8SXwIqgqPXAKNakTBWz0QbsX3cJeLAIgEK55LEP86zKaFNWA6C2gPUcuQuH+pmlXpuuY8c9LC4A=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974824.4333982, + "transaction_id": "bf0910788fd53f021d55feba6a1c56a686414411bbb31998991d5de20f100681" + } + } + ] + }, + { + "index": 8, + "previous_hash": "8ac34c7968dbd9c65bacdf7616dc530c2a10741940daca0c54efdb0100bd5a6a", + "proof": 51178, + "timestamp": 1728974884.3970501, + "transactions": [ + { + "digital_signature": "MEYCIQCWIOHbCPiG1sSo6yOI/7eIocb1Xss5IXc+2HW5zbIKxgIhAPmA9+9Sde/TGtwk3u8+Cb+QQSdeOFjli2QX9S/lNYBe", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974884.395058, + "transaction_id": "07e783e78e1d4cba0d6ac7fe28115489f7a4b909a10accee55d186837cf7db64" + } + } + ] + }, + { + "index": 9, + "previous_hash": "778b4cba347e2e70b9d8c47d070a001ea99e96434d4daeeb943763388eb046f9", + "proof": 71730, + "timestamp": 1728974944.4548056, + "transactions": [ + { + "digital_signature": "MEYCIQCrh0eQCAcGidTtQkbBDsx600H02d9ZNQ708JfwY7+VQQIhALyPxkf6BeNCzzere+eMq98IKXPHd0a0E9oWV7f/9x7r", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728974944.45279, + "transaction_id": "cc578313a9c526ffe96b0fe4600bb93b39332868ff496babe62b4346e007974f" + } + } + ] + }, + { + "index": 10, + "previous_hash": "a96ac9eee81499d1264dd115cfca31b6cea266ed12ed217d1ef5213cf15b6a9f", + "proof": 55589, + "timestamp": 1728975004.4618313, + "transactions": [ + { + "digital_signature": "MEQCIEngRwU14Jl3+ymWczqRJAvMk1qlOM5XvsXxvIkw+3KgAiAlCBncpektqKapPlu+p+kNhTu6U7obGwF57Nuc9PQZGA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975004.4600081, + "transaction_id": "12d3e76b4cab5591b2ee02f4a91324b6ef0eeee96e8adbc4bf965f5ed3e142fa" + } + } + ] + }, + { + "index": 11, + "previous_hash": "76d0b6e10e61ae1e75bc580b83a0be2d830d0b3166f62ef8f09cfb8ecf4eb693", + "proof": 35704, + "timestamp": 1728975064.4856203, + "transactions": [ + { + "digital_signature": "MEUCIAXISgaX+bU/DQ91NyaH+S1yRDClLURY5ZQRdYrDyixUAiEAySkoszgdHBU9bdIKCkRVU35TrS98DXvbeZoE2mXZUMs=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975064.4836202, + "transaction_id": "884db8e2299d319785795019582f8ad0c0723629f27c8f3b39e442e79de8aa44" + } + } + ] + }, + { + "index": 12, + "previous_hash": "32f12ca6cdb5be144ee46cee125bdced266194b97414624dc1fd210907b170ae", + "proof": 57342, + "timestamp": 1728975124.6048725, + "transactions": [ { - "index": 9, - "timestamp": 1728974944.4548056, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728974944.45279, - "transaction_id": "cc578313a9c526ffe96b0fe4600bb93b39332868ff496babe62b4346e007974f", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQCrh0eQCAcGidTtQkbBDsx600H02d9ZNQ708JfwY7+VQQIhALyPxkf6BeNCzzere+eMq98IKXPHd0a0E9oWV7f/9x7r" - } - ], - "proof": 71730, - "previous_hash": "778b4cba347e2e70b9d8c47d070a001ea99e96434d4daeeb943763388eb046f9" + "digital_signature": "MEYCIQC7fsDsJ3wQoY9QL/PlWs9ZjKERbxgWMh/63FTn1h7scAIhALF+GmxpNgq5uqhOgoUbVrmzwWAbDVqM5QGmRaC8Srku", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975124.6011765, + "transaction_id": "0834dc176b09859569aad7b4597893f2de845357b78af6d784abc528b69807e4" + } + } + ] + }, + { + "index": 13, + "previous_hash": "d377ab16295533389908fea265491cc7bed4e82bf0b547336fcff1c62bedd7dc", + "proof": 68975, + "timestamp": 1728975184.6077056, + "transactions": [ + { + "digital_signature": "MEYCIQDpt6olYojDgXBkvjn2sKIP6iw9nQc9cLZsOQO+ih6chQIhAL3rAnkYdGTpZDgEHUKuHcTZ3P9SfORIBVSott5cHeH0", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975184.604074, + "transaction_id": "39b5a25bd9e666c6b4057a57867ac80f68d545015e739c216bb0161aae06d0c5" + } + } + ] + }, + { + "index": 14, + "previous_hash": "cdb6a7ed569e50024e3a3077e8c8bb4ad09b17078bde559e5263bd93ce5dd1ec", + "proof": 153122, + "timestamp": 1728975244.689295, + "transactions": [ + { + "digital_signature": "MEQCICCoFk2hCTOqwcm5ij0MOuMV/hr9vgfg9IdA+AQW729gAiA4ZM7mH83nVDvDXOkxHl8pENKeEMjzQ6VdMu6Ske69mA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975244.6872911, + "transaction_id": "8ff23a738fce7cdf9f74b38987b6891f884503d90132a970fc236a824ab6bb0a" + } + } + ] + }, + { + "index": 15, + "previous_hash": "de7f3f590c5976a155a573957e8a2c2da2658a360a3ae21f78d6c504f06f2201", + "proof": 20760, + "timestamp": 1728975304.6244986, + "transactions": [ + { + "digital_signature": "MEQCIG4DXGXrCPYbtzRG+500+8tvDIBVR6aSMcei1k1NQcHsAiAaYmjiEUsH2k0WjLUuSC2vCh8YlAxIrfLLJl1vA6lZhA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975304.622504, + "transaction_id": "06143f8e221f1f7ec20d39e2bd904c3cbeed2259f013eaf3dae4a6f15a6dbd6c" + } + } + ] + }, + { + "index": 15, + "previous_hash": "2aabc645f76685ffb8e587f37d423c1396b2a2104489546d8ed2ff97966c123a", + "proof": 29341, + "timestamp": 1728975756.5164192, + "transactions": [ + { + "digital_signature": "MEQCIFLy8ImmWUnI2nIauS3XCG+sq96U/Fq/uPyt/HP0qP7eAiB2tKovBOYt/Xs2HZfXPfP6/c1CL0QaT1nvOevkdOZ5lg==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975756.5133958, + "transaction_id": "142270daba595b613abba390576fd2bb72767da33ce4b79de512ffc5a7f921a5" + } }, { - "index": 10, - "timestamp": 1728975004.4618313, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975004.4600081, - "transaction_id": "12d3e76b4cab5591b2ee02f4a91324b6ef0eeee96e8adbc4bf965f5ed3e142fa", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCIEngRwU14Jl3+ymWczqRJAvMk1qlOM5XvsXxvIkw+3KgAiAlCBncpektqKapPlu+p+kNhTu6U7obGwF57Nuc9PQZGA==" - } - ], - "proof": 55589, - "previous_hash": "a96ac9eee81499d1264dd115cfca31b6cea266ed12ed217d1ef5213cf15b6a9f" + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } }, { - "index": 11, - "timestamp": 1728975064.4856203, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975064.4836202, - "transaction_id": "884db8e2299d319785795019582f8ad0c0723629f27c8f3b39e442e79de8aa44", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIAXISgaX+bU/DQ91NyaH+S1yRDClLURY5ZQRdYrDyixUAiEAySkoszgdHBU9bdIKCkRVU35TrS98DXvbeZoE2mXZUMs=" - } - ], - "proof": 35704, - "previous_hash": "76d0b6e10e61ae1e75bc580b83a0be2d830d0b3166f62ef8f09cfb8ecf4eb693" + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } + } + ] + }, + { + "index": 16, + "previous_hash": "53e80a715d78a4e1d1682f609df04cd326fc389df00c214d385284afb68b1418", + "proof": 15889, + "timestamp": 1728975971.0139954, + "transactions": [ + { + "digital_signature": "MEQCIDoekApOmyc9UzeZ4YhR0f0bWD+/3Lolpmn/0S30tFqEAiBsGFGh7M8USbUG1vAXBmq3GDMKuKamjAWP/lcP7cEUNA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 350, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728975971.0119958, + "transaction_id": "b18f0804a34377d5c251a98767ef4c9d56e346169a27697a93b8c37cceb0d68c" + } }, { - "index": 12, - "timestamp": 1728975124.6048725, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975124.6011765, - "transaction_id": "0834dc176b09859569aad7b4597893f2de845357b78af6d784abc528b69807e4", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQC7fsDsJ3wQoY9QL/PlWs9ZjKERbxgWMh/63FTn1h7scAIhALF+GmxpNgq5uqhOgoUbVrmzwWAbDVqM5QGmRaC8Srku" - } - ], - "proof": 57342, - "previous_hash": "32f12ca6cdb5be144ee46cee125bdced266194b97414624dc1fd210907b170ae" + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } }, { - "index": 13, - "timestamp": 1728975184.6077056, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975184.604074, - "transaction_id": "39b5a25bd9e666c6b4057a57867ac80f68d545015e739c216bb0161aae06d0c5", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDpt6olYojDgXBkvjn2sKIP6iw9nQc9cLZsOQO+ih6chQIhAL3rAnkYdGTpZDgEHUKuHcTZ3P9SfORIBVSott5cHeH0" - } - ], - "proof": 68975, - "previous_hash": "d377ab16295533389908fea265491cc7bed4e82bf0b547336fcff1c62bedd7dc" + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } }, { - "index": 14, - "timestamp": 1728975244.689295, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975244.6872911, - "transaction_id": "8ff23a738fce7cdf9f74b38987b6891f884503d90132a970fc236a824ab6bb0a", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCICCoFk2hCTOqwcm5ij0MOuMV/hr9vgfg9IdA+AQW729gAiA4ZM7mH83nVDvDXOkxHl8pENKeEMjzQ6VdMu6Ske69mA==" - } - ], - "proof": 153122, - "previous_hash": "cdb6a7ed569e50024e3a3077e8c8bb4ad09b17078bde559e5263bd93ce5dd1ec" + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } + } + ] + }, + { + "index": 17, + "previous_hash": "e470f9d6a55cdd925aea82dc391a8ff7c0fcc9bb9a2394d0857af6bc4d8896b7", + "proof": 209765, + "timestamp": 1728976024.9810693, + "transactions": [ + { + "digital_signature": "MEYCIQDrZ7q9xHVJ7lh/3gT46y1adZlph4fN294IbkfDyVnBsQIhALPq8Fv4ZwSuwWvbLdy/eZgYVsaqSBFkETQps7/p2S2g", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 450, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976024.979543, + "transaction_id": "da8eea2ec16240110a5d913ecbae1cd9ca2fcc80cf13f031bfa23fb65e364403" + } }, { - "index": 15, - "timestamp": 1728975304.6244986, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728975304.622504, - "transaction_id": "06143f8e221f1f7ec20d39e2bd904c3cbeed2259f013eaf3dae4a6f15a6dbd6c", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCIG4DXGXrCPYbtzRG+500+8tvDIBVR6aSMcei1k1NQcHsAiAaYmjiEUsH2k0WjLUuSC2vCh8YlAxIrfLLJl1vA6lZhA==" - } - ], - "proof": 20760, - "previous_hash": "de7f3f590c5976a155a573957e8a2c2da2658a360a3ae21f78d6c504f06f2201" + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } }, { - "index": 15, - "timestamp": 1728975756.5164192, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 250.0, - "timestamp": 1728975756.5133958, - "transaction_id": "142270daba595b613abba390576fd2bb72767da33ce4b79de512ffc5a7f921a5", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCIFLy8ImmWUnI2nIauS3XCG+sq96U/Fq/uPyt/HP0qP7eAiB2tKovBOYt/Xs2HZfXPfP6/c1CL0QaT1nvOevkdOZ5lg==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975565.506355, - "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975756.4433064, - "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" - } - ], - "proof": 29341, - "previous_hash": "2aabc645f76685ffb8e587f37d423c1396b2a2104489546d8ed2ff97966c123a" + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } }, { - "index": 16, - "timestamp": 1728975971.0139954, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 350.0, - "timestamp": 1728975971.0119958, - "transaction_id": "b18f0804a34377d5c251a98767ef4c9d56e346169a27697a93b8c37cceb0d68c", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCIDoekApOmyc9UzeZ4YhR0f0bWD+/3Lolpmn/0S30tFqEAiBsGFGh7M8USbUG1vAXBmq3GDMKuKamjAWP/lcP7cEUNA==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975565.506355, - "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975756.4433064, - "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975970.9793785, - "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" - } - ], - "proof": 15889, - "previous_hash": "53e80a715d78a4e1d1682f609df04cd326fc389df00c214d385284afb68b1418" + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } }, { - "index": 17, - "timestamp": 1728976024.9810693, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 450.0, - "timestamp": 1728976024.979543, - "transaction_id": "da8eea2ec16240110a5d913ecbae1cd9ca2fcc80cf13f031bfa23fb65e364403", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDrZ7q9xHVJ7lh/3gT46y1adZlph4fN294IbkfDyVnBsQIhALPq8Fv4ZwSuwWvbLdy/eZgYVsaqSBFkETQps7/p2S2g" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975565.506355, - "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975756.4433064, - "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975970.9793785, - "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728976024.766627, - "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2" - } - ], - "proof": 209765, - "previous_hash": "e470f9d6a55cdd925aea82dc391a8ff7c0fcc9bb9a2394d0857af6bc4d8896b7" + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } + } + ] + }, + { + "index": 18, + "previous_hash": "8189da9ee091cdbe6fa9d0db1092cfcaf99f50aa5c0300c66b107686d6f580c5", + "proof": 3748, + "timestamp": 1728976103.733967, + "transactions": [ + { + "digital_signature": "MEYCIQDr+8jpV3tvLrVPcTadPNhxreM2THaFwKJ8vXZdsjEX6QIhAMACHbHEu7+5UJeMTX0YnkP25jOzy0+PPAjzqJ6bW5nQ", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 550, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976103.732963, + "transaction_id": "0dd94ebb25df84f6684c2caa0a849d5d1310924e43700a71ca1a763de36a89d7" + } }, { - "index": 18, - "timestamp": 1728976103.733967, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 550.0, - "timestamp": 1728976103.732963, - "transaction_id": "0dd94ebb25df84f6684c2caa0a849d5d1310924e43700a71ca1a763de36a89d7", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDr+8jpV3tvLrVPcTadPNhxreM2THaFwKJ8vXZdsjEX6QIhAMACHbHEu7+5UJeMTX0YnkP25jOzy0+PPAjzqJ6bW5nQ" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975565.506355, - "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975756.4433064, - "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728975970.9793785, - "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728976024.766627, - "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728976103.7194138, - "transaction_id": "134eea2fcf5c87cc930df55c430e67230bb892bce3a28ddba6ed0346105610d1" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEUCIQDTWXwJ9UOjJqjVgcd1hQiuTOa3DTzrGTwVGxl31FbU7gIgZ2BRMQPi8GHtdLr64NmmzLPlQaryFjBPG2Ral31jRxI=" - } - ], - "proof": 3748, - "previous_hash": "8189da9ee091cdbe6fa9d0db1092cfcaf99f50aa5c0300c66b107686d6f580c5" + "digital_signature": "MEUCIQClMPAgnkPVQaFGqrC+KYM/csFbWroa+lCA1CH7hdguPQIgY8kCYRci8tBC9ScasZigH1/MYU+mwJc/xtQdLnOSoZY=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975565.506355, + "transaction_id": "85d4e568352b16296ec07b3b171129499233df7053ee0eded63146c0d6baf7e7" + } }, { - "index": 19, - "timestamp": 1728976122.9694924, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 250.0, - "timestamp": 1728976122.9679887, - "transaction_id": "4ee9a79f736d808bbf0a689433353669687b3090cfa2444e0215d16cc019f95d", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQDjI7PnA3J3c1HGl9xZdTPAVLo7uDkkvEzvrJOLt6rdUQIhAKo5EFOj5xyWhTTgZ55Feroc5ZPzc7opBwni7HeLRwr1" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728976110.323565, - "transaction_id": "63575decc2edbfe96737b7bedcc2d4ba9d230fb1eecb8310c5ec97b73376d646" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEQCIBVU0sWBlx4mmyyDu9NxzUyJ1KHbuq2CyV7QbmEZAb0XAiBhrihf5gdZXaHNdpuckfITpqQ3odMCOj6BNKxKxH2/0w==" - }, - { - "transaction": { - "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", - "amount": 100.0, - "timestamp": 1728976122.9341776, - "transaction_id": "d05338d826bed327e000c0dadfe6dbf6696ee8c0c6b2f0b1c73d2d885ce4fe5b" - }, - "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "digital_signature": "MEYCIQCtaVYVEmovF9Q4fgXAtfGM9/1OLhmm2ydsceYS+kGZ1QIhAJQk+lYygkWBZ7VUsyQfMK1UUZIDDsy+dZQAyDZcHTGp" - } - ], - "proof": 28807, - "previous_hash": "0b4fe1a9f1692b542f4a9943d37863403bc5a9bc71b356c6dfc1e86777c5a329" + "digital_signature": "MEQCIB8cMy+VLxIHv4y5H2rhTnR9bRfuuHBK5LDNkvUw8FQ9AiBypoQIu9ZozdRaq+uje/hc4Nmy2Mwm4OLpbx0Y53XdyA==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975756.4433064, + "transaction_id": "adcb6921056e12dfb02c4e715f2627570f899335be0ff6f0ea80a1dc5dfbd213" + } }, { - "index": 20, - "timestamp": 1728982795.977029, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728982795.9745123, - "transaction_id": "07fd3b0e1a4c95f2c24d7b4f4c5279a5b877c28e9004a47881a1389536ede68c", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIQCbU4ZVg0fMROF21D7aEaN9xxHjwpEPxcG33CdRK4bLmwIgTCqmFMyVYmtGJbxm/mCbka6TXggZjLdgBonB8Xj6zK4=" - } - ], - "proof": 70832, - "previous_hash": "a16c2898879799c3dc1988c84a3eed4c16b0f3a8705993576d11f4b0fa31e4af" + "digital_signature": "MEUCIEqQLvhJrJ6pgLZCwxBfjIbpFCTpX0vt8DaXguPl16zXAiEAwx/kqm6KrSJUx6W6jHnSDKkKnFp23NgfRuyh/IQzZIk=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728975970.9793785, + "transaction_id": "95403b8f25eac4160760c1fdc034bfcb72b8a968639ca55c7c98e75f084149d8" + } }, { - "index": 21, - "timestamp": 1728982796.253764, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728982796.2499352, - "transaction_id": "423069ba8b1487f5c7394605b6628395fdf571fe56c86d95ee882ffd705d945e", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEYCIQCa6nS5/CQMb6xoPPaysXuANZMSy8W5EjVGgq4a+ETAgwIhANDKMlul8q6pf/OjmJRqkYpwJK/gBRVX11hzsvAsVfld" - } - ], - "proof": 47031, - "previous_hash": "4989e8b8021887d35737e9bd201eebb2e5e611a5c3c1d2b2455f1361a7472b33" + "digital_signature": "MEYCIQDectEoSamwbHGHxMwDYoyrj2JJPbFnjff+CCZtrt9/NAIhANfmrzHRYZTBL4a0Gmwch5WS4V6qbk8FRxDDFqfTDGX2", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976024.766627, + "transaction_id": "f97507353d952397206b174646e84d0df4e8e7c11c1558129a447141dcb428d6" + } }, { - "index": 22, - "timestamp": 1728983396.2543488, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728983396.248372, - "transaction_id": "0820117dececa39333cb2b17973f9f40327ae5177f45374bd79d64f3b9005fa0", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEQCIDa7z0tfh1xCbh7DtutvqeZPzKUDnhPU/efllO6CTJxtAiAw7XI4eA3g+RAP83feJaQvRPubamYnSrMATsbUxfgfDw==" - } - ], - "proof": 48098, - "previous_hash": "19f4fa00986014243d97fa5539db5a2dbda69a522c4d2f155dc203f6f3c5eda4" + "digital_signature": "MEUCIQDTWXwJ9UOjJqjVgcd1hQiuTOa3DTzrGTwVGxl31FbU7gIgZ2BRMQPi8GHtdLr64NmmzLPlQaryFjBPG2Ral31jRxI=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976103.7194138, + "transaction_id": "134eea2fcf5c87cc930df55c430e67230bb892bce3a28ddba6ed0346105610d1" + } + } + ] + }, + { + "index": 19, + "previous_hash": "0b4fe1a9f1692b542f4a9943d37863403bc5a9bc71b356c6dfc1e86777c5a329", + "proof": 28807, + "timestamp": 1728976122.9694924, + "transactions": [ + { + "digital_signature": "MEYCIQDjI7PnA3J3c1HGl9xZdTPAVLo7uDkkvEzvrJOLt6rdUQIhAKo5EFOj5xyWhTTgZ55Feroc5ZPzc7opBwni7HeLRwr1", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 250, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728976122.9679887, + "transaction_id": "4ee9a79f736d808bbf0a689433353669687b3090cfa2444e0215d16cc019f95d" + } }, { - "index": 23, - "timestamp": 1728983397.2447324, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728983397.2373633, - "transaction_id": "fb410938d213ed553a9c0886e6001598b5986fa6defa6cbcb7a2b9b2f132ff93", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIQDoYhvL1y5B13z2+hCMD8WfzyOKVt4Rb/l0tdnGeEjrZQIgFpGSGRxgLlnI3A9D02BD1B0cgu6boecY17yONJnfksw=" - } - ], - "proof": 30743, - "previous_hash": "f8d4e2cfcccaa25855f70902140bf9fda0b3cb70ad6d6f1545b3badbb4c71c6e" + "digital_signature": "MEQCIBVU0sWBlx4mmyyDu9NxzUyJ1KHbuq2CyV7QbmEZAb0XAiBhrihf5gdZXaHNdpuckfITpqQ3odMCOj6BNKxKxH2/0w==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976110.323565, + "transaction_id": "63575decc2edbfe96737b7bedcc2d4ba9d230fb1eecb8310c5ec97b73376d646" + } }, { - "index": 24, - "timestamp": 1728998883.4670477, - "transactions": [ - { - "transaction": { - "sender": "0", - "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", - "amount": 50.0, - "timestamp": 1728998883.4660475, - "transaction_id": "b54a2b509f074d94500a935d6573cc5bd7b322a9540371a445acc851a0422e5c", - "public_address": "" - }, - "public_address": "", - "digital_signature": "MEUCIQDKLP510ilNznCAND1x6bn2L6U7yNFOtTiQUoiJ0HLSHgIgA+YT8q3XbHCbGOiuLxiabcMjFB9/omh2uGSxstJ/yc0=" - } - ], - "proof": 41003, - "previous_hash": "25f25ddc2e3fa0e775e511bc94410080c86493649b598b65cb94363a9eef5a6f" + "digital_signature": "MEYCIQCtaVYVEmovF9Q4fgXAtfGM9/1OLhmm2ydsceYS+kGZ1QIhAJQk+lYygkWBZ7VUsyQfMK1UUZIDDsy+dZQAyDZcHTGp", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 100, + "recipient": "02cb4bd3f21125acd68a889f288cc8c0ccc3e359c8cd5c58f473c10eb1d36e6736", + "sender": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "timestamp": 1728976122.9341776, + "transaction_id": "d05338d826bed327e000c0dadfe6dbf6696ee8c0c6b2f0b1c73d2d885ce4fe5b" + } + } + ] + }, + { + "index": 20, + "previous_hash": "a16c2898879799c3dc1988c84a3eed4c16b0f3a8705993576d11f4b0fa31e4af", + "proof": 70832, + "timestamp": 1728982795.977029, + "transactions": [ + { + "digital_signature": "MEUCIQCbU4ZVg0fMROF21D7aEaN9xxHjwpEPxcG33CdRK4bLmwIgTCqmFMyVYmtGJbxm/mCbka6TXggZjLdgBonB8Xj6zK4=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982795.9745123, + "transaction_id": "07fd3b0e1a4c95f2c24d7b4f4c5279a5b877c28e9004a47881a1389536ede68c" + } + } + ] + }, + { + "index": 21, + "previous_hash": "4989e8b8021887d35737e9bd201eebb2e5e611a5c3c1d2b2455f1361a7472b33", + "proof": 47031, + "timestamp": 1728982796.253764, + "transactions": [ + { + "digital_signature": "MEYCIQCa6nS5/CQMb6xoPPaysXuANZMSy8W5EjVGgq4a+ETAgwIhANDKMlul8q6pf/OjmJRqkYpwJK/gBRVX11hzsvAsVfld", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728982796.2499352, + "transaction_id": "423069ba8b1487f5c7394605b6628395fdf571fe56c86d95ee882ffd705d945e" + } + } + ] + }, + { + "index": 22, + "previous_hash": "19f4fa00986014243d97fa5539db5a2dbda69a522c4d2f155dc203f6f3c5eda4", + "proof": 48098, + "timestamp": 1728983396.2543488, + "transactions": [ + { + "digital_signature": "MEQCIDa7z0tfh1xCbh7DtutvqeZPzKUDnhPU/efllO6CTJxtAiAw7XI4eA3g+RAP83feJaQvRPubamYnSrMATsbUxfgfDw==", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983396.248372, + "transaction_id": "0820117dececa39333cb2b17973f9f40327ae5177f45374bd79d64f3b9005fa0" + } + } + ] + }, + { + "index": 23, + "previous_hash": "f8d4e2cfcccaa25855f70902140bf9fda0b3cb70ad6d6f1545b3badbb4c71c6e", + "proof": 30743, + "timestamp": 1728983397.2447324, + "transactions": [ + { + "digital_signature": "MEUCIQDoYhvL1y5B13z2+hCMD8WfzyOKVt4Rb/l0tdnGeEjrZQIgFpGSGRxgLlnI3A9D02BD1B0cgu6boecY17yONJnfksw=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728983397.2373633, + "transaction_id": "fb410938d213ed553a9c0886e6001598b5986fa6defa6cbcb7a2b9b2f132ff93" + } + } + ] + }, + { + "index": 24, + "previous_hash": "25f25ddc2e3fa0e775e511bc94410080c86493649b598b65cb94363a9eef5a6f", + "proof": 41003, + "timestamp": 1728998883.4670477, + "transactions": [ + { + "digital_signature": "MEUCIQDKLP510ilNznCAND1x6bn2L6U7yNFOtTiQUoiJ0HLSHgIgA+YT8q3XbHCbGOiuLxiabcMjFB9/omh2uGSxstJ/yc0=", + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "transaction": { + "amount": 50, + "public_address": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "recipient": "02b91a05153e9ba81b55e1ade91241bf17bfdc1b8f553b0aff35636ec6f7f5078a", + "sender": "0", + "timestamp": 1728998883.4660475, + "transaction_id": "b54a2b509f074d94500a935d6573cc5bd7b322a9540371a445acc851a0422e5c" + } } - ], - "current_transactions": [ - - - ], - "nodes": [ - "127.0.0.1:5001", - "192.168.29.49:5001", - "127.0.0.1:5002", - "192.168.29.49:5000", - "127.0.0.1:5000", - "127.0.0.1:5003" - ], - "ttl": { - "192.168.29.49:5001": 1729003678.6687841, - "127.0.0.1:5001": 1729000243.9717484, - "127.0.0.1:5003": 1729000547.3061314, - "192.168.29.49:5000": 1729000850.8906484, - "127.0.0.1:5000": 1729000250.9020162, - "127.0.0.1:5002": 1729000565.1822047 + ] } + ], + "current_transactions": [], + "nodes": [ + "simplicity-server1.onrender.com", + "simplicity-server.onrender.com" + ], + "ttl": { + "simplicity-server1": 1729137155.8219357 + } } \ No newline at end of file diff --git a/blockchain.py b/blockchain.py index 03e59ba..34d5d8e 100644 --- a/blockchain.py +++ b/blockchain.py @@ -13,10 +13,11 @@ from typing import Dict from urllib.parse import urlparse import schedule - +from requests.adapters import HTTPAdapter import ecdsa import flask import requests +from urllib3 import Retry from account_db import AccountReader from nodeManager import NodeManager @@ -24,30 +25,30 @@ firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" -class Blockchain(object): +class Blockchain: + + def __init__(self): - """ - Initialize the Blockchain - :param proof: The proof given by the Proof of Work algorithm - :param previous_hash: (Optional) Hash of previous Block - :return: New Block - """ self.chain = [] self.current_transactions = [] self.hash_list = set() self.nodes = set() - self.ttl : dict= {} - self.public_address= "" + self.ttl = {} + self.public_address = "" self.private_address = "" self.ip_address = "" self.target = 4 # Easy target value self.max_block_size = 1000000 - self.max_mempool = 2 - self.new_block( proof=100 , prev_hash =1 ) - self.error = "" + self.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) + self.error = "" + self.session = requests.Session() + retries = Retry(total=3, backoff_factor=0.1, status_forcelist=[500, 502, 503, 504]) + self.session.mount('http://', HTTPAdapter(max_retries=retries)) + self.session.mount('https://', HTTPAdapter(max_retries=retries)) database = BlockchainDb() - db_chain = database.load_blockchain(self ) + db_chain = database.load_blockchain(self) self.mining_thread = None self.should_mine = False @@ -57,25 +58,17 @@ def __init__(self): accounts_data = accountDb.account_data for account in accounts_data: if account['publicKey']: - - self.publoc_key = account['publicKey'] + self.public_key = account['publicKey'] if account['privateKey']: self.private_address = account['privateKey'] + print("the db chain is : ", db_chain) if db_chain: - self.chain = self.validate_loaded_chain() - # print("Loaded chain is invalid. Starting with a new blockchain.") - - # #getting the longest chain in the network - # self.resolve_conflicts() - # #resetting the blockchain - # # self.hash_list = set() - # # self.chain = [] - # # self.nodes = set() - # # self.current_transactions = [] - # # self.new_block( proof=100 , prev_hash =1 ) - - + chain = self.validate_loaded_chain() + print("the validated chain is : ", chain) + if chain: + self.chain = chain + self.start_scheduled_mining() def Blockchain(self , public_address): self.public_address = public_address @@ -123,14 +116,23 @@ def generate_transaction_id(self , coinbase_tx): def validate_loaded_chain(self): """Validate the loaded chain for integrity.""" + + if len(self.chain) <= 2 : + print("No chain found. Starting with a new chain.") + return self.chain + print( + "Length of the chain is " + str(len(self.chain)) + ) for i in range(1, len(self.chain)): current_block = self.chain[i] previous_block = self.chain[i-1] if current_block['previous_hash'] != self.hash(previous_block): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) return self.chain[:i-1] if not self.valid_proof(previous_block['proof'], current_block['proof'] , self.target): + print("Loaded chain is valid. lenght is " + str(len(self.chain))) return self.chain[:i-1] - + print("Loaded chain is valid. lenght is " + str(len(self.chain))) return self.chain def create_mining_reward(self, miners_address, block_height): # Calculate the reward based on block height @@ -152,67 +154,103 @@ def create_mining_reward(self, miners_address, block_height): # The coinbase transaction will be added as the first transaction in the new block return total_reward, coinbase_tx - def register(self , ip_address): - # Create a NodeManager instance - node_manager = NodeManager() - self.ip_address = ip_address - # Get a random node - random_node = node_manager.get_random_node() - nodes = node_manager.load_nodes() - self.remove_expired_nodes() - - if self.ip_address not in nodes: - data = { - "nodes": [self.ip_address] - } - print("Registering node : {}".format(ip_address) ) - requests.post(f'http://{random_node}/nodes/register' , json=data) - if self.ttl: - requests.post(f'http://{random_node}/nodes/update_ttl' , json={ - "updated_nodes": self.ttl, - "node" : self.ip_address - }) - - - - - def register_node(self , address , current_address): + def register_node(self, address, current_address): """ Adds a new node to the list of nodes - - :param address: Address of node. Eg. 'http://192.168.0.5:5000' + + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :param current_address: Address of the current node :return: None """ + self.remove_expired_nodes() + try: + parsed_url = urlparse(address) + if not parsed_url.netloc: + raise ValueError(f"Invalid address: {address}") + + if parsed_url.netloc not in self.nodes: + self.nodes.add(parsed_url.netloc) + print(f"Added new node: {parsed_url.netloc}") + + current_url = urlparse(current_address) + if not current_url.netloc: + raise ValueError(f"Invalid current address: {current_address}") + + scheme = parsed_url.scheme or 'https' + base_url = f'{scheme}://{parsed_url.netloc}' + + self._update_node(base_url, current_url.netloc) + + except ValueError as e: + print(f"Error parsing URL: {e}") + except Exception as e: + print(f"Unexpected error in register_node: {e}") + + def _update_node(self, base_url, current_netloc): + try: + self.session.post(f'{base_url}/nodes/update_chain', + json=[self.chain, current_netloc, list(self.hash_list), list(self.nodes)], + timeout=5) + + self.session.post(f'{base_url}/nodes/update_nodes', + json={"nodes": list(self.nodes)}, + timeout=5) + + if self.ttl: + self.session.post(f'{base_url}/nodes/update_ttl', + json={"updated_nodes": self.ttl, "node": current_netloc}, + timeout=5) + + print(f"Successfully updated node: {base_url}") + except requests.RequestException as e: + print(f"Error communicating with node {base_url}: {e}") + + def register_node(self, address, current_address): + """ + Adds a new node to the list of nodes - #What is netloc? + :param address: Address of node. Eg. 'http://192.168.0.5:5000' + :param current_address: Address of the current node + :return: None """ - `netloc` is an attribute of the `ParseResult` object returned by the `urlparse` function in Python's `urllib.parse` module. + self.remove_expired_nodes() - `netloc` contains the network location part of the URL, which includes: + try: + parsed_url = urlparse(address) + if not parsed_url.netloc: + raise ValueError(f"Invalid address: {address}") - * The hostname or domain name - * The port number (if specified) + if parsed_url.netloc not in self.nodes: + self.nodes.add(parsed_url.netloc) - For example, if the URL is `http://example.com:8080/path`, `netloc` would be `example.com:8080`. + current_url = urlparse(current_address) + if not current_url.netloc: + raise ValueError(f"Invalid current address: {current_address}") - In the context of the original code snippet, `netloc` is used to extract the node's network location (i.e., its hostname or IP address) from the URL. - """ - self.remove_expired_nodes() + # Use https if the scheme is not specified + scheme = parsed_url.scheme or 'https' + + try: + requests.post(f'{scheme}://{parsed_url.netloc}/nodes/update_chain', + json=[self.chain, current_url.netloc, list(self.hash_list), list(self.nodes)], + timeout=5) + + requests.post(f'{scheme}://{parsed_url.netloc}/nodes/update_nodes', + json={"nodes": list(self.nodes)}, + timeout=5) + + if self.ttl: + requests.post(f'{scheme}://{parsed_url.netloc}/nodes/update_ttl', + json={"updated_nodes": self.ttl, "node": current_url.netloc}, + timeout=5) + except requests.RequestException as e: + print(f"Error communicating with node {parsed_url.netloc}: {e}") - parsed_url = urlparse(address) - if parsed_url not in self.nodes: - self.nodes.add(parsed_url) - current_url = urlparse(current_address) - requests.post(f'http://{parsed_url}/nodes/update_chain' , json=[self.chain , current_url , list(self.hash_list) , list(self.nodes)]) - requests.post(f'http://{parsed_url}/nodes/update_nodes' , json={ - "nodes": list(self.nodes) - }) - if self.ttl: - requests.post(f'http://{parsed_url}/nodes/update_ttl' , json={ - "updated_nodes": self.ttl, - "node" : current_url - }) - + except ValueError as e: + print(f"Error parsing URL: {e}") + except Exception as e: + print(f"Unexpected error in register_node: {e}") + def remove_expired_nodes(self): if self.ttl: # Iterate over a copy of the set to avoid modifying it while iterating @@ -406,6 +444,7 @@ def new_transaction(self, transaction , public_address , digital_signature): def start_scheduled_mining(self): + print("the chain is " , self.chain) schedule.every(10).minutes.do(self.scheduled_mine) threading.Thread(target=self.run_schedule, daemon=True).start() @@ -672,4 +711,4 @@ def resolve_conflicts(self): return False class SignatureVerificationError(Exception): - pass \ No newline at end of file + pass diff --git a/database.py b/database.py index 3042748..89857f8 100644 --- a/database.py +++ b/database.py @@ -1,12 +1,13 @@ import json from collections import OrderedDict -import random import firebase_admin from firebase_admin import credentials from firebase_admin import db -firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" -database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +import os +firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" +local_file_path = "blockchain.json" class BlockchainDb: def __init__(self): @@ -19,43 +20,101 @@ def __init__(self): def save_blockchain(self, blockchain): """ - Save the blockchain to Firebase. - + Save the blockchain to a local JSON file. + :param blockchain: The Blockchain instance to save """ - unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) - unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) - - data = { - 'chain': unique_chain, - 'current_transactions': unique_transactions, - 'nodes': list(blockchain.nodes), - 'ttl': blockchain.ttl - } + try: + print("Saving blockchain to local file") - self.ref.set(data) - print("Blockchain saved to Firebase") + unique_chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in blockchain.chain).values()) + unique_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in blockchain.current_transactions).values()) + + if not unique_chain: + print("No data to save. Starting with a new blockchain.") + return + + hashable_nodes = set(tuple(node) if isinstance(node, list) else node for node in blockchain.nodes) + + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + with open(local_file_path, 'w') as f: + json.dump(data, f, indent=2) + print("Blockchain saved to local file") + except Exception as e: + print(f"Error saving blockchain to local file: {e}") def load_blockchain(self, blockchain): """ Load the blockchain from Firebase. - + :param blockchain: The Blockchain instance to update :return: True if loaded successfully, False otherwise """ - data = self.ref.get() - - if not data: - print("No data found in Firebase. Starting with a new blockchain.") + try: + self.ref = db.reference('blockchain') + data = self.ref.get() + + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + return False + + print("Retrieving data from Firebase") + chain = data.get('chain', []) + current_transactions = data.get('current_transactions', []) + + # Ensure nodes are converted back to hashable types (set requires hashable types) + nodes = set(tuple(node) if isinstance(node, list) else node for node in data.get('nodes', [])) + ttl = data.get('ttl', blockchain.ttl) + + # Rebuild hash_list + + blockchain.chain = list(OrderedDict((json.dumps(block, sort_keys=True), block) for block in chain).values()) + if blockchain.current_transactions != []: + blockchain.current_transactions = list(OrderedDict((json.dumps(tx, sort_keys=True), tx) for tx in current_transactions).values()) + blockchain.nodes =nodes + blockchain.ttl = ttl + # Rebuild hash_list + blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + + + self.save_blockchain(blockchain) + return True + except Exception as e: + print(f"Error loading blockchain from Firebase: {e}") return False - blockchain.chain = data.get('chain', []) - blockchain.current_transactions = data.get('current_transactions', []) - blockchain.nodes = set(data.get('nodes', [])) - blockchain.ttl = data.get('ttl', blockchain.ttl) + def save_to_firebase(self): + """ + Save the blockchain from the local JSON file to Firebase after deleting the existing data. + """ + try: + # Check if the local file exists + if not os.path.exists(local_file_path): + print("No local data found to save to Firebase.") + return - # Rebuild hash_list - blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + # Load the data from the local file + with open(local_file_path, 'r') as f: + data = json.load(f) + + # Check if the Firebase reference exists, if not initialize it + if not self.ref: + self.ref = db.reference('blockchain') + + # Delete existing data from Firebase + print("Deleting existing data from Firebase...") + self.ref.delete() # Deletes all data under 'blockchain' reference - print("Blockchain loaded from Firebase") - return True + # Store the new data in Firebase + print("Saving new blockchain data to Firebase...") + self.ref.set(data) + print("Blockchain successfully saved to Firebase.") + + except Exception as e: + print(f"Error saving blockchain to Firebase: {e}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d307a7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,12 @@ +pybase64 +Flask +Flask-Cors +requests==2.21.0 +schedule +ecdsa +firebase-admin +base58 +starkbank-ecdsa +elliptic-curve +Flask-CORS +firebase-admin