cosmicledger-verifiable-off.../cosmic_ledger/ledger.py

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)