Skip to content

Commit e28bc28

Browse files
committed
implement new "server.ping" semantics for protocol 1.6
1 parent 06a19b0 commit e28bc28

File tree

1 file changed

+32
-11
lines changed

1 file changed

+32
-11
lines changed

electrumx/server/session.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
from collections import defaultdict
1919
from functools import partial
2020
from ipaddress import IPv4Address, IPv6Address, IPv4Network, IPv6Network
21-
from typing import Optional, TYPE_CHECKING, Tuple, Sequence, Set, Dict, Iterable, Any, Mapping
21+
from typing import (Optional, TYPE_CHECKING, Tuple, Sequence, Set, Dict, Iterable, Any, Mapping,
22+
Callable)
2223
import asyncio
2324

2425
import attr
2526
from aiorpcx import (Event, JSONRPCAutoDetect, JSONRPCConnection,
26-
ReplyAndDisconnect, Request, RPCError, RPCSession,
27+
ReplyAndDisconnect, Request, Notification, RPCError, RPCSession,
2728
handler_invocation, serve_rs, serve_ws, sleep,
2829
NewlineFramer, TaskTimeout, timeout_after, run_in_thread)
30+
from aiorpcx.jsonrpc import SingleRequest
2931

3032
import electrumx
3133
import electrumx.lib.util as util
@@ -938,6 +940,8 @@ class SessionBase(RPCSession):
938940
MAX_CHUNK_SIZE = 2016
939941
session_counter = itertools.count()
940942
log_new = False
943+
request_handlers: Dict[str, Callable]
944+
notification_handlers: Dict[str, Callable]
941945

942946
def __init__(
943947
self,
@@ -1025,14 +1029,13 @@ def sub_count_txoutpoints(self):
10251029
def sub_count_total(self):
10261030
return self.sub_count_scripthashes() + self.sub_count_txoutpoints()
10271031

1028-
async def handle_request(self, request):
1029-
'''Handle an incoming request. ElectrumX doesn't receive
1030-
notifications from client sessions.
1031-
'''
1032+
async def handle_request(self, request: SingleRequest):
1033+
'''Handle an incoming request.'''
1034+
handler = None
10321035
if isinstance(request, Request):
10331036
handler = self.request_handlers.get(request.method)
1034-
else:
1035-
handler = None
1037+
elif isinstance(request, Notification):
1038+
handler = self.notification_handlers.get(request.method)
10361039
method = 'invalid method' if handler is None else request.method
10371040

10381041
# Version negotiation must happen before any other messages.
@@ -1642,12 +1645,25 @@ async def estimatefee(self, number, mode=None):
16421645
cache[(number, mode)] = (blockhash, feerate, lock)
16431646
return feerate
16441647

1645-
async def ping(self):
1648+
async def ping(self, pong_len=0, data=""):
16461649
'''Serves as a connection keep-alive mechanism and for the client to
1647-
confirm the server is still responding.
1650+
confirm the server is still responding. It can also be used to obfuscate
1651+
traffic patterns.
16481652
'''
16491653
self.bump_cost(0.1)
1650-
return None
1654+
if self.protocol_tuple < (1, 6):
1655+
return None
1656+
assert_hex_str(data)
1657+
pong_len = non_negative_integer(pong_len)
1658+
if pong_len > self.env.max_send:
1659+
raise RPCError(BAD_REQUEST, f'pong_len value too high')
1660+
pong_data = pong_len * "0"
1661+
return {"data": pong_data}
1662+
1663+
async def on_ping_notification(self, data=""):
1664+
self.bump_cost(0.1) # note: the bandwidth cost for receiving 'data' has already been incurred
1665+
assert_hex_str(data)
1666+
# nothing to do
16511667

16521668
async def server_version(self, client_name='', protocol_version=None):
16531669
'''Returns the server version as a string.
@@ -1856,6 +1872,7 @@ def set_request_handlers(self, ptuple):
18561872
'server.ping': self.ping,
18571873
'server.version': self.server_version,
18581874
}
1875+
notif_handlers = {}
18591876

18601877
if ptuple < (1, 6):
18611878
handlers['blockchain.scripthash.get_balance'] = self.scripthash_get_balance
@@ -1878,8 +1895,10 @@ def set_request_handlers(self, ptuple):
18781895
handlers['blockchain.scriptpubkey.listunspent'] = self.scriptpubkey_listunspent
18791896
handlers['blockchain.scriptpubkey.subscribe'] = self.scriptpubkey_subscribe
18801897
handlers['blockchain.scriptpubkey.unsubscribe'] = self.scriptpubkey_unsubscribe
1898+
notif_handlers['server.ping'] = self.on_ping_notification
18811899

18821900
self.request_handlers = handlers
1901+
self.notification_handlers = notif_handlers
18831902

18841903

18851904
class LocalRPC(SessionBase):
@@ -1893,6 +1912,8 @@ def __init__(self, *args, **kwargs):
18931912
self.sv_negotiated.set()
18941913
self.client = 'RPC'
18951914
self.connection.max_response_size = 0
1915+
# note: self.request_handlers are set on the class, in SessionManager.__init__
1916+
self.notification_handlers = {}
18961917

18971918
def protocol_version_string(self):
18981919
return 'RPC'

0 commit comments

Comments
 (0)