From 779f522d16f0a55cbd24b0a30f688cb113bbdead Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 12 Feb 2020 10:38:26 +0000 Subject: [PATCH] new MoneroWalletDaemon, MoneroWalletRPCConnection classes --- data_files/mmgen.cfg | 9 ++++++++ mmgen/daemon.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ mmgen/globalvars.py | 5 ++++ mmgen/opts.py | 3 +++ mmgen/rpc.py | 34 +++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) diff --git a/data_files/mmgen.cfg b/data_files/mmgen.cfg index 2bc4c629..05a9d5d3 100644 --- a/data_files/mmgen.cfg +++ b/data_files/mmgen.cfg @@ -84,6 +84,15 @@ # Set the Ethereum testnet name # eth_testnet_chain_name kovan +# Set the Monero wallet RPC host: +# monero_wallet_rpc_host localhost + +# Set the Monero wallet RPC username: +# monero_wallet_rpc_user monero + +# Set the Monero wallet RPC password to something secure: +# monero_wallet_rpc_password passw0rd + ##################################################################### # The following options are probably of interest only to developers # ##################################################################### diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 162411ce..8696fe63 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -137,6 +137,61 @@ class Daemon(MMGenObject): for k in cls.subclasses_must_implement: assert k in subcls.__dict__, m.format(k,subcls.__name__) +class MoneroWalletDaemon(Daemon): + + desc = 'RPC daemon' + net_desc = 'Monero wallet' + daemon_id = 'xmr' + network = 'wallet RPC' + + def __init__(self,wallet_dir,test_suite=False): + self.platform = g.platform + self.wallet_dir = wallet_dir + if test_suite: + self.datadir = os.path.join('test','monero-wallet-rpc') + self.rpc_port = 13142 + else: + self.datadir = 'monero-wallet-rpc' + self.rpc_port = 13131 + self.daemon_port = CoinDaemon('xmr',test_suite=test_suite).rpc_port + self.pidfile = os.path.join(self.datadir,'monero-wallet-rpc.pid') + self.logfile = os.path.join(self.datadir,'monero-wallet-rpc.log') + + if not g.monero_wallet_rpc_password: + die(1, + 'You must set your Monero wallet RPC password.\n' + + 'This can be done on the command line, with the --monero-wallet-rpc-password\n' + + "option (insecure, not recommended), or by setting 'monero_wallet_rpc_password'\n" + + "in the MMGen config file." ) + + @property + def start_cmd(self): + return ['monero-wallet-rpc', + '--daemon-port={}'.format(self.daemon_port), + '--rpc-bind-port={}'.format(self.rpc_port), + '--wallet-dir='+self.wallet_dir, + '--detach', + '--log-file='+self.logfile, + '--rpc-login={}:{}'.format(g.monero_wallet_rpc_user,g.monero_wallet_rpc_password), + '--pidfile='+self.pidfile] + + @property + def state(self): + from mmgen.rpc import MoneroWalletRPCConnection + try: + MoneroWalletRPCConnection( + g.monero_wallet_rpc_host, + self.rpc_port, + g.monero_wallet_rpc_user, + g.monero_wallet_rpc_password).get_version() + return 'ready' + except: + return 'stopped' + + @property + def stop_cmd(self): + return ['kill','-Wf',self.pid] if self.platform == 'win' else ['kill',self.pid] + class CoinDaemon(Daemon): cfg_file_hdr = '' subclasses_must_implement = ('state','stop_cmd') diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index a879b1c0..16cac51c 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -92,6 +92,9 @@ class g(object): rpc_port = 0 rpc_user = '' rpc_password = '' + monero_wallet_rpc_host = 'localhost' + monero_wallet_rpc_user = 'monero' + monero_wallet_rpc_password = '' rpc_fail_on_command = '' rpch = None # global RPC handle use_cached_balances = False @@ -141,6 +144,7 @@ class g(object): # 'long' opts - opt sets global var common_opts = ( 'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password', + 'monero_wallet_rpc_host','monero_wallet_rpc_user','monero_wallet_rpc_password', 'daemon_data_dir','force_256_color','regtest','coin','bob','alice', 'accept_defaults','token' ) @@ -162,6 +166,7 @@ class g(object): cfg_file_opts = ( 'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port', 'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password', + 'monero_wallet_rpc_host','monero_wallet_rpc_user','monero_wallet_rpc_password', 'daemon_data_dir','force_256_color','regtest','subseeds', 'btc_max_tx_fee','ltc_max_tx_fee','bch_max_tx_fee','eth_max_tx_fee', 'eth_mainnet_chain_name','eth_testnet_chain_name', diff --git a/mmgen/opts.py b/mmgen/opts.py index 8d768309..72d1107c 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -205,6 +205,9 @@ common_opts_data = { --, --rpc-port=p Communicate with {dn} listening on port 'p' --, --rpc-user=user Override 'rpcuser' in {pn}.conf --, --rpc-password=pass Override 'rpcpassword' in {pn}.conf +--, --monero-wallet-rpc-host=host Override 'monero_wallet_rpc_host' in mmgen.cfg +--, --monero-wallet-rpc-user=user Override 'monero_wallet_rpc_user' in mmgen.cfg +--, --monero-wallet-rpc-password=pass Override 'monero_wallet_rpc_password' in mmgen.cfg --, --regtest=0|1 Disable or enable regtest mode --, --testnet=0|1 Disable or enable testnet --, --skip-cfg-file Skip reading the configuration file diff --git a/mmgen/rpc.py b/mmgen/rpc.py index ea616a9e..8e4624ab 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -261,6 +261,40 @@ class EthereumRPCConnection(RPCConnection): 'parity_versionInfo', ) +class MoneroWalletRPCConnection(RPCConnection): + rpcmethods = ( + 'get_version', + 'get_height', # sync height of the open wallet + 'get_balance', # { "account_index":0,"address_indices":[0,1] } + 'create_wallet', # { "filename":"name","password":"passw0rd","language":"English" } + 'open_wallet', # { "filename":"name","password":"passw0rd" } + 'close_wallet', + 'restore_deterministic_wallet', # name,password,seed (restore_height,language,seed_offset,autosave_current) + 'refresh', # {"start_height":100000} + ) + + def request(self,cmd,*args,**kwargs): + from subprocess import run,PIPE + data = { + 'jsonrpc': '2.0', + 'id': '0', + 'method': cmd, + 'params': kwargs, + } + exec_cmd = [ + 'curl', '--silent','--insecure', '--request', 'POST', + '--digest', '--user', '{}:{}'.format(g.monero_wallet_rpc_user,g.monero_wallet_rpc_password), + '--header', 'Content-Type: application/json', + '--data', json.dumps(data), + 'https://{}:{}/json_rpc'.format(self.host,self.port) ] + + cp = run(exec_cmd,stdout=PIPE,check=True) + + res = json.loads(cp.stdout) + if 'error' in res: + raise RPCFailure(repr(res['error'])) + return(res['result']) + def rpc_error(ret): return type(ret) is tuple and ret and ret[0] == 'rpcfail'