|
42 | 42 | from p2p.constants import MAX_REORG_DEPTH, SEAL_CHECK_RANDOM_SAMPLE_RATE
|
43 | 43 | from p2p.exceptions import NoEligiblePeers, OperationCancelled
|
44 | 44 | from p2p.p2p_proto import DisconnectReason
|
45 |
| -from p2p.peer import BasePeer, ETHPeer, LESPeer, PeerPool, PeerSubscriber |
| 45 | +from p2p.peer import BasePeer, ETHPeer, LESPeer, HeaderRequest, PeerPool, PeerSubscriber |
46 | 46 | from p2p.rlp import BlockBody
|
47 | 47 | from p2p.service import BaseService
|
48 | 48 | from p2p.utils import (
|
49 |
| - get_block_numbers_for_request, |
50 | 49 | get_asyncio_executor,
|
51 | 50 | Timer,
|
52 | 51 | )
|
@@ -205,6 +204,13 @@ async def _sync(self, peer: HeaderRequestingPeer) -> None:
|
205 | 204 | self.logger.warn("Timeout waiting for header batch from %s, aborting sync", peer)
|
206 | 205 | await peer.disconnect(DisconnectReason.timeout)
|
207 | 206 | break
|
| 207 | + except ValidationError as err: |
| 208 | + self.logger.warn( |
| 209 | + "Invalid header response sent by peer %s disconnecting: %s", |
| 210 | + peer, err, |
| 211 | + ) |
| 212 | + await peer.disconnect(DisconnectReason.useless_peer) |
| 213 | + break |
208 | 214 |
|
209 | 215 | if not headers:
|
210 | 216 | self.logger.info("Got no new headers from %s, aborting sync", peer)
|
@@ -244,22 +250,39 @@ async def _fetch_missing_headers(
|
244 | 250 | self, peer: HeaderRequestingPeer, start_at: int) -> Tuple[BlockHeader, ...]:
|
245 | 251 | """Fetch a batch of headers starting at start_at and return the ones we're missing."""
|
246 | 252 | self.logger.debug("Fetching chain segment starting at #%d", start_at)
|
247 |
| - peer.request_block_headers(start_at, peer.max_headers_fetch, reverse=False) |
| 253 | + request = peer.request_block_headers( |
| 254 | + start_at, |
| 255 | + peer.max_headers_fetch, |
| 256 | + skip=0, |
| 257 | + reverse=False, |
| 258 | + ) |
| 259 | + |
248 | 260 | # Pass the peer's token to self.wait() because we want to abort if either we
|
249 | 261 | # or the peer terminates.
|
250 |
| - headers = list(await self.wait( |
| 262 | + headers = tuple(await self.wait( |
251 | 263 | self._new_headers.get(),
|
252 | 264 | token=peer.cancel_token,
|
253 | 265 | timeout=self._reply_timeout))
|
254 |
| - for header in headers.copy(): |
255 |
| - try: |
256 |
| - await self.wait(self.db.coro_get_block_header_by_hash(header.hash)) |
257 |
| - except HeaderNotFound: |
258 |
| - break |
259 |
| - else: |
260 |
| - self.logger.debug("Discarding %s as we already have it", header) |
261 |
| - headers.remove(header) |
262 |
| - return tuple(headers) |
| 266 | + |
| 267 | + # check that the response headers are a valid match for our |
| 268 | + # requested headers. |
| 269 | + request.validate_headers(headers) |
| 270 | + |
| 271 | + # the inner list comprehension is required to get python to evaluate |
| 272 | + # the asynchronous comprehension |
| 273 | + missing_headers = tuple([ |
| 274 | + header |
| 275 | + for header |
| 276 | + in headers |
| 277 | + if not (await self.wait(self.db.coro_header_exists(header.hash))) |
| 278 | + ]) |
| 279 | + if len(missing_headers) != len(headers): |
| 280 | + self.logger.debug( |
| 281 | + "Discarding %d / %d headers that we already have", |
| 282 | + len(headers) - len(missing_headers), |
| 283 | + len(headers), |
| 284 | + ) |
| 285 | + return headers |
263 | 286 |
|
264 | 287 | def _handle_block_headers(self, headers: Tuple[BlockHeader, ...]) -> None:
|
265 | 288 | if not headers:
|
@@ -298,9 +321,13 @@ async def _handle_msg(self, peer: HeaderRequestingPeer, cmd: protocol.Command,
|
298 | 321 |
|
299 | 322 | async def _handle_get_block_headers(self, peer: LESPeer, msg: Dict[str, Any]) -> None:
|
300 | 323 | self.logger.debug("Peer %s made header request: %s", peer, msg)
|
301 |
| - query = msg['query'] |
302 |
| - headers = await self._handler.lookup_headers( |
303 |
| - query.block_number_or_hash, query.max_headers, query.skip, query.reverse) |
| 324 | + request = HeaderRequest( |
| 325 | + msg['query'].block_number_or_hash, |
| 326 | + msg['query'].max_headers, |
| 327 | + msg['query'].skip, |
| 328 | + msg['query'].reverse, |
| 329 | + ) |
| 330 | + headers = await self._handler.lookup_headers(request) |
304 | 331 | self.logger.trace("Replying to %s with %d headers", peer, len(headers))
|
305 | 332 | peer.sub_proto.send_block_headers(headers, buffer_value=0, request_id=msg['request_id'])
|
306 | 333 |
|
@@ -581,12 +608,16 @@ async def _handle_block_bodies(self,
|
581 | 608 | async def _handle_get_block_headers(
|
582 | 609 | self,
|
583 | 610 | peer: ETHPeer,
|
584 |
| - header_request: Dict[str, Any]) -> None: |
585 |
| - self.logger.debug("Peer %s made header request: %s", peer, header_request) |
586 |
| - |
587 |
| - headers = await self._handler.lookup_headers( |
588 |
| - header_request['block_number_or_hash'], header_request['max_headers'], |
589 |
| - header_request['skip'], header_request['reverse']) |
| 611 | + query: Dict[str, Any]) -> None: |
| 612 | + self.logger.debug("Peer %s made header request: %s", peer, query) |
| 613 | + request = HeaderRequest( |
| 614 | + query['block_number_or_hash'], |
| 615 | + query['max_headers'], |
| 616 | + query['skip'], |
| 617 | + query['reverse'], |
| 618 | + ) |
| 619 | + |
| 620 | + headers = await self._handler.lookup_headers(request) |
590 | 621 | self.logger.trace("Replying to %s with %d headers", peer, len(headers))
|
591 | 622 | peer.sub_proto.send_block_headers(headers)
|
592 | 623 |
|
@@ -697,34 +728,49 @@ async def handle_get_node_data(self, peer: ETHPeer, node_hashes: List[Hash32]) -
|
697 | 728 | self.logger.trace("Replying to %s with %d trie nodes", peer, len(nodes))
|
698 | 729 | peer.sub_proto.send_node_data(nodes)
|
699 | 730 |
|
700 |
| - async def lookup_headers(self, block_number_or_hash: Union[int, bytes], max_headers: int, |
701 |
| - skip: int, reverse: bool) -> List[BlockHeader]: |
| 731 | + async def lookup_headers(self, |
| 732 | + request: HeaderRequest) -> Tuple[BlockHeader, ...]: |
702 | 733 | """
|
703 | 734 | Lookup :max_headers: headers starting at :block_number_or_hash:, skipping :skip: items
|
704 | 735 | between each, in reverse order if :reverse: is True.
|
705 | 736 | """
|
706 |
| - if isinstance(block_number_or_hash, bytes): |
707 |
| - try: |
708 |
| - header = await self.wait( |
709 |
| - self.db.coro_get_block_header_by_hash(cast(Hash32, block_number_or_hash))) |
710 |
| - except HeaderNotFound: |
711 |
| - self.logger.debug( |
712 |
| - "Peer requested starting header %r that is unavailable, returning nothing", |
713 |
| - block_number_or_hash) |
714 |
| - return [] |
715 |
| - block_number = header.block_number |
716 |
| - elif isinstance(block_number_or_hash, int): |
717 |
| - block_number = block_number_or_hash |
| 737 | + try: |
| 738 | + block_numbers = await self._get_block_numbers_for_request(request) |
| 739 | + except HeaderNotFound: |
| 740 | + self.logger.debug( |
| 741 | + "Peer requested starting header %r that is unavailable, returning nothing", |
| 742 | + request.block_number_or_hash) |
| 743 | + block_numbers = tuple() # type: ignore |
| 744 | + |
| 745 | + headers: Tuple[BlockHeader, ...] = tuple([ |
| 746 | + header |
| 747 | + async for header |
| 748 | + in self._generate_available_headers(block_numbers) |
| 749 | + ]) |
| 750 | + return headers |
| 751 | + |
| 752 | + async def _get_block_numbers_for_request(self, |
| 753 | + request: HeaderRequest) -> Tuple[BlockNumber, ...]: |
| 754 | + """ |
| 755 | + Generate the block numbers for a given `HeaderRequest`. |
| 756 | + """ |
| 757 | + if isinstance(request.block_number_or_hash, bytes): |
| 758 | + header = await self.wait( |
| 759 | + self.db.coro_get_block_header_by_hash(cast(Hash32, request.block_number_or_hash))) |
| 760 | + return request.generate_block_numbers(header.block_number) |
| 761 | + elif isinstance(request.block_number_or_hash, int): |
| 762 | + # We don't need to pass in the block number to |
| 763 | + # `generate_block_numbers` since the request is based on a numbered |
| 764 | + # block identifier. |
| 765 | + return request.generate_block_numbers() |
718 | 766 | else:
|
719 | 767 | raise TypeError(
|
720 |
| - "Unexpected type for 'block_number_or_hash': %s", type(block_number_or_hash)) |
721 |
| - |
722 |
| - block_numbers = get_block_numbers_for_request(block_number, max_headers, skip, reverse) |
723 |
| - headers = [header async for header in self._generate_available_headers(block_numbers)] |
724 |
| - return headers |
| 768 | + "Invariant: unexpected type for 'block_number_or_hash': %s", |
| 769 | + type(request.block_number_or_hash), |
| 770 | + ) |
725 | 771 |
|
726 | 772 | async def _generate_available_headers(
|
727 |
| - self, block_numbers: Tuple[BlockNumber]) -> AsyncGenerator[BlockHeader, None]: |
| 773 | + self, block_numbers: Tuple[BlockNumber, ...]) -> AsyncGenerator[BlockHeader, None]: |
728 | 774 | """
|
729 | 775 | Generates the headers requested, halting on the first header that is not locally available.
|
730 | 776 | """
|
|
0 commit comments