/src/full_node/sync_blocks_processor.py

https://github.com/Chia-Network/chia-blockchain · Python · 126 lines · 105 code · 16 blank · 5 comment · 20 complexity · 7a3df58f86502ac9331d9c6614c8f4d1 MD5 · raw file

  1. import asyncio
  2. import concurrent
  3. import logging
  4. import time
  5. from typing import Optional
  6. from src.full_node.blockchain import Blockchain, ReceiveBlockResult
  7. from src.full_node.sync_store import SyncStore
  8. from src.types.full_block import FullBlock
  9. from src.util.errors import ConsensusError
  10. from src.util.ints import uint32
  11. log = logging.getLogger(__name__)
  12. class SyncBlocksProcessor:
  13. def __init__(
  14. self,
  15. sync_store: SyncStore,
  16. fork_height: uint32,
  17. tip_height: uint32,
  18. blockchain: Blockchain,
  19. ):
  20. self.sync_store = sync_store
  21. self.blockchain = blockchain
  22. self.fork_height = fork_height
  23. self.tip_height = tip_height
  24. self._shut_down = False
  25. self.BATCH_SIZE = 10
  26. self.SLEEP_INTERVAL = 10
  27. self.TOTAL_TIMEOUT = 200
  28. def shut_down(self):
  29. self._shut_down = True
  30. async def process(self) -> None:
  31. header_hashes = self.sync_store.get_potential_hashes()
  32. # TODO: run this in a new process so it doesn't have to share CPU time with other things
  33. for batch_start_height in range(
  34. self.fork_height + 1, self.tip_height + 1, self.BATCH_SIZE
  35. ):
  36. if self._shut_down:
  37. return
  38. total_time_slept = 0
  39. batch_end_height = min(
  40. batch_start_height + self.BATCH_SIZE - 1, self.tip_height
  41. )
  42. for height in range(batch_start_height, batch_end_height + 1):
  43. # If we have already added this block to the chain, skip it
  44. if header_hashes[height] in self.blockchain.headers:
  45. batch_start_height = height + 1
  46. while True:
  47. if self._shut_down:
  48. return
  49. if total_time_slept > self.TOTAL_TIMEOUT:
  50. raise TimeoutError("Took too long to fetch blocks")
  51. awaitables = [
  52. (self.sync_store.potential_blocks_received[uint32(height)]).wait()
  53. for height in range(batch_start_height, batch_end_height + 1)
  54. ]
  55. future = asyncio.gather(*awaitables, return_exceptions=True)
  56. try:
  57. await asyncio.wait_for(future, timeout=self.SLEEP_INTERVAL)
  58. break
  59. # https://github.com/python/cpython/pull/13528
  60. except (concurrent.futures.TimeoutError, asyncio.TimeoutError):
  61. try:
  62. await future
  63. except asyncio.CancelledError:
  64. pass
  65. total_time_slept += self.SLEEP_INTERVAL
  66. log.info(
  67. f"Did not receive desired blocks ({batch_start_height}, {batch_end_height})"
  68. )
  69. # Verifies this batch, which we are guaranteed to have (since we broke from the above loop)
  70. blocks = []
  71. for height in range(batch_start_height, batch_end_height + 1):
  72. b: Optional[FullBlock] = self.sync_store.potential_blocks[
  73. uint32(height)
  74. ]
  75. assert b is not None
  76. blocks.append(b)
  77. validation_start_time = time.time()
  78. prevalidate_results = (
  79. await self.blockchain.pre_validate_blocks_multiprocessing(blocks)
  80. )
  81. if self._shut_down:
  82. return
  83. for index, block in enumerate(blocks):
  84. assert block is not None
  85. # The block gets permanantly added to the blockchain
  86. validated, pos = prevalidate_results[index]
  87. async with self.blockchain.lock:
  88. (
  89. result,
  90. header_block,
  91. error_code,
  92. ) = await self.blockchain.receive_block(
  93. block, validated, pos, sync_mode=True
  94. )
  95. if (
  96. result == ReceiveBlockResult.INVALID_BLOCK
  97. or result == ReceiveBlockResult.DISCONNECTED_BLOCK
  98. ):
  99. if error_code is not None:
  100. raise ConsensusError(error_code, block.header_hash)
  101. raise RuntimeError(f"Invalid block {block.header_hash}")
  102. assert (
  103. max([h.height for h in self.blockchain.get_current_tips()])
  104. >= block.height
  105. )
  106. del self.sync_store.potential_blocks[block.height]
  107. log.info(
  108. f"Took {time.time() - validation_start_time} seconds to validate and add blocks "
  109. f"{batch_start_height} to {batch_end_height + 1}."
  110. )