From 08c9a1f9c24d5fc34abfcada2e1a4c866fef90a7 Mon Sep 17 00:00:00 2001 From: Youngjoon Lee <5462944+youngjoon-lee@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:43:49 +0900 Subject: [PATCH] make sync function return bool --- cryptarchia/sync.py | 14 ++++++++++++-- cryptarchia/test_sync.py | 27 ++++++++++++++++++--------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/cryptarchia/sync.py b/cryptarchia/sync.py index c87549a..b7df20a 100644 --- a/cryptarchia/sync.py +++ b/cryptarchia/sync.py @@ -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 diff --git a/cryptarchia/test_sync.py b/cryptarchia/test_sync.py index b0ce4ad..1c09635 100644 --- a/cryptarchia/test_sync.py +++ b/cryptarchia/test_sync.py @@ -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]))