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
13 changes: 13 additions & 0 deletions genesis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"chain_id": "minichain_testnet_1",
"timestamp": 1716880000000,
"difficulty": 4,
"alloc": {
"0000000000000000000000000000000000000001": {
"balance": 1000000000
},
"0000000000000000000000000000000000000002": {
"balance": 500000000
}
}
}
53 changes: 47 additions & 6 deletions minichain/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from .pow import calculate_hash
import logging
import threading
import json
import os
import sys

logger = logging.getLogger(__name__)

Expand All @@ -28,22 +31,60 @@ class Blockchain:
Manages the blockchain, validates blocks, and commits state transitions.
"""

def __init__(self):
def __init__(self, genesis_path="genesis.json"):
self.chain = []
self.state = State()
self._lock = threading.RLock()
self._create_genesis_block()
self._create_genesis_block(genesis_path)

def _create_genesis_block(self):
def _create_genesis_block(self, genesis_path):
"""
Creates the genesis block with a fixed hash.
Creates the genesis block and initializes state from config.
"""
config = {}
if os.path.exists(genesis_path):
try:
with open(genesis_path, "r") as f:
config = json.load(f)
except Exception as e:
logger.error(f"Failed to load genesis config: {e}")
sys.exit(1)
else:
logger.error(f"Failed to load genesis config: file {genesis_path} does not exist.")
sys.exit(1)

Comment thread
coderabbitai[bot] marked this conversation as resolved.
# 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.")
sys.exit(1)
account = self.state.get_account(address)
account['balance'] = balance

Comment thread
coderabbitai[bot] marked this conversation as resolved.
timestamp = config.get("timestamp")
difficulty = config.get("difficulty")

genesis_block = Block(
index=0,
previous_hash="0",
transactions=[]
transactions=[],
timestamp=timestamp,
difficulty=difficulty
)
genesis_block.hash = "0" * 64

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}")
sys.exit(1)
genesis_block.hash = config_hash
else:
genesis_block.hash = computed_hash

self.chain.append(genesis_block)

@property
Expand Down
5 changes: 4 additions & 1 deletion minichain/persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,11 @@ def load(path: str = ".") -> Blockchain:
def _verify_chain_integrity(blocks: list[Block]) -> None:
"""Verify genesis, hash linkage, and block hashes."""
genesis = blocks[0]
if genesis.index != 0 or genesis.hash != "0" * 64:
if genesis.index != 0:
raise ValueError("Invalid genesis block")
from .pow import calculate_hash
if genesis.hash != calculate_hash(genesis.to_header_dict()):
raise ValueError("Invalid genesis block hash")

for i in range(1, len(blocks)):
block = blocks[i]
Expand Down
4 changes: 2 additions & 2 deletions tests/test_persistence.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_genesis_only_chain(self):
save(bc, path=self.tmpdir)
restored = load(path=self.tmpdir)
self.assertEqual(len(restored.chain), 1)
self.assertEqual(restored.chain[0].hash, "0" * 64)
self.assertEqual(restored.chain[0].hash, bc.chain[0].hash)

def test_state_snapshot_preserved(self):
bc, alice_pk, bob_pk = self._chain_with_tx()
Expand Down Expand Up @@ -254,7 +254,7 @@ def test_corrupt_sqlite_falls_back_to_legacy_json(self):

restored = load(path=self.tmpdir)
self.assertEqual(len(restored.chain), 1)
self.assertEqual(restored.chain[0].hash, "0" * 64)
self.assertEqual(restored.chain[0].hash, bc.chain[0].hash)


if __name__ == "__main__":
Expand Down