71 lines
2.5 KiB
Python
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)
|