Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions electrum/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,13 @@ def assert_hex_str(val: Any) -> None:
raise RequestCorrupted(f'{val!r} should be a hex str')


def assert_dict_contains_field(d: Any, *, field_name: str) -> Any:
def assert_dict(d: Any) -> None:
if not isinstance(d, dict):
raise RequestCorrupted(f'{d!r} should be a dict')


def assert_dict_contains_field(d: Any, *, field_name: str) -> Any:
assert_dict(d)
if field_name not in d:
raise RequestCorrupted(f'required field {field_name!r} missing from dict')
return d[field_name]
Expand Down Expand Up @@ -163,8 +167,8 @@ class NotificationSession(RPCSession):

def __init__(self, *args, interface: 'Interface', **kwargs):
super(NotificationSession, self).__init__(*args, **kwargs)
self.subscriptions = defaultdict(list)
self.cache = {}
self.subscriptions = defaultdict(list) # type: defaultdict[str, list[asyncio.Queue]]
self.subs_cache = {} # type: dict[str, Any]
self._msg_counter = itertools.count(start=1)
self.interface = interface
self.taskgroup = interface.taskgroup
Expand All @@ -181,7 +185,7 @@ async def handle_request(self, request):
params, result = request.args[:-1], request.args[-1]
key = self.get_hashable_key_for_rpc_call(request.method, params)
if key in self.subscriptions:
self.cache[key] = result
self.subs_cache[key] = result
for queue in self.subscriptions[key]:
await queue.put(request.args)
else:
Expand Down Expand Up @@ -223,15 +227,17 @@ def set_default_timeout(self, timeout):
self.max_send_delay = timeout

async def subscribe(self, method: str, params: List, queue: asyncio.Queue):
# note: until the cache is written for the first time,
# each 'subscribe' call might make a request on the network.
key = self.get_hashable_key_for_rpc_call(method, params)
# note: multiple Synchronizers (from different Wallet objects) might sub to the same key,
# hence subscriptions map key->list[queue]
self.subscriptions[key].append(queue)
if key in self.cache:
result = self.cache[key]
if key in self.subs_cache:
result = self.subs_cache[key]
else:
# note: until subs_cache is written for the first time,
# each 'subscribe' call might make a request on the network.
result = await self.send_request(method, params)
self.cache[key] = result
self.subs_cache[key] = result
await queue.put(params + [result])

def unsubscribe(self, queue):
Expand Down Expand Up @@ -1093,10 +1099,17 @@ async def run_fetch_blocks(self):
await self.session.subscribe('blockchain.headers.subscribe', [], header_queue)
Comment thread
ecdsa marked this conversation as resolved.
while True:
item = await header_queue.get()
raw_header = item[0]
height = raw_header['height']
header_bytes = bfh(raw_header['hex'])
# parse response
assert len(item) == 1
resp_header = item[0]
assert_dict(resp_header)
height = assert_dict_contains_field(resp_header, field_name='height')
assert_non_negative_integer(height)
header_hex = assert_dict_contains_field(resp_header, field_name='hex')
header_bytes = bfh(header_hex)
assert len(resp_header) == 2, f"resp_header contains redundant fields. got {resp_header.keys()}"
header_dict = blockchain.deserialize_header(header_bytes, height)
# process header
self.tip_header = header_dict
self.tip = height
if self.tip < constants.net.max_checkpoint():
Expand Down
11 changes: 8 additions & 3 deletions electrum/synchronizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from .util import make_aiohttp_session, NetworkJobOnDefaultServer, random_shuffled_copy, OldTaskGroup
from .bitcoin import address_to_scripthash, is_address, neuter_bitcoin_address
from .logging import Logger
from .interface import GracefulDisconnect, NetworkTimeout
from .interface import GracefulDisconnect, NetworkTimeout, assert_hash256_str

if TYPE_CHECKING:
from .network import Network
Expand Down Expand Up @@ -117,8 +117,13 @@ async def _subscribe_to_address(self, addr):

async def handle_status(self):
while True:
h, status = await self.status_queue.get()
addr = self.scripthash_to_address[h]
sh, status = await self.status_queue.get()
# basic checks for response
assert_hash256_str(sh)
if status is not None:
assert_hash256_str(status)
# process status
addr = self.scripthash_to_address[sh]
self._handling_addr_statuses.add(addr)
self.requested_addrs.discard(addr) # ok for addr not to be present
await self.taskgroup.spawn(self._on_address_status, addr, status)
Expand Down