commit d9c525801420d07b3597295bfb43003ff92811bd
parent 35093434473ef4331c93d49d4220f0c56922b260
Author: ThomasV <thomasv@electrum.org>
Date: Tue, 9 Jun 2020 19:37:31 +0200
Merge pull request #6220 from spesmilo/jsonrpc_nodeps
Remove dependencies: jsonrpcserver, jsonrpcclient
Diffstat:
6 files changed, 72 insertions(+), 41 deletions(-)
diff --git a/contrib/build-wine/deterministic.spec b/contrib/build-wine/deterministic.spec
@@ -50,8 +50,6 @@ datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
datas += collect_data_files('bitbox02')
-datas += collect_data_files('jsonrpcserver')
-datas += collect_data_files('jsonrpcclient')
# We don't put these files in to actually include them in the script but to make the Analysis method scan them for imports
a = Analysis([home+'run_electrum',
diff --git a/contrib/osx/osx.spec b/contrib/osx/osx.spec
@@ -83,8 +83,6 @@ datas += collect_data_files('btchip')
datas += collect_data_files('keepkeylib')
datas += collect_data_files('ckcc')
datas += collect_data_files('bitbox02')
-datas += collect_data_files('jsonrpcserver')
-datas += collect_data_files('jsonrpcclient')
# Add the QR Scanner helper app
datas += [(electrum + "contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app", "./contrib/osx/CalinsQRReader/build/Release/CalinsQRReader.app")]
diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt
@@ -9,6 +9,4 @@ aiohttp>=3.3.0,<4.0.0
aiohttp_socks>=0.3
certifi
bitstring
-jsonrpcserver
-jsonrpcclient
attrs
diff --git a/electrum/daemon.py b/electrum/daemon.py
@@ -29,18 +29,15 @@ import time
import traceback
import sys
import threading
-from typing import Dict, Optional, Tuple, Iterable
+from typing import Dict, Optional, Tuple, Iterable, Callable, Union, Sequence, Mapping
from base64 import b64decode, b64encode
from collections import defaultdict
import concurrent
from concurrent import futures
+import json
import aiohttp
from aiohttp import web, client_exceptions
-import jsonrpcclient
-import jsonrpcserver
-from jsonrpcserver import response
-from jsonrpcclient.clients.aiohttp_client import AiohttpClient
from aiorpcx import TaskGroup
from . import util
@@ -107,10 +104,8 @@ def request(config: SimpleConfig, endpoint, args=(), timeout=60):
loop = asyncio.get_event_loop()
async def request_coroutine():
async with aiohttp.ClientSession(auth=auth) as session:
- server = AiohttpClient(session, server_url, timeout=timeout)
- f = getattr(server, endpoint)
- response = await f(*args)
- return response.data.result
+ c = util.JsonRPCClient(session, server_url)
+ return await c.request(endpoint, *args)
try:
fut = asyncio.run_coroutine_threadsafe(request_coroutine(), loop)
return fut.result(timeout=timeout)
@@ -156,6 +151,11 @@ class AuthenticatedServer(Logger):
self.rpc_user = rpc_user
self.rpc_password = rpc_password
self.auth_lock = asyncio.Lock()
+ self._methods = {} # type: Dict[str, Callable]
+
+ def register_method(self, f):
+ assert f.__name__ not in self._methods, f"name collision for {f.__name__}"
+ self._methods[f.__name__] = f
async def authenticate(self, headers):
if self.rpc_password == '':
@@ -184,16 +184,28 @@ class AuthenticatedServer(Logger):
text='Unauthorized', status=401)
except AuthenticationCredentialsInvalid:
return web.Response(text='Forbidden', status=403)
- request = await request.text()
- response = await jsonrpcserver.async_dispatch(request, methods=self.methods)
- if isinstance(response, jsonrpcserver.response.ExceptionResponse):
- self.logger.error(f"error handling request: {request}", exc_info=response.exc)
- # this exposes the error message to the client
- response.message = str(response.exc)
- if response.wanted:
- return web.json_response(response.deserialized(), status=response.http_status)
- else:
- return web.Response()
+ try:
+ request = await request.text()
+ request = json.loads(request)
+ method = request['method']
+ _id = request['id']
+ params = request.get('params', []) # type: Union[Sequence, Mapping]
+ if method not in self._methods:
+ raise Exception(f"attempting to use unregistered method: {method}")
+ f = self._methods[method]
+ except Exception as e:
+ self.logger.exception("invalid request")
+ return web.Response(text='Invalid Request', status=500)
+ response = {'id': _id}
+ try:
+ if isinstance(params, dict):
+ response['result'] = await f(**params)
+ else:
+ response['result'] = await f(*params)
+ except BaseException as e:
+ self.logger.exception("internal error while executing RPC")
+ response['error'] = str(e)
+ return web.json_response(response)
class CommandsServer(AuthenticatedServer):
@@ -208,13 +220,12 @@ class CommandsServer(AuthenticatedServer):
self.port = self.config.get('rpcport', 0)
self.app = web.Application()
self.app.router.add_post("/", self.handle)
- self.methods = jsonrpcserver.methods.Methods()
- self.methods.add(self.ping)
- self.methods.add(self.gui)
+ self.register_method(self.ping)
+ self.register_method(self.gui)
self.cmd_runner = Commands(config=self.config, network=self.daemon.network, daemon=self.daemon)
for cmdname in known_commands:
- self.methods.add(getattr(self.cmd_runner, cmdname))
- self.methods.add(self.run_cmdline)
+ self.register_method(getattr(self.cmd_runner, cmdname))
+ self.register_method(self.run_cmdline)
async def run(self):
self.runner = web.AppRunner(self.app)
@@ -276,9 +287,8 @@ class WatchTowerServer(AuthenticatedServer):
self.lnwatcher = network.local_watchtower
self.app = web.Application()
self.app.router.add_post("/", self.handle)
- self.methods = jsonrpcserver.methods.Methods()
- self.methods.add(self.get_ctn)
- self.methods.add(self.add_sweep_tx)
+ self.register_method(self.get_ctn)
+ self.register_method(self.add_sweep_tx)
async def run(self):
self.runner = web.AppRunner(self.app)
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
@@ -10,6 +10,7 @@ import time
from typing import Optional, Sequence, Tuple, List, Dict, TYPE_CHECKING, NamedTuple, Union, Mapping
import threading
import socket
+import aiohttp
import json
from datetime import datetime, timezone
from functools import partial
@@ -25,7 +26,7 @@ from . import constants, util
from . import keystore
from .util import profiler
from .invoices import PR_TYPE_LN, PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, LNInvoice, LN_EXPIRY_NEVER
-from .util import NetworkRetryManager
+from .util import NetworkRetryManager, JsonRPCClient
from .lnutil import LN_MAX_FUNDING_SAT
from .keystore import BIP32_KeyStore
from .bitcoin import COIN
@@ -525,12 +526,6 @@ class LNWallet(LNWorker):
@ignore_exceptions
@log_exceptions
async def sync_with_remote_watchtower(self):
- import aiohttp
- from jsonrpcclient.clients.aiohttp_client import AiohttpClient
- class myAiohttpClient(AiohttpClient):
- async def request(self, *args, **kwargs):
- r = await super().request(*args, **kwargs)
- return r.data.result
while True:
await asyncio.sleep(5)
watchtower_url = self.config.get('watchtower_url')
@@ -538,7 +533,9 @@ class LNWallet(LNWorker):
continue
try:
async with make_aiohttp_session(proxy=self.network.proxy) as session:
- watchtower = myAiohttpClient(session, watchtower_url)
+ watchtower = JsonRPCClient(session, watchtower_url)
+ watchtower.add_method('get_ctn')
+ watchtower.add_method('add_sweep_tx')
for chan in self.channels.values():
await self.sync_channel_with_watchtower(chan, watchtower)
except aiohttp.client_exceptions.ClientConnectorError:
diff --git a/electrum/util.py b/electrum/util.py
@@ -1368,3 +1368,33 @@ class MySocksProxy(aiorpcx.SOCKSProxy):
else:
raise NotImplementedError # http proxy not available with aiorpcx
return ret
+
+
+class JsonRPCClient:
+
+ def __init__(self, session: aiohttp.ClientSession, url: str):
+ self.session = session
+ self.url = url
+ self._id = 0
+
+ async def request(self, endpoint, *args):
+ self._id += 1
+ data = ('{"jsonrpc": "2.0", "id":"%d", "method": "%s", "params": %s }'
+ % (self._id, endpoint, json.dumps(args)))
+ async with self.session.post(self.url, data=data) as resp:
+ if resp.status == 200:
+ r = await resp.json()
+ result = r.get('result')
+ error = r.get('error')
+ if error:
+ return 'Error: ' + str(error)
+ else:
+ return result
+ else:
+ text = await resp.text()
+ return 'Error: ' + str(text)
+
+ def add_method(self, endpoint):
+ async def coro(*args):
+ return await self.request(endpoint, *args)
+ setattr(self, endpoint, coro)