make sync function return bool

This commit is contained in:
Youngjoon Lee 2025-03-06 15:43:49 +09:00
parent 2e2675380f
commit 08c9a1f9c2
No known key found for this signature in database
GPG Key ID: D94003D91DE12141
2 changed files with 30 additions and 11 deletions

View File

@ -12,16 +12,22 @@ from cryptarchia.cryptarchia import (
)
def sync(local: Follower, peers: list[Follower]):
def sync(local: Follower, peers: list[Follower]) -> bool:
# Syncs the local block tree with the peers, starting from the local tip.
# This covers the case where the local tip is not on the latest honest chain anymore.
#
# The caller should call this function repeatedly until it returns True,
# which means that no peers have blocks ahead of the local tip.
# Fetch blocks from the peers in the range of slots from the local tip to the latest tip.
# Gather orphaned blocks, which are blocks from forks that are absent in the local block tree.
start_slot = local.tip().slot
orphans: set[BlockHeader] = set()
# Filter and group peers by their tip to minimize the number of fetches.
for group in filter_and_group_peers_by_tip(peers, start_slot).values():
groups = filter_and_group_peers_by_tip(peers, start_slot)
if len(groups) == 0:
return True # No peers have blocks ahead of the local tip.
for group in groups.values():
for block in fetch_blocks_by_slot(group, start_slot):
try:
local.on_block(block)
@ -37,6 +43,10 @@ def sync(local: Follower, peers: list[Follower]):
if orphan not in local.ledger_state:
backfill_fork(local, peers, orphan)
# The caller should call this function again,
# assuming that peers' tips have been updated during the sync.
return False
def filter_and_group_peers_by_tip(
peers: list[Follower], start_slot: Slot

View File

@ -22,9 +22,10 @@ class TestSync(TestCase):
self.assertEqual(peer.forks, [])
local = Follower(genesis, config)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertTrue(sync(local, [peer]))
def test_sync_single_chain_from_middle(self):
# b0 - b1 - b2 - b3
@ -46,9 +47,10 @@ class TestSync(TestCase):
for b in [b0, b1]:
peer.on_block(b)
# start syncing from b1
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertTrue(sync(local, [peer]))
def test_sync_forks_from_genesis(self):
# b0 - b1 - b2 - b5 == tip
@ -70,9 +72,10 @@ class TestSync(TestCase):
self.assertEqual(peer.forks, [b4.id()])
local = Follower(genesis, config)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertTrue(sync(local, [peer]))
def test_sync_forks_from_middle(self):
# b0 - b1 - b2 - b5 == tip
@ -99,9 +102,10 @@ class TestSync(TestCase):
local = Follower(genesis, config)
for b in [b0, b1, b3]:
peer.on_block(b)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertTrue(sync(local, [peer]))
def test_sync_forks_by_backfilling(self):
# b0 - b1 - b2 - b5 == tip
@ -127,10 +131,11 @@ class TestSync(TestCase):
local = Follower(genesis, config)
for b in [b0, b1]:
peer.on_block(b)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertEqual(len(local.ledger_state), len(peer.ledger_state))
self.assertTrue(sync(local, [peer]))
def test_sync_multiple_peers_from_genesis(self):
# Peer-0: b5
@ -164,10 +169,11 @@ class TestSync(TestCase):
self.assertEqual(peer2.forks, [])
local = Follower(genesis, config)
sync(local, [peer0, peer1, peer2])
self.assertFalse(sync(local, [peer0, peer1, peer2]))
self.assertEqual(local.tip(), b5)
self.assertEqual(local.forks, [b4.id()])
self.assertEqual(len(local.ledger_state), 7)
self.assertTrue(sync(local, [peer0, peer1, peer2]))
class TestSyncFromCheckpoint(TestCase):
@ -195,7 +201,7 @@ class TestSyncFromCheckpoint(TestCase):
checkpoint = peer.ledger_state[b2.id()]
local = Follower(genesis, config)
local.apply_checkpoint(checkpoint)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
# Result:
# () - () - b2 - b3
# ||
@ -205,6 +211,7 @@ class TestSyncFromCheckpoint(TestCase):
self.assertEqual(
set(local.ledger_state.keys()), set([genesis.block.id(), b2.id(), b3.id()])
)
self.assertTrue(sync(local, [peer]))
def test_sync_forks(self):
# checkpoint
@ -234,7 +241,7 @@ class TestSyncFromCheckpoint(TestCase):
checkpoint = peer.ledger_state[b2.id()]
local = Follower(genesis, config)
local.apply_checkpoint(checkpoint)
sync(local, [peer])
self.assertFalse(sync(local, [peer]))
# Result:
# b0 - b1 - b2 - b5 == tip
# \
@ -242,6 +249,7 @@ class TestSyncFromCheckpoint(TestCase):
self.assertEqual(local.tip(), peer.tip())
self.assertEqual(local.forks, peer.forks)
self.assertEqual(set(local.ledger_state.keys()), set(peer.ledger_state.keys()))
self.assertTrue(sync(local, [peer]))
def test_sync_from_dishonest_checkpoint(self):
# Peer0: b0 - b1 - b2 - b5 == tip
@ -276,10 +284,11 @@ class TestSyncFromCheckpoint(TestCase):
checkpoint = peer1.ledger_state[b4.id()]
local = Follower(genesis, config)
local.apply_checkpoint(checkpoint)
sync(local, [peer0, peer1])
self.assertFalse(sync(local, [peer0, peer1]))
# b0 - b1 - b2 - b5 == tip
# \
# b3 - b4
self.assertEqual(local.tip(), b5)
self.assertEqual(local.forks, [b4.id()])
self.assertEqual(len(local.ledger_state.keys()), 7)
self.assertTrue(sync(local, [peer0, peer1]))