diff --git a/basicswap/base.py b/basicswap/base.py index beb70b5..8c22a4d 100644 --- a/basicswap/base.py +++ b/basicswap/base.py @@ -18,6 +18,8 @@ from .chainparams import ( ) from .util import ( pubkeyToAddress, +) +from .rpc import ( callrpc, ) diff --git a/basicswap/rpc.py b/basicswap/rpc.py new file mode 100644 index 0000000..e9843af --- /dev/null +++ b/basicswap/rpc.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2020 tecnovert +# Distributed under the MIT software license, see the accompanying +# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. + +import os +import time +import json +import urllib +import logging +import traceback +import subprocess +from xmlrpc.client import ( + Transport, + Fault, +) +from .util import jsonDecimal + + +def waitForRPC(rpc_func, wallet=None): + for i in range(5): + try: + rpc_func('getwalletinfo') + return + except Exception as ex: + logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i)) + time.sleep(1 + i) + raise ValueError('waitForRPC failed') + + +class Jsonrpc(): + # __getattr__ complicates extending ServerProxy + def __init__(self, uri, transport=None, encoding=None, verbose=False, + allow_none=False, use_datetime=False, use_builtin_types=False, + *, context=None): + # establish a "logical" server connection + + # get the url + type, uri = urllib.parse.splittype(uri) + if type not in ("http", "https"): + raise OSError("unsupported XML-RPC protocol") + self.__host, self.__handler = urllib.parse.splithost(uri) + if not self.__handler: + self.__handler = "/RPC2" + + if transport is None: + handler = Transport + extra_kwargs = {} + transport = handler(use_datetime=use_datetime, + use_builtin_types=use_builtin_types, + **extra_kwargs) + self.__transport = transport + + self.__encoding = encoding or 'utf-8' + self.__verbose = verbose + self.__allow_none = allow_none + + def close(self): + if self.__transport is not None: + self.__transport.close() + + def json_request(self, method, params): + try: + connection = self.__transport.make_connection(self.__host) + headers = self.__transport._extra_headers[:] + + request_body = { + 'method': method, + 'params': params, + 'id': 2 + } + + connection.putrequest("POST", self.__handler) + headers.append(("Content-Type", "application/json")) + headers.append(("User-Agent", 'jsonrpc')) + self.__transport.send_headers(connection, headers) + self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8')) + + resp = connection.getresponse() + return resp.read() + + except Fault: + raise + except Exception: + # All unexpected errors leave connection in + # a strange state, so we clear it. + self.__transport.close() + raise + + +def callrpc(rpc_port, auth, method, params=[], wallet=None): + try: + url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port) + if wallet: + url += 'wallet/' + wallet + x = Jsonrpc(url) + + v = x.json_request(method, params) + x.close() + r = json.loads(v.decode('utf-8')) + except Exception as ex: + traceback.print_exc() + raise ValueError('RPC Server Error') + + if 'error' in r and r['error'] is not None: + raise ValueError('RPC error ' + str(r['error'])) + + return r['result'] + + +def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli'): + cli_bin = os.path.join(bindir, cli_bin) + + args = cli_bin + ('' if chain == 'mainnet' else ' -' + chain) + ' -datadir=' + datadir + ' ' + cmd + p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + out = p.communicate() + + if len(out[1]) > 0: + raise ValueError('RPC error ' + str(out[1])) + + r = out[0].decode('utf-8').strip() + try: + r = json.loads(r) + except Exception: + pass + return r diff --git a/basicswap/util.py b/basicswap/util.py index fcc1897..f0f3339 100644 --- a/basicswap/util.py +++ b/basicswap/util.py @@ -4,17 +4,9 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php. -import os import decimal -import subprocess import json -import traceback import hashlib -import urllib -from xmlrpc.client import ( - Transport, - Fault, -) from .segwit_addr import bech32_decode, convertbits, bech32_encode COIN = 100000000 @@ -196,102 +188,3 @@ def DeserialiseNum(b, o=0): if b[o + nb - 1] & 0x80: return -(v & ~(0x80 << (8 * (nb - 1)))) return v - - -class Jsonrpc(): - # __getattr__ complicates extending ServerProxy - def __init__(self, uri, transport=None, encoding=None, verbose=False, - allow_none=False, use_datetime=False, use_builtin_types=False, - *, context=None): - # establish a "logical" server connection - - # get the url - type, uri = urllib.parse.splittype(uri) - if type not in ("http", "https"): - raise OSError("unsupported XML-RPC protocol") - self.__host, self.__handler = urllib.parse.splithost(uri) - if not self.__handler: - self.__handler = "/RPC2" - - if transport is None: - handler = Transport - extra_kwargs = {} - transport = handler(use_datetime=use_datetime, - use_builtin_types=use_builtin_types, - **extra_kwargs) - self.__transport = transport - - self.__encoding = encoding or 'utf-8' - self.__verbose = verbose - self.__allow_none = allow_none - - def close(self): - if self.__transport is not None: - self.__transport.close() - - def json_request(self, method, params): - try: - connection = self.__transport.make_connection(self.__host) - headers = self.__transport._extra_headers[:] - - request_body = { - 'method': method, - 'params': params, - 'id': 2 - } - - connection.putrequest("POST", self.__handler) - headers.append(("Content-Type", "application/json")) - headers.append(("User-Agent", 'jsonrpc')) - self.__transport.send_headers(connection, headers) - self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8')) - - resp = connection.getresponse() - return resp.read() - - except Fault: - raise - except Exception: - # All unexpected errors leave connection in - # a strange state, so we clear it. - self.__transport.close() - raise - - -def callrpc(rpc_port, auth, method, params=[], wallet=None): - - try: - url = 'http://%s@127.0.0.1:%d/' % (auth, rpc_port) - if wallet: - url += 'wallet/' + wallet - x = Jsonrpc(url) - - v = x.json_request(method, params) - x.close() - r = json.loads(v.decode('utf-8')) - except Exception as ex: - traceback.print_exc() - raise ValueError('RPC Server Error') - - if 'error' in r and r['error'] is not None: - raise ValueError('RPC error ' + str(r['error'])) - - return r['result'] - - -def callrpc_cli(bindir, datadir, chain, cmd, cli_bin='particl-cli'): - cli_bin = os.path.join(bindir, cli_bin) - - args = cli_bin + ('' if chain == 'mainnet' else ' -' + chain) + ' -datadir=' + datadir + ' ' + cmd - p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - out = p.communicate() - - if len(out[1]) > 0: - raise ValueError('RPC error ' + str(out[1])) - - r = out[0].decode('utf-8').strip() - try: - r = json.loads(r) - except Exception: - pass - return r diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 649786a..260b264 100644 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -20,7 +20,6 @@ import mmap import tarfile import zipfile import stat -import time from urllib.request import urlretrieve import urllib.parse import logging @@ -29,7 +28,10 @@ import platform import gnupg import basicswap.config as cfg -from basicswap.util import callrpc_cli +from basicswap.rpc import ( + callrpc_cli, + waitForRPC, +) from bin.basicswap_run import startDaemon if platform.system() == 'Darwin': @@ -53,7 +55,7 @@ if not len(logger.handlers): def make_reporthook(): - read = 0 # number of bytes read so far + read = 0 # Number of bytes read so far last_percent_str = '' def reporthook(blocknum, blocksize, totalsize): @@ -280,17 +282,6 @@ def make_rpc_func(bin_dir, data_dir, chain): return rpc_func -def waitForRPC(rpc_func, wallet=None): - for i in range(10): - try: - rpc_func('getwalletinfo') - return - except Exception as ex: - logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i)) - time.sleep(1 + i) - raise ValueError('waitForRPC failed') - - def exitWithError(error_msg): sys.stderr.write('Error: {}, exiting.\n'.format(error_msg)) sys.exit(1) diff --git a/tests/basicswap/test_nmc.py b/tests/basicswap/test_nmc.py index 3e745b2..f4e1bca 100644 --- a/tests/basicswap/test_nmc.py +++ b/tests/basicswap/test_nmc.py @@ -34,7 +34,10 @@ from basicswap.basicswap import ( from basicswap.util import ( COIN, toWIF, +) +from basicswap.rpc import ( callrpc_cli, + waitForRPC, ) from basicswap.key import ( ECKey, @@ -206,17 +209,6 @@ def run_loop(self): btcRpc('generatetoaddress 1 {}'.format(self.btc_addr)) -def waitForRPC(rpc_func, wallet=None): - for i in range(5): - try: - rpc_func('getwalletinfo') - return - except Exception as ex: - logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i)) - time.sleep(1 + i) - raise ValueError('waitForRPC failed') - - class Test(unittest.TestCase): @classmethod diff --git a/tests/basicswap/test_reload.py b/tests/basicswap/test_reload.py index 3afb0c5..79a4612 100644 --- a/tests/basicswap/test_reload.py +++ b/tests/basicswap/test_reload.py @@ -31,7 +31,7 @@ from unittest.mock import patch from urllib.request import urlopen from urllib import parse -from basicswap.util import ( +from basicswap.rpc import ( callrpc_cli, ) diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py index bef4dc4..48a9dae 100644 --- a/tests/basicswap/test_run.py +++ b/tests/basicswap/test_run.py @@ -37,9 +37,12 @@ from basicswap.basicswap import ( from basicswap.util import ( COIN, toWIF, - callrpc_cli, dumpje, ) +from basicswap.rpc import ( + callrpc_cli, + waitForRPC, +) from basicswap.key import ( ECKey, ) @@ -210,17 +213,6 @@ def run_loop(self): btcRpc('generatetoaddress 1 {}'.format(self.btc_addr)) -def waitForRPC(rpc_func, wallet=None): - for i in range(5): - try: - rpc_func('getwalletinfo') - return - except Exception as ex: - logging.warning('Can\'t connect to daemon RPC: %s. Trying again in %d second/s.', str(ex), (1 + i)) - time.sleep(1 + i) - raise ValueError('waitForRPC failed') - - def checkForks(ro): if 'bip9_softforks' in ro: assert(ro['bip9_softforks']['csv']['status'] == 'active')