diff --git a/genesis.json b/genesis.json new file mode 100644 index 0000000..fe44101 --- /dev/null +++ b/genesis.json @@ -0,0 +1,13 @@ +{ + "chain_id": "minichain_testnet_1", + "timestamp": 1716880000000, + "difficulty": 4, + "alloc": { + "0000000000000000000000000000000000000001": { + "balance": 1000000000 + }, + "0000000000000000000000000000000000000002": { + "balance": 500000000 + } + } +} diff --git a/minichain/chain.py b/minichain/chain.py index b65d575..bea653b 100644 --- a/minichain/chain.py +++ b/minichain/chain.py @@ -3,6 +3,9 @@ from .pow import calculate_hash import logging import threading +import json +import os +import sys logger = logging.getLogger(__name__) @@ -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) + + # 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 + + 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 diff --git a/minichain/persistence.py b/minichain/persistence.py index 10ec21b..6fa1fd6 100644 --- a/minichain/persistence.py +++ b/minichain/persistence.py @@ -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] diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 09ae89c..ff46454 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -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() @@ -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__":