Refactor transfer query

This commit is contained in:
Michał Sałaban 2018-01-29 15:11:53 +01:00
parent e058155124
commit a814197057
7 changed files with 343 additions and 104 deletions

View file

@ -1,5 +1,6 @@
from . import address
from . import prio
from .transaction import PaymentManager
class Account(object):
@ -8,6 +9,8 @@ class Account(object):
def __init__(self, backend, index):
self.index = index
self._backend = backend
self.incoming = PaymentManager(index, backend, 'in')
self.outgoing = PaymentManager(index, backend, 'out')
def balances(self):
return self._backend.balances(account=self.index)
@ -27,17 +30,6 @@ class Account(object):
def new_address(self, label=None):
return self._backend.new_address(account=self.index, label=label)
def payments(self, payment_id=None):
return self._backend.payments(account=self.index, payment_id=payment_id)
def transactions_in(self, confirmed=True, unconfirmed=False):
return self._backend.transactions_in(
account=self.index, confirmed=confirmed, unconfirmed=unconfirmed)
def transactions_out(self, confirmed=True, unconfirmed=True):
return self._backend.transactions_out(
account=self.index, confirmed=confirmed, unconfirmed=unconfirmed)
def transfer(self, address, amount,
priority=prio.NORMAL, ringsize=5, payment_id=None, unlock_time=0,
relay=True):

View file

@ -169,35 +169,48 @@ class JSONRPCWallet(object):
_balance = self.raw_request('getbalance', {'account_index': account})
return (from_atomic(_balance['balance']), from_atomic(_balance['unlocked_balance']))
def payments(self, account=0, payment_id=0):
payment_id = PaymentID(payment_id)
_log.debug("Getting payments for account {acc}, payment_id {pid}".format(
acc=account, pid=payment_id))
_payments = self.raw_request('get_payments', {
def transfers_in(self, account, pmtfilter):
params = {'account_index': account}
method = 'get_transfers'
if pmtfilter.unconfirmed:
params['in'] = pmtfilter.confirmed
params['out'] = False
params['pool'] = True
else:
if pmtfilter.payment_ids:
method = 'get_bulk_payments'
params['payment_id'] = pmtfilter.payment_ids
else:
params['in'] = pmtfilter.confirmed
params['out'] = False
params['pool'] = False
if method == 'get_transfers':
arg = 'in'
if pmtfilter.min_height:
params['min_height'] = pmtfilter.min_height
params['filter_by_height'] = True
if pmtfilter.max_height:
params['max_height'] = pmtfilter.max_height
params['filter_by_height'] = True
elif pmtfilter.min_height:
arg = 'payments'
params['min_block_height'] = pmtfilter.min_height
_pmts = self.raw_request(method, params)
pmts = _pmts.get(arg, [])
if pmtfilter.unconfirmed:
pmts.extend(_pmts.get('pool', []))
return list(pmtfilter.filter(map(self._inpayment, pmts)))
def transfers_out(self, account, pmtfilter):
_pmts = self.raw_request('get_transfers', {
'account_index': account,
'payment_id': str(payment_id)})
pmts = []
for data in _payments['payments']:
# Monero <= 0.11 : no address is passed because there's only one
data['address'] = data['address'] or self._master_address
pmts.append(self._inpayment(data))
return pmts
def transactions_in(self, account=0, confirmed=True, unconfirmed=False):
_txns = self.raw_request('get_transfers',
{'account_index': account, 'in': confirmed, 'out': False, 'pool': unconfirmed})
txns = _txns.get('in', [])
if unconfirmed:
txns.extend(_txns.get('pool', []))
return [self._inpayment(tx) for tx in sorted(txns, key=operator.itemgetter('timestamp'))]
def transactions_out(self, account=0, confirmed=True, unconfirmed=True):
_txns = self.raw_request('get_transfers',
{'account_index': account, 'in': False, 'out': confirmed, 'pool': unconfirmed})
txns = _txns.get('out', [])
if unconfirmed:
txns.extend(_txns.get('pool', []))
return [self._outpayment(tx) for tx in sorted(txns, key=operator.itemgetter('timestamp'))]
'in': False,
'out': pmtfilter.confirmed,
'pool': pmtfilter.unconfirmed})
pmts = _pmts.get('out', [])
if pmtfilter.unconfirmed:
pmts.extend(_pmts.get('pool', []))
return list(pmtfilter.filter(map(self._outpayment, pmts)))
def get_transaction(self, txhash):
_tx = self.raw_request('get_transfer_by_txid', {'txid': str(txhash)})['transfer']
@ -209,24 +222,24 @@ class JSONRPCWallet(object):
def _paymentdict(self, data):
pid = data.get('payment_id', None)
addr = data.get('address', None)
if addr:
addr = address(addr)
return {
'txhash': data.get('txid', data.get('tx_hash')),
'payment_id': None if pid is None else PaymentID(pid),
'amount': from_atomic(data['amount']),
'timestamp': datetime.fromtimestamp(data['timestamp']) if 'timestamp' in data else None,
'note': data.get('note'),
'transaction': self._tx(data)
'note': data.get('note', None),
'transaction': self._tx(data),
'address': addr,
}
def _inpayment(self, data):
p = self._paymentdict(data)
p.update({'received_by': address(data['address']) if 'address' in data else None})
return IncomingPayment(**p)
return IncomingPayment(**self._paymentdict(data))
def _outpayment(self, data):
p = self._paymentdict(data)
p.update({'sent_from': address(data['address']) if 'address' in data else None})
return OutgoingPayment(**p)
return OutgoingPayment(**self._paymentdict(data))
def _tx(self, data):
return Transaction(**{

View file

@ -1,34 +1,39 @@
import sys
from .address import address
from .numbers import PaymentID
class Payment(object):
tx_hash = None
payment_id = None
amount = None
timestamp = None
transaction = None
address = None
def __init__(self, **kwargs):
self.tx_hash = kwargs.get('tx_hash', self.tx_hash)
self.amount = kwargs.get('amount', self.amount)
self.timestamp = kwargs.get('timestamp', self.timestamp)
self.payment_id = kwargs.get('payment_id', self.payment_id)
self.transaction = kwargs.get('transaction', self.transaction)
self.address = kwargs.get('address', self.address)
def __repr__(self):
return "{} {:.12f} id={}".format(self.transaction.hash, self.amount, self.payment_id)
class IncomingPayment(Payment):
received_by = None
def __init__(self, **kwargs):
super(IncomingPayment, self).__init__(**kwargs)
self.received_by = kwargs.get('received_by', self.received_by)
def __repr__(self):
return "in: {} {:.12f} id={}".format(self.transaction.hash, self.amount, self.payment_id)
class OutgoingPayment(Payment):
sent_from = None
note = ''
def __init__(self, **kwargs):
super(OutgoingPayment, self).__init__(**kwargs)
self.sent_from = kwargs.get('sent_from', self.sent_from)
self.note = kwargs.get('note', self.sent_from)
self.note = kwargs.get('note', self.note)
def __repr__(self):
return "out: {} {:.12f} id={}".format(self.transaction.hash, self.amount, self.payment_id)
class Transaction(object):
@ -46,3 +51,87 @@ class Transaction(object):
self.timestamp = kwargs.get('timestamp', self.timestamp)
self.key = kwargs.get('key', self.key)
self.blob = kwargs.get('blob', self.blob)
def __repr__(self):
return self.hash
if sys.version_info < (3,):
_str_types = (str, bytes, unicode)
else:
_str_types = (str, bytes)
class PaymentManager(object):
account_idx = 0
backend = None
def __init__(self, account_idx, backend, direction):
self.account_idx = account_idx
self.backend = backend
self.direction = direction
def __call__(self, **filterparams):
fetch = self.backend.transfers_in if self.direction == 'in' else self.backend.transfers_out
return fetch(self.account_idx, PaymentFilter(**filterparams))
class PaymentFilter(object):
def __init__(self, **filterparams):
self.min_height = filterparams.pop('min_height', None)
self.max_height = filterparams.pop('max_height', None)
self.unconfirmed = filterparams.pop('unconfirmed', False)
self.confirmed = filterparams.pop('confirmed', True)
_address = filterparams.pop('address', None)
_payment_id = filterparams.pop('payment_id', None)
if len(filterparams) > 0:
raise ValueError("Excessive arguments for payment query: {:r}".format(filterparams))
if _address is None:
self.addresses = []
else:
if isinstance(_address, _str_types):
addresses = [_address]
else:
try:
iter(_address)
addresses = _address
except TypeError:
addresses = [_address]
self.addresses = list(map(address, addresses))
if _payment_id is None:
self.payment_ids = []
else:
if isinstance(_payment_id, _str_types):
payment_ids = [_payment_id]
else:
try:
iter(_payment_id)
payment_ids = _payment_id
except TypeError:
payment_ids = [_payment_id]
self.payment_ids = list(map(PaymentID, payment_ids))
def check(self, payment):
ht = payment.transaction.height
if ht is None:
if not self.unconfirmed:
return False
if self.min_height is not None or self.max_height is not None:
# mempool txns are filtered out if any height range check is present
return False
else:
if not self.confirmed:
return False
if self.min_height is not None and ht < self.min_height:
return False
if self.max_height is not None and ht > self.max_height:
return False
if self.payment_ids and payment.payment_id not in self.payment_ids:
return False
if self.addresses and payment.address not in self.addresses:
return False
return True
def filter(self, payments):
return filter(self.check, payments)

View file

@ -1,13 +1,15 @@
from . import address
from . import prio
from . import account
from . import transaction
from .transaction import PaymentManager
class Wallet(object):
accounts = None
def __init__(self, backend):
self._backend = backend
self.incoming = PaymentManager(0, backend, 'in')
self.outgoing = PaymentManager(0, backend, 'out')
self.refresh()
def refresh(self):
@ -77,14 +79,14 @@ class Wallet(object):
def new_address(self, label=None):
return self.accounts[0].new_address(label=label)
def payments(self, payment_id=None):
return self.accounts[0].payments(payment_id=payment_id)
def payments(self, payment_id):
return self.accounts[0].payments(payment_id)
def transactions_in(self, confirmed=True, unconfirmed=False):
return self.accounts[0].transactions_in(confirmed=confirmed, unconfirmed=unconfirmed)
def transfers_in(self, confirmed=True, unconfirmed=False):
return self.accounts[0].transfers_in(confirmed=confirmed, unconfirmed=unconfirmed)
def transactions_out(self, confirmed=True, unconfirmed=True):
return self.accounts[0].transactions_out(confirmed=confirmed, unconfirmed=unconfirmed)
def transfers_out(self, confirmed=True, unconfirmed=True):
return self.accounts[0].transfers_out(confirmed=confirmed, unconfirmed=unconfirmed)
def transfer(self, address, amount,
priority=prio.NORMAL, ringsize=5, payment_id=None, unlock_time=0,