Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,24 +137,12 @@ async def handler(data):
logger.info("🔄 Accepted state sync from %s — %d accounts", peer_addr, len(chain.state.accounts))

elif msg_type == "tx":
tx = Transaction(**payload)
tx = Transaction.from_dict(payload)
if mempool.add_transaction(tx):
logger.info("📥 Received tx from %s... (amount=%s)", tx.sender[:8], tx.amount)

elif msg_type == "block":
txs_raw = payload.get("transactions", [])
block_hash = payload.get("hash")
transactions = [Transaction(**t) for t in txs_raw]

block = Block(
index=payload["index"],
previous_hash=payload["previous_hash"],
transactions=transactions,
timestamp=payload.get("timestamp"),
difficulty=payload.get("difficulty"),
)
block.nonce = payload.get("nonce", 0)
block.hash = block_hash
block = Block.from_dict(payload)

if chain.add_block(block):
logger.info("📥 Received Block #%d — added to chain", block.index)
Expand Down Expand Up @@ -355,9 +343,6 @@ async def on_peer_connected(writer):
chain.state.credit_mining_reward(pk, reward=fund)
logger.info("💰 Funded %s... with %d coins", pk[:12], fund)

# Nonce counter kept as a mutable list so the CLI closure can mutate it
nonce_counter = [0]

try:
await cli_loop(sk, pk, chain, mempool, network)
finally:
Expand Down
19 changes: 19 additions & 0 deletions minichain/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,22 @@ def to_dict(self):
# -------------------------
def compute_hash(self) -> str:
return canonical_json_hash(self.to_header_dict())

@classmethod
def from_dict(cls, payload: dict):
transactions = [
Transaction.from_dict(tx_payload)
for tx_payload in payload.get("transactions", [])
]
block = cls(
index=payload["index"],
previous_hash=payload["previous_hash"],
transactions=transactions,
timestamp=payload.get("timestamp"),
difficulty=payload.get("difficulty"),
)
block.nonce = payload.get("nonce", 0)
block.hash = payload.get("hash")
if "merkle_root" in payload:
block.merkle_root = payload["merkle_root"]
return block
33 changes: 20 additions & 13 deletions minichain/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
logger = logging.getLogger(__name__)


def validate_block_link_and_hash(previous_block, block):
if block.previous_hash != previous_block.hash:
raise ValueError(
f"invalid previous hash {block.previous_hash} != {previous_block.hash}"
)

if block.index != previous_block.index + 1:
raise ValueError(
f"invalid index {block.index} != {previous_block.index + 1}"
)

expected_hash = calculate_hash(block.to_header_dict())
if block.hash != expected_hash:
raise ValueError(f"invalid hash {block.hash}")


class Blockchain:
"""
Manages the blockchain, validates blocks, and commits state transitions.
Expand Down Expand Up @@ -45,19 +61,10 @@ def add_block(self, block):
"""

with self._lock:
# Check previous hash linkage
if block.previous_hash != self.last_block.hash:
logger.warning("Block %s rejected: Invalid previous hash %s != %s", block.index, block.previous_hash, self.last_block.hash)
return False

# Check index linkage
if block.index != self.last_block.index + 1:
logger.warning("Block %s rejected: Invalid index %s != %s", block.index, block.index, self.last_block.index + 1)
return False

# Verify block hash
if block.hash != calculate_hash(block.to_header_dict()):
logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash)
try:
validate_block_link_and_hash(self.last_block, block)
except ValueError as exc:
logger.warning("Block %s rejected: %s", block.index, exc)
return False

# Validate transactions on a temporary state copy
Expand Down
53 changes: 6 additions & 47 deletions minichain/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@
import copy

from .block import Block
from .transaction import Transaction
from .chain import Blockchain
from .state import State
from .pow import calculate_hash
from .chain import Blockchain, validate_block_link_and_hash

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -128,23 +125,10 @@ def _verify_chain_integrity(blocks: list) -> None:
for i in range(1, len(blocks)):
block = blocks[i]
prev = blocks[i - 1]

if block.index != prev.index + 1:
raise ValueError(
f"Block #{block.index}: index gap (expected {prev.index + 1})"
)

if block.previous_hash != prev.hash:
raise ValueError(
f"Block #{block.index}: previous_hash mismatch"
)

expected_hash = calculate_hash(block.to_header_dict())
if block.hash != expected_hash:
raise ValueError(
f"Block #{block.index}: hash mismatch "
f"(stored={block.hash[:16]}..., computed={expected_hash[:16]}...)"
)
try:
validate_block_link_and_hash(prev, block)
except ValueError as exc:
raise ValueError(f"Block #{block.index}: {exc}") from exc


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -190,29 +174,4 @@ def _read_json(filepath: str):

def _deserialize_block(data: dict) -> Block:
"""Reconstruct a Block (including its transactions) from a plain dict."""
transactions = [
Transaction(
sender=tx["sender"],
receiver=tx["receiver"],
amount=tx["amount"],
nonce=tx["nonce"],
data=tx.get("data"),
signature=tx.get("signature"),
timestamp=tx["timestamp"],
)
for tx in data.get("transactions", [])
]

block = Block(
index=data["index"],
previous_hash=data["previous_hash"],
transactions=transactions,
timestamp=data["timestamp"],
difficulty=data.get("difficulty"),
)
block.nonce = data["nonce"]
block.hash = data["hash"]
# Only overwrite merkle_root if explicitly saved; otherwise keep computed value
if "merkle_root" in data:
block.merkle_root = data["merkle_root"]
return block
return Block.from_dict(data)
12 changes: 12 additions & 0 deletions minichain/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ def to_signing_dict(self):
"timestamp": self.timestamp,
}

@classmethod
def from_dict(cls, payload: dict):
return cls(
sender=payload["sender"],
receiver=payload.get("receiver"),
amount=payload["amount"],
nonce=payload["nonce"],
data=payload.get("data"),
signature=payload.get("signature"),
timestamp=payload.get("timestamp"),
)

@property
def hash_payload(self):
"""Returns the bytes to be signed."""
Expand Down
Loading