interface: implement support for protocol 1.6

ref https://github.com/spesmilo/electrum-protocol/pull/6
This commit is contained in:
SomberNight
2025-11-03 16:27:56 +00:00
parent cd3173a289
commit e57087daf7
3 changed files with 65 additions and 15 deletions

View File

@@ -868,13 +868,25 @@ class Interface(Logger):
res = await self.session.send_request('blockchain.block.headers', [start_height, count], timeout=timeout)
# 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')
if self.active_protocol_tuple >= (1, 6):
hex_headers_list = assert_dict_contains_field(res, field_name='headers')
assert_list_or_tuple(hex_headers_list)
for item in hex_headers_list:
assert_hex_str(item)
if len(item) != HEADER_SIZE * 2:
raise RequestCorrupted(f"invalid header size. got {len(item)//2}, expected {HEADER_SIZE}")
if len(hex_headers_list) != res['count']:
raise RequestCorrupted(f"{len(hex_headers_list)=} != {res['count']=}")
headers = list(bfh(hex_header) for hex_header in hex_headers_list)
else: # proto 1.4
hex_headers_concat = assert_dict_contains_field(res, field_name='hex')
assert_hex_str(hex_headers_concat)
if len(hex_headers_concat) != HEADER_SIZE * 2 * res['count']:
raise RequestCorrupted('inconsistent chunk hex and count')
headers = list(util.chunks(bfh(hex_headers_concat), size=HEADER_SIZE))
# 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}")
@@ -887,7 +899,6 @@ class Interface(Logger):
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_below_max_checkpoint(
@@ -1405,6 +1416,33 @@ class Interface(Logger):
# the status of a scripthash we are subscribed to. Caching here will save a future get_transaction RPC.
self._rawtx_cache[txid_calc] = bytes.fromhex(rawtx)
async def broadcast_txpackage(self, txs: Sequence['Transaction']) -> bool:
assert self.active_protocol_tuple >= (1, 6), f"server using old protocol: {self.active_protocol_tuple}"
rawtxs = [tx.serialize() for tx in txs]
assert all(is_hex_str(rawtx) for rawtx in rawtxs)
assert all(tx.txid() is not None for tx in txs)
timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Urgent)
for tx in txs:
if any(DummyAddress.is_dummy_address(txout.address) for txout in tx.outputs()):
raise DummyAddressUsedInTxException("tried to broadcast tx with dummy address!")
try:
res = await self.session.send_request('blockchain.transaction.broadcast_package', [rawtxs], timeout=timeout)
except aiorpcx.jsonrpc.CodeMessageError as e:
self.logger.info(f"broadcast_txpackage error [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(e))}. {rawtxs=}")
return False
success = assert_dict_contains_field(res, field_name='success')
if not success:
errors = assert_dict_contains_field(res, field_name='errors')
self.logger.info(f"broadcast_txpackage error [DO NOT TRUST THIS MESSAGE]: {error_text_str_to_safe_str(repr(errors))}. {rawtxs=}")
return False
assert success
# broadcast succeeded.
# We now cache the rawtx, for *this interface only*. The tx likely touches some ismine addresses, affecting
# the status of a scripthash we are subscribed to. Caching here will save a future get_transaction RPC.
for tx, rawtx in zip(txs, rawtxs):
self._rawtx_cache[tx.txid()] = bytes.fromhex(rawtx)
return True
async def get_history_for_scripthash(self, sh: str) -> List[dict]:
if not is_hash256_str(sh):
raise Exception(f"{repr(sh)} is not a scripthash")
@@ -1525,10 +1563,14 @@ class Interface(Logger):
async def get_relay_fee(self) -> int:
"""Returns the min relay feerate in sat/kbyte."""
# do request
res = await self.session.send_request('blockchain.relayfee')
if self.active_protocol_tuple >= (1, 6):
res = await self.session.send_request('mempool.get_info')
minrelaytxfee = assert_dict_contains_field(res, field_name='minrelaytxfee')
else:
minrelaytxfee = await self.session.send_request('blockchain.relayfee')
# check response
assert_non_negative_int_or_float(res)
relayfee = int(res * bitcoin.COIN)
assert_non_negative_int_or_float(minrelaytxfee)
relayfee = int(minrelaytxfee * bitcoin.COIN)
relayfee = max(0, relayfee)
return relayfee