refactored get_transactions and get_txos

This commit is contained in:
Lex Berezhny 2018-10-04 19:27:39 -04:00
parent 0960762694
commit 356ab9666f
3 changed files with 64 additions and 65 deletions

View file

@ -7,6 +7,7 @@ from twisted.enterprise import adbapi
from torba.hash import TXRefImmutable from torba.hash import TXRefImmutable
from torba.basetransaction import BaseTransaction, TXORefResolvable from torba.basetransaction import BaseTransaction, TXORefResolvable
from torba.baseaccount import BaseAccount
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -15,6 +16,13 @@ def clean_arg_name(arg):
return arg.replace('.', '_') return arg.replace('.', '_')
def prepare_constraints(constraints):
if 'account' in constraints:
if isinstance(constraints['account'], BaseAccount):
constraints['account'] = constraints['account'].public_key.address
return constraints.pop('my_account', constraints.get('account'))
def constraints_to_sql(constraints, joiner=' AND ', prepend_sql=' AND ', prepend_key=''): def constraints_to_sql(constraints, joiner=' AND ', prepend_sql=' AND ', prepend_key=''):
if not constraints: if not constraints:
return '' return ''
@ -36,7 +44,7 @@ def constraints_to_sql(constraints, joiner=' AND ', prepend_sql=' AND ', prepend
col, op = key[:-len('__in')], 'IN' col, op = key[:-len('__in')], 'IN'
else: else:
col, op = key[:-len('__not_in')], 'NOT IN' col, op = key[:-len('__not_in')], 'NOT IN'
if isinstance(constraint, list): if isinstance(constraint, (list, set)):
placeholders = [] placeholders = []
for item_no, item in enumerate(constraint, 1): for item_no, item in enumerate(constraint, 1):
constraints['{}_{}'.format(clean_arg_name(col), item_no)] = item constraints['{}_{}'.format(clean_arg_name(col), item_no)] = item
@ -288,38 +296,34 @@ class BaseDatabase(SQLiteMixin):
return txs[0] return txs[0]
@defer.inlineCallbacks @defer.inlineCallbacks
def get_transactions(self, account=None, txid=None, offset=0, limit=1000): def get_transactions(self, offset=0, limit=1000000, **constraints):
my_account = prepare_constraints(constraints)
account = constraints.pop('account', None)
tx_where = "" if 'txid' not in constraints and account is not None:
account_id = account.public_key.address if account is not None else None constraints['txid__in'] = """
SELECT txo.txid FROM txo
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :account
UNION
SELECT txi.txid FROM txi
JOIN txo USING (txoid)
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :account
"""
if txid is not None: where = constraints_to_sql(constraints, prepend_sql='WHERE ')
tx_where = """
WHERE txid = :txid
"""
elif account is not None:
tx_where = """
WHERE txid IN (
SELECT txo.txid FROM txo
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :account
UNION
SELECT txi.txid FROM txi
JOIN txo USING (txoid)
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :account
)
"""
tx_rows = yield self.run_query( tx_rows = yield self.run_query(
""" """
SELECT txid, raw, height, position, is_verified FROM tx {} SELECT txid, raw, height, position, is_verified FROM tx {}
ORDER BY height DESC, position DESC LIMIT :offset, :limit ORDER BY height DESC, position DESC LIMIT :offset, :limit
""".format(tx_where), { """.format(where), {
'account': account_id, **constraints,
'txid': txid, 'account': account,
'offset': min(offset, 0), 'offset': max(offset, 0),
'limit': max(limit, 100) 'limit': max(limit, 1)
} }
) )
txids, txs = [], [] txids, txs = [], []
for row in tx_rows: for row in tx_rows:
txids.append(row[0]) txids.append(row[0])
@ -327,49 +331,39 @@ class BaseDatabase(SQLiteMixin):
raw=row[1], height=row[2], position=row[3], is_verified=row[4] raw=row[1], height=row[2], position=row[3], is_verified=row[4]
)) ))
txo_rows = yield self.run_query( annotated_txos = {
""" txo.id: txo for txo in
SELECT txoid, chain, account (yield self.get_txos(
FROM txo JOIN pubkey_address USING (address) my_account=my_account,
WHERE txid IN ({}) txid__in=txids
""".format(', '.join(['?']*len(txids))), txids ))
) }
txos = {}
for row in txo_rows:
txos[row[0]] = {
'is_change': row[1] == 1,
'is_my_account': row[2] == account_id
}
referenced_txos = yield self.get_txos( referenced_txos = {
account=account, txo.id: txo for txo in
txoid__in="SELECT txoid FROM txi WHERE txi.txid IN ({})".format( (yield self.get_txos(
','.join("'{}'".format(txid) for txid in txids) my_account=my_account,
) txoid__in="SELECT txoid FROM txi WHERE txi.txid IN ({})".format(
) ','.join("'{}'".format(txid) for txid in txids)
referenced_txos_map = {txo.id: txo for txo in referenced_txos} )
))
}
for tx in txs: for tx in txs:
for txi in tx.inputs: for txi in tx.inputs:
if txi.txo_ref.id in referenced_txos_map: txo = referenced_txos.get(txi.txo_ref.id)
txi.txo_ref = TXORefResolvable(referenced_txos_map[txi.txo_ref.id]) if txo:
txi.txo_ref = txo.ref
for txo in tx.outputs: for txo in tx.outputs:
txo_meta = txos.get(txo.id) _txo = annotated_txos.get(txo.id)
if txo_meta is not None: if _txo:
txo.is_change = txo_meta['is_change'] txo.update_annotations(_txo)
txo.is_my_account = txo_meta['is_my_account']
else:
txo.is_change = False
txo.is_my_account = False
return txs return txs
@defer.inlineCallbacks @defer.inlineCallbacks
def get_txos(self, account=None, **constraints): def get_txos(self, **constraints):
account_id = None my_account = prepare_constraints(constraints)
if account is not None:
account_id = account.public_key.address
constraints['account'] = account_id
rows = yield self.run_query( rows = yield self.run_query(
""" """
SELECT amount, script, txid, txo.position, chain, account SELECT amount, script, txid, txo.position, chain, account
@ -384,18 +378,18 @@ class BaseDatabase(SQLiteMixin):
tx_ref=TXRefImmutable.from_id(row[2]), tx_ref=TXRefImmutable.from_id(row[2]),
position=row[3], position=row[3],
is_change=row[4] == 1, is_change=row[4] == 1,
is_my_account=row[5] == account_id is_my_account=row[5] == my_account
) for row in rows ) for row in rows
] ]
def get_utxos(self, **constraints): def get_utxos(self, **constraints):
constraints['txoid__not_in'] = 'SELECT txoid FROM txi' constraints['txoid__not_in'] = 'SELECT txoid FROM txi'
constraints['is_reserved'] = 0 constraints['is_reserved'] = False
return self.get_txos(**constraints) return self.get_txos(**constraints)
def get_balance_for_account(self, account, include_reserved=False, **constraints): def get_balance_for_account(self, account, include_reserved=False, **constraints):
if not include_reserved: if not include_reserved:
constraints['is_reserved'] = 0 constraints['is_reserved'] = False
values = {'account': account.public_key.address} values = {'account': account.public_key.address}
values.update(constraints) values.update(constraints)
return self.query_one_value( return self.query_one_value(

View file

@ -133,11 +133,8 @@ class BaseLedger(metaclass=LedgerRegistry):
def add_account(self, account: baseaccount.BaseAccount): def add_account(self, account: baseaccount.BaseAccount):
self.accounts.append(account) self.accounts.append(account)
@defer.inlineCallbacks
def get_transaction(self, txhash): def get_transaction(self, txhash):
raw, _, _, _ = yield self.db.get_transaction(txhash) return self.db.get_transaction(txhash)
if raw is not None:
return self.transaction_class(raw)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_private_key_for_address(self, address): def get_private_key_for_address(self, address):

View file

@ -200,6 +200,14 @@ class BaseOutput(InputOutput):
self.is_change = is_change self.is_change = is_change
self.is_my_account = is_my_account self.is_my_account = is_my_account
def update_annotations(self, annotated):
if annotated is None:
self.is_change = False
self.is_my_account = False
else:
self.is_change = annotated.is_change
self.is_my_account = annotated.is_my_account
@property @property
def ref(self): def ref(self):
return TXORefResolvable(self) return TXORefResolvable(self)