From 1a539dda1545d852a795b9949f58e5d3d2e1f3ce Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:32:51 +0530 Subject: [PATCH 01/24] Update README.md --- README.md | Bin 170 -> 2 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index b0d8f509bf00aed0d8110d3e6545955979cea590..d3f5a12faa99758192ecc4ed3fc22c9249232e86 100644 GIT binary patch literal 2 Jcmd<(0ssIe02lxO literal 170 ucmezWPnki1p_n0)A(x?mAqPk&191sMB||(=G?k% Date: Wed, 16 Oct 2024 12:37:17 +0530 Subject: [PATCH 02/24] Create requirements.txt --- requirements.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..32b938c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +base64 +Flask +Flask-Cors +requests +schedule +ecdsa +ellipticcurve +firebase-admin From efbf2adf3d4716fa0fe425ff142126f429d07426 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:38:09 +0530 Subject: [PATCH 03/24] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 32b938c..92e183f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -base64 +pybase64 Flask Flask-Cors requests From d702ae190d60359f672bed0b72959998e159719b Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:39:31 +0530 Subject: [PATCH 04/24] Update requirements.txt --- requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/requirements.txt b/requirements.txt index 92e183f..cde250d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,9 @@ schedule ecdsa ellipticcurve firebase-admin +Flask +ecdsa +base58 +starkbank-ecdsa +elliptic-curve +Flask-CORS From eef72326c07cefda756220fa565504faa51073f3 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:41:48 +0530 Subject: [PATCH 05/24] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cde250d..e607103 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ Flask-Cors requests schedule ecdsa -ellipticcurve firebase-admin Flask ecdsa From 878a1dedc5bcd755b4db7e2c548d89fbe21bcda9 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:48:06 +0530 Subject: [PATCH 06/24] Update database.py --- database.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/database.py b/database.py index 3042748..1141e6a 100644 --- a/database.py +++ b/database.py @@ -25,14 +25,17 @@ def save_blockchain(self, blockchain): """ 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 + data = { 'chain': unique_chain, 'current_transactions': unique_transactions, 'nodes': list(blockchain.nodes), 'ttl': blockchain.ttl } - + self.ref.set(data) print("Blockchain saved to Firebase") From cfc2ccb399a0e7ed43068d3294fd820e1101afaf Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 12:57:15 +0530 Subject: [PATCH 07/24] Update database.py --- database.py | 70 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/database.py b/database.py index 1141e6a..a55c79a 100644 --- a/database.py +++ b/database.py @@ -1,13 +1,12 @@ 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/" - class BlockchainDb: def __init__(self): if not firebase_admin._apps: @@ -23,21 +22,28 @@ def save_blockchain(self, blockchain): :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()) - if not unique_chain or not unique_transactions: - print("No data to save to Firebase. Starting with a new blockchain.") - return - - data = { - 'chain': unique_chain, - 'current_transactions': unique_transactions, - 'nodes': list(blockchain.nodes), - 'ttl': blockchain.ttl - } - - self.ref.set(data) - print("Blockchain saved to Firebase") + 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 + } + + self.ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain: {e}") def load_blockchain(self, blockchain): """ @@ -46,19 +52,25 @@ def load_blockchain(self, blockchain): :param blockchain: The Blockchain instance to update :return: True if loaded successfully, False otherwise """ - data = self.ref.get() + try: + data = self.ref.get() - if not data: - print("No data found in Firebase. Starting with a new blockchain.") - return False + if not data: + print("No data found in Firebase. Starting with a new blockchain.") + 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) + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) - # Rebuild hash_list - blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) + # 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) - print("Blockchain loaded from Firebase") - return True + # 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 From f99397c0518deb8125c505c38f05d7c9edbf342f Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Wed, 16 Oct 2024 13:24:39 +0530 Subject: [PATCH 08/24] Update database.py --- database.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/database.py b/database.py index a55c79a..9c90c9c 100644 --- a/database.py +++ b/database.py @@ -21,7 +21,10 @@ def save_blockchain(self, blockchain): Save the blockchain to Firebase. :param blockchain: The Blockchain instance to save + """ + + self.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()) @@ -54,17 +57,29 @@ def load_blockchain(self, blockchain): """ 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.chain = data.get('chain', []) - blockchain.current_transactions = data.get('current_transactions', []) + blockchain.current_transactions = ref.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) + 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') + + + blockchain.nodes = set(nodes_list) + blockchain.ttl = ref.get('ttl', blockchain.ttl) # Rebuild hash_list blockchain.hash_list = set(blockchain.hash(block) for block in blockchain.chain) From a84ee1ebb2fc47c3bf68fc08fb42840e8e141314 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:24:20 +0530 Subject: [PATCH 09/24] Update database.py --- database.py | 51 ++++++++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/database.py b/database.py index 9c90c9c..8025a88 100644 --- a/database.py +++ b/database.py @@ -16,37 +16,37 @@ def __init__(self): }) self.ref = db.reference('blockchain') - def save_blockchain(self, blockchain): - """ - Save the blockchain to Firebase. + # def save_blockchain(self, blockchain): + # """ + # Save the blockchain to Firebase. - :param blockchain: The Blockchain instance to save + # :param blockchain: The Blockchain instance to save - """ + # """ - self.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()) + # self.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 + # 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) + # # 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 - } + # data = { + # 'chain': unique_chain, + # 'current_transactions': unique_transactions, + # 'nodes': list(hashable_nodes), + # 'ttl': blockchain.ttl + # } - self.ref.set(data) - print("Blockchain saved to Firebase") - except Exception as e: - print(f"Error saving blockchain: {e}") + # self.ref.set(data) + # print("Blockchain saved to Firebase") + # except Exception as e: + # print(f"Error saving blockchain: {e}") def load_blockchain(self, blockchain): """ @@ -77,10 +77,11 @@ def load_blockchain(self, blockchain): ref = db.reference('blockchain') + print("nodes" ,bodes_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) From 674a55194444b018f736d01b6bd4d15d52ff2dd1 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:26:48 +0530 Subject: [PATCH 10/24] Update database.py --- database.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/database.py b/database.py index 8025a88..1db0c0b 100644 --- a/database.py +++ b/database.py @@ -16,37 +16,37 @@ def __init__(self): }) self.ref = db.reference('blockchain') - # def save_blockchain(self, blockchain): - # """ - # Save the blockchain to Firebase. + def save_blockchain(self, blockchain): + """ + Save the blockchain to Firebase. - # :param blockchain: The Blockchain instance to save + :param blockchain: The Blockchain instance to save - # """ + """ - # self.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()) + # self.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 + # 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) + # # 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 - # } + # data = { + # 'chain': unique_chain, + # 'current_transactions': unique_transactions, + # 'nodes': list(hashable_nodes), + # 'ttl': blockchain.ttl + # } - # self.ref.set(data) - # print("Blockchain saved to Firebase") - # except Exception as e: - # print(f"Error saving blockchain: {e}") + # self.ref.set(data) + # print("Blockchain saved to Firebase") + # except Exception as e: + # print(f"Error saving blockchain: {e}") def load_blockchain(self, blockchain): """ From f68760e44ccbad84d699917eb1c18f4a20275438 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Thu, 17 Oct 2024 09:40:08 +0530 Subject: [PATCH 11/24] Update blockchain.py --- blockchain.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/blockchain.py b/blockchain.py index 03e59ba..d46c55a 100644 --- a/blockchain.py +++ b/blockchain.py @@ -159,7 +159,11 @@ def register(self , 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 = { @@ -672,4 +676,4 @@ def resolve_conflicts(self): return False class SignatureVerificationError(Exception): - pass \ No newline at end of file + pass From cc4a32d78753e3f443cdac77de1fa07edb131d27 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:05:40 +0530 Subject: [PATCH 12/24] Update database.py --- database.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/database.py b/database.py index 1db0c0b..77b0e8f 100644 --- a/database.py +++ b/database.py @@ -24,29 +24,33 @@ def save_blockchain(self, blockchain): """ - # self.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()) + 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 + 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) + # 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 - # } + data = { + 'chain': unique_chain, + 'current_transactions': unique_transactions, + 'nodes': list(hashable_nodes), + 'ttl': blockchain.ttl + } + + print( + "the data is : ", data + ) - # self.ref.set(data) - # print("Blockchain saved to Firebase") - # except Exception as e: - # print(f"Error saving blockchain: {e}") + ref.set(data) + print("Blockchain saved to Firebase") + except Exception as e: + print(f"Error saving blockchain: {e}") def load_blockchain(self, blockchain): """ @@ -77,7 +81,7 @@ def load_blockchain(self, blockchain): ref = db.reference('blockchain') - print("nodes" ,bodes_list) + print("nodes" ,nodes_list) blockchain.nodes = set(nodes_list) blockchain.ttl = ref.get('ttl', blockchain.ttl) From 6c1e376eceaee96cb148480ab8967ee7cf87271b Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 10:15:33 +0530 Subject: [PATCH 13/24] checking the issue un register node --- .history/account_db_20241012145317.py | 23 +++ .history/account_db_20241016123601.py | 23 +++ .history/app_20241017101423.py | 230 ++++++++++++++++++++++ .history/app_20241017101438.py | 234 +++++++++++++++++++++++ .history/requirements_20241016123409.txt | 0 .history/requirements_20241016123604.txt | 8 + app.py | 12 +- requirements.txt | 9 + 8 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 .history/account_db_20241012145317.py create mode 100644 .history/account_db_20241016123601.py create mode 100644 .history/app_20241017101423.py create mode 100644 .history/app_20241017101438.py create mode 100644 .history/requirements_20241016123409.txt create mode 100644 .history/requirements_20241016123604.txt 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/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/app.py b/app.py index 4db7581..c2d3a3f 100644 --- a/app.py +++ b/app.py @@ -59,8 +59,8 @@ def register_nodes(): 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") + print("this is parent node", "simplicity_server1.onrender.com") + blockchain.register_node(node, "simplicity_server1.onrender.com") response = { 'message': 'New nodes have been added', @@ -78,7 +78,7 @@ def update_nodes(): return "Error: Please supply a valid list of nodes", 400 for node in nodes: - print("this is parent node", "simplicity_server.onrender.com") + print("this is parent node", "simplicity_server1.onrender.com") if node not in blockchain.nodes: blockchain.nodes.add(node) @@ -217,7 +217,11 @@ def shutdown_session(exception=None): def register_node(port): print(f"Registering node with port {port}...") - blockchain.register(f'simplicity_server.onrender.com') + 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__': diff --git a/requirements.txt b/requirements.txt index e607103..28e782e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,14 @@ +<<<<<<< HEAD pybase64 +======= +base64 +>>>>>>> c006167 (needs to be tested) Flask Flask-Cors requests schedule ecdsa +<<<<<<< HEAD firebase-admin Flask ecdsa @@ -11,3 +16,7 @@ base58 starkbank-ecdsa elliptic-curve Flask-CORS +======= +ellipticcurve +firebase-admin +>>>>>>> c006167 (needs to be tested) From 44b9621d6d0b4891610ddf4a2a2df6d05f783079 Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 10:40:48 +0530 Subject: [PATCH 14/24] checking the issue un register node --- .history/app_20241017101516.py | 234 ++++++++ .history/blockchain_20241017103342.py | 679 +++++++++++++++++++++++ .history/blockchain_20241017103343.py | 679 +++++++++++++++++++++++ .history/blockchain_20241017103630.py | 684 ++++++++++++++++++++++++ .history/database_20241017101423.py | 96 ++++ .history/database_20241017102653.py | 96 ++++ .history/database_20241017102950.py | 96 ++++ .history/database_20241017103339.py | 96 ++++ .history/database_20241017104033.py | 98 ++++ __pycache__/account_db.cpython-311.pyc | Bin 1916 -> 1919 bytes __pycache__/blockchain.cpython-311.pyc | Bin 29962 -> 30216 bytes __pycache__/database.cpython-311.pyc | Bin 5186 -> 7221 bytes __pycache__/nodeManager.cpython-311.pyc | Bin 2449 -> 2694 bytes blockchain.json => bloc.json | 0 blockchain.py | 5 + database.py | 80 +-- 16 files changed, 2804 insertions(+), 39 deletions(-) create mode 100644 .history/app_20241017101516.py create mode 100644 .history/blockchain_20241017103342.py create mode 100644 .history/blockchain_20241017103343.py create mode 100644 .history/blockchain_20241017103630.py create mode 100644 .history/database_20241017101423.py create mode 100644 .history/database_20241017102653.py create mode 100644 .history/database_20241017102950.py create mode 100644 .history/database_20241017103339.py create mode 100644 .history/database_20241017104033.py rename blockchain.json => bloc.json (100%) 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/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/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/__pycache__/account_db.cpython-311.pyc b/__pycache__/account_db.cpython-311.pyc index 109b7d1aa67a2a2976ae0da133bcfccb317fcd13..17daf1152e3ec7c22ee96a584cda2615aee10254 100644 GIT binary patch delta 55 zcmeyv_n(h@IWI340}$Ly<=@Di!6<9!Y!wq)oLW>IlbDv4m=_aJl$o1YRH@*XpORX< JxtH-hD*)Po5`O>y delta 52 zcmey*_lJ*rIWI340}!w|aBbwyV3g8xwu%WYPAw{qNlZ&i%!_f#$xqHs&PdG6+dP5s GJu3iyX%KJ# diff --git a/__pycache__/blockchain.cpython-311.pyc b/__pycache__/blockchain.cpython-311.pyc index fb4b2fd49b3385426bfb4bf6766a37359a0b0172..56505369ae3ed98239fb2678145030240d0e4988 100644 GIT binary patch delta 3012 zcmaJ@3v5%@8NTQG!L?(@PD1R2#Ib{&hZ)3x^Db$C1`L4&C{P|t3C8y(z9F&Q<4b_x z6r{pHhyvj+H02?pjZ)f88q-Cft<#{}G|+~I&0JxkGKEcy39Xg;(A0^o+c`InB^34g z`n%`;-~Tx0|Igzdeu57D17*HvH0l|0m7g+qzSlL7nTLu7${sG}4V!H&<70T^eumE! ziY}@c=EvkedTq8VoQZH|!SM`N%%io8I}4Vgfx5n$sMr(f76YQRdrlxMhURpLSlH%| z3UmB?j~I&b+vju!MPd7#s!E}fuNL?^zM?A{4$1dWOlzO_QhnR{sDPtw{*DfRsBJYC zd;EB}qczM6(YCb^=H{b9_#t;MPorx{GXxI#QYOpsJxP-@&9KIr5g5?jMyus5`Vtjd zEAKb{1)*JXr|A-lVzN2ApGC*yqtkhW4#JnYzhEm__^IV}kE9zPD8uk5roPCM!IS^! zg_tQk8-v#hb6{k(Yr!*&D#mo7ajWn2p^m8`@nQ~aw3>@GldU?Di25~C;%Oo7wZM66 zjd5B`b%x=&bE=r;CpyKx;zb?&*P1_Vl+lAduRxP=mL+Uso-Jr-?K4Y3!4aa_bNI2~ zsCV?`QVI5l_;8OxIK(IwPp4ur;_&l47NXHHX~~#rha_MJ7J90QBLlZ!F;6oSnJ`| z!TN-uG-)V}8%iHoiU#*4ES{vrb135hXFg(#TN;P-1V-;$>f8GG`mG-aI^)6aWUx08 z*qIFM{J8J+G{Y=po7k`DqnZ7jVfLUE^o>3Uw+yYM4_!+g<#a8SBB+Si=fp`Fa!#%r zbe)}*$Sq0cmZVkeGB{#)7cClUj?Y*$dZ$X<7dEDBMDyU-`{&x;>v&`~&Fwg`PGKP)Rk2u`@Aj6Ry5S1G&+d)+le1%a2^GlBxiVt^se;tJ zaxQ{mY`oVc`5ZnjFa$=I(w$h`>3dNs6SCul`N_{v?}HD#$8R9`(<6d1!>~;`ImUP?1da;S=#7RGdI+tE)BJ{3o@cjp&tDs+AOYo)TFQX6S zQ_EvWo1jL0khj8%Zo|qIc63edUU3zn--ENoHR}(Qy+MJ7#J4FtH_R1yttA&-mk+iq zWYHkpT2+jGCqG%$gvc`Ttns2h$$>S;HQJNZfsZU=3z#<4pl@W~hD*p!ONLKTSVQ4u z3d*{syEK*|bCZDH2WitH+i4;gHcR9%i~0kS7!I}5FA40RdY9qurZR1cH2a)j-rz)E z$j;5T)yOTUw@gE|=Lz8^M*>xSHc3~hQSPL~cl)DK`!-q#4|ujd%8OGg3xOzIwhqTw z!S29pzYW#P&HfKr6bCK85-pL}@)c~A()~7SrR+^k(sFA?C#BK@8gbYd*nzPSlG?=( zFYLlc;c7<>+9ZG0ahlBnYU`u0n7}%G58e@HqG9-@=p-o}5oe(L@Km&+?Q(wCLl!-S zr`-)?ORVczfey$AdW=Y`Wc5uL4B7S`rhb%@gq|?N>TTh0H&(XAQp$`__!)(36g(8@ zT*TKYC^JN<{1y62=lHN8D)}Wb&?88}@Yp6w6OWrI$whEDB4BAZrlZ7nn6(6zG(4s6 z_bG5>+T#Fi)l#UYKu=r+=n9IyLRRU%iC>!aYD&}01O(w=IB2qJa?;EM)XLw4|App0 zCf2QP71q%ZdI}lO2}ZRrg^N&zZC2WH&%}SF(!W!nb;dW~`?wq3gj?7}mh7jvp)7m+ zK~& delta 2800 zcma)84NM#N6~A}>z&17z9E<}P^I-xe2_FH%M@qh$-~@;ZlywxS5a5HDfbE_yv<0(F zUD{<`8HZO+t5o0Mw%zatQn zMbnPsclW;E`~TnjpAUWoPkjj~FPKaQj$Jx2W%w2EkrXRr9VwpQ#B(mrV?4llOoHv_ zDvo=J{V!bIxr!`>$x;RT9kM(R+|N1Fuo;fXEBTN%r|jN_-fsm3G1%KbG}Q0w-8SkU z+&j3Z-|OoQ;yK-V$j0ls53Fi^W1M4f&=pO!9G;A%7REU+)kbkne;M5JDMOJ8+U0jm z*8#@m7gAs3VM1<8pW@*e`N~QUz!O+yd6Pc_SkYp~l*~TNE3@LiH<>G8G3}6g9K>-C zgt(C<-Y|G!N+TsN>TsNg$2OM*z1CWjDx^BWdDK5vg*0b1__DPoX@R4~FRa;`q?0`3 z>Q~u#of{9WI+C+e6vq7HLZ9$xz$*%#KA+zs1Y??j==DjsG3z_p<19)r3E$4DO^@n~ zAL#P0>hkAwg%Mq0SXYSd?2q|69@pl$i$?gxd|0ka%g(a5C(xK*W>u+1czoEp3g6Dj zZBHm-M7!vKmCJM%DOL{8%Hc+m7C9mHZK(sBgr|lou|9Q$>r>h8q=O`o_0UwKGqP)UOQ)87cs628`phe$vw7z&Qcz+lpjp~ zM3?roDQsz)HAM0%*>mB3Pr5u2?j4Tw`u_0nzVP_O*Bti)iL+A^`dTrBO;+PU;}Cc>2XqRfXZKp{sW#(Fs*IH$ZD`r)p(xOVyj*R= zc2}W#JNM9Wyir}!x~wwols=?S%z%~DnMjB9s}`%^rVLW*5~1XcJvGJaZWpfqufla9 zJu6%vGB7kKIi0IT3-YBJtnywTP~AyDWv#5k#`OAOD35}yZyl}Q)>K`yw|;jhvRs*Egvq(SK77% zn?-X6izT}{4r^H1(QkLaKjk%DFM^%+N_?K6ouGqYX=TM<;NKq<;0$(kZ-o;$)13|H z@wM(^_z?fpT>@XpNj;ai$m@(bk&%$;Xn#=Z8=#*GPBiy_ zW}PD|3xgo_eO}(JT?k{^fDNkUvVmXoFo$1yT4A$XE0pmti9^Ho4DuT@4ek>~!6)^3 zeIDUaaT;faYvF$R>hKJ|;yLo`BG|^DQ;g!&NCA9=vm=E$)5I1S1nG0hV$!v+`!T#W zVgo_`W@Mg+n|N)k5qPxuT42BI^_f7ctlA+A`EC1uNM4k0lAgWA)B}G1sHhy7t4T6X z@G8L@1my(p5L_To8bR4$+DoNpW5%G=FL?*Y1Zj`|)&ct=37Z+jbOF&H5Jc%QkxCcC zoSC6=eBUHGMxe(vfgA_zL+}Ci)8M;He{p`jW_6rff+5@+*pq73nB&|MRLIu@|ALCo znYG)g5)Eu5#AE^^!65msBT$Bfv_yhgOb*&I>CKrheoEXg2xu?Gi?}&B3h&|Bpo7iY zT(Hrs^$&~)gOW(co_L1fbF7h8!zsB}YJlpvcDn&G;@sWwu;qp#`8rYFis5~A{{i7ZP$T5|`kW=7-aJq}aq{JQj_~ zXZ_J+Ea4xCiNbJ168sTH-4&&_EqL1JubofPzL~tS+5BXo~C^j^<@xQBL?V@Voa%Aah6tn!m=)a#EX&%jK?d zT%KHVX`Vyq3Y5NX)|jGXlSgRWtev5FN@lHEG0HwW6q2i~uj8hCp0>-+ps+h%jz=&? zCZw115jldVo`niOm~JOlarE&O;0cH7DPEV5{pJ;L9a0jVlxO& zM&vQQGM0#8bBLt{Tuo>5?SxZeT*O1u-J&o8Hk618IsbWMm|g7&AJ%C3*@sa zK(2HW$TV84@@Z8~OUU7_TJ&zvyj%agZJ+Ax)V!TDJ&Wu*jcvKQDeLilkXd5rU6pqc zT~T!(lO;^PccI!fvmNuS>h)`0|BT~e=?vWWKr)J&3kP0Y@FT|ZbI6@TZd5qQvS}6w zmhc6Xp~lU+J?IKly$(O)vQOrGLCt&#FeqSGt1(5d+A=HwXgTjx(0{#UCPRyKhLNh} za;vXAV;>Q?Bu77z@dV{A-bwmZl7D}Azor}+Xrpk98Hc?E7^Af~T)%cqIc0BNHbQc( z#q%ZhCDw+fTynkjecW7*8E3}Dm)>w+f%&*@&Iuk1zU@*dlqhdPyO88KO`l<2LZ>MT zy@V*ZZF(uQ_u9})6Oz|i?QcS&Z>q#rt2|TkEA&CplE?CS$5hF=60BQ;J58NNM(ZtX z`g|FeyZR$<2%J17aF!F`8kRzld%W`N9k}LZ3u)pxBE( zp6*B2m;ne#ZYe3s;Zwp{DRd3o430CplTS@dO5k!JDzJE{iO<40CrsfqcfzyeX zJ}+AcepRdPJgn_JtZY1fxo%$msNv&=`G(s)xiUscLW!1J9J-TWDZMtD5+O#*VOfkM zq)1ebB@>eFBpucnS&r+}>F;pQVU@XJ2@C?$8?5C*7CVpU%J3rCfuuJd62+v5KjB%k zR+7C~JJ(VvZUM<6_NwA`AiC>~NIWG-q94CFC&gGo))`5V#Q-j{1Lr!DlR`qTBF%+y za|Dlw*eZ1gUh=y8(3z+(i3ieBDq{aHVw|nBsYLAcln^$??ajT}hFv)pk;Wp!Ak!GX z0~?}Fjp&ph;lPsfq{bZTRYi^>z24HxayNo~j&uin+ca8Y>)c(`VngHQ@|u8+FF0Me&g)9 zCAYJ-atQ&M>G{4A)vS3pIWMWz+qCL!ONgrQUwwViQ#p6$Ra zG4PcXA>=MDd><1lZzb-*g9j^Rf6G=YHLWVUTVr=C`Sf71>M;a8EpsbXjsRz(Ys1A(O4Kw}b!HOOq`m(x{;ns)VK*u?I8l{n_mk1I75w4y!!&KX^#AnJuv~RP4CMgVtGFTh**>F zH$ns=GPYW5hFJw7R!lMHU?&U^k#GeNu`<12g0rw#&_gpP@)W0F-n-JZt^ zBgs^P2M}s?^iincaN5@gaU~{@7)1bwZKNa~kvR1KgcsryFyCS`CR>3#3PND*Jsi0n z!cqz##N#Oi(hDztzI9>XtDxF?L~A{wH1%C>yIT9v-jDar@4e0A0`^28gcXu)3c`z* zK>UNiyi1t~Z0!{F7CjGn%dcXLmBUx-Fuo!R0O;Ysk-3E-FjnM128rnsUS=FOy(*@9 zv_g)81PKF+2RSU?Jft)a{ppGM^e5+kbN;iGx_O_rc^_mIB?GKBS`j~Qx;85sNN+K) z5bj737_t>?=Rz%IB3|7(GYWPY1kN%+u?@dk7|4ya5yn7M{1JR-xyHRV>#d)CYOdp*j<-8!;rQf^w;lro9{~t` zs;6J`^edkJZ2kI6DH36Kscfspwkm9Ewz28MGw+|dTJl+q+8ERtgEPl8c6~O``svtj z$CN$K-WJtBpBCuD+1udMg=@;0TR8enc=YS=s4|vRUYFFctc7Lxq&}X~fHtPI#*`t- zxADUl-+yslxH@>_Rn^y_`8sBfT?~P!O`8j6?RLBN%*&9WHVsW|{iWxF)COJTdR-p4MrzSDVmhw5$Bysg$|c-52I3fYs_ZU}-KFGHeoL_*t*5aM3PUzHaD!Z;qdFJNZj}$uEDd48x)_$Bn0FdGQvIHzls?45`vFM zrG6mO=DkF_oPi}|5>JB@Z>I`qyUz*k)Fho%PSDG8Vzh^wYAH7iu{@fL#|7hT!ZEUHuTDT|sFd&(l8VozD*o+kI5x{!iuvQ>ws*=$YIbk&k$fTCc~E7Q9q|Nbgp H$uRy2L!+}s literal 5186 zcmcgwU2GFq7QQncJ9gsykr)UVG9>I0LotEEQXr**q@fMFfe_dxZi*Q^T?w{*=;KT^QlpU~b+tm;zL^Df5l?&0c>I?T z(EgM=*PqY*J@+1;bMH6)th&0AK)O3zGyGSO{)UBo;%wn@2ME`RNJJ)1Lio+bnK3rR zGPycO$ib2$M08vtB6o`=YXA7>RhJxa6Az?7a0*^a@X1CwPy>Z z;HZ+2BZc+Bj1}-f<&Xc#pyD($&8i^flbT-`REW$ak|v{t(Osl`S+`)^ox<4pl9Bn@ z$V*2|J66=pTAK5}2>HKj=F*NdSEjwHxMtY1D_<(#V|fYsp*5`}I&N^&PPIN?CM3-+ z^^QGc+NCxWdyHDXh~>01?Gl}LUAMr7cM5g{f2+W!+{YLqSGAIzMBygbNyi|WU>Gt; z7K4&d?IQowU%X%TE6(gJduKPf?=HSc@sX5y8(`Zxxo3!NB(K1)D1=pBhnC$ zcbRh2rmj5yONrpA&A6xm?A>9kC% zU>x!M^EW#851mpZstgIys2~p&4Kma((en}=5(QN_D=1PhdEvRIV_1@<$s|2EwWHXx z>ueAW7Pcm~!ampmF)zfGgTcZ8ALvu|0{k7GS)y$j$Na(27matyv>pA}Se_}yX@E1GmUYd>NQzQPR>LY4WJQRm zv4pIcPB|e;is?|*xXDcXfX5a1gDWPB(6x7}vG{{o{w<=T2fQX1k0~m}FsB%?rt7>A zPe}@0iv@=wsk8~()?txLBqf=8EfIanbS7y`Rs$7gW%0*1-T6>4Jt&2ff;wV)Q*vxP zC53Z4Xs}CV8{p8@OeU(}Ic6USmetMLCH^(#FK%YJ_u+8m7Z5wMB)gh4`ZJ!VF0$5t z^Qi4rXGV=PNs#p|;|8FA-0+Wo=b!x6KdJkt4F8nA=AyCYV#d3;vE|bP_AhaI#5g?- zvL2i<09$8_)|r11vbmf6hlP&--(#Ox2y*lLC|RXV0F!sQRJ1TB~WF9|VA`yLTGyomxIC z==y(oYi+0B@s*$J^vV@oKpQx* z0>dYlUO^m6)}J5o3d-VCSJLgabbEe0-IqzYKp0A|df0JgQEU0{1Y_{*uoCpED)xhx z>U>M-Y#9<|MDHc{WincmZ-2QJVWxsj(#~OW&LS|hBJG5h!W?B@RlNc^sR43QD+J&m zDcs>dPY7^smh+P+O^m&EhLTFcVfkNhl=uo8DC}@QmDCiKf#uK?kgX_>BP5YmQjv(H zDA80re&G$i8!mU?KVS(u5mSomm~wU_N1NKl%L$c_CQ`B(DG<=8n-%u)eE>N+lO^Q9du$FhG5Y7OKlR+F+NnXU zBcykPjE;~tbXq$QF739GoHGhCk0R+PGY^s)i#8ty;S;7noSUYZ%j`VVEl+8t{1CD! z^E#10cr*A~%#8@jNWftcK3WGe(9Hk}9vnPB`#%`!ZJcHYz%!fpwG3!21NWr){hz(_ zyLZ0aqqpueTK7TKl5Ck_YE6;jnkx(Oji&+uoY z8?lW0pv?%DVR@AB#t1wdgkMqxtLztX4JN23F~QE^ww*uj?V=Ea#{EG z8Qwn4+qclL_UfL8)%E2Ri3f_*u;$vnxxU|YTEq^{Unlx$+O zwAWKNsrP;Ip&i15A8Ds0V=xLuogX#;eZz)t_&cBctxwi{NyC@a>&A_`@r-kEYw(MU zrQWWM%1}%)-N+BmF-PpU0m=w51XX|_w_7qGP|WsYNHGDiz7P8Vg1qen%7B?l!@Xs3 z{l+Y7Kef@T0{dTNdyU?^@PP(x^Q+cK4VRK8VkKLa#j(h+Wddq+T*gRk~58x30 z_f>UQ4}W|hb6}x)bEe1e`WKNBzWKQa0J84hZMb)9?%fNu4H?y9gb&=lLjlKw0C#A6 z#e;SR5JHNgm%u+k?r<1il*3`OG8`UDh$(m*2%Fw;cswPZyFcey73 diff --git a/__pycache__/nodeManager.cpython-311.pyc b/__pycache__/nodeManager.cpython-311.pyc index ca1c86c724ea43e6e8fbf7864c9451d985302a47..835b0cffd10a5e109ccae2b672e0842c1e74ac10 100644 GIT binary patch literal 2694 zcma)8UvCpf5Z^odoJ(@CA!mn#l5kZ~5|{jeDzs_@NG)w?C9qHtFIJ?}#k)=}*k^a| zOpEJ?Jn)bQR238{v;{Q}6{X@E^i$Z#71l|SDph@`)CZ%`7oIwE=eyWRA3ArQo86t6 z-JRd;tbZOF8X!PdZzNW}kqG$%8v#W-!rnCywh1LvD3GlG6|w@f#ez^2v!Z}~5s)L< z2*}ZFluB7iB$o+|-6gc|iAcyZ`1zELQQC7=oLTPB<+;F{S(?k0iu1Y7%Dj)zY3xmcuuT}jon}QU+$Grv6@f=-1h_<_ zz++T`y~NzXOo_6qhGncUo(o){CkQHuz;EwwVB5q29?)8-XP{nv=zAL!M{j+Z1sJNP~U;aSii zU@;gpD1o3vGVUNX90Osy@%a^3vLHluT5;n+_;n9VSJI7g+2*)wckmM9j+r-ehQr+9 zP!TgkFB{IP8#gTzGsUbjG;@_;3cbP$98=cq=Pb`SvvSOhfanesN(R-v**R`1ExJ*g z74mKr-P)L#n)dpgON&c4Y{u;+BcC^{rEAkBQ@DfO+v@OCOmsE z73s-7jE9$fk3iT4jMh-aqn)n&3Hl(*K-|Z4U8o8C&F&>O>#0hhW zKoRL3703!^i&*`1K0CErGTczzVxAVO5xe)0&Wyz ztQizR?b2A(GU=FqfTHD!z3N76=5)}6V$3Y+qZsi}bfcIot^#v;$mvjwZQLXrb01q7 zX7EAnsh>gb6zJMnU>=&IC$_%YUVpHD|IXH(O{FC#U&y1+<`-ONO{1OqvUSxQ!+W4<&BYfIopET7bAsg0zMpXX7B3s?mj+*O}yc~b@0i@9ZL< zfj&HB_SeAL5kU}IWN5>CS|n8u-WHjx_r5JMUhjQ>Pi5+4xRv;LLureX0xbD3dx?Fo J{$s#f+`s(LSZM$N literal 2449 zcmZ`)-D?zA6hC)nHv5rD;$}C|Bvr>mOjgp^f<>^kLbRzVbz2CnWC;w@y|c;2%}jb{ zVsu$+C_eb$Ls|k6s*s1)w)o@Pb#{hvx(Ozv2nE#dKpc{iseBt!3RRE>XaF&SUT4y#=4Hb& z=1sO2v2fx{EIPK8u0wHL&dd1jOd;8X0x5uwoZkwtMe2O(_z)+L8UnOvAW}frB$<%C za3-5G^<~3d@FF?ept=aa3y2{hd5UA^GAxKF4!72{y;B#?&VAvS%$YMX8N-^J%-QLy z=>;Qe&CQtXn#tz4ZS8|5mT!0=UC&xsSJ(6LWXnojihBai5hQC+85q8!-0iOP4Q%S) z>!rRE<-QXrh}^-xgBp--{mu>)h=1}YN;e@4Zp&%`78^54+PCjLxJg{@w$>Ioif9G$ z5Un0HEN@CwDoEFWopQr<9e@H#2O3jXq?bQ!_rv1tnrVq*0=$|`1oy)2YQOTd zg@b^Lp*re5n88;tLvO}h1wZCQIDa{!)!L_+lA6dxEyL_3&0R3HPi8(#YCLT z((=KYcK)PT$=E9vozOndnTBI(rsb?KQ@9nOnAg#{Gm#&z8BG}&Ak|!3qh=bjz7{qA zhCMQo2z!dXY+9nQJcXCLr||OgB3X>aaSbbNq69I$z%t8Pt{0s`z@+U?@i2rFuZRDM zivnH<5;Eht=qWrRPoaiuI6SrSU$Rad8Q-aVe`^soOP>EB=PG9Y8dO8w;r{Jt-#Xo} zw$i2O$#V2$6@pzyexch3hc<3(#lO3?acf=qJ2rTauAg5&|5N_)@yDxA-u^va8l5VS zPOYD>3?1DH-al6jqm?jilII-h1t(3}OBMPCi-WU&#Q!>i13smAsvJ)hXov3rKn!1#QN?` z^S<2^EBA~vjRYqwFFkgjd{gYblMn= z4y!|yVY5*4v83(GG~*i9kMUK`oJb}VQN3P=kLY!z7wl}>Wc?UXf{2WF_<`-`xYsjp zy1K6_?y~$)Yrem-co^-R(@1#IgL>fZ*(xaEL))=|jdNS^9|udZ@p5dusE+^36JoTa zj+NE1qB>TI9b7-n_@SM|5cpM+=-~I9Wh|Mx?nQKc$)+oKvFe_x>tC-JIe&$zs4B{k z@f+_~5Ey=O@*|aT!e5aYYZ#W_tCAEL#r$d`q22(dY=ex20*9*bI`Lg_P&^-cxs$gz z;Q}_y3I7Zc!d~bDKkN_L2+H_Rb*7P2WkN^=I@d(6K-Ze+Rpl5#kFS>=rt01>x#{ D$KNV& diff --git a/blockchain.json b/bloc.json similarity index 100% rename from blockchain.json rename to bloc.json diff --git a/blockchain.py b/blockchain.py index d46c55a..7b9761b 100644 --- a/blockchain.py +++ b/blockchain.py @@ -46,6 +46,7 @@ def __init__(self): self.max_mempool = 2 self.new_block( proof=100 , prev_hash =1 ) self.error = "" + database = BlockchainDb() db_chain = database.load_blockchain(self ) @@ -123,6 +124,10 @@ def generate_transaction_id(self , coinbase_tx): 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] diff --git a/database.py b/database.py index 77b0e8f..5041628 100644 --- a/database.py +++ b/database.py @@ -3,9 +3,11 @@ 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): @@ -18,24 +20,20 @@ def __init__(self): def save_blockchain(self, blockchain): """ - Save the blockchain to Firebase. - - :param blockchain: The Blockchain instance to save + Save the blockchain to a local JSON file. + :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.") + print("No data to save. 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, @@ -43,54 +41,58 @@ def save_blockchain(self, blockchain): 'ttl': blockchain.ttl } - print( - "the data is : ", data - ) - - ref.set(data) - print("Blockchain saved to Firebase") + 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: {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() - 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("Retrieving data from Firebase") + blockchain.chain = data.get('chain', []) + blockchain.current_transactions = data.get('current_transactions', []) - print("nodes" ,nodes_list) + # 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) - 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}") + 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}") From 5fa4e8311b0388770aeb3663f84b0c0a173a51f7 Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 12:21:22 +0530 Subject: [PATCH 15/24] checking the issue un register node --- .history/app_20241017111434.py | 232 ++++++++ .history/app_20241017111435.py | 232 ++++++++ .history/app_20241017111441.py | 234 ++++++++ .history/app_20241017111442.py | 234 ++++++++ .history/app_20241017112341.py | 234 ++++++++ .history/app_20241017112351.py | 234 ++++++++ .history/app_20241017113300.py | 241 +++++++++ .history/app_20241017113311.py | 241 +++++++++ .history/app_20241017113605.py | 241 +++++++++ .history/app_20241017113646.py | 234 ++++++++ .history/app_20241017115251.py | 238 +++++++++ .history/app_20241017115252.py | 238 +++++++++ .history/app_20241017115410.py | 231 ++++++++ .history/app_20241017115438.py | 231 ++++++++ .history/app_20241017115447.py | 231 ++++++++ .history/app_20241017115551.py | 232 ++++++++ .history/app_20241017115656.py | 232 ++++++++ .history/app_20241017115807.py | 232 ++++++++ .history/app_20241017115843.py | 233 ++++++++ .history/app_20241017115846.py | 233 ++++++++ .history/app_20241017120108.py | 233 ++++++++ .history/app_20241017122022.py | 233 ++++++++ .history/app_20241017122030.py | 233 ++++++++ .history/app_20241017122034.py | 233 ++++++++ .history/app_20241017122053.py | 233 ++++++++ .history/blockchain_20241017111211.json | 0 .history/blockchain_20241017111314.py | 684 ++++++++++++++++++++++++ .history/blockchain_20241017113520.py | 680 +++++++++++++++++++++++ .history/blockchain_20241017113526.py | 680 +++++++++++++++++++++++ .history/blockchain_20241017115926.py | 671 +++++++++++++++++++++++ .history/blockchain_20241017115931.py | 668 +++++++++++++++++++++++ .history/blockchain_20241017120105.py | 668 +++++++++++++++++++++++ .history/blockchain_20241017120106.py | 668 +++++++++++++++++++++++ .history/blockchain_20241017120203.py | 668 +++++++++++++++++++++++ .history/blockchain_20241017120357.py | 669 +++++++++++++++++++++++ .history/blockchain_20241017120416.py | 670 +++++++++++++++++++++++ .history/blockchain_20241017120432.py | 672 +++++++++++++++++++++++ .history/blockchain_20241017120523.py | 672 +++++++++++++++++++++++ .history/blockchain_20241017120541.py | 673 +++++++++++++++++++++++ .history/blockchain_20241017120717.py | 674 +++++++++++++++++++++++ .history/blockchain_20241017120759.py | 674 +++++++++++++++++++++++ .history/blockchain_20241017120806.py | 674 +++++++++++++++++++++++ .history/blockchain_20241017120829.py | 674 +++++++++++++++++++++++ .history/blockchain_20241017121104.py | 675 +++++++++++++++++++++++ .history/blockchain_20241017121117.py | 675 +++++++++++++++++++++++ .history/blockchain_20241017121138.py | 675 +++++++++++++++++++++++ .history/blockchain_20241017121144.py | 675 +++++++++++++++++++++++ .history/blockchain_20241017121147.py | 675 +++++++++++++++++++++++ .history/blockchain_20241017121549.py | 673 +++++++++++++++++++++++ .history/blockchain_20241017121653.py | 676 +++++++++++++++++++++++ .history/database_20241017105036.py | 99 ++++ .history/database_20241017110649.py | 99 ++++ .history/database_20241017110658.py | 99 ++++ .history/database_20241017110659.py | 99 ++++ .history/database_20241017110743.py | 100 ++++ .history/database_20241017110744.py | 100 ++++ .history/database_20241017110750.py | 100 ++++ .history/database_20241017110757.py | 100 ++++ .history/database_20241017110822.py | 99 ++++ .history/database_20241017110828.py | 99 ++++ .history/database_20241017110842.py | 100 ++++ .history/database_20241017110927.py | 100 ++++ .history/database_20241017110953.py | 100 ++++ .history/database_20241017110954.py | 100 ++++ .history/database_20241017110956.py | 100 ++++ .history/database_20241017111034.py | 99 ++++ .history/database_20241017111203.py | 103 ++++ .history/database_20241017111617.py | 110 ++++ .history/database_20241017111620.py | 112 ++++ .history/database_20241017111923.py | 112 ++++ .history/database_20241017111940.py | 112 ++++ .history/database_20241017111949.py | 112 ++++ .history/database_20241017112755.py | 113 ++++ .history/database_20241017112756.py | 113 ++++ .history/database_20241017112807.py | 113 ++++ .history/database_20241017112808.py | 113 ++++ .history/database_20241017112829.py | 113 ++++ .history/database_20241017112839.py | 113 ++++ .history/database_20241017112840.py | 113 ++++ .history/database_20241017113825.py | 119 +++++ .history/database_20241017113827.py | 119 +++++ .history/database_20241017113828.py | 119 +++++ .history/database_20241017113841.py | 113 ++++ .history/database_20241017113844.py | 112 ++++ .history/database_20241017113849.py | 113 ++++ .history/database_20241017114943.py | 107 ++++ .history/database_20241017115417.py | 107 ++++ .history/database_20241017115510.py | 109 ++++ .history/database_20241017115511.py | 109 ++++ .history/database_20241017115536.py | 107 ++++ .history/database_20241017115630.py | 107 ++++ .history/database_20241017115704.py | 107 ++++ .history/database_20241017115750.py | 108 ++++ .history/database_20241017120019.py | 103 ++++ .history/database_20241017120022.py | 103 ++++ .history/database_20241017120110.py | 103 ++++ .history/database_20241017120139.py | 102 ++++ .history/database_20241017121411.py | 110 ++++ .history/database_20241017121424.py | 110 ++++ .history/database_20241017121440.py | 110 ++++ .history/database_20241017121504.py | 109 ++++ .history/database_20241017122020.py | 109 ++++ __pycache__/blockchain.cpython-311.pyc | Bin 30216 -> 30869 bytes __pycache__/database.cpython-311.pyc | Bin 7221 -> 8583 bytes app.py | 37 +- blockchain.json | 668 +++++++++++++++++++++++ blockchain.py | 48 +- database.py | 25 +- 108 files changed, 28300 insertions(+), 54 deletions(-) create mode 100644 .history/app_20241017111434.py create mode 100644 .history/app_20241017111435.py create mode 100644 .history/app_20241017111441.py create mode 100644 .history/app_20241017111442.py create mode 100644 .history/app_20241017112341.py create mode 100644 .history/app_20241017112351.py create mode 100644 .history/app_20241017113300.py create mode 100644 .history/app_20241017113311.py create mode 100644 .history/app_20241017113605.py create mode 100644 .history/app_20241017113646.py create mode 100644 .history/app_20241017115251.py create mode 100644 .history/app_20241017115252.py create mode 100644 .history/app_20241017115410.py create mode 100644 .history/app_20241017115438.py create mode 100644 .history/app_20241017115447.py create mode 100644 .history/app_20241017115551.py create mode 100644 .history/app_20241017115656.py create mode 100644 .history/app_20241017115807.py create mode 100644 .history/app_20241017115843.py create mode 100644 .history/app_20241017115846.py create mode 100644 .history/app_20241017120108.py create mode 100644 .history/app_20241017122022.py create mode 100644 .history/app_20241017122030.py create mode 100644 .history/app_20241017122034.py create mode 100644 .history/app_20241017122053.py create mode 100644 .history/blockchain_20241017111211.json create mode 100644 .history/blockchain_20241017111314.py create mode 100644 .history/blockchain_20241017113520.py create mode 100644 .history/blockchain_20241017113526.py create mode 100644 .history/blockchain_20241017115926.py create mode 100644 .history/blockchain_20241017115931.py create mode 100644 .history/blockchain_20241017120105.py create mode 100644 .history/blockchain_20241017120106.py create mode 100644 .history/blockchain_20241017120203.py create mode 100644 .history/blockchain_20241017120357.py create mode 100644 .history/blockchain_20241017120416.py create mode 100644 .history/blockchain_20241017120432.py create mode 100644 .history/blockchain_20241017120523.py create mode 100644 .history/blockchain_20241017120541.py create mode 100644 .history/blockchain_20241017120717.py create mode 100644 .history/blockchain_20241017120759.py create mode 100644 .history/blockchain_20241017120806.py create mode 100644 .history/blockchain_20241017120829.py create mode 100644 .history/blockchain_20241017121104.py create mode 100644 .history/blockchain_20241017121117.py create mode 100644 .history/blockchain_20241017121138.py create mode 100644 .history/blockchain_20241017121144.py create mode 100644 .history/blockchain_20241017121147.py create mode 100644 .history/blockchain_20241017121549.py create mode 100644 .history/blockchain_20241017121653.py create mode 100644 .history/database_20241017105036.py create mode 100644 .history/database_20241017110649.py create mode 100644 .history/database_20241017110658.py create mode 100644 .history/database_20241017110659.py create mode 100644 .history/database_20241017110743.py create mode 100644 .history/database_20241017110744.py create mode 100644 .history/database_20241017110750.py create mode 100644 .history/database_20241017110757.py create mode 100644 .history/database_20241017110822.py create mode 100644 .history/database_20241017110828.py create mode 100644 .history/database_20241017110842.py create mode 100644 .history/database_20241017110927.py create mode 100644 .history/database_20241017110953.py create mode 100644 .history/database_20241017110954.py create mode 100644 .history/database_20241017110956.py create mode 100644 .history/database_20241017111034.py create mode 100644 .history/database_20241017111203.py create mode 100644 .history/database_20241017111617.py create mode 100644 .history/database_20241017111620.py create mode 100644 .history/database_20241017111923.py create mode 100644 .history/database_20241017111940.py create mode 100644 .history/database_20241017111949.py create mode 100644 .history/database_20241017112755.py create mode 100644 .history/database_20241017112756.py create mode 100644 .history/database_20241017112807.py create mode 100644 .history/database_20241017112808.py create mode 100644 .history/database_20241017112829.py create mode 100644 .history/database_20241017112839.py create mode 100644 .history/database_20241017112840.py create mode 100644 .history/database_20241017113825.py create mode 100644 .history/database_20241017113827.py create mode 100644 .history/database_20241017113828.py create mode 100644 .history/database_20241017113841.py create mode 100644 .history/database_20241017113844.py create mode 100644 .history/database_20241017113849.py create mode 100644 .history/database_20241017114943.py create mode 100644 .history/database_20241017115417.py create mode 100644 .history/database_20241017115510.py create mode 100644 .history/database_20241017115511.py create mode 100644 .history/database_20241017115536.py create mode 100644 .history/database_20241017115630.py create mode 100644 .history/database_20241017115704.py create mode 100644 .history/database_20241017115750.py create mode 100644 .history/database_20241017120019.py create mode 100644 .history/database_20241017120022.py create mode 100644 .history/database_20241017120110.py create mode 100644 .history/database_20241017120139.py create mode 100644 .history/database_20241017121411.py create mode 100644 .history/database_20241017121424.py create mode 100644 .history/database_20241017121440.py create mode 100644 .history/database_20241017121504.py create mode 100644 .history/database_20241017122020.py create mode 100644 blockchain.json 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/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/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/__pycache__/blockchain.cpython-311.pyc b/__pycache__/blockchain.cpython-311.pyc index 56505369ae3ed98239fb2678145030240d0e4988..e4a75af9a01075ea2a595e4bbe8e15ebb934c23f 100644 GIT binary patch delta 4236 zcmbtX3v8R!75=YZ`QvvUPU3_l&O1&T$7$2GNz*p(7ipR{p_I@zb@IE3^RUxvr=`Yi zBcM}QBTBht?FNjDp|%W!M$IO+6;VVdF@cI*S;APWU}Eitgw#~PHi0H~&UKzJ0n+{` zU+&|abI(1`o0(6^lXr;zf=;Iv;Jb0jIDE@>TAxAgI$d&m&3Tz1ya>PXuRlj7vt^q%D%sbnj4FS!Z^UVl1}#G)4wu{F@>*&welxBea*VqqhtDak+_IXP zIpJSA*U-S2^GHlNNnLIqOB3xRfwhU7H8c}W^x~x~>``&IJ_n~0L=dKhk4)+8j#!YG zVwzFRiqRYk%T6rZl6BXNA|hr)MeQm5PxR+{FE-5R!=f!B+Cri&YDx!M_05#r(_7AL znGvHYS?{NmUr#B&*c?u&ilkJrDgL)R6Z0gAqf)yg+mtqAZ+PyzQo_ z3+eOc+QVW+M63vj6;VwJQWr#1Gu}_NT~D=zQ|*yd`%KFXZBi(?;^I_TyFQ{_AJVRm z8d6bEK{Px6am5++N1~RctM5s!f-6xA-V5W!Vxb$@F?B9!VZTtDNloxI^+}nA(`-h< zEv#FYmfnHGcI4rvvG5NVAJnyzc6L?wn5qUghh*$ZQz{$Rf1=_7y4d}O9MuNo?PSLd zC1iqKFmzZrDVTKWON0@|=VzaTMrQ>^wiP!^WQ>P1P4g+22n9)Y9vEL-Vuqj+Mwe#2 zf^R7tAq3?ph^6)F)y=}*`J)6$*&d_0e2F1IB)K2bX*u4H$^$YuMo0&~&4D98i zgshfTswjP$WySbWM>6!A{jm03I!da=Lr=(-j~S8?2y!_Jrj<;3J(A7vjVnMf7;BB?SSo|UXDxv1|T5PSDQbW96hs06KH z#y9JKbnEQabB^np+>j=hXV{joc56htHKg4dO-Vmf8`5UqOiFwF;4=m1q^C!JR{3;g zIH@d>R5mY&iKPJSaB^+o4WlWPR`Yt(pX67XuXes^xM~R3^+f7=!f8EWsCBGZ-rK-#SJ^GcuwJrngIebsN9UYEbte7W=YhRcRfoNPo@%*3Q`lTf>JzDhR?KbMh)|Ev4AA|8V3oyWHhWCwdtkX% zoJ?&bC)u^ssiGPnvdU<@OyF1!ww;=wStwx}E~sf08!Vkmu1AquH=}> z6pQ8LlpMn24g++$c@Nfax0Ozw~*dJLz>S#E9?NZgd$01}Vpt|7tTuXFc;M0Uqu z4(8>pCk>l`(5j-jNdIF5E<;6~6P`oPm>fC<#WDqV=H=}OjF;?!~d30eCJQWN(x;nTBB)_u+o@AAaD&^zt(Daj>!c zOLWcVijd5*7g?SF_~dK&{JIV|&r=IGMJx5O3cFb|g+y`3u^o2nMxLfB$K<5bEm1BZ z8!1j8aF?Pd795Eqbdfa?qU(~~x`qcJY8umGf;**wxY@aXu(xn!7ZgsY$|32&;;Ivb zw=DWV@RhZ9lx1k~xY-rl>=c>fk?<6Zyo$IOFg|;CV-4BER5feC?lmH`n zS#|Nicz|Ws){`S_Uu_%2+WFc-(j0uJwn0f6nXbN*G_l?F+cfpSK-&;}>^Jqf#uX;P zMaDj?F96x{hHB$CEx_6uitRiEdD>c#o_A=Tz)d*icVFGBpJ}iY8obt^l4X1fBxeQR zQXhn78Sjqz-ZXZzrBpVmU?1so6wonM>|lnCJ-bb-mgFxf;_o!}&bGDdtjcb`4xd>| z@2*cU@A@D+3gB-{B{?8(+*S?GXI_ojtIhW%q269R8NvsU+F4FZarQY_@eaX!TDT$V zW;}C>=hb1cJR+8d#B#R1r3Z{X*YZ4=v!ykUJi~@ttN4mJc&XJ)VENs)0`eT2Z=mUM_EZPs?{bmFWbY_wZ z%-{KQILUPPEgjFOGN6Qvtzu$VK4??kRY+cCJze)MDEVgBW2j_LcP6>a#=2jIWXasV zj~Kt{L^jit19TU9ipg8-TF-u9uiSBY&vulqqB~KiUId<}eK@)Y;a&i+dprkt@DD(f zgVwsLJfR`{hDIz&+-Ek-mJn;(*#YXD-uX`jY51;6M1P7$@kbz5jTM#X_Pv|PSFC(D zx@7O}3epoixx1a1cttsjOv{Sf*gx(&6n`DIGpT0p2C&xR|1&|g7r2NUjJr_k}+ ziVIAvZJ=tA{@6fe36Gh(@g`X-ZsK(fy9ZWO)?yYJxCgW;+yC|^&Vlasn^tge&vR3) zfja5Me}Sfjd81%Vo%Nr~pFI{5v!PqgInAuq@ykM9Ffpvr$MA=S5Yhk^sxsC>%^4k1jd->JfrkxmPWXnP zod}f(JewcJ(Mf~=d)8A*?Cfoiy^-4{imMnJF%eRF(n)PTb_!UMP-f?>AC#n#qo8u}VnKh5uyj~U0?49g_cRe}AZg@+2)}jaX-Z2w$MJh4&X4Wh z=eVt#&N-Xaox=+B6s%5@AJ5f{E>%>o|X7 zuj!Zj&OPtkbI44%)*XO8phzu2TWp4Up!75txi z4i;&oHc8GtCCL`WxooaD(ZXCBHkKID-p;a(W0KF3{!TW*+qxk5^p3*3v}{Z-XWG&) z5;xO3MD+Q_F%7qkNtdjU(_LagEAu)iTtmW*Op=XVl6z(RdWLiE=?Dd*LH}?tt~jFu zinC=n92g1=_=BMgc;K$Wlietb@NoDpXMZrJgq(fHh1wn=Lh1f6A9DJK`@=jK9T=In zS|9Oq{|J7m#Dd{b)i>Z*2kM=!-H~W89P$slosDXgH#yr;>_jVRz0~pxRyfoLvQ~8} zN0&{&RzsD&b7#^N8SNVm26idOlNk{njQOJqj@|k2lHp&5aKJaD9EVp{c$fkHvSOQF zHDE-b!gzytt)9-U28XFW zUvJtrDw6;GNmA#%F12wzDQ-^{8266tO_!8xBaFjkOMHkJFMq&;6l(C8-@&LVPXh>8NOunK82B6E(<7Nma)^@N{lfDj%ER&$;Vd zV<=bOLYR>LmSa742%RrVZcS3N$&=JXk1cTW9cv4F5LT@j zT}PB~Yxq3%+}fm89Ub9zvhV|%%s}t2t~rsn3tv=fopa>FU&z14{CajT9L>Lk{L8o9 z(6GI*vY=;0QjgUgib8)uE%U%r1@){J-Y+O&PbU6d(8Ex-%ki?t&e2*tw<;8t6}4(K zLZSDGR>oB*D0Zxm;yfQk=tM70&5yg-o#7mey6kTAkoSwl0o$cs9UOnAp8J$tTwn@vCd5n@Bo5a%VpBmVw>Y< zair41PUi#{rFs{9T~WnuK=}p_tAqU;y4Vi5vVpS$;HWIm161Wyf}#z~6XZEVd`aF|MIZ#t=%(gPbS*r$n88^56l zo~)YEI1kY$=Mg^p4?b~oM@VqoDdMn`a|l=4GF4JaWelL&T}NIe4gN?(3CUboC?t(j z1R|G|hPe#Vi1On$A=$pFc2^2MlBda)R8)%WOT}Cy$At2ONYx>r84NyTHQda4p}nCR<9?#yDvN`wvBLZ)D!9!l zR>Z*9xSbt^OO0(vvcET$vG#;{TeF@u!|oO@Yk?YekI-E;SY`yIJL`$y%0|JSe@7m$$)rd%~kF5$h0f-4gZpnZX*}kR@ii%pHU5V zW3`mx@1#NYV;zOuCb+rnKn}U7(h`^!L|z5Ywv}gIM2Wf=-zOyaq-_tz*wbNyJ?-~n zBtK{`W|!c*_RYvOPe(OFQNUZuo`ZY697W%FFSBrjObG^+d!H^QqUbsPRBEiog=e8%%$E&w}ViuKEfx1|^9{6cz z3A*=2XBm4HKJDC}+DGD!-Dg>W*zGS+F>YCAj=u()y)WZdUfKIYW?7y`DC%;c&8Dt$ z_7*5zy*eWBg`HNo-u3LEPFmQ&DRTUF0#V9cH0mbkLBKqu%0$$M4+}mR>R-~nC~!+Q zNd@&M_^7)BopSB_TF18k-%iCJrgeyS%RAMN4mPmw;EjWrH~8w{Mz%Xqa%Vfsqmto| z5G;5dhm(h5se4Hqe0!*;h}fGn234QvBLCZfB8NA!e?ahHv&XrKq7YxbD zvDA4&*CNpx(edqrE<8GdgYTi^%|pLpdCl}`D}gvmL_2ALtPw{#sMRB`HN++^4u+)- z<;X}08%jJhvW`xLUx!8*h#6My!thytE<;_apkLRpkikYt#&-cLLV_QT|`Y)P}xfB2Z diff --git a/__pycache__/database.cpython-311.pyc b/__pycache__/database.cpython-311.pyc index a8629b7333454acc81bf275ee886bf87f90f22dd..6d8c6934cf8eb673114b947ed5a3f36f5023ae46 100644 GIT binary patch delta 2749 zcmaJ@ZERE589wKJ`Rn>Kj<21>_=dOyM@byd4oej%apHWGc1=qxbxfe(a2+QhvC;LV ziI}rw8xvtdTIs0-RW~WBC@o@It5MXkFOd-t`*)45MC6HSKUQtpFL!IT4^3$2Tqllc zVCP!TecyB5`@H9#`<(aqX3x0++Xq&w8Q^y6=Z?&X^P=r1k=nwV~waTDoCK`BF9F61P42%9~-%|;V^wI???^Iw7 z8FLkT*Q-4cNbDF;tThpwPGcZ6#zm$;6=;P<&$F%2fKsd<8qpHl;WPczd=<~*dUY;@ zh+OC{8wynE@$4--6Cx`oSZiZ=6ybb+cG2)11DTGbTk~)pEYL^5JPcXSETLZsesq?b zXg(7dOT96j&4?4Txf3T(Or@r?qLLG_lbRJLr)Sd$8jqtPg9xvoj3Ehkp8Hdzhoa_UxWt~4r?ZOOMjj?F zFLz=nac`t%Pp9*8m^gGkt;k(uEP|qzwWhz|S6v2WUNFVsBi^aHqD4NY@iCQ;eTE?a z?u{#aFYIebw1MC4iu3q(r_GzN(d!hk*KJI~WmtF7#IA?L_PB`MW*&6X>oM0LPv7O) zK_?2>1n&x-(m7ZW6`sImn}KM~<8>6kIC*uw6~GLIE;8-tl+7TTwt9)!c6=^+4D|^C zsz9Lt<3Wtw#Vlf35oeFYWx!R;dA-WJ$S9T?HUrj@9!g0@^s>E^S!63dCz;N}84i8d zsF289cabzOI9YYiu#?$&~29o*^DMMZkqJVt+-d-OQ0RY zSF{>;#aTLFIV*Sv4Sv{6g6BEV6gqE04rAc)l^JL)v z^JD?ta~*FdiyDF@W-67R3ekGCkehLz{tfa(TcWtURW$G-!!dNOZa?~SUB{9`CgoZt z!j)YZN^}NSfNtV`oj`=ZcE#97Tv9gla-lDA$wbnQ&ratRxu1-Xj~6n^b;KqjkPi}g z@}UZE9AHuAjd)l*F5-qV?tQys*v&u2b?0BW@Yn(--y^~Ic4779q7c!9$V%eE2>O}3 zcggL&H1gKS<=0myitdo+4y_Dsw6uQE^InhI-haEl*fOlO46D5OB?s!go7zZz^I`Jn zgXB^5n50fl6_e9iavBd6g&7T_dq#85U>29>(%!fBUhZCPyV6;7MKo7rW#CIA5QFc( zwE6L;=G5>~wiwQ8;cPLO)q>fF!P5_dr;EXPEjV8cENFp+l|vfev|;yY_Rf!YKd{GC zd+gEI_TBvUj<1=A!&{R0ydsbYN@35iz|mNyz*Jg9+a+s&Qs=q)%lPYAMoO8Ly>RS_;!_VNAJ|X*h{v?_v7?X z16VT-I`HjYgLxRz_kymW{q(*4EVfV79C(1k@kCPpXrgX3dfX6~HXAx9hX1K_AX?&N;ckK}J2Jb+(e7Qs%lYK8b5CosrV_4(s;~n%`Gf(UeCLS@Y zMQCEjAhEmS9oV@OG$$MwI+yhQ_Y#@v7=~-0QV8{b3kK3lfSeQg zB!=bcRi-$$vka=>@UwAP1~vG?9@bX|Rd^LT*@I>9RX{>xck~sK$VR|HKwo-2`Rl|L w2#^W=$GCqkC!L;6zaSq$OO10cCYJdPSIe@!%%mX1@~ delta 1991 zcmZ`(ZERCj7(VBo`_YfvZtYgKZoBK<3XIOR3)>wyR`)?Wg{gqsqWFQ`+%4X;t?wQc7N9`ZkY!*P8>x1&~d-n78U!ZdSuXfnF&;qYA@NJwy6_p7@8HJ|8 z(_juxGWcD#34d+(;t|&2fyG$>)^Hhv%dX;&W9E?VNLU}NQqeWyW4`1E0+k&C z3EL_LhwHg^2=O4-1bKXnlc0d#=lpKd4f8dU-+U=Yx|Tk8Z|#=wnjn^k1@VGJ&4Vx- zfhHXn)~vGPjKf{a9v4msG%3}vj>4m07CjG+LZ66F@UP=O*9cAuGW6jq!Z57Ee+W0- zK1TBp*EuFJt24xqWz9uiS_A&dq(EumzR3@v2RB$Yz`~FhPg^pr+7?Qp`_WnmH95mQDAmLDvv|Z_Rs9sn{|tN& zn86&FBw-8fGQ^8^AL0PO@7SyHy&9o`*u}QKsB?_5qOwtC$H6t?a-JTG(ayn0se(mX zo60?e#uk^XsS;15w&I;p1w3CCC?_uYBDXjvtEO{sQYa3u%R#`c4!2-FO<9QOcJn^s z#Z;sxR_|`Tnyf>BsL5Gm17N5Ltyv}FBX#at+i`RPsSHt*VXab=9dphvaLRdrr?uDo zxZCB%y%pnin`n14g=PZ()sssZ=%>R$0zZ)_3DS)-^!MD0x0*j6{zl2P?aH?8N_z)C z?6_WerR!?f<*wW63a7v{P^%&iuv}4v4{q4kLSy=1j*X4#F&{F-tjT0dqn`|&)=QR? zB~7~&xEohh1sjfec232|ULA{#P8^b_;-hL*l@Dt1Y5DO9E&9r+9>qgd{%0*3J*gZT zCrVbR)53rjzJ)w1yzK_)^D1j!SJw~C2fKGKGk6^KSo1=~PyU3OJ zliUgW2D2Nh)mru`QWW~BWfwr5mPh#oqPv6!ufx&EGkPbic zI}c><%JD?TzK@K57a32-;^`xLCX&cT5@agVIFlvlnaO%)3a+GvOZzYEzZ|{(!i|?R zQZOq8&knq`hq&6@Hfqrvb1bc;QME#a zRu`k2vI@n(KTA99&yc=eQTQsEthlrOBA4fZ&4JCHc&L|5zR|wZ3qErZSQK4-eze#i zQrpk=Z50-`B5Es;+8r&#Uh-N(7PRDdhB&m$aUl!-(i1pI{b)~6*hAoBV5qmCM|#CI z3L^RILj;ngC(rO)AP-7VUd#2tJXnQ0B+i`&CFm~Wdh%c$;7M6&AE1Hk6v`9 diff --git a/app.py b/app.py index c2d3a3f..8785a8b 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) @@ -59,8 +61,8 @@ def register_nodes(): 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") + print("this is parent node", "simplicity_server.onrender.com") + blockchain.register_node(node, "simplicity_server.onrender.com") response = { 'message': 'New nodes have been added', @@ -78,7 +80,7 @@ def update_nodes(): return "Error: Please supply a valid list of nodes", 400 for node in nodes: - print("this is parent node", "simplicity_server1.onrender.com") + print("this is parent node", "simplicity_server.onrender.com") if node not in blockchain.nodes: blockchain.nodes.add(node) @@ -201,27 +203,24 @@ def delete_chain(): return flask.jsonify(f"removed Node from the network"), 200 -@app.teardown_appcontext 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) + - 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') +# 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__': @@ -230,5 +229,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/blockchain.json b/blockchain.json new file mode 100644 index 0000000..42aadd5 --- /dev/null +++ b/blockchain.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/blockchain.py b/blockchain.py index 7b9761b..8b06b43 100644 --- a/blockchain.py +++ b/blockchain.py @@ -24,31 +24,27 @@ 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.max_mempool = 2 + self.new_block(proof=100, prev_hash=1) self.error = "" database = BlockchainDb() - db_chain = database.load_blockchain(self ) + db_chain = database.load_blockchain(self) self.mining_thread = None self.should_mine = False @@ -58,25 +54,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 @@ -126,16 +114,19 @@ 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 @@ -415,6 +406,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() diff --git a/database.py b/database.py index 5041628..6b405ce 100644 --- a/database.py +++ b/database.py @@ -6,7 +6,7 @@ import os firebase_cred_path = "simplicity-coin-firebase-adminsdk-ghiek-54e4d6ed9d.json" -database_url = "https://simplicity-coin-default-rtdb.firebaseio.com/" +database_url = "https://simplicity-coin-default-rtdb.firebaseio.com" local_file_path = "blockchain.json" class BlockchainDb: @@ -25,10 +25,12 @@ def save_blockchain(self, blockchain): :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 or not unique_transactions: + if not unique_chain: print("No data to save. Starting with a new blockchain.") return @@ -55,6 +57,7 @@ def load_blockchain(self, blockchain): :return: True if loaded successfully, False otherwise """ try: + self.ref = db.reference('blockchain') data = self.ref.get() if not data: @@ -62,17 +65,25 @@ def load_blockchain(self, blockchain): return False print("Retrieving data from Firebase") - blockchain.chain = data.get('chain', []) - blockchain.current_transactions = data.get('current_transactions', []) + chain = data.get('chain', []) + 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) + 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) - print("Blockchain loaded from Firebase") + + self.save_blockchain(blockchain) return True except Exception as e: print(f"Error loading blockchain from Firebase: {e}") From 86ee37a031e432dacacdafc5a907806a1c9ce579 Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 12:34:28 +0530 Subject: [PATCH 16/24] checking the issue un register node --- .history/app_20241017123229.py | 234 +++++++++ .history/blockchain_20241017123254.py | 676 +++++++++++++++++++++++++ .history/blockchain_20241017123329.py | 676 +++++++++++++++++++++++++ .history/blockchain_20241017123330.py | 676 +++++++++++++++++++++++++ .history/blockchain_20241017123410.py | 678 ++++++++++++++++++++++++++ .history/blockchain_20241017123413.py | 678 ++++++++++++++++++++++++++ .history/database_20241017123421.py | 109 +++++ app.py | 1 + blockchain.py | 4 +- 9 files changed, 3731 insertions(+), 1 deletion(-) create mode 100644 .history/app_20241017123229.py create mode 100644 .history/blockchain_20241017123254.py create mode 100644 .history/blockchain_20241017123329.py create mode 100644 .history/blockchain_20241017123330.py create mode 100644 .history/blockchain_20241017123410.py create mode 100644 .history/blockchain_20241017123413.py create mode 100644 .history/database_20241017123421.py 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/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/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/app.py b/app.py index 8785a8b..70b7a96 100644 --- a/app.py +++ b/app.py @@ -204,6 +204,7 @@ def delete_chain(): def shutdown_session(exception=None): + blockchain.resolve_conflicts() database = BlockchainDb() database.save_blockchain(blockchain) database.save_to_firebase() diff --git a/blockchain.py b/blockchain.py index 8b06b43..5e6d620 100644 --- a/blockchain.py +++ b/blockchain.py @@ -116,7 +116,9 @@ def validate_loaded_chain(self): 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] From dc89bc10359e1700e8adcfd92bb475f4bb181606 Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 12:35:10 +0530 Subject: [PATCH 17/24] checking the issue un register node --- .history/requirements_20241017123503.txt | 12 ++++++++++++ requirements.txt | 12 +----------- 2 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .history/requirements_20241017123503.txt 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/requirements.txt b/requirements.txt index 28e782e..2b0f954 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,12 @@ -<<<<<<< HEAD pybase64 -======= -base64 ->>>>>>> c006167 (needs to be tested) Flask Flask-Cors requests schedule ecdsa -<<<<<<< HEAD firebase-admin -Flask -ecdsa base58 starkbank-ecdsa elliptic-curve Flask-CORS -======= -ellipticcurve -firebase-admin ->>>>>>> c006167 (needs to be tested) +firebase-admin \ No newline at end of file From 9e8d2268237b82dc0e45a133f59af45369e7d7b2 Mon Sep 17 00:00:00 2001 From: Affan Date: Thu, 17 Oct 2024 12:47:26 +0530 Subject: [PATCH 18/24] checking the issue un register node --- .history/app_20241017124342.py | 234 ++++++++ .history/app_20241017124401.py | 234 ++++++++ .history/app_20241017124720.py | 234 ++++++++ .history/blockchain_20241017123827.py | 678 ++++++++++++++++++++++++ .history/blockchain_20241017123837.py | 678 ++++++++++++++++++++++++ .history/blockchain_20241017124143.json | 668 +++++++++++++++++++++++ .history/blockchain_20241017124413.json | 668 +++++++++++++++++++++++ .history/database_20241017124007.py | 109 ++++ .history/database_20241017124110.py | 120 +++++ .history/database_20241017124111.py | 120 +++++ .history/database_20241017124123.py | 120 +++++ __pycache__/blockchain.cpython-311.pyc | Bin 30869 -> 31036 bytes __pycache__/database.cpython-311.pyc | Bin 8583 -> 8762 bytes blockchain.py | 2 +- database.py | 23 +- 15 files changed, 3881 insertions(+), 7 deletions(-) create mode 100644 .history/app_20241017124342.py create mode 100644 .history/app_20241017124401.py create mode 100644 .history/app_20241017124720.py create mode 100644 .history/blockchain_20241017123827.py create mode 100644 .history/blockchain_20241017123837.py create mode 100644 .history/blockchain_20241017124143.json create mode 100644 .history/blockchain_20241017124413.json create mode 100644 .history/database_20241017124007.py create mode 100644 .history/database_20241017124110.py create mode 100644 .history/database_20241017124111.py create mode 100644 .history/database_20241017124123.py 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_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_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/__pycache__/blockchain.cpython-311.pyc b/__pycache__/blockchain.cpython-311.pyc index e4a75af9a01075ea2a595e4bbe8e15ebb934c23f..3a6411c1fce27f020fe97c1d3c9d728680827f43 100644 GIT binary patch delta 679 zcmZutZAepL7`^A-yAPLhyLYB-NGwPlS(=WbX!fJTBrGdUqUK=k&Rw@PX`57HKLVpj zg5WKppbw%_L!o;5Krnv(k7OW_c5o- z3X$EYDr0eb?Mksno9J8;aVLacUNPj}EaP;t&-PNmxM&U-6;m%1k;SEIn>nZAG>@4L zOWuEF_41N4h&=XMu7HEh%T>;v;>K_yl2+Ya9qzOmc8AnpXTsf?aetMioOO?Rp11rf<7lJjRLN+s^6+qQUDD7JWyO8x6^ z=wr(l1Afu7gM(Tc#r!qUmc7>YQiO+M?OTCLx*Ku}wUI-_d@ zMCroen1H?qN38nB1<8{de?B>8a~ND1E(abul&a@(n{IaZ^XnTdSf1hjkJ-ik?B8q) IYdf~-H$e}_dH?_b delta 578 zcmZusO-NK>6n*FW-n=Jc^Ydm##ese(GjPgsUSMDrgh>)IW2Bp!2b0fyHX+TMF)*fu zWe^B$KB6{4BsvLg>KZjLv}qGm;wA_&T1ah2LkMcqH~k8A7YFV+=bn3S`7>^>!(a6I zyo#*%kAuUXMjrWJ2G|A@NVcdpGE8Ve)2CPUpJj7#G}$Z`gPpth6*<@VTTrg@0<+b> zeL=aWiizz_;*?RxJ^5O(6tXm1D?mmp8f^i6la<-J@Q2KQZS~@LDDaC;qejHTF)(3; z_lvo3!tl81(cz(G)k)`^WuD8sLW_*S5`{<`|7g_HN@Rs$ib@SOaGka_PO7*=Gg~Vv zh{R5V;~mZJ-lL&HZ(gAD5Ulr_n%xgCBI9rAmhv za?{ttC3Mk3C)455)25Dl6sT%8h{=-^WTS+NSb^ zxhQRVMcQ-%%M=cvGG3tzLe>{~Y_9OwT;Q>}!6VS)(eK~of00M_3Xkjs9$Bao+lxGQ zS9t6$@Yq3=_;mTq5V^=BcZEmp0*~BX9>EJjb{BcTJbukVn)}m-(O~4~VVH;%LZz(2yC#Hem#@O`SlT!&Y1_ip)n?gj{5pkI1mPC{ErZ kKSNZ8)&2tmIx#s@;l6ziBO_1>JNXeT{so6@kvK3I0ovxkVgLXD delta 460 zcmdnx((cT+oR^o20SF2<38Z@nZsaSKVw9QOBK4nX0q5j{d`gqUW%wumW)YMq5e2D& zfGlnpD}{CPTxqGvsxpp~MPvjgm&>R!vQM5XW6aD^V)4CubyP<|z~>mZhdBl;kV8Wfr9-B^IYn{v;bEbc-zosHG&e zh!v>5NDxR&j+0B_ab{#-Xkhrj!OLlXck($oP3EV9qLcr~<-|VV5$N&g_wVw*$Rm4& zNA?1b>|Gwg3qtxAc?_=b7+l~nxGOF>rDlQ8MRCI`;)WeAJsvl}I()i(W{6zmk-Nep zcY#L^tjFLYkKq*_!wWF< Date: Fri, 18 Oct 2024 19:55:03 +0530 Subject: [PATCH 19/24] Update requirements.txt --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2b0f954..4d307a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ pybase64 Flask Flask-Cors -requests +requests==2.21.0 schedule ecdsa firebase-admin @@ -9,4 +9,4 @@ base58 starkbank-ecdsa elliptic-curve Flask-CORS -firebase-admin \ No newline at end of file +firebase-admin From 994f1a6b236f4fe70e17a8ce7f0554a35b7e8677 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:07:07 +0530 Subject: [PATCH 20/24] Update register_node (testing) --- blockchain.py | 65 +++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/blockchain.py b/blockchain.py index 901cd57..8c864c6 100644 --- a/blockchain.py +++ b/blockchain.py @@ -178,43 +178,52 @@ def register(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' - :return: None - """ - #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 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 From 797d18fb667d988bb1f0216aaf4800b8905c9580 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:26:45 +0530 Subject: [PATCH 21/24] Update blockchain.py --- blockchain.py | 55 ++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/blockchain.py b/blockchain.py index 8c864c6..c4281e7 100644 --- a/blockchain.py +++ b/blockchain.py @@ -150,32 +150,37 @@ 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() - 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' + :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) + self.logger.info(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: + self.logger.error(f"Error parsing URL: {e}") + except Exception as e: + self.logger.exception(f"Unexpected error in register_node: {e}") def register_node(self, address, current_address): From 14ebc3b9181d78b1c914bdaaf535c3cb82301159 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:27:41 +0530 Subject: [PATCH 22/24] Update app.py --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index 70b7a96..78986ee 100644 --- a/app.py +++ b/app.py @@ -62,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', From 3003943949bb87f0f512c5c5ecc021901258ccf7 Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:31:06 +0530 Subject: [PATCH 23/24] Update blockchain.py --- blockchain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain.py b/blockchain.py index c4281e7..01789f6 100644 --- a/blockchain.py +++ b/blockchain.py @@ -150,7 +150,7 @@ 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_node(self, address, current_address): + def register_node(self, address, current_address): """ Adds a new node to the list of nodes From 6548d686fdb0d7dc1cacee04d797ebfda655fb5a Mon Sep 17 00:00:00 2001 From: Affan Shaikhsurab <51104750+AffanShaikhsurab@users.noreply.github.com> Date: Fri, 18 Oct 2024 20:42:49 +0530 Subject: [PATCH 24/24] Update blockchain.py --- blockchain.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/blockchain.py b/blockchain.py index 01789f6..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 @@ -41,8 +42,11 @@ def __init__(self): self.max_block_size = 1000000 self.max_mempool = 2 self.new_block(proof=100, prev_hash=1) - self.error = "" - + 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) @@ -166,7 +170,7 @@ def register_node(self, address, current_address): if parsed_url.netloc not in self.nodes: self.nodes.add(parsed_url.netloc) - self.logger.info(f"Added new node: {parsed_url.netloc}") + print(f"Added new node: {parsed_url.netloc}") current_url = urlparse(current_address) if not current_url.netloc: @@ -178,11 +182,29 @@ def register_node(self, address, current_address): self._update_node(base_url, current_url.netloc) except ValueError as e: - self.logger.error(f"Error parsing URL: {e}") + print(f"Error parsing URL: {e}") except Exception as e: - self.logger.exception(f"Unexpected error in register_node: {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 @@ -221,7 +243,7 @@ def register_node(self, address, current_address): requests.post(f'{scheme}://{parsed_url.netloc}/nodes/update_ttl', json={"updated_nodes": self.ttl, "node": current_url.netloc}, timeout=5) - except RequestException as e: + except requests.RequestException as e: print(f"Error communicating with node {parsed_url.netloc}: {e}") except ValueError as e: