Browse Source

xmr: Support daemon rpc login details.

pull/22/head
tecnovert 2 years ago
parent
commit
2a35148a4b
No known key found for this signature in database GPG Key ID: 8ED6D8750C4E3F93
  1. 2
      basicswap/__init__.py
  2. 20
      basicswap/basicswap.py
  3. 2
      basicswap/chainparams.py
  4. 12
      basicswap/interface/xmr.py
  5. 55
      basicswap/rpc_xmr.py
  6. 8
      bin/basicswap_prepare.py
  7. 7
      bin/basicswap_run.py
  8. 56
      doc/notes.md
  9. 22
      tests/basicswap/common_xmr.py
  10. 10
      tests/basicswap/extended/test_xmr_persistent.py
  11. 15
      tests/basicswap/test_xmr.py

2
basicswap/__init__.py

@ -1,3 +1,3 @@
name = "basicswap"
__version__ = "0.11.49"
__version__ = "0.11.50"

20
basicswap/basicswap.py

@ -467,17 +467,25 @@ class BasicSwap(BaseApp):
else:
raise ValueError('Missing XMR wallet rpc credentials.')
self.coin_clients[coin]['rpcuser'] = chain_client_settings.get('rpcuser', '')
self.coin_clients[coin]['rpcpassword'] = chain_client_settings.get('rpcpassword', '')
def selectXMRRemoteDaemon(self, coin):
self.log.info('Selecting remote XMR daemon.')
chain_client_settings = self.getChainClientSettings(coin)
remote_daemon_urls = chain_client_settings.get('remote_daemon_urls', [])
rpchost = self.coin_clients[coin]['rpchost']
rpcport = self.coin_clients[coin]['rpcport']
coin_settings = self.coin_clients[coin]
rpchost = coin_settings['rpchost']
rpcport = coin_settings['rpcport']
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
current_daemon_url = f'{rpchost}:{rpcport}'
if current_daemon_url in remote_daemon_urls:
self.log.info(f'Trying last used url {rpchost}:{rpcport}.')
try:
rpc_cb2 = make_xmr_rpc2_func(rpcport, rpchost)
rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc_cb2('get_height', timeout=20)['height']
return True
except Exception as e:
@ -487,10 +495,10 @@ class BasicSwap(BaseApp):
self.log.info(f'Trying url {url}.')
try:
rpchost, rpcport = url.rsplit(':', 1)
rpc_cb2 = make_xmr_rpc2_func(rpcport, rpchost)
rpc_cb2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost)
test = rpc_cb2('get_height', timeout=20)['height']
self.coin_clients[coin]['rpchost'] = rpchost
self.coin_clients[coin]['rpcport'] = rpcport
coin_settings['rpchost'] = rpchost
coin_settings['rpcport'] = rpcport
data = {
'rpchost': rpchost,
'rpcport': rpcport,

2
basicswap/chainparams.py

@ -411,6 +411,8 @@ class CoinInterface:
str_error = str(ex).lower()
if 'not enough unlocked money' in str_error:
return True
if 'No unlocked balance' in str_error:
return True
if 'transaction was rejected by daemon' in str_error:
return True
if 'invalid unlocked_balance' in str_error:

12
basicswap/interface/xmr.py

@ -31,8 +31,7 @@ from basicswap.util import (
TemporaryError)
from basicswap.rpc_xmr import (
make_xmr_rpc_func,
make_xmr_rpc2_func,
make_xmr_wallet_rpc_func)
make_xmr_rpc2_func)
from basicswap.util import (
b2i, b2h)
from basicswap.chainparams import XMR_COIN, CoinInterface, Coins
@ -65,9 +64,12 @@ class XMRInterface(CoinInterface):
def __init__(self, coin_settings, network, swap_client=None):
super().__init__(network)
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1'))
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
self.rpc_wallet_cb = make_xmr_wallet_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
daemon_login = None
if coin_settings.get('rpcuser', '') != '':
daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', ''))
self.rpc_cb = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1'))
self.rpc_cb2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint
self.rpc_wallet_cb = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1'))
self.blocks_confirmed = coin_settings['blocks_confirmed']
self._restore_height = coin_settings.get('restore_height', 0)

55
basicswap/rpc_xmr.py

@ -159,7 +159,7 @@ class JsonrpcDigest():
raise
def callrpc_xmr(rpc_port, auth, method, params=[], rpc_host='127.0.0.1', path='json_rpc', timeout=120):
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120):
# auth is a tuple: (username, password)
try:
if rpc_host.count('://') > 0:
@ -168,27 +168,10 @@ def callrpc_xmr(rpc_port, auth, method, params=[], rpc_host='127.0.0.1', path='j
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
x = JsonrpcDigest(url)
v = x.json_request(method, params, username=auth[0], password=auth[1], timeout=timeout)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
raise ValueError('RPC Server Error: {}'.format(str(ex)))
if 'error' in r and r['error'] is not None:
raise ValueError('RPC error ' + str(r['error']))
return r['result']
def callrpc_xmr_na(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', timeout=120):
try:
if rpc_host.count('://') > 0:
url = '{}:{}/{}'.format(rpc_host, rpc_port, path)
if auth:
v = x.json_request(method, params, username=auth[0], password=auth[1], timeout=timeout)
else:
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
x = JsonrpcDigest(url)
v = x.json_request(method, params, timeout=timeout)
v = x.json_request(method, params, timeout=timeout)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
@ -200,7 +183,7 @@ def callrpc_xmr_na(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json
return r['result']
def callrpc_xmr2(rpc_port, method, params=None, rpc_host='127.0.0.1', timeout=120):
def callrpc_xmr2(rpc_port, method, params=None, auth=None, rpc_host='127.0.0.1', timeout=120):
try:
if rpc_host.count('://') > 0:
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
@ -208,7 +191,10 @@ def callrpc_xmr2(rpc_port, method, params=None, rpc_host='127.0.0.1', timeout=12
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
x = JsonrpcDigest(url)
v = x.post_request(method, params, timeout=timeout)
if auth:
v = x.json_request(method, params, username=auth[0], password=auth[1], timeout=timeout)
else:
v = x.json_request(method, params, timeout=timeout)
x.close()
r = json.loads(v.decode('utf-8'))
except Exception as ex:
@ -217,34 +203,23 @@ def callrpc_xmr2(rpc_port, method, params=None, rpc_host='127.0.0.1', timeout=12
return r
def make_xmr_rpc_func(port, host='127.0.0.1'):
port = port
host = host
def rpc_func(method, params=None, wallet=None, timeout=120):
nonlocal port
nonlocal host
return callrpc_xmr_na(port, method, params, rpc_host=host, timeout=timeout)
return rpc_func
def make_xmr_rpc2_func(port, host='127.0.0.1'):
def make_xmr_rpc2_func(port, auth, host='127.0.0.1'):
port = port
auth = auth
host = host
def rpc_func(method, params=None, wallet=None, timeout=120):
nonlocal port
nonlocal host
return callrpc_xmr2(port, method, params, rpc_host=host, timeout=timeout)
nonlocal port, auth, host
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout)
return rpc_func
def make_xmr_wallet_rpc_func(port, auth, host='127.0.0.1'):
def make_xmr_rpc_func(port, auth, host='127.0.0.1'):
port = port
auth = auth
host = host
def rpc_func(method, params=None, wallet=None, timeout=120):
nonlocal port, auth, host
return callrpc_xmr(port, auth, method, params, rpc_host=host, timeout=timeout)
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout)
return rpc_func

8
bin/basicswap_prepare.py

@ -116,6 +116,8 @@ BASE_XMR_WALLET_PORT = int(os.getenv('BASE_XMR_WALLET_PORT', 29998))
XMR_WALLET_RPC_HOST = os.getenv('XMR_WALLET_RPC_HOST', '127.0.0.1')
XMR_WALLET_RPC_USER = os.getenv('XMR_WALLET_RPC_USER', 'xmr_wallet_user')
XMR_WALLET_RPC_PWD = os.getenv('XMR_WALLET_RPC_PWD', 'xmr_wallet_pwd')
XMR_RPC_USER = os.getenv('XMR_RPC_USER', '')
XMR_RPC_PWD = os.getenv('XMR_RPC_PWD', '')
DEFAULT_XMR_RESTORE_HEIGHT = int(os.getenv('DEFAULT_XMR_RESTORE_HEIGHT', 2245107))
LTC_RPC_HOST = os.getenv('LTC_RPC_HOST', '127.0.0.1')
@ -631,6 +633,9 @@ def prepareDataDir(coin, settings, chain, particl_mnemonic, extra_opts={}):
fp.write('proxy-allow-dns-leaks=0\n')
fp.write('no-igd=1\n')
if XMR_RPC_USER != '':
fp.write(f'rpc-login={XMR_RPC_USER}:{XMR_RPC_PWD}\n')
wallets_dir = core_settings.get('walletsdir', data_dir)
if not os.path.exists(wallets_dir):
os.makedirs(wallets_dir)
@ -1339,6 +1344,9 @@ def main():
if BTC_RPC_USER != '':
chainclients['bitcoin']['rpcuser'] = BTC_RPC_USER
chainclients['bitcoin']['rpcpassword'] = BTC_RPC_PWD
if XMR_RPC_USER != '':
chainclients['monero']['rpcuser'] = XMR_RPC_USER
chainclients['monero']['rpcpassword'] = XMR_RPC_PWD
if PIVX_RPC_USER != '':
chainclients['pivx']['rpcuser'] = PIVX_RPC_USER
chainclients['pivx']['rpcpassword'] = PIVX_RPC_PWD

7
bin/basicswap_run.py

@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2020 tecnovert
# Copyright (c) 2019-2022 tecnovert
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
@ -162,6 +162,11 @@ def runClient(fp, data_dir, chain):
daemon_addr = '{}:{}'.format(v['rpchost'], v['rpcport'])
swap_client.log.info('daemon-address: {}'.format(daemon_addr))
opts = ['--daemon-address', daemon_addr, ]
daemon_rpcuser = v.get('rpcuser', '')
daemon_rpcpass = v.get('rpcpassword', '')
if daemon_rpcuser != '':
opts.append('--daemon-login')
opts.append(daemon_rpcuser + ':' + daemon_rpcpass)
daemons.append(startXmrWalletDaemon(v['datadir'], v['bindir'], 'monero-wallet-rpc', opts))
pid = daemons[-1].pid
swap_client.log.info('Started {} {}'.format('monero-wallet-rpc', pid))

56
doc/notes.md

@ -1,4 +1,60 @@
## Monero remote private node without ssh tunneling
Example connecting a basicswap instance running on a local node to a private
remote monero node running at 192.168.1.9 with rpc username and password:
test_user:test_pwd
Set the following in basicswap.json:
In chainclients.monero:
- connection_type - rpc
- manage_daemon - false
- manage_wallet_daemon - true
- rpchost - ip of remote monero node (192.168.1.9)
- rpcport - rpcport that monero is listening on remote node (18081)
- rpcuser - test_user
- rpcpassword - test_pwd
Edit monerod.conf on the remote node:
data-dir=PATH_TO_MONERO_DATADIR
restricted-rpc=1
rpc-login=test_user:test_pwd
rpc-bind-port=18081
rpc-bind-ip=192.168.1.9
prune-blockchain=1
Start the remote monerod binary with `--confirm-external-bind`
Remember to open port 18081 in the remote machine's firewall if necessary.
You can debug the connection using curl (from the local node)
curl http://192.168.1.9:18081/json_rpc -u test_user:test_pwd --digest -d '{"jsonrpc":"2.0","id":"0","method":"get_info"}' -H 'Content-Type: application/json'
## Monero remote private node with ssh tunneling
Example connecting to a private remote monero node running at 192.168.1.9
Set the following in basicswap.json:
In chainclients.monero:
- connection_type - rpc
- manage_daemon - false
- manage_wallet_daemon - true
- rpchost - localhost
- rpcport - rpcport that monero is listening on remote node (18081)
On the remote machine open an ssh tunnel to port 18081:
ssh -R 18081:localhost:18081 -N user@LOCAL_NODE_IP
And start monerod
## Run One Test
```

22
tests/basicswap/common_xmr.py

@ -19,7 +19,7 @@ from urllib.request import urlopen
from unittest.mock import patch
from basicswap.rpc_xmr import (
callrpc_xmr_na,
callrpc_xmr,
)
from tests.basicswap.mnemonics import mnemonics
from tests.basicswap.util import (
@ -63,10 +63,10 @@ def waitForBidState(delay_event, port, bid_id, state_str, wait_for=60):
raise ValueError('waitForBidState failed')
def updateThread(xmr_addr, delay_event):
def updateThread(xmr_addr, delay_event, xmr_auth):
while not delay_event.is_set():
try:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xmr_addr, 'amount_of_blocks': 1})
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xmr_addr, 'amount_of_blocks': 1}, auth=xmr_auth)
except Exception as e:
print('updateThread error', str(e))
delay_event.wait(2)
@ -85,6 +85,10 @@ def run_prepare(node_id, datadir_path, bins_path, with_coins, mnemonic_in=None,
os.environ['PART_RPC_PORT'] = str(PARTICL_RPC_PORT_BASE)
os.environ['BTC_RPC_PORT'] = str(BITCOIN_RPC_PORT_BASE)
os.environ['XMR_RPC_USER'] = 'xmr_user'
os.environ['XMR_RPC_PWD'] = 'xmr_pwd'
import bin.basicswap_prepare as prepareSystem
# Hack: Reload module to set env vars as the basicswap_prepare module is initialised if imported from elsewhere earlier
from importlib import reload
@ -333,12 +337,16 @@ class XmrTestBase(TestBase):
num_blocks = 100
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
xmr_auth = None
if os.getenv('XMR_RPC_USER', '') != '':
xmr_auth = (os.getenv('XMR_RPC_USER', ''), os.getenv('XMR_RPC_PWD', ''))
if callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count', auth=xmr_auth)['count'] < num_blocks:
logging.info('Mining {} Monero blocks to {}.'.format(num_blocks, xmr_addr1))
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xmr_addr1, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': xmr_addr1, 'amount_of_blocks': num_blocks}, auth=xmr_auth)
logging.info('XMR blocks: %d', callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count', auth=xmr_auth)['count'])
self.update_thread = threading.Thread(target=updateThread, args=(xmr_addr1, self.delay_event))
self.update_thread = threading.Thread(target=updateThread, args=(xmr_addr1, self.delay_event, xmr_auth))
self.update_thread.start()
# Wait for height, or sequencelock is thrown off by genesis blocktime

10
tests/basicswap/extended/test_xmr_persistent.py

@ -28,7 +28,7 @@ import multiprocessing
from unittest.mock import patch
from basicswap.rpc_xmr import (
callrpc_xmr_na,
callrpc_xmr,
)
from basicswap.rpc import (
callrpc,
@ -93,7 +93,7 @@ def updateThreadXmr(cls):
while not cls.delay_event.is_set():
try:
if cls.xmr_addr is not None:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
except Exception as e:
print('updateThreadXmr error', str(e))
cls.delay_event.wait(random.randrange(cls.xmr_update_min, cls.xmr_update_max))
@ -151,10 +151,10 @@ class Test(unittest.TestCase):
self.xmr_addr = wallets['XMR']['main_address']
num_blocks = 100
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
if callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
logging.info('Mining {} Monero blocks to {}.'.format(num_blocks, self.xmr_addr))
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': self.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': self.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
self.btc_addr = callbtcrpc(0, 'getnewaddress', ['mining_addr', 'bech32'])
num_blocks = 500 # Mine enough to activate segwit

15
tests/basicswap/test_xmr.py

@ -46,7 +46,6 @@ from basicswap.rpc import (
)
from basicswap.rpc_xmr import (
callrpc_xmr,
callrpc_xmr_na,
)
from basicswap.interface.xmr import (
XMR_COIN,
@ -256,7 +255,7 @@ def signal_handler(sig, frame):
def waitForXMRNode(rpc_offset, max_tries=7):
for i in range(max_tries + 1):
try:
callrpc_xmr_na(XMR_BASE_RPC_PORT + rpc_offset, 'get_block_count')
callrpc_xmr(XMR_BASE_RPC_PORT + rpc_offset, 'get_block_count')
return
except Exception as ex:
if i < max_tries:
@ -268,7 +267,7 @@ def waitForXMRNode(rpc_offset, max_tries=7):
def waitForXMRWallet(rpc_offset, auth, max_tries=7):
for i in range(max_tries + 1):
try:
callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + rpc_offset, auth, 'get_languages')
callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + rpc_offset, 'get_languages', auth=auth)
return
except Exception as ex:
if i < max_tries:
@ -549,10 +548,10 @@ class BaseTest(unittest.TestCase):
num_blocks = 100
if cls.start_xmr_nodes:
cls.xmr_addr = cls.callxmrnodewallet(cls, 1, 'get_address')['address']
if callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
if callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'] < num_blocks:
logging.info('Mining %d Monero blocks to %s.', num_blocks, cls.xmr_addr)
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': num_blocks})
logging.info('XMR blocks: %d', callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'get_block_count')['count'])
logging.info('Adding anon outputs')
outputs = []
@ -634,7 +633,7 @@ class BaseTest(unittest.TestCase):
if cls.ltc_addr is not None:
ltcCli('generatetoaddress 1 {}'.format(cls.ltc_addr))
if cls.xmr_addr is not None:
callrpc_xmr_na(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
callrpc_xmr(XMR_BASE_RPC_PORT + 1, 'generateblocks', {'wallet_address': cls.xmr_addr, 'amount_of_blocks': 1})
@classmethod
def waitForParticlHeight(cls, num_blocks, node_id=0):
@ -651,7 +650,7 @@ class BaseTest(unittest.TestCase):
assert particl_blocks >= num_blocks
def callxmrnodewallet(self, node_id, method, params=None):
return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, self.xmr_wallet_auth[node_id], method, params)
return callrpc_xmr(XMR_BASE_WALLET_RPC_PORT + node_id, method, params, auth=self.xmr_wallet_auth[node_id])
def getXmrBalance(self, js_wallets):
return float(js_wallets[Coins.XMR.name]['unconfirmed']) + float(js_wallets[Coins.XMR.name]['balance'])

Loading…
Cancel
Save