Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
10 changes: 8 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

from minichain import Transaction, Blockchain, Block, State, Mempool, P2PNetwork, mine_block
from minichain.validators import is_valid_receiver
from minichain.block import calculate_receipt_root


logger = logging.getLogger(__name__)

BURN_ADDRESS = "0" * 40
TRUSTED_PEERS = set()
LOCALHOST_PEERS = {"127.0.0.1", "::1", "localhost", "0:0:0:0:0:0:0:1"}

Expand Down Expand Up @@ -61,13 +61,17 @@ def mine_and_process_block(chain, mempool, miner_pk):
temp_state = chain.state.copy()
mineable_txs = []
stale_txs = []
receipts = []
for tx in pending_txs:
expected_nonce = temp_state.get_account(tx.sender).get("nonce", 0)
if tx.nonce < expected_nonce:
stale_txs.append(tx)
continue
if temp_state.validate_and_apply(tx):

receipt = temp_state.validate_and_apply(tx)
if receipt is not None:
mineable_txs.append(tx)
receipts.append(receipt)

if stale_txs:
mempool.remove_transactions(stale_txs)
Expand All @@ -83,6 +87,8 @@ def mine_and_process_block(chain, mempool, miner_pk):
previous_hash=chain.last_block.hash,
transactions=mineable_txs,
state_root=temp_state.state_root(),
receipt_root=calculate_receipt_root(receipts),
receipts=receipts,
miner=miner_pk,
)

Expand Down
56 changes: 37 additions & 19 deletions minichain/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,35 @@
import hashlib
from typing import List, Optional
from .transaction import Transaction
from .receipt import Receipt
from .serialization import canonical_json_hash

def _sha256(data: str) -> str:
return hashlib.sha256(data.encode()).hexdigest()


def _calculate_merkle_root(transactions: List[Transaction]) -> Optional[str]:
if not transactions:
def _calculate_merkle_tree(hashes: List[str]) -> Optional[str]:
if not hashes:
return None

# Hash each transaction deterministically
tx_hashes = [
tx.tx_id
for tx in transactions
]

# Build Merkle tree
while len(tx_hashes) > 1:
if len(tx_hashes) % 2 != 0:
tx_hashes.append(tx_hashes[-1]) # duplicate last if odd

while len(hashes) > 1:
if len(hashes) % 2 != 0:
hashes.append(hashes[-1])
new_level = []
for i in range(0, len(tx_hashes), 2):
combined = tx_hashes[i] + tx_hashes[i + 1]
for i in range(0, len(hashes), 2):
combined = hashes[i] + hashes[i + 1]
new_level.append(_sha256(combined))
hashes = new_level
return hashes[0]

tx_hashes = new_level
def _calculate_merkle_root(transactions: List[Transaction]) -> Optional[str]:
if not transactions:
return None
return _calculate_merkle_tree([tx.tx_id for tx in transactions])

return tx_hashes[0]
def calculate_receipt_root(receipts: List[Receipt]) -> Optional[str]:
if not receipts:
return None
return _calculate_merkle_tree([canonical_json_hash(r.to_dict()) for r in receipts])


class Block:
Expand All @@ -42,11 +42,14 @@ def __init__(
timestamp: Optional[float] = None,
difficulty: Optional[int] = None,
state_root: Optional[str] = None,
receipt_root: Optional[str] = None,
receipts: Optional[List[Receipt]] = None,
miner: Optional[str] = None,
):
self.index = index
self.previous_hash = previous_hash
self.transactions: List[Transaction] = transactions or []
self.receipts: List[Receipt] = receipts or []

# Deterministic timestamp (ms)
self.timestamp: int = (
Expand All @@ -59,10 +62,15 @@ def __init__(
self.nonce: int = 0
self.hash: Optional[str] = None
self.state_root: Optional[str] = state_root
self.receipt_root: Optional[str] = receipt_root
self.miner: Optional[str] = miner

# NEW: compute merkle root once
# NEW: compute merkle roots once
self.merkle_root: Optional[str] = _calculate_merkle_root(self.transactions)

# If receipt_root is missing but we have receipts, calculate it.
if self.receipt_root is None and self.receipts:
self.receipt_root = calculate_receipt_root(self.receipts)

# -------------------------
# HEADER (used for mining)
Expand All @@ -73,6 +81,7 @@ def to_header_dict(self):
"previous_hash": self.previous_hash,
"merkle_root": self.merkle_root,
"state_root": self.state_root,
"receipt_root": self.receipt_root,
"timestamp": self.timestamp,
"difficulty": self.difficulty,
"nonce": self.nonce,
Expand All @@ -86,6 +95,9 @@ def to_body_dict(self):
return {
"transactions": [
tx.to_dict() for tx in self.transactions
],
"receipts": [
r.to_dict() for r in self.receipts
]
}

Expand All @@ -111,13 +123,19 @@ def from_dict(cls, payload: dict):
Transaction.from_dict(tx_payload)
for tx_payload in payload.get("transactions", [])
]
receipts = [
Receipt.from_dict(r_payload)
for r_payload in payload.get("receipts", [])
]
block = cls(
index=payload["index"],
previous_hash=payload["previous_hash"],
transactions=transactions,
timestamp=payload.get("timestamp"),
difficulty=payload.get("difficulty"),
state_root=payload.get("state_root"),
receipt_root=payload.get("receipt_root"),
receipts=receipts,
miner=payload.get("miner"),
)
block.nonce = payload.get("nonce", 0)
Expand Down
28 changes: 19 additions & 9 deletions minichain/chain.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .block import Block
from .block import Block, calculate_receipt_root
from .state import State
from .pow import calculate_hash
import logging
Expand Down Expand Up @@ -47,18 +47,18 @@ def _create_genesis_block(self, genesis_path):
with open(genesis_path, "r") as f:
config = json.load(f)
except Exception as e:
logger.error(f"Failed to load genesis config: {e}")
logger.error("Failed to load genesis config: %s", e)
sys.exit(1)
else:
logger.error(f"Failed to load genesis config: file {genesis_path} does not exist.")
logger.error("Failed to load genesis config: file %s does not exist.", genesis_path)
sys.exit(1)

# Apply genesis allocations
alloc = config.get("alloc", {})
for address, data in alloc.items():
balance = data.get("balance", 0)
if not isinstance(balance, int) or balance < 0:
logger.error(f"Invalid genesis balance for {address}: {balance}. Must be a non-negative integer.")
logger.error("Invalid genesis balance for %s: %s. Must be a non-negative integer.", address, balance)
sys.exit(1)
account = self.state.get_account(address)
account['balance'] = balance
Expand All @@ -72,15 +72,17 @@ def _create_genesis_block(self, genesis_path):
transactions=[],
timestamp=timestamp,
difficulty=difficulty,
state_root=self.state.state_root()
state_root=self.state.state_root(),
receipt_root=None,
receipts=[]
)

computed_hash = calculate_hash(genesis_block.to_header_dict())
config_hash = config.get("hash")

if config_hash:
if config_hash != computed_hash:
logger.error(f"Genesis hash mismatch. Config hash: {config_hash}, Computed hash: {computed_hash}")
logger.error("Genesis hash mismatch. Config hash: %s, Computed hash: %s", config_hash, computed_hash)
sys.exit(1)
genesis_block.hash = config_hash
else:
Expand Down Expand Up @@ -111,17 +113,25 @@ def add_block(self, block):

# Validate transactions on a temporary state copy
temp_state = self.state.copy()
receipts = []

for tx in block.transactions:
result = temp_state.validate_and_apply(tx)
receipt = temp_state.validate_and_apply(tx)

# Reject block if any transaction fails
if not result:
# Reject block if any transaction fails mathematical validation (None)
if receipt is None:
logger.warning("Block %s rejected: Transaction failed validation", block.index)
return False

receipts.append(receipt)

if block.miner:
temp_state.credit_mining_reward(block.miner)

computed_receipt_root = calculate_receipt_root(receipts)
if block.receipt_root != computed_receipt_root:
logger.warning("Block %s rejected: Invalid receipt root. Expected %s, got %s", block.index, computed_receipt_root, block.receipt_root)
return False
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Verify state root
if block.state_root != temp_state.state_root():
Expand Down
4 changes: 2 additions & 2 deletions minichain/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def execute(self, contract_address, sender_address, payload, amount):
logger.error("Contract execution crashed without result")
return False
if result["status"] != "success":
logger.error(f"Contract Execution Failed: {result.get('error')}")
logger.error("Contract Execution Failed: %s", result.get('error'))
return False

# Validate storage is JSON serializable
Expand Down Expand Up @@ -155,7 +155,7 @@ def _validate_code_ast(self, code):
logger.warning("Rejected type() call.")
return False
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "delattr"}:
logger.warning(f"Rejected direct call to {node.func.id}.")
logger.warning("Rejected direct call to %s.", node.func.id)
return False
if isinstance(node, ast.Constant) and isinstance(node.value, str):
if "__" in node.value:
Expand Down
Loading