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

83 lines
3.2 KiB
Python

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)