wownero-python/monero/backends/jsonrpc.py

180 lines
7 KiB
Python
Raw Normal View History

from datetime import datetime
import operator
import json
import logging
import pprint
import requests
from .. import exceptions
from ..account import Account
2017-11-30 03:25:42 +00:00
from ..address import address, Address
2018-01-06 22:12:42 +00:00
from ..numbers import from_atomic, to_atomic, PaymentID
2017-12-27 00:49:59 +00:00
from ..transaction import Transaction, Payment, Transfer
_log = logging.getLogger(__name__)
2017-12-26 21:02:17 +00:00
class JSONRPCWallet(object):
2017-12-27 00:49:59 +00:00
_master_address = None
_addresses = None
def __init__(self, protocol='http', host='127.0.0.1', port=18082, path='/json_rpc', user='', password=''):
self.url = '{protocol}://{host}:{port}/json_rpc'.format(
protocol=protocol,
host=host,
port=port)
_log.debug("JSONRPC backend URL: {url}".format(url=self.url))
self.user = user
self.password = password
_log.debug("JSONRPC backend auth: '{user}'/'{stars}'".format(
user=user, stars=('*' * len(password)) if password else ''))
def get_accounts(self):
accounts = []
try:
_accounts = self.raw_request('get_accounts')
except MethodNotFound:
2017-12-27 00:49:59 +00:00
# monero <= 0.11 : there's only one account and one address
_lg.debug('Monero <= 0.11 found, no accounts')
self._master_address = self.get_addresses()[0]
return [Account(self, 0)]
idx = 0
2017-12-27 00:49:59 +00:00
self._master_address = Address(_accounts['subaddress_accounts'][0]['base_address'])
for _acc in _accounts['subaddress_accounts']:
assert idx == _acc['account_index']
accounts.append(Account(self, _acc['account_index']))
idx += 1
return accounts
def get_addresses(self, account=0):
_addresses = self.raw_request('getaddress', {'account_index': account})
if 'addresses' not in _addresses:
# monero <= 0.11
2017-12-27 00:49:59 +00:00
_lg.debug('Monero <= 0.11 found, assuming single address')
return [Address(_addresses['address'])]
addresses = [None] * (max(map(operator.itemgetter('address_index'), _addresses['addresses'])) + 1)
for _addr in _addresses['addresses']:
2017-11-30 03:25:42 +00:00
addresses[_addr['address_index']] = address(_addr['address'])
return addresses
def get_balances(self, account=0):
_balance = self.raw_request('getbalance', {'account_index': account})
return (from_atomic(_balance['balance']), from_atomic(_balance['unlocked_balance']))
2017-12-27 00:49:59 +00:00
def get_payments(self, account=0, payment_id=0):
2018-01-06 22:12:42 +00:00
payment_id = PaymentID(payment_id)
2017-12-27 00:49:59 +00:00
_log.debug("Getting payments for account {acc}, payment_id {pid}".format(
acc=account, pid=payment_id))
_payments = self.raw_request('get_payments', {
'account_index': account,
2018-01-06 22:12:42 +00:00
'payment_id': str(payment_id)})
2017-12-27 00:49:59 +00:00
pmts = []
for tx in _payments['payments']:
data = self._tx2dict(tx)
# Monero <= 0.11 : no address is passed because there's only one
2018-01-06 15:44:38 +00:00
data['local_address'] = data['local_address'] or self._master_address
2017-12-27 00:49:59 +00:00
pmts.append(Payment(**data))
return pmts
def get_transactions_in(self, account=0):
_transfers = self.raw_request('get_transfers',
{'account_index': account, 'in': True, 'out': False, 'pool': False})
2017-12-27 00:49:59 +00:00
return [Transaction(**self._tx2dict(tx)) for tx in _transfers.get('in', [])]
2017-12-27 00:49:59 +00:00
def get_transactions_out(self, account=0):
_transfers = self.raw_request('get_transfers',
{'account_index': account, 'in': False, 'out': True, 'pool': False})
2017-12-27 00:49:59 +00:00
return [Transaction(**self._tx2dict(tx)) for tx in _transfers.get('out', [])]
2017-11-29 03:38:29 +00:00
2017-12-27 00:49:59 +00:00
def _tx2dict(self, tx):
2017-11-29 03:38:29 +00:00
return {
2017-12-27 00:49:59 +00:00
'hash': tx.get('txid', tx.get('tx_hash')),
'timestamp': datetime.fromtimestamp(tx['timestamp']) if 'timestamp' in tx else None,
'amount': from_atomic(tx['amount']),
'fee': from_atomic(tx['fee']) if 'fee' in tx else None,
'height': tx.get('height', tx.get('block_height')),
2018-01-06 22:12:42 +00:00
'payment_id': PaymentID(tx.get('payment_id', 0)),
2017-12-27 00:49:59 +00:00
'note': tx.get('note'),
# NOTE: address will be resolved only after PR#3010 has been merged to Monero
2018-01-06 15:44:38 +00:00
'local_address': address(tx['address']) if 'address' in tx else None,
2017-12-27 00:49:59 +00:00
'key': tx.get('key'),
'blob': tx.get('blob', None),
2017-11-29 03:38:29 +00:00
}
2018-01-06 22:12:42 +00:00
def transfer(self, destinations, priority, mixin, payment_id, unlock_time, account=0):
2017-11-29 03:38:29 +00:00
data = {
'account_index': account,
'destinations': list(map(
2017-11-30 02:43:34 +00:00
lambda dst: {'address': str(address(dst[0])), 'amount': to_atomic(dst[1])},
2017-11-29 03:38:29 +00:00
destinations)),
'mixin': mixin,
'priority': priority,
'unlock_time': 0,
2018-01-06 22:12:42 +00:00
'payment_id': payment_id,
2017-11-29 03:38:29 +00:00
'get_tx_keys': True,
'get_tx_hex': True,
'new_algorithm': True,
}
_transfers = self.raw_request('transfer_split', data)
2017-12-27 00:49:59 +00:00
keys = ('txid', 'amount', 'fee', 'key', 'blob')
return [
Transfer(**self._tx2dict(tx)) for tx in [
dict(_tx) for _tx in map(
lambda vs: zip(keys,vs),
zip(
*[_transfers[k] for k in (
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')
]
))
]
]
def raw_request(self, method, params=None):
hdr = {'Content-Type': 'application/json'}
data = {'jsonrpc': '2.0', 'id': 0, 'method': method, 'params': params or {}}
_log.debug(u"Method: {method}\nParams:\n{params}".format(
method=method,
params=pprint.pformat(params)))
auth = requests.auth.HTTPDigestAuth(self.user, self.password)
rsp = requests.post(self.url, headers=hdr, data=json.dumps(data), auth=auth)
if rsp.status_code == 401:
raise Unauthorized("401 Unauthorized. Invalid RPC user name or password.")
elif rsp.status_code != 200:
raise RPCError("Invalid HTTP status {code} for method {method}.".format(
code=rsp.status_code,
method=method))
result = rsp.json()
_ppresult = pprint.pformat(result)
_log.debug(u"Result:\n{result}".format(result=_ppresult))
if 'error' in result:
err = result['error']
_log.error(u"JSON RPC error:\n{result}".format(result=_ppresult))
if err['code'] in _err2exc:
2017-11-29 03:38:29 +00:00
raise _err2exc[err['code']](err['message'])
else:
raise RPCError(
"Method '{method}' failed with RPC Error of unknown code {code}, "
"message: {message}".format(method=method, data=data, result=result, **err))
return result['result']
2017-11-29 03:38:29 +00:00
class RPCError(exceptions.BackendException):
pass
class Unauthorized(RPCError):
pass
class MethodNotFound(RPCError):
pass
_err2exc = {
2017-11-29 03:38:29 +00:00
-2: exceptions.WrongAddress,
-4: exceptions.NotEnoughUnlockedMoney,
-20: exceptions.AmountIsZero,
-32601: MethodNotFound,
}