29
29
BLANK_ROOT_HASH , EMPTY_UNCLE_HASH , GENESIS_BLOCK_NUMBER , GENESIS_PARENT_HASH )
30
30
from eth .chains import AsyncChain
31
31
from eth .db .trie import make_trie_root_and_nodes
32
- from eth .exceptions import HeaderNotFound , ValidationError
32
+ from eth .exceptions import (
33
+ HeaderNotFound ,
34
+ ValidationError as EthValidationError ,
35
+ )
33
36
from eth .rlp .headers import BlockHeader
34
37
from eth .rlp .receipts import Receipt
35
38
from eth .rlp .transactions import BaseTransaction , BaseTransactionFields
42
45
from p2p .constants import MAX_REORG_DEPTH , SEAL_CHECK_RANDOM_SAMPLE_RATE
43
46
from p2p .exceptions import NoEligiblePeers , OperationCancelled
44
47
from p2p .p2p_proto import DisconnectReason
45
- from p2p .peer import BasePeer , ETHPeer , LESPeer , PeerPool , PeerSubscriber
48
+ from p2p .peer import BasePeer , ETHPeer , LESPeer , HeaderRequest , PeerPool , PeerSubscriber
46
49
from p2p .rlp import BlockBody
47
50
from p2p .service import BaseService
48
51
from p2p .utils import (
49
- get_block_numbers_for_request ,
50
52
get_asyncio_executor ,
51
53
Timer ,
52
54
)
@@ -205,6 +207,13 @@ async def _sync(self, peer: HeaderRequestingPeer) -> None:
205
207
self .logger .warn ("Timeout waiting for header batch from %s, aborting sync" , peer )
206
208
await peer .disconnect (DisconnectReason .timeout )
207
209
break
210
+ except ValueError as err :
211
+ self .logger .warn (
212
+ "Invalid header response sent by peer %s disconnecting: %s" ,
213
+ peer , err ,
214
+ )
215
+ await peer .disconnect (DisconnectReason .useless_peer )
216
+ break
208
217
209
218
if not headers :
210
219
self .logger .info ("Got no new headers from %s, aborting sync" , peer )
@@ -220,7 +229,7 @@ async def _sync(self, peer: HeaderRequestingPeer) -> None:
220
229
self .logger .debug ("Got new header chain starting at #%d" , first .block_number )
221
230
try :
222
231
await self .chain .coro_validate_chain (headers , self ._seal_check_random_sample_rate )
223
- except ValidationError as e :
232
+ except EthValidationError as e :
224
233
self .logger .warn ("Received invalid headers from %s, aborting sync: %s" , peer , e )
225
234
break
226
235
try :
@@ -244,22 +253,39 @@ async def _fetch_missing_headers(
244
253
self , peer : HeaderRequestingPeer , start_at : int ) -> Tuple [BlockHeader , ...]:
245
254
"""Fetch a batch of headers starting at start_at and return the ones we're missing."""
246
255
self .logger .debug ("Fetching chain segment starting at #%d" , start_at )
247
- peer .request_block_headers (start_at , peer .max_headers_fetch , reverse = False )
256
+ request = peer .request_block_headers (
257
+ start_at ,
258
+ peer .max_headers_fetch ,
259
+ skip = 0 ,
260
+ reverse = False ,
261
+ )
262
+
248
263
# Pass the peer's token to self.wait() because we want to abort if either we
249
264
# or the peer terminates.
250
- headers = list (await self .wait (
265
+ headers = tuple (await self .wait (
251
266
self ._new_headers .get (),
252
267
token = peer .cancel_token ,
253
268
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 )
269
+
270
+ # check that the response headers are a valid match for our
271
+ # requested headers.
272
+ request .validate_headers (headers )
273
+
274
+ # the inner list comprehension is required to get python to evaluate
275
+ # the asynchronous comprehension
276
+ missing_headers = tuple ([
277
+ header
278
+ for header
279
+ in headers
280
+ if not (await self .wait (self .db .coro_header_exists (header .hash )))
281
+ ])
282
+ if len (missing_headers ) != len (headers ):
283
+ self .logger .debug (
284
+ "Discarding %d / %d headers that we already have" ,
285
+ len (headers ) - len (missing_headers ),
286
+ len (headers ),
287
+ )
288
+ return headers
263
289
264
290
def _handle_block_headers (self , headers : Tuple [BlockHeader , ...]) -> None :
265
291
if not headers :
@@ -298,9 +324,13 @@ async def _handle_msg(self, peer: HeaderRequestingPeer, cmd: protocol.Command,
298
324
299
325
async def _handle_get_block_headers (self , peer : LESPeer , msg : Dict [str , Any ]) -> None :
300
326
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 )
327
+ request = HeaderRequest (
328
+ msg ['query' ].block_number_or_hash ,
329
+ msg ['query' ].max_headers ,
330
+ msg ['query' ].skip ,
331
+ msg ['query' ].reverse ,
332
+ )
333
+ headers = await self ._handler .lookup_headers (request )
304
334
self .logger .trace ("Replying to %s with %d headers" , peer , len (headers ))
305
335
peer .sub_proto .send_block_headers (headers , buffer_value = 0 , request_id = msg ['request_id' ])
306
336
@@ -581,12 +611,16 @@ async def _handle_block_bodies(self,
581
611
async def _handle_get_block_headers (
582
612
self ,
583
613
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' ])
614
+ query : Dict [str , Any ]) -> None :
615
+ self .logger .debug ("Peer %s made header request: %s" , peer , query )
616
+ request = HeaderRequest (
617
+ query ['block_number_or_hash' ],
618
+ query ['max_headers' ],
619
+ query ['skip' ],
620
+ query ['reverse' ],
621
+ )
622
+
623
+ headers = await self ._handler .lookup_headers (request )
590
624
self .logger .trace ("Replying to %s with %d headers" , peer , len (headers ))
591
625
peer .sub_proto .send_block_headers (headers )
592
626
@@ -697,34 +731,49 @@ async def handle_get_node_data(self, peer: ETHPeer, node_hashes: List[Hash32]) -
697
731
self .logger .trace ("Replying to %s with %d trie nodes" , peer , len (nodes ))
698
732
peer .sub_proto .send_node_data (nodes )
699
733
700
- async def lookup_headers (self , block_number_or_hash : Union [ int , bytes ], max_headers : int ,
701
- skip : int , reverse : bool ) -> List [BlockHeader ]:
734
+ async def lookup_headers (self ,
735
+ request : HeaderRequest ) -> Tuple [BlockHeader , ... ]:
702
736
"""
703
737
Lookup :max_headers: headers starting at :block_number_or_hash:, skipping :skip: items
704
738
between each, in reverse order if :reverse: is True.
705
739
"""
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
740
+ try :
741
+ block_numbers = await self ._get_block_numbers_for_request (request )
742
+ except HeaderNotFound :
743
+ self .logger .debug (
744
+ "Peer requested starting header %r that is unavailable, returning nothing" ,
745
+ request .block_number_or_hash )
746
+ block_numbers = tuple () # type: ignore
747
+
748
+ headers : Tuple [BlockHeader , ...] = tuple ([
749
+ header
750
+ async for header
751
+ in self ._generate_available_headers (block_numbers )
752
+ ])
753
+ return headers
754
+
755
+ async def _get_block_numbers_for_request (self ,
756
+ request : HeaderRequest ) -> Tuple [BlockNumber , ...]:
757
+ """
758
+ Generate the block numbers for a given `HeaderRequest`.
759
+ """
760
+ if isinstance (request .block_number_or_hash , bytes ):
761
+ header = await self .wait (
762
+ self .db .coro_get_block_header_by_hash (cast (Hash32 , request .block_number_or_hash )))
763
+ return request .generate_block_numbers (header .block_number )
764
+ elif isinstance (request .block_number_or_hash , int ):
765
+ # We don't need to pass in the block number to
766
+ # `generate_block_numbers` since the request is based on a numbered
767
+ # block identifier.
768
+ return request .generate_block_numbers ()
718
769
else :
719
770
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
771
+ "Invariant: unexpected type for 'block_number_or_hash': %s" ,
772
+ type (request .block_number_or_hash ),
773
+ )
725
774
726
775
async def _generate_available_headers (
727
- self , block_numbers : Tuple [BlockNumber ]) -> AsyncGenerator [BlockHeader , None ]:
776
+ self , block_numbers : Tuple [BlockNumber , ... ]) -> AsyncGenerator [BlockHeader , None ]:
728
777
"""
729
778
Generates the headers requested, halting on the first header that is not locally available.
730
779
"""
0 commit comments