1
0

interface: split request_chunk method

This commit is contained in:
SomberNight
2025-06-07 15:16:26 +00:00
parent 57bb98dc7f
commit b01e69cf45
2 changed files with 46 additions and 20 deletions

View File

@@ -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

View File

@@ -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