from __future__ import annotations from dataclasses import dataclass from .ledger import GENESIS_HASH, ChainDivergenceError, SQLiteLedger from .models import LedgerEntry @dataclass(frozen=True) class SyncCursor: sequence: int entry_hash: str @dataclass(frozen=True) class DeltaPacket: base_cursor: SyncCursor entries: list[LedgerEntry] target_cursor: SyncCursor def prepare_delta(ledger: SQLiteLedger, peer_cursor: SyncCursor | None) -> DeltaPacket: head = ledger.head if head is None: cursor = SyncCursor(sequence=-1, entry_hash=GENESIS_HASH) return DeltaPacket(base_cursor=cursor, entries=[], target_cursor=cursor) if peer_cursor is None: entries = ledger.all_entries() return DeltaPacket( base_cursor=SyncCursor(sequence=-1, entry_hash=GENESIS_HASH), entries=entries, target_cursor=SyncCursor(sequence=head.sequence, entry_hash=head.entry_hash), ) if peer_cursor.sequence == -1 and peer_cursor.entry_hash == GENESIS_HASH: return DeltaPacket( base_cursor=peer_cursor, entries=ledger.all_entries(), target_cursor=SyncCursor(sequence=head.sequence, entry_hash=head.entry_hash), ) local_anchor = ledger.entry_at(peer_cursor.sequence) if local_anchor is None or local_anchor.entry_hash != peer_cursor.entry_hash: raise ChainDivergenceError("peer cursor does not match this chain") delta = ledger.entries_after(peer_cursor.sequence) return DeltaPacket( base_cursor=peer_cursor, entries=delta, target_cursor=SyncCursor(sequence=head.sequence, entry_hash=head.entry_hash), ) def apply_delta(ledger: SQLiteLedger, packet: DeltaPacket) -> SyncCursor: current = ledger.head if packet.base_cursor.sequence == -1: if current is not None: raise ChainDivergenceError("refusing to overwrite a non-empty ledger with a genesis delta") else: if current is None: raise ChainDivergenceError("cannot apply a non-empty delta to an empty ledger without genesis state") if current.sequence != packet.base_cursor.sequence or current.entry_hash != packet.base_cursor.entry_hash: raise ChainDivergenceError("ledger head does not match delta base cursor") ledger.import_entries(packet.entries) head = ledger.head if head is None: return SyncCursor(sequence=-1, entry_hash=GENESIS_HASH) return SyncCursor(sequence=head.sequence, entry_hash=head.entry_hash)