Skip to content

Commit 031040d

Browse files
committed
light peer: get code recursion -> capped loop
1 parent 41b47b2 commit 031040d

File tree

3 files changed

+40
-7
lines changed

3 files changed

+40
-7
lines changed

p2p/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535

3636
# Timeout used when waiting for a reply from a remote node.
3737
REPLY_TIMEOUT = 3
38+
MAX_REQUEST_ATTEMPTS = 3
3839

3940
# Timeout used when performing the check to ensure peers are on the same side of chain splits as
4041
# us.

p2p/exceptions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ class BadAckMessage(BaseP2PError):
138138
class BadLESResponse(BaseP2PError):
139139
"""
140140
Raised when the response to a LES request doesn't contain the data we asked for.
141+
142+
The peer can be treated as violating protocol. Often, the repurcussion should be
143+
disconnection and blacklisting.
141144
"""
142145
pass
143146

p2p/lightchain.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from p2p import protocol
4242
from p2p.constants import (
4343
COMPLETION_TIMEOUT,
44+
MAX_REQUEST_ATTEMPTS,
4445
REPLY_TIMEOUT,
4546
)
4647
from p2p.p2p_proto import (
@@ -159,16 +160,42 @@ async def get_contract_code(self, block_hash: Hash32, address: Address) -> bytes
159160
:param address: which contract to look up
160161
161162
:return: bytecode of the contract, ``b''`` if no code is set
162-
"""
163-
peer = cast(LESPeer, self.peer_pool.highest_td_peer)
164163
164+
:raise NoEligiblePeers: if no peers are available to fulfill the request
165+
:raise TimeoutError: if an individual request or the overall process times out
166+
"""
165167
# get account for later verification, and
166168
# to confirm that our highest total difficulty peer has the info
167169
try:
168170
account = await self.get_account(block_hash, address)
169171
except HeaderNotFound as exc:
170172
raise NoEligiblePeers("Our best peer does not have header %s" % block_hash) from exc
171173

174+
code_hash = account.code_hash
175+
176+
for _ in range(MAX_REQUEST_ATTEMPTS):
177+
peer = cast(LESPeer, self.peer_pool.highest_td_peer)
178+
try:
179+
return await self._get_contract_code_from_peer(block_hash, address, peer, code_hash)
180+
except BadLESResponse as exc:
181+
self.logger.warn("Disconnecting from peer, because: %s", exc)
182+
await self.disconnect_peer(peer, DisconnectReason.subprotocol_error)
183+
# reattempt after removing this peer from our pool
184+
185+
raise TimeoutError("Could not get contract code within %d attempts" % MAX_REQUEST_ATTEMPTS)
186+
187+
async def _get_contract_code_from_peer(
188+
self,
189+
block_hash: Hash32,
190+
address: Address,
191+
peer: LESPeer,
192+
code_hash: Hash32) -> bytes:
193+
"""
194+
A single attempt to get the contract code from the given peer
195+
196+
:raise BadLESResponse: if the peer replies with contract code that does not match the
197+
account's code hash
198+
"""
172199
# request contract code
173200
request_id = gen_request_id()
174201
peer.sub_proto.send_get_contract_code(block_hash, keccak(address), request_id)
@@ -180,18 +207,20 @@ async def get_contract_code(self, block_hash: Hash32, address: Address) -> bytes
180207
bytecode = reply['codes'][0]
181208

182209
# validate bytecode against a proven account
183-
if account.code_hash == keccak(bytecode):
210+
if code_hash == keccak(bytecode):
184211
return bytecode
185212
elif bytecode == b'':
186213
# TODO disambiguate failure types here, and raise the appropriate exception
187214
# An (incorrectly) empty bytecode might indicate a bad-acting peer, or it might not
188215
raise NoEligiblePeers("Our best peer incorrectly responded with an empty code value")
189216
else:
190217
# a bad-acting peer sent an invalid non-empty bytecode
191-
# disconnect from the peer
192-
await self.disconnect_peer(peer, DisconnectReason.subprotocol_error)
193-
# try again with another peer
194-
return await self.get_contract_code(block_hash, address)
218+
raise BadLESResponse("Peer %s sent code %s that did not match hash %s in account %s" % (
219+
peer,
220+
encode_hex(bytecode),
221+
encode_hex(code_hash),
222+
encode_hex(address),
223+
))
195224

196225
async def _get_block_header_by_hash(self, peer: LESPeer, block_hash: Hash32) -> BlockHeader:
197226
self.logger.debug("Fetching header %s from %s", encode_hex(block_hash), peer)

0 commit comments

Comments
 (0)