diff --git a/lbry/extras/daemon/daemon.py b/lbry/extras/daemon/daemon.py index a9ff849cb..cd65af76c 100644 --- a/lbry/extras/daemon/daemon.py +++ b/lbry/extras/daemon/daemon.py @@ -2213,7 +2213,7 @@ class Daemon(metaclass=JSONRPCServerType): List my stream and channel claims. Usage: - claim_list [--claim_type=...] [--claim_id=...] [--name=...] + claim_list [--claim_type=...] [--claim_id=...] [--name=...] [--is_spent] [--channel_id=...] [--account_id=] [--wallet_id=] [--page=] [--page_size=] [--resolve] [--order_by=] [--no_totals] [--include_received_tips] @@ -2223,6 +2223,7 @@ class Daemon(metaclass=JSONRPCServerType): --claim_id= : (str or list) claim id --channel_id= : (str or list) streams in this channel --name= : (str or list) claim name + --is_spent : (bool) shows previous claim updates and abandons --account_id= : (str) id of the account to query --wallet_id= : (str) restrict results to specific wallet --page= : (int) page to return during paginating @@ -2236,7 +2237,8 @@ class Daemon(metaclass=JSONRPCServerType): Returns: {Paginated[Output]} """ kwargs['type'] = claim_type or CLAIM_TYPE_NAMES - kwargs['unspent'] = True + if 'is_spent' not in kwargs: + kwargs['is_not_spent'] = True return self.jsonrpc_txo_list(**kwargs) @requires(WALLET_COMPONENT) @@ -2750,12 +2752,13 @@ class Daemon(metaclass=JSONRPCServerType): Usage: channel_list [ | --account_id=] [--wallet_id=] - [--name=...] [--claim_id=...] + [--name=...] [--claim_id=...] [--is_spent] [--page=] [--page_size=] [--resolve] [--no_totals] Options: --name= : (str or list) channel name --claim_id= : (str or list) channel id + --is_spent : (bool) shows previous channel updates and abandons --account_id= : (str) id of the account to use --wallet_id= : (str) restrict results to specific wallet --page= : (int) page to return during paginating @@ -2767,7 +2770,8 @@ class Daemon(metaclass=JSONRPCServerType): Returns: {Paginated[Output]} """ kwargs['type'] = 'channel' - kwargs['unspent'] = True + if 'is_spent' not in kwargs: + kwargs['is_not_spent'] = True return self.jsonrpc_txo_list(*args, **kwargs) @requires(WALLET_COMPONENT) @@ -3504,12 +3508,13 @@ class Daemon(metaclass=JSONRPCServerType): Usage: stream_list [ | --account_id=] [--wallet_id=] - [--name=...] [--claim_id=...] + [--name=...] [--claim_id=...] [--is_spent] [--page=] [--page_size=] [--resolve] [--no_totals] Options: --name= : (str or list) stream name --claim_id= : (str or list) stream id + --is_spent : (bool) shows previous stream updates and abandons --account_id= : (str) id of the account to query --wallet_id= : (str) restrict results to specific wallet --page= : (int) page to return during paginating @@ -3521,7 +3526,8 @@ class Daemon(metaclass=JSONRPCServerType): Returns: {Paginated[Output]} """ kwargs['type'] = 'stream' - kwargs['unspent'] = True + if 'is_spent' not in kwargs: + kwargs['is_not_spent'] = True return self.jsonrpc_txo_list(*args, **kwargs) @requires(WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT, @@ -3968,19 +3974,23 @@ class Daemon(metaclass=JSONRPCServerType): return tx @requires(WALLET_COMPONENT) - def jsonrpc_support_list(self, *args, tips=None, **kwargs): + def jsonrpc_support_list(self, *args, received=False, sent=False, staked=False, **kwargs): """ - List supports and tips in my control. + List staked supports and sent/received tips. Usage: support_list [ | --account_id=] [--wallet_id=] - [--name=...] [--claim_id=...] [--tips] + [--name=...] [--claim_id=...] + [--received | --sent | --staked] [--is_spent] [--page=] [--page_size=] [--no_totals] Options: --name= : (str or list) claim name --claim_id= : (str or list) claim id - --tips : (bool) only show tips + --received : (bool) only show received (tips) + --sent : (bool) only show sent (tips) + --staked : (bool) only show my staked supports + --is_spent : (bool) show abandoned supports --account_id= : (str) id of the account to query --wallet_id= : (str) restrict results to specific wallet --page= : (int) page to return during paginating @@ -3991,9 +4001,20 @@ class Daemon(metaclass=JSONRPCServerType): Returns: {Paginated[Output]} """ kwargs['type'] = 'support' - kwargs['unspent'] = True - if tips is True: + if 'is_spent' not in kwargs: + kwargs['is_not_spent'] = True + if received: kwargs['is_not_my_input'] = True + kwargs['is_my_output'] = True + elif sent: + kwargs['is_my_input'] = True + kwargs['is_not_my_output'] = True + # spent for not my outputs is undetermined + kwargs.pop('is_spent', None) + kwargs.pop('is_not_spent', None) + elif staked: + kwargs['is_my_input'] = True + kwargs['is_my_output'] = True return self.jsonrpc_txo_list(*args, **kwargs) @requires(WALLET_COMPONENT) @@ -4168,11 +4189,15 @@ class Daemon(metaclass=JSONRPCServerType): @staticmethod def _constrain_txo_from_kwargs( constraints, type=None, txid=None, # pylint: disable=redefined-builtin - claim_id=None, channel_id=None, name=None, unspent=False, reposted_claim_id=None, + claim_id=None, channel_id=None, name=None, reposted_claim_id=None, + is_spent=False, is_not_spent=False, is_my_input_or_output=None, exclude_internal_transfers=False, is_my_output=None, is_not_my_output=None, is_my_input=None, is_not_my_input=None): - constraints['unspent'] = unspent + if is_spent: + constraints['is_spent'] = True + elif is_not_spent: + constraints['is_spent'] = False constraints['exclude_internal_transfers'] = exclude_internal_transfers if is_my_input_or_output is True: constraints['is_my_input_or_output'] = True @@ -4201,8 +4226,9 @@ class Daemon(metaclass=JSONRPCServerType): List my transaction outputs. Usage: - txo_list [--account_id=] [--type=...] [--txid=...] [--unspent] + txo_list [--account_id=] [--type=...] [--txid=...] [--claim_id=...] [--channel_id=...] [--name=...] + [--is_spent | --is_not_spent] [--is_my_input_or_output | [[--is_my_output | --is_not_my_output] [--is_my_input | --is_not_my_input]] ] @@ -4217,7 +4243,8 @@ class Daemon(metaclass=JSONRPCServerType): --claim_id= : (str or list) claim id --channel_id= : (str or list) claims in this channel --name= : (str or list) claim name - --unspent : (bool) hide spent outputs, show only unspent ones + --is_spent : (bool) only show spent txos + --is_not_spent : (bool) only show not spent txos --is_my_input_or_output : (bool) txos which have your inputs or your outputs, if using this flag the other related flags are ignored (--is_my_output, --is_my_input, etc) @@ -4306,7 +4333,7 @@ class Daemon(metaclass=JSONRPCServerType): accounts = [wallet.get_account_or_error(account_id)] if account_id else wallet.accounts txos = await self.ledger.get_txos( wallet=wallet, accounts=accounts, read_only=True, - **self._constrain_txo_from_kwargs({}, unspent=True, is_my_output=True, **kwargs) + **self._constrain_txo_from_kwargs({}, is_not_spent=True, is_my_output=True, **kwargs) ) txs = [] while txos: @@ -4330,7 +4357,8 @@ class Daemon(metaclass=JSONRPCServerType): Usage: txo_list [--account_id=] [--type=...] [--txid=...] - [--claim_id=...] [--name=...] [--unspent] + [--claim_id=...] [--name=...] + [--is_spent] [--is_not_spent] [--is_my_input_or_output | [[--is_my_output | --is_not_my_output] [--is_my_input | --is_not_my_input]] ] @@ -4342,7 +4370,8 @@ class Daemon(metaclass=JSONRPCServerType): --txid= : (str or list) transaction id of outputs --claim_id= : (str or list) claim id --name= : (str or list) claim name - --unspent : (bool) hide spent outputs, show only unspent ones + --is_spent : (bool) only show spent txos + --is_not_spent : (bool) only show not spent txos --is_my_input_or_output : (bool) txos which have your inputs or your outputs, if using this flag the other related flags are ignored (--is_my_output, --is_my_input, etc) @@ -4374,7 +4403,7 @@ class Daemon(metaclass=JSONRPCServerType): Usage: txo_plot [--account_id=] [--type=...] [--txid=...] - [--claim_id=...] [--name=...] [--unspent] + [--claim_id=...] [--name=...] [--is_spent] [--is_not_spent] [--is_my_input_or_output | [[--is_my_output | --is_not_my_output] [--is_my_input | --is_not_my_input]] ] @@ -4389,7 +4418,8 @@ class Daemon(metaclass=JSONRPCServerType): --txid= : (str or list) transaction id of outputs --claim_id= : (str or list) claim id --name= : (str or list) claim name - --unspent : (bool) hide spent outputs, show only unspent ones + --is_spent : (bool) only show spent txos + --is_not_spent : (bool) only show not spent txos --is_my_input_or_output : (bool) txos which have your inputs or your outputs, if using this flag the other related flags are ignored (--is_my_output, --is_my_input, etc) @@ -4446,7 +4476,7 @@ class Daemon(metaclass=JSONRPCServerType): Returns: {Paginated[Output]} """ kwargs['type'] = ['other', 'purchase'] - kwargs['unspent'] = True + kwargs['is_not_spent'] = True return self.jsonrpc_txo_list(*args, **kwargs) @requires(WALLET_COMPONENT) diff --git a/lbry/wallet/database.py b/lbry/wallet/database.py index 949442247..d3590d015 100644 --- a/lbry/wallet/database.py +++ b/lbry/wallet/database.py @@ -694,7 +694,7 @@ class Database(SQLiteMixin): self, cols, accounts=None, is_my_input=None, is_my_output=True, is_my_input_or_output=None, exclude_internal_transfers=False, include_is_spent=False, include_is_my_input=False, - read_only=False, **constraints): + is_spent=None, read_only=False, **constraints): for rename_col in ('txid', 'txoid'): for rename_constraint in (rename_col, rename_col+'__in', rename_col+'__not_in'): if rename_constraint in constraints: @@ -737,23 +737,18 @@ class Database(SQLiteMixin): 'txi.address__not_in': my_addresses } sql = [f"SELECT {cols} FROM txo JOIN tx ON (tx.txid=txo.txid)"] - if include_is_spent: + if is_spent: + constraints['spent.txoid__is_not_null'] = True + elif is_spent is False: + constraints['is_reserved'] = False + constraints['spent.txoid__is_null'] = True + if include_is_spent or is_spent is not None: sql.append("LEFT JOIN txi AS spent ON (spent.txoid=txo.txoid)") if include_is_my_input: sql.append("LEFT JOIN txi ON (txi.position=0 AND txi.txid=txo.txid)") return await self.db.execute_fetchall(*query(' '.join(sql), **constraints), read_only=read_only) - @staticmethod - def constrain_unspent(constraints): - constraints['is_reserved'] = False - constraints['include_is_spent'] = True - constraints['spent.txoid__is_null'] = True - - async def get_txos(self, wallet=None, no_tx=False, unspent=False, read_only=False, **constraints): - - if unspent: - self.constrain_unspent(constraints) - + async def get_txos(self, wallet=None, no_tx=False, read_only=False, **constraints): include_is_spent = constraints.get('include_is_spent', False) include_is_my_input = constraints.get('include_is_my_input', False) include_is_my_output = constraints.pop('include_is_my_output', False) @@ -869,7 +864,8 @@ class Database(SQLiteMixin): return txos - def _clean_txo_constraints_for_aggregation(self, unspent, constraints): + @staticmethod + def _clean_txo_constraints_for_aggregation(constraints): constraints.pop('include_is_spent', None) constraints.pop('include_is_my_input', None) constraints.pop('include_is_my_output', None) @@ -879,22 +875,19 @@ class Database(SQLiteMixin): constraints.pop('offset', None) constraints.pop('limit', None) constraints.pop('order_by', None) - if unspent: - self.constrain_unspent(constraints) - async def get_txo_count(self, unspent=False, **constraints): - self._clean_txo_constraints_for_aggregation(unspent, constraints) + async def get_txo_count(self, **constraints): + self._clean_txo_constraints_for_aggregation(constraints) count = await self.select_txos('COUNT(*) AS total', **constraints) return count[0]['total'] or 0 - async def get_txo_sum(self, unspent=False, **constraints): - self._clean_txo_constraints_for_aggregation(unspent, constraints) + async def get_txo_sum(self, **constraints): + self._clean_txo_constraints_for_aggregation(constraints) result = await self.select_txos('SUM(amount) AS total', **constraints) return result[0]['total'] or 0 - async def get_txo_plot( - self, unspent=False, start_day=None, days_back=0, end_day=None, days_after=None, **constraints): - self._clean_txo_constraints_for_aggregation(unspent, constraints) + async def get_txo_plot(self, start_day=None, days_back=0, end_day=None, days_after=None, **constraints): + self._clean_txo_constraints_for_aggregation(constraints) if start_day is None: constraints['day__gte'] = self.ledger.headers.estimated_julian_day( self.ledger.headers.height @@ -915,17 +908,18 @@ class Database(SQLiteMixin): ) def get_utxos(self, read_only=False, **constraints): - return self.get_txos(unspent=True, read_only=read_only, **constraints) + return self.get_txos(is_spent=False, read_only=read_only, **constraints) def get_utxo_count(self, **constraints): - return self.get_txo_count(unspent=True, **constraints) + return self.get_txo_count(is_spent=False, **constraints) async def get_balance(self, wallet=None, accounts=None, read_only=False, **constraints): assert wallet or accounts, \ "'wallet' or 'accounts' constraints required to calculate balance" constraints['accounts'] = accounts or wallet.accounts - self.constrain_unspent(constraints) - balance = await self.select_txos('SUM(amount) as total', read_only=read_only, **constraints) + balance = await self.select_txos( + 'SUM(amount) as total', is_spent=False, read_only=read_only, **constraints + ) return balance[0]['total'] or 0 async def select_addresses(self, cols, read_only=False, **constraints): @@ -1084,7 +1078,7 @@ class Database(SQLiteMixin): def get_supports_summary(self, read_only=False, **constraints): return self.get_txos( txo_type=TXO_TYPES['support'], - unspent=True, is_my_output=True, + is_spent=False, is_my_output=True, include_is_my_input=True, no_tx=True, read_only=read_only, **constraints diff --git a/lbry/wallet/ledger.py b/lbry/wallet/ledger.py index eed6155f8..71b815568 100644 --- a/lbry/wallet/ledger.py +++ b/lbry/wallet/ledger.py @@ -714,7 +714,7 @@ class Ledger(metaclass=LedgerRegistry): if include_is_my_output: mine = await self.db.get_txo_count( claim_id=txo.claim_id, txo_type__in=CLAIM_TYPES, is_my_output=True, - unspent=True, accounts=accounts + is_spent=False, accounts=accounts ) if mine: txo_copy.is_my_output = True @@ -724,7 +724,7 @@ class Ledger(metaclass=LedgerRegistry): supports = await self.db.get_txo_sum( claim_id=txo.claim_id, txo_type=TXO_TYPES['support'], is_my_input=True, is_my_output=True, - unspent=True, accounts=accounts + is_spent=False, accounts=accounts ) txo_copy.sent_supports = supports if include_sent_tips: diff --git a/tests/integration/blockchain/test_claim_commands.py b/tests/integration/blockchain/test_claim_commands.py index 5520f0e88..b1a3fd2ee 100644 --- a/tests/integration/blockchain/test_claim_commands.py +++ b/tests/integration/blockchain/test_claim_commands.py @@ -423,18 +423,18 @@ class TransactionOutputCommands(ClaimTestCase): async def test_txo_list_and_sum_filtering(self): channel_id = self.get_claim_id(await self.channel_create()) - self.assertEqual('1.0', lbc(await self.txo_sum(type='channel', unspent=True))) + self.assertEqual('1.0', lbc(await self.txo_sum(type='channel', is_not_spent=True))) await self.channel_update(channel_id, bid='0.5') - self.assertEqual('0.5', lbc(await self.txo_sum(type='channel', unspent=True))) + self.assertEqual('0.5', lbc(await self.txo_sum(type='channel', is_not_spent=True))) self.assertEqual('1.5', lbc(await self.txo_sum(type='channel'))) stream_id = self.get_claim_id(await self.stream_create(bid='1.3')) - self.assertEqual('1.3', lbc(await self.txo_sum(type='stream', unspent=True))) + self.assertEqual('1.3', lbc(await self.txo_sum(type='stream', is_not_spent=True))) await self.stream_update(stream_id, bid='0.7') - self.assertEqual('0.7', lbc(await self.txo_sum(type='stream', unspent=True))) + self.assertEqual('0.7', lbc(await self.txo_sum(type='stream', is_not_spent=True))) self.assertEqual('2.0', lbc(await self.txo_sum(type='stream'))) - self.assertEqual('1.2', lbc(await self.txo_sum(type=['stream', 'channel'], unspent=True))) + self.assertEqual('1.2', lbc(await self.txo_sum(type=['stream', 'channel'], is_not_spent=True))) self.assertEqual('3.5', lbc(await self.txo_sum(type=['stream', 'channel']))) # type filtering @@ -536,7 +536,7 @@ class TransactionOutputCommands(ClaimTestCase): self.assertEqual([sent_channel, kept_channel, initial_funds], r) # my unspent stuff and stuff i sent excluding "change" - r = await self.txo_list(is_my_input_or_output=True, unspent=True, exclude_internal_transfers=True) + r = await self.txo_list(is_my_input_or_output=True, is_not_spent=True, exclude_internal_transfers=True) self.assertEqual([sent_channel, kept_channel], r) # only "change" @@ -544,11 +544,15 @@ class TransactionOutputCommands(ClaimTestCase): self.assertEqual([change2, change1], r) # only unspent "change" - r = await self.txo_list(is_my_input=True, is_my_output=True, type="other", unspent=True) + r = await self.txo_list(is_my_input=True, is_my_output=True, type="other", is_not_spent=True) self.assertEqual([change2], r) + # only spent "change" + r = await self.txo_list(is_my_input=True, is_my_output=True, type="other", is_spent=True) + self.assertEqual([change1], r) + # all my unspent stuff - r = await self.txo_list(is_my_output=True, unspent=True) + r = await self.txo_list(is_my_output=True, is_not_spent=True) self.assertEqual([change2, kept_channel], r) # stuff i sent @@ -615,14 +619,14 @@ class TransactionOutputCommands(ClaimTestCase): for _ in range(10): await self.support_create(stream_id, '0.1') await self.assertBalance(self.account, '7.978478') - self.assertEqual('1.0', lbc(await self.txo_sum(type='support', unspent=True))) + self.assertEqual('1.0', lbc(await self.txo_sum(type='support', is_not_spent=True))) txs = await self.txo_spend(type='support', batch_size=3, include_full_tx=True) self.assertEqual(4, len(txs)) self.assertEqual(3, len(txs[0]['inputs'])) self.assertEqual(3, len(txs[1]['inputs'])) self.assertEqual(3, len(txs[2]['inputs'])) self.assertEqual(1, len(txs[3]['inputs'])) - self.assertEqual('0.0', lbc(await self.txo_sum(type='support', unspent=True))) + self.assertEqual('0.0', lbc(await self.txo_sum(type='support', is_not_spent=True))) await self.assertBalance(self.account, '8.977606') await self.support_create(stream_id, '0.1')