idea33-buildledger-verifiab.../buildledger/sync.py

71 lines
2.5 KiB
Python

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)