async def _load_chain_from_store(self, ) -> None: """ Initializes the state of the Blockchain class from the database. Sets the LCA, tips, headers, height_to_hash, and block_store DiffStores. """ lca_db: Optional[Header] = await self.block_store.get_lca() tips_db: List[Header] = await self.block_store.get_tips() headers_db: Dict[bytes32, Header] = await self.block_store.get_headers() assert (lca_db is None) == (len(tips_db) == 0) == (len(headers_db) == 0) if lca_db is None: result, removed, error_code = await self.receive_block( self.genesis, sync_mode=False) if result != ReceiveBlockResult.ADDED_TO_HEAD: if error_code is not None: raise ConsensusError(error_code) else: raise RuntimeError(f"Invalid genesis block {self.genesis}") return await self.block_store.init_challenge_hashes() # Set the state (lca block and tips) self.lca_block = lca_db self.tips = tips_db # Find the common ancestor of the tips, and add intermediate blocks to headers cur: List[Header] = self.tips[:] while any(b.header_hash != cur[0].header_hash for b in cur): heights = [b.height for b in cur] i = heights.index(max(heights)) self.headers[cur[i].header_hash] = cur[i] prev: Header = headers_db[cur[i].prev_header_hash] challenge_hash = self.block_store.get_challenge_hash( cur[i].header_hash) self.block_store.add_proof_of_time( challenge_hash, uint64(cur[i].data.total_iters - prev.data.total_iters), cur[i].data.height, ) cur[i] = prev # Consistency check, tips should have an LCA equal to the DB LCA assert cur[0] == self.lca_block # Sets the header for remaining blocks, and height_to_hash dict cur_b: Header = self.lca_block while True: self.headers[cur_b.header_hash] = cur_b self.height_to_hash[cur_b.height] = cur_b.header_hash if cur_b.height == 0: break prev_b: Header = headers_db[cur_b.prev_header_hash] challenge_hash = self.block_store.get_challenge_hash( cur_b.header_hash) self.block_store.add_proof_of_time( challenge_hash, uint64(cur_b.data.total_iters - prev_b.data.total_iters), cur_b.data.height, ) cur_b = prev_b # Asserts that the DB genesis block is correct assert cur_b == self.genesis.header # Adds the blocks to the db between LCA and tip await self.recreate_diff_stores()
async def process(self) -> None: header_hashes = self.sync_store.get_potential_hashes() # TODO: run this in a new process so it doesn't have to share CPU time with other things for batch_start_height in range(self.fork_height + 1, self.tip_height + 1, self.BATCH_SIZE): total_time_slept = 0 batch_end_height = min(batch_start_height + self.BATCH_SIZE - 1, self.tip_height) for height in range(batch_start_height, batch_end_height + 1): # If we have already added this block to the chain, skip it if header_hashes[height] in self.blockchain.headers: batch_start_height = height + 1 while True: if self._shut_down: return if total_time_slept > self.TOTAL_TIMEOUT: raise TimeoutError("Took too long to fetch blocks") awaitables = [ (self.sync_store.potential_blocks_received[uint32(height)] ).wait() for height in range(batch_start_height, batch_end_height + 1) ] future = asyncio.gather(*awaitables, return_exceptions=True) try: await asyncio.wait_for(future, timeout=self.SLEEP_INTERVAL) break except concurrent.futures.TimeoutError: try: await future except asyncio.CancelledError: pass total_time_slept += self.SLEEP_INTERVAL log.info( f"Did not receive desired blocks ({batch_start_height}, {batch_end_height})" ) # Verifies this batch, which we are guaranteed to have (since we broke from the above loop) blocks = [] for height in range(batch_start_height, batch_end_height + 1): b: Optional[FullBlock] = self.sync_store.potential_blocks[ uint32(height)] assert b is not None blocks.append(b) validation_start_time = time.time() prevalidate_results = await self.blockchain.pre_validate_blocks_multiprocessing( blocks) if self._shut_down: return for index, block in enumerate(blocks): assert block is not None # The block gets permanantly added to the blockchain validated, pos = prevalidate_results[index] async with self.blockchain.lock: ( result, header_block, error_code, ) = await self.blockchain.receive_block(block, validated, pos, sync_mode=True) if (result == ReceiveBlockResult.INVALID_BLOCK or result == ReceiveBlockResult.DISCONNECTED_BLOCK): if error_code is not None: raise ConsensusError(error_code, block.header_hash) raise RuntimeError( f"Invalid block {block.header_hash}") assert (max([ h.height for h in self.blockchain.get_current_tips() ]) >= block.height) del self.sync_store.potential_blocks[block.height] log.info( f"Took {time.time() - validation_start_time} seconds to validate and add blocks " f"{batch_start_height} to {batch_end_height + 1}.")