Skip to content

Commit 759b852

Browse files
committed
feat(blockchain): upgrade to simulated private blockchain with deterministic consensus and multi-node propagation
- Standardized NODE_ID and peer configuration across nodes - Hardened block propagation with idempotency and loop prevention - Implemented deterministic round-robin leader consensus (block_height % N) - Enforced leader-only block creation to prevent conflicts - Added proposed_by field for block traceability - Stabilized hash determinism and serialization consistency - Aligned tests with consensus and propagation rules Result: system now behaves as a viva-defensible private blockchain simulation
1 parent 62f05a8 commit 759b852

9 files changed

Lines changed: 341 additions & 41 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,5 @@ htmlcov/
182182
.coverage
183183
dist/
184184
build/
185-
*.egg-info/
185+
*.egg-info/
186+
credify-verify/

app/app.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,19 @@ def init_extensions(app):
6767
blockchain.create_genesis_block()
6868

6969
# P2P multi-node init
70-
peer_nodes_env = os.environ.get("PEER_NODES", "")
71-
if peer_nodes_env:
72-
for peer in peer_nodes_env.split(","):
73-
if peer.strip():
74-
try:
75-
blockchain.register_node(peer.strip())
76-
except Exception as e:
77-
logging.warning(f"Invalid peer URI: {peer.strip()}")
70+
blockchain.node_id = app.config.get("NODE_ID", "standalone")
71+
blockchain.node_address = (app.config.get("NODE_ADDRESS", "") or "").strip().rstrip("/")
72+
local_node_address = (app.config.get("NODE_ADDRESS", "") or "").strip().rstrip("/")
73+
peer_nodes = app.config.get("PEER_NODES", [])
74+
75+
if peer_nodes:
76+
for peer in peer_nodes:
77+
if local_node_address and peer == local_node_address:
78+
continue
79+
try:
80+
blockchain.register_node(peer)
81+
except Exception:
82+
logging.warning(f"Invalid peer URI: {peer}")
7883
if blockchain.nodes:
7984
threading.Thread(target=_initial_sync, args=(app,), daemon=True).start()
8085

@@ -84,7 +89,8 @@ def _initial_sync(app):
8489
time.sleep(5)
8590
with app.app_context():
8691
try:
87-
logging.info(f"Syncing with peers: {blockchain.nodes}...")
92+
node_id = app.config.get("NODE_ID", "standalone")
93+
logging.info(f"Node {node_id} syncing with peers: {blockchain.nodes}...")
8894
if blockchain.resolve_conflicts():
8995
logging.info(f"Synchronized chain. New length: {len(blockchain.chain)}")
9096
except Exception as e:

app/blueprints/admin/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def api_system_stats():
425425
stats["blockchain"] = {
426426
"blocks": len(blockchain.chain),
427427
"peers": len(blockchain.nodes),
428-
"node_name": os.environ.get("NODE_NAME", "standalone"),
428+
"node_name": current_app.config.get("NODE_ID") or os.environ.get("NODE_NAME", "standalone"),
429429
"validators": blockchain.VALIDATORS,
430430
}
431431

app/blueprints/api/routes.py

Lines changed: 48 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -79,29 +79,19 @@ def receive_peer_block():
7979
if not block_data:
8080
return jsonify({"success": False, "message": "No block data provided"}), 400
8181

82-
# 1. Reconstruct block object
83-
new_block = blockchain.block_model(
84-
index=block_data["index"],
85-
timestamp=block_data["timestamp"],
86-
data=json.dumps(block_data["data"]),
87-
merkle_root=block_data.get("merkle_root"),
88-
previous_hash=block_data["previous_hash"],
89-
nonce=block_data["nonce"],
90-
hash=block_data["hash"],
91-
signed_by=block_data.get("signed_by"),
92-
signature=block_data.get("signature"),
93-
)
82+
required_fields = {"index", "timestamp", "data", "previous_hash", "nonce", "hash"}
83+
missing = [field for field in required_fields if field not in block_data]
84+
if missing:
85+
return jsonify({"success": False, "message": f"Missing required fields: {', '.join(missing)}"}), 400
9486

95-
# 2. Simple validation against local chain
96-
last_block = blockchain.get_latest_block()
97-
if last_block and block_data["index"] <= last_block.index:
98-
return jsonify({"success": False, "message": "Block already exists or is outdated"}), 409
87+
source_node = blockchain.normalize_node_ref(request.headers.get("X-Node-Address") or request.headers.get("X-Source-Node"))
88+
origin_node = (request.headers.get("X-Origin-Node") or request.headers.get("X-Source-Node") or "unknown").strip()
9989

100-
if last_block and block_data["previous_hash"] != last_block.hash:
101-
return jsonify({"success": False, "message": "Previous hash mismatch. Sync required."}), 400
90+
# Idempotency gate: if already present, ignore safely.
91+
if blockchain.has_block(block_data["index"], block_data["hash"]):
92+
return jsonify({"success": True, "message": "Duplicate block ignored"}), 200
10293

103-
# 3. Cryptographic validation (simplified for the model bridge)
104-
# Create a Block object for validation methods
94+
# 1. Validate block object before any persistence
10595
from core.blockchain import Block
10696

10797
v_block = Block(
@@ -110,6 +100,7 @@ def receive_peer_block():
110100
block_data["previous_hash"],
111101
signed_by=block_data.get("signed_by"),
112102
signature=block_data.get("signature"),
103+
proposed_by=block_data.get("proposed_by"),
113104
)
114105
v_block.timestamp = block_data["timestamp"]
115106
v_block.nonce = block_data["nonce"]
@@ -119,16 +110,50 @@ def receive_peer_block():
119110
if v_block.hash != v_block.calculate_hash():
120111
return jsonify({"success": False, "message": "Invalid block hash"}), 400
121112

122-
if blockchain.crypto_manager and v_block.signature:
113+
if v_block.merkle_root and v_block.merkle_root != v_block.calculate_merkle_root():
114+
return jsonify({"success": False, "message": "Invalid Merkle root"}), 400
115+
116+
if v_block.index > 0 and v_block.signed_by not in blockchain.VALIDATORS:
117+
return jsonify({"success": False, "message": "Unauthorized block signer"}), 403
118+
119+
if blockchain.crypto_manager:
120+
if not v_block.signature:
121+
return jsonify({"success": False, "message": "Missing digital signature"}), 400
123122
if not blockchain.crypto_manager.verify_signature(v_block.hash, v_block.signature):
124123
return jsonify({"success": False, "message": "Invalid digital signature"}), 400
125124

126-
# All checks passed, add to local DB and chain
125+
# 2. Validate against local chain linkage
126+
last_block = blockchain.get_latest_block()
127+
if last_block and block_data["index"] <= last_block.index:
128+
return jsonify({"success": True, "message": "Outdated block ignored"}), 200
129+
130+
if last_block and block_data["previous_hash"] != last_block.hash:
131+
return jsonify({"success": False, "message": "Previous hash mismatch. Sync required."}), 400
132+
133+
# 3. Persist only after full validation
134+
new_block = blockchain.block_model(
135+
index=block_data["index"],
136+
timestamp=block_data["timestamp"],
137+
data=json.dumps(block_data["data"]),
138+
merkle_root=block_data.get("merkle_root"),
139+
previous_hash=block_data["previous_hash"],
140+
nonce=block_data["nonce"],
141+
hash=block_data["hash"],
142+
signed_by=block_data.get("signed_by"),
143+
signature=block_data.get("signature"),
144+
)
145+
127146
db.session.add(new_block)
128147
db.session.commit()
129148
blockchain.chain.append(v_block)
130149

131-
logging.info(f"Accepted peer block {block_data['index']} from {block_data.get('signed_by')}")
150+
# Controlled gossip propagation: relay accepted blocks, never back to sender.
151+
blockchain.broadcast_block(v_block, source_node=source_node, origin_node=origin_node)
152+
153+
logging.info(
154+
f"Accepted peer block {block_data['index']} from signer={block_data.get('signed_by')} "
155+
f"source={source_node or 'unknown'} origin={origin_node}"
156+
)
132157
return jsonify({"success": True, "message": "Block accepted and added to chain"})
133158

134159
except Exception as e:

app/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ class Config:
3737
SECRET_KEY = os.environ.get("SESSION_SECRET", "dev-secret-key-change-in-production")
3838
DEBUG = True
3939
PORT = int(os.environ.get("PORT", 5000))
40+
HOST = os.environ.get("HOST", "0.0.0.0")
41+
42+
# Multi-node settings (backward compatible aliases)
43+
NODE_ID = os.environ.get("NODE_ID") or os.environ.get("NODE_NAME") or "standalone"
44+
NODE_ADDRESS = (os.environ.get("NODE_ADDRESS") or "").strip()
45+
PEER_NODES = [
46+
peer.strip().rstrip("/")
47+
for peer in os.environ.get("PEER_NODES", "").split(",")
48+
if peer.strip()
49+
]
4050

4151
# IPFS settings
4252
IPFS_ENDPOINTS = [

0 commit comments

Comments
 (0)