import json import time import os import hashlib from .contracts import DataContractRegistry from .delta import DeltaLog from .crypto import Signer, digest class LedgerEntry: def __init__(self, entry_id: str, ts: float, entry_type: str, payload: dict, contract_version: int, signer_name: str, signature: bytes): self.id = entry_id self.ts = ts self.entry_type = entry_type self.payload = payload self.contract_version = contract_version self.signer_name = signer_name self.signature = signature def to_dict(self): return { "id": self.id, "ts": self.ts, "entry_type": self.entry_type, "payload": self.payload, "contract_version": self.contract_version, "signer_name": self.signer_name, "signature": self.signature.hex(), } @staticmethod def from_dict(d: dict): return LedgerEntry( d["id"], d["ts"], d["entry_type"], d["payload"], d["contract_version"], d["signer_name"], bytes.fromhex(d["signature"]) ) class LocalLedger: def __init__(self, signer_key: bytes = None, node_id: str = None): self.node_id = node_id or (node_id := os.urandom(4).hex()) self.contracts = DataContractRegistry() self.delta_log = DeltaLog() self._entries = [] # store LedgerEntry objects locally self._signer = Signer(signer_key or os.urandom(32)) def register_contract(self, name: str, schema: dict, version: int = 1): self.contracts.register(name, schema, version) def add_entry(self, entry_type: str, payload: dict, signer_name: str = None, contract_name: str = None) -> LedgerEntry: if not self.contracts.get(entry_type) and contract_name is None: # Allow ad-hoc entry without contract in MVP for simplicity contract_version = 1 else: contract = self.contracts.get(entry_type) contract_version = contract["version"] if contract else 1 ts = time.time() # Build a lightweight id: hash of type+ts+payload raw = json.dumps({"type": entry_type, "ts": ts, "payload": payload}, sort_keys=True).encode('utf-8') entry_id = hashlib.sha256(raw).hexdigest() payload_bytes = json.dumps(payload, sort_keys=True).encode('utf-8') signature = self._signer.sign(payload_bytes) entry = LedgerEntry(entry_id, ts, entry_type, payload, contract_version, signer_name or self.node_id, signature) self._entries.append(entry) self.delta_log.add_entry(entry.to_dict()) return entry def export_delta(self, since_index: int = 0) -> list: # Return serialized entries since index; this is a simple MVP delta export delta = self.delta_log.delta_from_index(since_index) return delta def import_delta(self, delta_entries: list): # delta_entries is a list of entry dicts (as produced by export_delta) to merge for d in delta_entries: entry = LedgerEntry.from_dict(d) self._entries.append(entry) self.delta_log.add_entry(entry.to_dict()) def root(self) -> str: return self.delta_log.root() def __len__(self): return len(self._entries)