diff --git a/cryptarchia/sync/__init__.py b/cryptarchia/sync/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cryptarchia/sync/range_sync.py b/cryptarchia/sync/range_sync.py new file mode 100644 index 0000000..a1247d2 --- /dev/null +++ b/cryptarchia/sync/range_sync.py @@ -0,0 +1,43 @@ +from collections import defaultdict +from typing import Generator + +from dill.tests.test_diff import A + +from cryptarchia.cryptarchia import BlockHeader, Follower, Id, Slot + +SLOT_TOLERANCE = 2 + + +def range_sync(local: Follower, remotes: list[Follower], start_slot: Slot): + while groups := { + tip: group + for tip, group in group_by_tip(remotes).items() + if group[0].tip().slot.absolute_slot - start_slot.absolute_slot > SLOT_TOLERANCE + }: + for _, group in groups.items(): + remote = group[0] + for block in request_blocks_by_range(remote, start_slot, remote.tip().slot): + local.on_block(block) + start_slot = Slot(local.tip().slot.absolute_slot + 1) + + +def group_by_tip(remotes: list[Follower]) -> dict[Id, list[Follower]]: + groups: dict[Id, list[Follower]] = defaultdict(list) + for remote in remotes: + groups[remote.tip_id()].append(remote) + return groups + + +def request_blocks_by_range( + remote: Follower, from_slot: Slot, to_slot: Slot +) -> Generator[BlockHeader, None, None]: + # TODO: Optimize this by keeping blocks by slot in the Follower + blocks_by_slot: dict[int, list[BlockHeader]] = defaultdict(list) + for ledger_state in remote.ledger_state.values(): + if from_slot <= ledger_state.block.slot <= to_slot: + blocks_by_slot[ledger_state.block.slot.absolute_slot].append( + ledger_state.block + ) + for slot in range(from_slot.absolute_slot, to_slot.absolute_slot + 1): + for block in blocks_by_slot[slot]: + yield block diff --git a/cryptarchia/sync/test_range_sync.py b/cryptarchia/sync/test_range_sync.py new file mode 100644 index 0000000..dafc7e8 --- /dev/null +++ b/cryptarchia/sync/test_range_sync.py @@ -0,0 +1,26 @@ +from unittest import TestCase + +from cryptarchia.cryptarchia import Coin, Follower +from cryptarchia.sync.range_sync import range_sync +from cryptarchia.test_common import mk_block, mk_config, mk_genesis_state + + +class TestRangeSync(TestCase): + def test_no_fork(self): + # b0 - b1 - b2 + coin = Coin(sk=0, value=10) + config = mk_config([coin]) + genesis = mk_genesis_state([coin]) + follower = Follower(genesis, config) + b0, coin = mk_block(genesis.block, 1, coin), coin.evolve() + b1, coin = mk_block(b0, 2, coin), coin.evolve() + b2, coin = mk_block(b1, 3, coin), coin.evolve() + for b in [b0, b1, b2]: + follower.on_block(b) + assert follower.tip() == b2 + assert follower.forks == [] + + new_follower = Follower(genesis, config) + range_sync(new_follower, [follower], genesis.block.slot) + assert new_follower.tip() == b2 + assert new_follower.forks == []