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)vQOrGLCtFeqSGt1(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-WUQ!>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: