async def _get_block_header_by_hash(self, block_hash: Hash32, peer: LESPeer) -> BlockHeader: """ A single attempt to get the block header from the given peer. :raise BadLESResponse: if the peer replies with a header that has a different hash """ self.logger.debug("Fetching header %s from %s", encode_hex(block_hash), peer) max_headers = 1 # TODO: Figure out why mypy thinks the first parameter to `get_block_headers` # should be of type `int` headers = await peer.chain_api.get_block_headers( block_hash, max_headers, skip=0, reverse=False, ) if not headers: raise HeaderNotFound( f"Peer {peer} has no block with hash {block_hash}") header = headers[0] if header.hash != block_hash: raise BadLESResponse( f"Received header hash ({header.hex_hash}) does not " f"match what we requested ({encode_hex(block_hash)})") return header
async def _get_contract_code_from_peer( self, block_hash: Hash32, address: ETHAddress, code_hash: Hash32, peer: LESPeer) -> bytes: """ A single attempt to get the contract code from the given peer :raise BadLESResponse: if the peer replies with contract code that does not match the account's code hash """ # request contract code request_id = peer.sub_proto.send_get_contract_code(block_hash, keccak(address)) reply = await self._wait_for_reply(request_id) if not reply['codes']: bytecode = b'' else: bytecode = reply['codes'][0] # validate bytecode against a proven account if code_hash == keccak(bytecode): return bytecode elif bytecode == b'': await self._raise_for_empty_code(block_hash, address, code_hash, peer) # The following is added for mypy linting: raise RuntimeError("Unreachable, _raise_for_empty_code must raise its own exception") else: # a bad-acting peer sent an invalid non-empty bytecode raise BadLESResponse( "Peer {peer} sent code {encode_hex(bytecode)} that did not match " f"hash {encode_hex(code_hash)} in account {encode_hex(address)}" )
async def _get_block_header_by_hash(self, block_hash: Hash32, peer: LESPeer) -> BlockHeader: """ A single attempt to get the block header from the given peer. :raise BadLESResponse: if the peer replies with a header that has a different hash """ self.logger.debug("Fetching header %s from %s", encode_hex(block_hash), peer) max_headers = 1 headers = await peer.requests.get_block_headers( block_hash, max_headers, skip=0, reverse=False, ) if not headers: raise HeaderNotFound("Peer {} has no block with hash {}".format( peer, block_hash)) header = headers[0] if header.hash != block_hash: raise BadLESResponse( "Received header hash (%s) does not match what we requested (%s)", header.hex_hash, encode_hex(block_hash)) return header
async def _raise_for_empty_code( self, block_hash: Hash32, address: Address, code_hash: Hash32, peer: LESPeer) -> None: """ A peer might return b'' if it doesn't have the block at the requested header, or it might maliciously return b'' when the code is non-empty. This method tries to tell the difference. This method MUST raise an exception, it's trying to determine the appropriate one. :raise BadLESResponse: if peer seems to be maliciously responding with invalid empty code :raise NoEligiblePeers: if peer might simply not have the code available """ try: header = await self._get_block_header_by_hash(block_hash, peer) except HeaderNotFound: # We presume that the current peer is the best peer. Because # our best peer doesn't have the header we want, there are no eligible peers. raise NoEligiblePeers("Our best peer does not have the header %s" % block_hash) head_number = peer.head_info.block_number if head_number - header.block_number > MAX_REORG_DEPTH: # The peer claims to be far ahead of the header we requested if self.headerdb.get_canonical_block_hash(header.block_number) == block_hash: # Our node believes that the header at the reference hash is canonical, # so treat the peer as malicious raise BadLESResponse( "Peer %s sent empty code that did not match hash %s in account %s" % ( peer, encode_hex(code_hash), encode_hex(address), ) ) else: # our header isn't canonical, so treat the empty response as missing data raise NoEligiblePeers( "Our best peer does not have the non-canonical header %s" % block_hash ) elif head_number - header.block_number < 0: # The peer claims to be behind the header we requested, but somehow served it to us. # Odd, it might be a race condition. Treat as if there are no eligible peers for now. raise NoEligiblePeers("Our best peer's head does include header %s" % block_hash) else: # The peer is ahead of the current block header, but only by a bit. It might be on # an uncle, or we might be. So we can't tell the difference between missing and # malicious. We don't want to aggressively drop this peer, so treat the code as missing. raise NoEligiblePeers( "Peer %s claims to be ahead of %s, but returned empty code with hash %s. " "It is on number %d, maybe an uncle. Retry with an older block hash." % ( peer, header, code_hash, head_number, ) )
async def _get_block_header_by_hash(self, peer: LESPeer, block_hash: Hash32) -> BlockHeader: self.logger.debug("Fetching header %s from %s", encode_hex(block_hash), peer) request_id = gen_request_id() max_headers = 1 peer.sub_proto.send_get_block_headers(block_hash, max_headers, request_id) reply = await self._wait_for_reply(request_id) if not reply['headers']: raise HeaderNotFound("Peer {} has no block with hash {}".format(peer, block_hash)) header = reply['headers'][0] if header.hash != block_hash: raise BadLESResponse( "Received header hash (%s) does not match what we requested (%s)", header.hex_hash, encode_hex(block_hash)) return header
async def _get_account_from_peer( self, block_hash: Hash32, address: ETHAddress, peer: LESPeer) -> Account: key = keccak(address) proof = await self._get_proof(peer, block_hash, account_key=b'', key=key) header = await self._get_block_header_by_hash(block_hash, peer) try: rlp_account = HexaryTrie.get_from_proof(header.state_root, key, proof) except BadTrieProof as exc: raise BadLESResponse( f"Peer {peer} returned an invalid proof for account {encode_hex(address)} " f"at block {encode_hex(block_hash)}" ) from exc return rlp.decode(rlp_account, sedes=Account)
async def _get_block_header_by_hash(self, block_hash: Hash32, peer: LESPeer) -> BlockHeader: """ A single attempt to get the block header from the given peer. :raise BadLESResponse: if the peer replies with a header that has a different hash """ self.logger.debug("Fetching header %s from %s", encode_hex(block_hash), peer) request_id = gen_request_id() max_headers = 1 peer.sub_proto.send_get_block_headers(block_hash, max_headers, 0, False, request_id) reply = await self._wait_for_reply(request_id) if not reply['headers']: raise HeaderNotFound("Peer {} has no block with hash {}".format( peer, block_hash)) header = reply['headers'][0] if header.hash != block_hash: raise BadLESResponse( "Received header hash (%s) does not match what we requested (%s)", header.hex_hash, encode_hex(block_hash)) return header