From c255c606a7e40d97517670343b6dd91acece04e3 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Fri, 6 Mar 2020 20:12:38 -0500 Subject: [PATCH] added is_spent attribute to transaction outputs --- lbry/extras/daemon/json_response_encoder.py | 3 +++ lbry/testcase.py | 7 +++++++ lbry/wallet/database.py | 7 +++++-- lbry/wallet/ledger.py | 18 ++++++++++++------ lbry/wallet/transaction.py | 12 ++++++++---- .../blockchain/test_claim_commands.py | 18 ++++++++++++++++++ 6 files changed, 53 insertions(+), 12 deletions(-) diff --git a/lbry/extras/daemon/json_response_encoder.py b/lbry/extras/daemon/json_response_encoder.py index 248474417..4c400f8f6 100644 --- a/lbry/extras/daemon/json_response_encoder.py +++ b/lbry/extras/daemon/json_response_encoder.py @@ -25,6 +25,7 @@ def encode_txo_doc(): 'address': "address of who can spend the txo", 'confirmations': "number of confirmed blocks", 'is_change': "payment to change address, only available when it can be determined", + 'is_spent': "true if txo is spent, false or None if it could not be determined", 'is_mine': "payment to one of your accounts, only available when it can be determined", 'type': "one of 'claim', 'support' or 'purchase'", 'name': "when type is 'claim' or 'support', this is the claim name", @@ -168,6 +169,8 @@ class JSONResponseEncoder(JSONEncoder): } if txo.is_change is not None: output['is_change'] = txo.is_change + if txo.is_spent is not None: + output['is_spent'] = txo.is_spent if txo.is_my_account is not None: output['is_mine'] = txo.is_my_account diff --git a/lbry/testcase.py b/lbry/testcase.py index 4878f36fb..fe86a7ee0 100644 --- a/lbry/testcase.py +++ b/lbry/testcase.py @@ -548,6 +548,13 @@ class CommandTestCase(IntegrationTestCase): self.daemon.jsonrpc_support_create(claim_id, bid, **kwargs), confirm ) + async def support_abandon(self, *args, confirm=True, **kwargs): + if 'blocking' not in kwargs: + kwargs['blocking'] = False + return await self.confirm_and_render( + self.daemon.jsonrpc_support_abandon(*args, **kwargs), confirm + ) + async def resolve(self, uri): return (await self.out(self.daemon.jsonrpc_resolve(uri)))[uri] diff --git a/lbry/wallet/database.py b/lbry/wallet/database.py index 415463e02..2c6ec8bc3 100644 --- a/lbry/wallet/database.py +++ b/lbry/wallet/database.py @@ -488,6 +488,8 @@ class Database(SQLiteMixin): *query(f"SELECT {cols} FROM tx", **constraints) ) + TXO_NOT_MINE = Output(None, None, is_my_account=False) + async def get_transactions(self, wallet=None, **constraints): tx_rows = await self.select_transactions( 'txid, raw, height, position, is_verified', @@ -538,7 +540,7 @@ class Database(SQLiteMixin): if _txo: txo.update_annotations(_txo) else: - txo.update_annotations(None) + txo.update_annotations(self.TXO_NOT_MINE) for tx in txs: txos = tx.outputs @@ -577,7 +579,7 @@ class Database(SQLiteMixin): tx.txid, raw, tx.height, tx.position, tx.is_verified, txo.position, amount, script, ( select group_concat(account||"|"||chain) from account_address where account_address.address=txo.address - ) + ), exists(select txoid from txi where txi.txoid=txo.txoid) """, **constraints ) @@ -599,6 +601,7 @@ class Database(SQLiteMixin): txo = txs[row[0]].outputs[row[5]] row_accounts = dict(a.split('|') for a in row[8].split(',')) account_match = set(row_accounts) & my_accounts + txo.is_spent = bool(row[9]) if account_match: txo.is_my_account = True txo.is_change = row_accounts[account_match.pop()] == '1' diff --git a/lbry/wallet/ledger.py b/lbry/wallet/ledger.py index 955d6d507..6215c7bd2 100644 --- a/lbry/wallet/ledger.py +++ b/lbry/wallet/ledger.py @@ -844,7 +844,8 @@ class Ledger(metaclass=LedgerRegistry): 'amount': dewies_to_lbc(txo.amount), 'claim_id': txo.claim_id, 'claim_name': txo.claim_name, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) for txo in tx.my_update_outputs: if is_my_inputs: # updating my own claim @@ -863,7 +864,8 @@ class Ledger(metaclass=LedgerRegistry): 'amount': dewies_to_lbc(txo.amount), 'claim_id': txo.claim_id, 'claim_name': txo.claim_name, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) else: # someone sent us their claim item['update_info'].append({ @@ -872,7 +874,8 @@ class Ledger(metaclass=LedgerRegistry): 'amount': dewies_to_lbc(txo.amount), 'claim_id': txo.claim_id, 'claim_name': txo.claim_name, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) for txo in tx.my_support_outputs: item['support_info'].append({ @@ -882,7 +885,8 @@ class Ledger(metaclass=LedgerRegistry): 'claim_id': txo.claim_id, 'claim_name': txo.claim_name, 'is_tip': not is_my_inputs, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) if is_my_inputs: for txo in tx.other_support_outputs: @@ -893,7 +897,8 @@ class Ledger(metaclass=LedgerRegistry): 'claim_id': txo.claim_id, 'claim_name': txo.claim_name, 'is_tip': is_my_inputs, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) for txo in tx.my_abandon_outputs: item['abandon_info'].append({ @@ -910,7 +915,8 @@ class Ledger(metaclass=LedgerRegistry): 'balance_delta': dewies_to_lbc(txo.amount if not is_my_inputs else -txo.amount), 'amount': dewies_to_lbc(txo.amount), 'claim_id': txo.purchased_claim_id, - 'nout': txo.position + 'nout': txo.position, + 'is_spent': txo.is_spent, }) history.append(item) return history diff --git a/lbry/wallet/transaction.py b/lbry/wallet/transaction.py index 1b13eaee6..882e93df4 100644 --- a/lbry/wallet/transaction.py +++ b/lbry/wallet/transaction.py @@ -207,7 +207,7 @@ class OutputEffectiveAmountEstimator: class Output(InputOutput): __slots__ = ( - 'amount', 'script', 'is_change', 'is_my_account', + 'amount', 'script', 'is_change', 'is_spent', 'is_my_account', 'channel', 'private_key', 'meta', 'purchase', 'purchased_claim', 'purchase_receipt', 'reposted_claim', 'claims', @@ -215,13 +215,15 @@ class Output(InputOutput): def __init__(self, amount: int, script: OutputScript, tx_ref: TXRef = None, position: int = None, - is_change: Optional[bool] = None, is_my_account: Optional[bool] = None, + is_change: Optional[bool] = None, is_spent: Optional[bool] = None, + is_my_account: Optional[bool] = None, channel: Optional['Output'] = None, private_key: Optional[str] = None ) -> None: super().__init__(tx_ref, position) self.amount = amount self.script = script self.is_change = is_change + self.is_spent = is_spent self.is_my_account = is_my_account self.channel = channel self.private_key = private_key @@ -234,10 +236,12 @@ class Output(InputOutput): def update_annotations(self, annotated): if annotated is None: - self.is_change = False - self.is_my_account = False + self.is_change = None + self.is_spent = None + self.is_my_account = None else: self.is_change = annotated.is_change + self.is_spent = annotated.is_spent self.is_my_account = annotated.is_my_account self.channel = annotated.channel if annotated else None self.private_key = annotated.private_key if annotated else None diff --git a/tests/integration/blockchain/test_claim_commands.py b/tests/integration/blockchain/test_claim_commands.py index 8dcf53dda..1b0959eb4 100644 --- a/tests/integration/blockchain/test_claim_commands.py +++ b/tests/integration/blockchain/test_claim_commands.py @@ -1369,6 +1369,7 @@ class StreamCommands(ClaimTestCase): self.assertEqual(txs[0]['confirmations'], 1) self.assertEqual(txs[0]['claim_info'][0]['balance_delta'], '-2.5') self.assertEqual(txs[0]['claim_info'][0]['claim_id'], claim_id) + self.assertFalse(txs[0]['claim_info'][0]['is_spent']) self.assertEqual(txs[0]['value'], '0.0') self.assertEqual(txs[0]['fee'], '-0.020107') await self.assertBalance(self.account, '7.479893') @@ -1382,6 +1383,8 @@ class StreamCommands(ClaimTestCase): self.assertEqual(len(txs[0]['update_info']), 1) self.assertEqual(txs[0]['update_info'][0]['balance_delta'], '1.5') self.assertEqual(txs[0]['update_info'][0]['claim_id'], claim_id) + self.assertFalse(txs[0]['update_info'][0]['is_spent']) + self.assertTrue(txs[1]['claim_info'][0]['is_spent']) self.assertEqual(txs[0]['value'], '0.0') self.assertEqual(txs[0]['fee'], '-0.0002165') await self.assertBalance(self.account, '8.9796765') @@ -1391,6 +1394,9 @@ class StreamCommands(ClaimTestCase): self.assertEqual(len(txs[0]['abandon_info']), 1) self.assertEqual(txs[0]['abandon_info'][0]['balance_delta'], '1.0') self.assertEqual(txs[0]['abandon_info'][0]['claim_id'], claim_id) + self.assertTrue(txs[0]['abandon_info'][0]['is_spent']) + self.assertTrue(txs[1]['update_info'][0]['is_spent']) + self.assertTrue(txs[2]['claim_info'][0]['is_spent']) self.assertEqual(txs[0]['value'], '0.0') self.assertEqual(txs[0]['fee'], '-0.000107') await self.assertBalance(self.account, '9.9795695') @@ -1494,6 +1500,7 @@ class SupportCommands(CommandTestCase): self.assertEqual(txs[0]['support_info'][0]['balance_delta'], '1.0') self.assertEqual(txs[0]['support_info'][0]['claim_id'], claim_id) self.assertTrue(txs[0]['support_info'][0]['is_tip']) + self.assertFalse(txs[0]['support_info'][0]['is_spent']) self.assertEqual(txs[0]['value'], '1.0') self.assertEqual(txs[0]['fee'], '0.0') @@ -1505,6 +1512,7 @@ class SupportCommands(CommandTestCase): self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-1.0') self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id) self.assertTrue(txs2[0]['support_info'][0]['is_tip']) + self.assertFalse(txs2[0]['support_info'][0]['is_spent']) self.assertEqual(txs2[0]['value'], '-1.0') self.assertEqual(txs2[0]['fee'], '-0.0001415') @@ -1525,9 +1533,19 @@ class SupportCommands(CommandTestCase): self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-2.0') self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id) self.assertFalse(txs2[0]['support_info'][0]['is_tip']) + self.assertFalse(txs2[0]['support_info'][0]['is_spent']) self.assertEqual(txs2[0]['value'], '0.0') self.assertEqual(txs2[0]['fee'], '-0.0001415') + # abandoning the tip increases balance and shows tip as spent + await self.support_abandon(claim_id) + await self.assertBalance(self.account, '4.979662') + txs = (await self.out(self.daemon.jsonrpc_transaction_list(self.account.id)))['items'] + self.assertEqual(len(txs[0]['abandon_info']), 1) + self.assertEqual(len(txs[1]['support_info']), 1) + self.assertTrue(txs[1]['support_info'][0]['is_tip']) + self.assertTrue(txs[1]['support_info'][0]['is_spent']) + class CollectionCommands(CommandTestCase):