interface: split request_chunk method
This commit is contained in:
@@ -648,10 +648,9 @@ class Blockchain(Logger):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def connect_chunk(self, idx: int, hexdata: str) -> bool:
|
def connect_chunk(self, idx: int, data: bytes) -> bool:
|
||||||
assert idx >= 0, idx
|
assert idx >= 0, idx
|
||||||
try:
|
try:
|
||||||
data = bfh(hexdata)
|
|
||||||
self.verify_chunk(idx, data)
|
self.verify_chunk(idx, data)
|
||||||
self.save_chunk(idx, data)
|
self.save_chunk(idx, data)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -76,6 +76,9 @@ _KNOWN_NETWORK_PROTOCOLS = {'t', 's'}
|
|||||||
PREFERRED_NETWORK_PROTOCOL = 's'
|
PREFERRED_NETWORK_PROTOCOL = 's'
|
||||||
assert PREFERRED_NETWORK_PROTOCOL in _KNOWN_NETWORK_PROTOCOLS
|
assert PREFERRED_NETWORK_PROTOCOL in _KNOWN_NETWORK_PROTOCOLS
|
||||||
|
|
||||||
|
MAX_NUM_HEADERS_PER_REQUEST = 2016
|
||||||
|
assert MAX_NUM_HEADERS_PER_REQUEST >= CHUNK_SIZE
|
||||||
|
|
||||||
|
|
||||||
class NetworkTimeout:
|
class NetworkTimeout:
|
||||||
# seconds
|
# seconds
|
||||||
@@ -762,6 +765,42 @@ class Interface(Logger):
|
|||||||
res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout)
|
res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout)
|
||||||
return blockchain.deserialize_header(bytes.fromhex(res), height)
|
return blockchain.deserialize_header(bytes.fromhex(res), height)
|
||||||
|
|
||||||
|
async def get_block_headers(self, *, start_height: int, count: int) -> Sequence[bytes]:
|
||||||
|
"""Request a number of consecutive block headers, starting at `start_height`.
|
||||||
|
`count` is the num of requested headers, BUT note the server might return fewer than this
|
||||||
|
(if range would extend beyond its tip).
|
||||||
|
note: the returned headers are not verified or parsed at all.
|
||||||
|
"""
|
||||||
|
if not is_non_negative_integer(start_height):
|
||||||
|
raise Exception(f"{repr(start_height)} is not a block height")
|
||||||
|
if not is_non_negative_integer(count) or not (0 < count <= MAX_NUM_HEADERS_PER_REQUEST):
|
||||||
|
raise Exception(f"{repr(count)} not an int in range ]0, {MAX_NUM_HEADERS_PER_REQUEST}]")
|
||||||
|
self.logger.info(f'requesting block headers: [{start_height}, {start_height+count-1}], {count=}')
|
||||||
|
res = await self.session.send_request('blockchain.block.headers', [start_height, count])
|
||||||
|
# check response
|
||||||
|
assert_dict_contains_field(res, field_name='count')
|
||||||
|
assert_dict_contains_field(res, field_name='hex')
|
||||||
|
assert_dict_contains_field(res, field_name='max')
|
||||||
|
assert_non_negative_integer(res['count'])
|
||||||
|
assert_non_negative_integer(res['max'])
|
||||||
|
assert_hex_str(res['hex'])
|
||||||
|
if len(res['hex']) != HEADER_SIZE * 2 * res['count']:
|
||||||
|
raise RequestCorrupted('inconsistent chunk hex and count')
|
||||||
|
# we never request more than MAX_NUM_HEADERS_IN_REQUEST headers, but we enforce those fit in a single response
|
||||||
|
if res['max'] < MAX_NUM_HEADERS_PER_REQUEST:
|
||||||
|
raise RequestCorrupted(f"server uses too low 'max' count for block.headers: {res['max']} < {MAX_NUM_HEADERS_PER_REQUEST}")
|
||||||
|
if res['count'] > count:
|
||||||
|
raise RequestCorrupted(f"asked for {count} headers but got more: {res['count']}")
|
||||||
|
elif res['count'] < count:
|
||||||
|
# we only tolerate getting fewer headers if it is due to reaching the tip
|
||||||
|
end_height = start_height + res['count'] - 1
|
||||||
|
if end_height < self.tip: # still below tip. why did server not send more?!
|
||||||
|
raise RequestCorrupted(
|
||||||
|
f"asked for {count} headers but got fewer: {res['count']}. ({start_height=}, {self.tip=})")
|
||||||
|
# checks done.
|
||||||
|
headers = list(util.chunks(bfh(res['hex']), size=HEADER_SIZE))
|
||||||
|
return headers
|
||||||
|
|
||||||
async def request_chunk(
|
async def request_chunk(
|
||||||
self,
|
self,
|
||||||
height: int,
|
height: int,
|
||||||
@@ -774,33 +813,20 @@ class Interface(Logger):
|
|||||||
index = height // CHUNK_SIZE
|
index = height // CHUNK_SIZE
|
||||||
if can_return_early and index in self._requested_chunks:
|
if can_return_early and index in self._requested_chunks:
|
||||||
return None
|
return None
|
||||||
self.logger.info(f"requesting chunk from height {height}")
|
#self.logger.debug(f"requesting chunk from height {height}")
|
||||||
size = CHUNK_SIZE
|
size = CHUNK_SIZE
|
||||||
if tip is not None:
|
if tip is not None:
|
||||||
size = min(size, tip - index * CHUNK_SIZE + 1)
|
size = min(size, tip - index * CHUNK_SIZE + 1)
|
||||||
size = max(size, 0)
|
size = max(size, 0)
|
||||||
try:
|
try:
|
||||||
self._requested_chunks.add(index)
|
self._requested_chunks.add(index)
|
||||||
res = await self.session.send_request('blockchain.block.headers', [index * CHUNK_SIZE, size])
|
headers = await self.get_block_headers(start_height=index * CHUNK_SIZE, count=size)
|
||||||
finally:
|
finally:
|
||||||
self._requested_chunks.discard(index)
|
self._requested_chunks.discard(index)
|
||||||
assert_dict_contains_field(res, field_name='count')
|
conn = self.blockchain.connect_chunk(index, data=b"".join(headers))
|
||||||
assert_dict_contains_field(res, field_name='hex')
|
|
||||||
assert_dict_contains_field(res, field_name='max')
|
|
||||||
assert_non_negative_integer(res['count'])
|
|
||||||
assert_non_negative_integer(res['max'])
|
|
||||||
assert_hex_str(res['hex'])
|
|
||||||
if len(res['hex']) != HEADER_SIZE * 2 * res['count']:
|
|
||||||
raise RequestCorrupted('inconsistent chunk hex and count')
|
|
||||||
# we never request more than CHUNK_SIZE headers, but we enforce those fit in a single response
|
|
||||||
if res['max'] < CHUNK_SIZE:
|
|
||||||
raise RequestCorrupted(f"server uses too low 'max' count for block.headers: {res['max']} < {CHUNK_SIZE}")
|
|
||||||
if res['count'] != size:
|
|
||||||
raise RequestCorrupted(f"expected {size} headers but only got {res['count']}")
|
|
||||||
conn = self.blockchain.connect_chunk(index, res['hex'])
|
|
||||||
if not conn:
|
if not conn:
|
||||||
return conn, 0
|
return conn, 0
|
||||||
return conn, res['count']
|
return conn, len(headers)
|
||||||
|
|
||||||
def is_main_server(self) -> bool:
|
def is_main_server(self) -> bool:
|
||||||
return (self.network.interface == self or
|
return (self.network.interface == self or
|
||||||
@@ -916,7 +942,8 @@ class Interface(Logger):
|
|||||||
self.tip_header = header
|
self.tip_header = header
|
||||||
self.tip = height
|
self.tip = height
|
||||||
if self.tip < constants.net.max_checkpoint():
|
if self.tip < constants.net.max_checkpoint():
|
||||||
raise GracefulDisconnect('server tip below max checkpoint')
|
raise GracefulDisconnect(
|
||||||
|
f"server tip below max checkpoint. ({self.tip} < {constants.net.max_checkpoint()})")
|
||||||
self._mark_ready()
|
self._mark_ready()
|
||||||
blockchain_updated = await self._process_header_at_tip()
|
blockchain_updated = await self._process_header_at_tip()
|
||||||
# header processing done
|
# header processing done
|
||||||
|
|||||||
Reference in New Issue
Block a user