115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
import json
|
|
import time
|
|
import os
|
|
import hashlib
|
|
from .contracts import DataContractRegistry
|
|
from .delta import DeltaLog
|
|
from .crypto import Signer, digest
|
|
|
|
class GovernanceLedger:
|
|
"""Lightweight governance/ audit ledger for data-access events."""
|
|
def __init__(self):
|
|
self._events = []
|
|
|
|
def log_event(self, event: str, details: dict) -> dict:
|
|
ts = time.time()
|
|
payload = {
|
|
"event": event,
|
|
"details": details,
|
|
"ts": ts,
|
|
}
|
|
payload_bytes = json.dumps(payload, sort_keys=True).encode('utf-8')
|
|
event_id = hashlib.sha256(payload_bytes).hexdigest()
|
|
entry = {
|
|
"id": event_id,
|
|
"ts": ts,
|
|
"event": event,
|
|
"details": details,
|
|
}
|
|
self._events.append(entry)
|
|
return entry
|
|
|
|
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))
|
|
# Simple governance log (extensible in future iterations)
|
|
self.governance = GovernanceLedger()
|
|
# Track known entry IDs to avoid duplicating imported deltas
|
|
self._entry_ids = set()
|
|
|
|
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._entry_ids.add(entry_id)
|
|
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)
|
|
# Avoid duplicating entries we've already seen
|
|
if entry.id in self._entry_ids:
|
|
continue
|
|
self._entries.append(entry)
|
|
self._entry_ids.add(entry.id)
|
|
self.delta_log.add_entry(entry.to_dict())
|
|
|
|
def root(self) -> str:
|
|
return self.delta_log.root()
|
|
|
|
def __len__(self):
|
|
return len(self._entries)
|