wownero-python/monero/backends/jsonrpc.py

177 lines
7.1 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})
2018-01-06 22:25:41 +00:00
return [Transaction(**self._tx2dict(tx)) for tx in
sorted(_transfers.get('in', []), key=operator.itemgetter('timestamp'))]
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})
2018-01-06 22:25:41 +00:00
return [Transaction(**self._tx2dict(tx)) for tx in
sorted(_transfers.get('out', []), key=operator.itemgetter('timestamp'))]
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-07 00:26:50 +00:00
'payment_id': str(PaymentID(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)
2018-01-07 00:26:50 +00:00
_pertx = [dict(_tx) for _tx in map(
lambda vs: zip(('txid', 'amount', 'fee', 'key', 'blob', 'payment_id'), vs),
zip(*[_transfers[k] for k in (
'tx_hash_list', 'amount_list', 'fee_list', 'tx_key_list', 'tx_blob_list')]))]
for d in _pertx:
d['payment_id'] = payment_id
return [Transfer(**self._tx2dict(tx)) for tx in _pertx]
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,
}