Skip to content

Commit f5d6e6b

Browse files
committed
fix(sync-v2): Fix n-ary search to handle reorgs during its execution
1 parent bf0d0bc commit f5d6e6b

File tree

1 file changed

+40
-23
lines changed

1 file changed

+40
-23
lines changed

hathor/p2p/sync_v2/agent.py

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,17 @@ def run_sync_blocks(self) -> Generator[Any, Any, None]:
364364
# Get my best block.
365365
my_best_block = self.get_my_best_block()
366366

367-
# Find peer's best block
367+
# Get peer's best block
368368
self.peer_best_block = yield self.get_peer_best_block()
369369
assert self.peer_best_block is not None
370370

371-
# find best common block
371+
# Find best common block
372372
self.synced_block = yield self.find_best_common_block(my_best_block, self.peer_best_block)
373-
assert self.synced_block is not None
373+
if self.synced_block is None:
374+
# Find best common block failed. Try again soon.
375+
# This might happen if a reorg occurs during the search.
376+
return
377+
374378
self.log.debug('run_sync_blocks',
375379
my_best_block=my_best_block,
376380
peer_best_block=self.peer_best_block,
@@ -516,7 +520,7 @@ def partial_vertex_exists(self, vertex_id: VertexId) -> bool:
516520
@inlineCallbacks
517521
def find_best_common_block(self,
518522
my_best_block: _HeightInfo,
519-
peer_best_block: _HeightInfo) -> Generator[Any, Any, _HeightInfo]:
523+
peer_best_block: _HeightInfo) -> Generator[Any, Any, Optional[_HeightInfo]]:
520524
""" Search for the highest block/height where we're synced.
521525
"""
522526
self.log.debug('find_best_common_block', peer_best_block=peer_best_block, my_best_block=my_best_block)
@@ -545,36 +549,49 @@ def find_best_common_block(self,
545549
# Run an n-ary search in the interval [lo, hi).
546550
# `lo` is always a height where we are synced.
547551
# `hi` is always a height where sync state is unknown.
548-
hi = min(peer_best_block.height, my_best_block.height)
549-
lo = 0
550-
551-
lo_block_hash = self._settings.GENESIS_BLOCK_HASH
552+
hi = min(peer_best_block, my_best_block, key=lambda x: x.height)
553+
lo = _HeightInfo(height=0, id=self._settings.GENESIS_BLOCK_HASH)
552554

553-
while hi - lo > 1:
555+
while hi.height - lo.height > 1:
554556
self.log.info('find_best_common_block n-ary search query', lo=lo, hi=hi)
555-
step = math.ceil((hi - lo) / 10)
556-
heights = list(range(lo, hi, step))
557-
heights.append(hi)
558-
559-
block_height_list = yield self.get_peer_block_hashes(heights)
560-
block_height_list.sort(key=lambda x: x.height, reverse=True)
561-
562-
for height, block_hash in block_height_list:
557+
step = math.ceil((hi.height - lo.height) / 10)
558+
heights = list(range(lo.height, hi.height, step))
559+
heights.append(hi.height)
560+
561+
block_info_list = yield self.get_peer_block_hashes(heights)
562+
block_info_list.sort(key=lambda x: x.height, reverse=True)
563+
564+
# As we are supposed to be always synced at `lo`, we expect to receive a response
565+
# with at least one item equals to lo. If it does not happen, we stop the search
566+
# and return None. This might be caused when a reorg occurs during the search.
567+
if not block_info_list:
568+
self.log.info('n-ary search failed because it got a response with no lo_block_info',
569+
lo=lo,
570+
hi=hi)
571+
return None
572+
lo_block_info = block_info_list[-1]
573+
if lo_block_info != lo:
574+
self.log.info('n-ary search failed because lo != lo_block_info',
575+
lo=lo,
576+
hi=hi,
577+
lo_block_info=lo_block_info)
578+
return None
579+
580+
for info in block_info_list:
563581
try:
564582
# We must check only fully validated transactions.
565-
blk = self.tx_storage.get_transaction(block_hash)
583+
blk = self.tx_storage.get_transaction(info.id)
566584
except TransactionDoesNotExist:
567-
hi = height
585+
hi = info
568586
else:
569587
assert blk.get_metadata().validation.is_fully_connected()
570588
assert isinstance(blk, Block)
571-
assert height == blk.get_height()
572-
lo = height
573-
lo_block_hash = block_hash
589+
assert info.height == blk.get_height()
590+
lo = info
574591
break
575592

576593
self.log.debug('find_best_common_block n-ary search finished', lo=lo, hi=hi)
577-
return _HeightInfo(height=lo, id=lo_block_hash)
594+
return lo
578595

579596
def get_peer_block_hashes(self, heights: list[int]) -> Deferred[list[_HeightInfo]]:
580597
""" Returns the peer's block hashes in the given heights.

0 commit comments

Comments
 (0)