diff --git a/lbry/db/database.py b/lbry/db/database.py index 829ed8ed5..c65668aab 100644 --- a/lbry/db/database.py +++ b/lbry/db/database.py @@ -38,7 +38,7 @@ def clean_wallet_account_ids(constraints): async def add_channel_keys_to_txo_results(accounts: List, txos: Iterable[Output]): sub_channels = set() for txo in txos: - if txo.claim.is_channel: + if txo.is_claim and txo.claim.is_channel: for account in accounts: private_key = await account.get_channel_private_key( txo.claim.channel.public_key_bytes diff --git a/lbry/db/queries/txio.py b/lbry/db/queries/txio.py index b25ff3e1b..c6a73dc4a 100644 --- a/lbry/db/queries/txio.py +++ b/lbry/db/queries/txio.py @@ -380,8 +380,8 @@ def select_txos( (TXI.c.address.notin_(my_addresses)) ) joins = TXO.join(TX) - #if constraints.get('is_spent', None) is False: - # s = s.where((TXO.c.is_spent == False) & (TXO.c.is_reserved == False)) + if constraints.pop('is_spent', None) is False: + s = s.where((TXO.c.spent_height == 0) & (TXO.c.is_reserved == False)) if include_is_my_input: joins = joins.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True) if claim_id_not_in_claim_table: diff --git a/lbry/db/tables.py b/lbry/db/tables.py index 88cdf4e37..9569b9b1e 100644 --- a/lbry/db/tables.py +++ b/lbry/db/tables.py @@ -203,6 +203,7 @@ txi_join_account = TXI.join(AccountAddress, TXI.columns.address == AccountAddres pg_add_txi_constraints_and_indexes = [ "ALTER TABLE txi ADD PRIMARY KEY (txo_hash);", "CREATE INDEX txi_height ON txi (height);", + "CREATE INDEX txi_first_address ON txi (tx_hash) INCLUDE (address) WHERE position = 0;", ] diff --git a/lbry/service/api.py b/lbry/service/api.py index 91a36fca1..09a9a3729 100644 --- a/lbry/service/api.py +++ b/lbry/service/api.py @@ -859,10 +859,11 @@ class API: {kwargs} """ - args = transaction(**transaction_kwargs) - wallet = self.wallets.get_or_default_for_spending(args['wallet_id']) - account = wallet.accounts.get_or_default(args['change_account_id']) - accounts = wallet.accounts.get_or_all(args['funding_account_id']) + tx_dict, kwargs = pop_kwargs('tx', extract_tx(**tx_kwargs)) + assert_consumed_kwargs(kwargs) + wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) + account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id')) + accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id')) amount = self.ledger.get_dewies_or_error("amount", amount) if addresses and not isinstance(addresses, list): addresses = [addresses] @@ -876,7 +877,7 @@ class API: ) tx = await wallet.create_transaction([], outputs, accounts, account) await wallet.sign(tx) - await self.service.maybe_broadcast_or_release(tx, args['blocking'], args['preview']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx ACCOUNT_DOC = """ @@ -1474,7 +1475,7 @@ class API: tx = await self.wallets.create_purchase_transaction( accounts, txo, self.exchange_rate_manager, override_max_key_fee ) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx CLAIM_DOC = """ @@ -1635,7 +1636,7 @@ class API: name=name, amount=amount, holding_account=holding_account, funding_accounts=funding_accounts, save_key=not tx_dict['preview'], **remove_nulls(channel_dict) ) - await self.service.maybe_broadcast_or_release(tx, tx_dict['preview'], tx_dict['no_wait']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def channel_update( @@ -1678,7 +1679,7 @@ class API: save_key=not tx_dict['preview'], **remove_nulls(channel_edit_dict) ) - await self.service.maybe_broadcast_or_release(tx, tx_dict['blocking'], tx_dict['preview']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx @@ -1719,7 +1720,7 @@ class API: tx = await Transaction.create( [Input.spend(txo) for txo in claims], [], [account], account ) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def channel_list( @@ -1922,7 +1923,7 @@ class API: new_txo.sign(channel) await tx.sign(funding_accounts) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def stream_create( @@ -1948,6 +1949,7 @@ class API: amount = self.ledger.get_dewies_or_error('bid', bid, positive_value=True) holding_account = wallet.accounts.get_or_default(stream_dict.pop('account_id')) funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id')) + change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id')) signing_channel = None channel_id = stream_dict.pop('channel_id') channel_name = stream_dict.pop('channel_name') @@ -1977,9 +1979,10 @@ class API: name=name, amount=amount, file_path=stream_dict.pop('file_path'), create_file_stream=create_file_stream, holding_address=holding_address, funding_accounts=funding_accounts, - signing_channel=signing_channel, **remove_nulls(stream_dict) + change_account=change_account, signing_channel=signing_channel, + **remove_nulls(stream_dict) ) - await self.service.maybe_broadcast_or_release(tx, tx_dict['preview'], tx_dict['no_wait']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def stream_update( @@ -2002,6 +2005,7 @@ class API: wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) holding_account = wallet.accounts.get_or_default(stream_dict.pop('account_id')) funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id')) + change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id')) replace = stream_dict.pop('replace') old = await wallet.claims.get(claim_id=claim_id) @@ -2050,9 +2054,10 @@ class API: old=old, amount=amount, file_path=stream_dict.pop('file_path'), create_file_stream=create_file_stream, replace=replace, holding_address=holding_address, funding_accounts=funding_accounts, - signing_channel=signing_channel, **remove_nulls(stream_dict) + change_account=change_account, signing_channel=signing_channel, + **remove_nulls(stream_dict) ) - await self.service.maybe_broadcast_or_release(tx, tx_dict['preview'], tx_dict['no_wait']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def stream_abandon( @@ -2093,7 +2098,7 @@ class API: [Input.spend(txo) for txo in claims], [], accounts, account ) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, tx_dict) return tx async def stream_list( @@ -2179,7 +2184,7 @@ class API: new_txo.sign(channel) await tx.sign(funding_accounts) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, tx_dict) return tx async def collection_update( @@ -2259,7 +2264,7 @@ class API: new_txo.sign(channel) await tx.sign(funding_accounts) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, tx_dict) return tx async def collection_abandon( @@ -2365,16 +2370,22 @@ class API: wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) amount = self.ledger.get_dewies_or_error('amount', amount, positive_value=True) funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id')) - claim = await wallet.claims.get(claim_id=claim_id) + change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id')) + claim = await self.service.get_claim_by_claim_id(wallet.accounts, claim_id) + if claim is None: + raise Exception(f"Could not find claim with claim_id '{claim_id}'. ") + claim_address = claim.get_address(self.ledger) if not tip: holding_account = wallet.accounts.get_or_default(account_id) claim_address = await holding_account.receiving.get_or_create_usable_address() tx = await wallet.supports.create( - claim.claim_name, claim_id, amount, claim_address, funding_accounts, funding_accounts[0] + name=claim.claim_name, claim_id=claim_id, + amount=amount, holding_address=claim_address, + funding_accounts=funding_accounts, change_account=change_account ) - await self.service.maybe_broadcast_or_release(tx, tx_dict['preview'], tx_dict['no_wait']) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx async def support_list( @@ -2499,23 +2510,19 @@ class API: {kwargs} """ - wallet = self.wallets.get_or_default(wallet_id) - assert not wallet.is_locked, "Cannot spend funds with locked wallet, unlock first." - if account_id: - account = wallet.get_account_or_error(account_id) - accounts = [account] - else: - account = wallet.default_account - accounts = wallet.accounts + abandon_dict, kwargs = pop_kwargs('abandon', extract_abandon(**abandon_and_tx_kwargs)) + tx_dict, kwargs = pop_kwargs('tx', extract_tx(**kwargs)) + assert_consumed_kwargs(kwargs) + wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) + funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id')) + change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id')) - if txid is not None and nout is not None: - supports = await self.ledger.get_supports( - wallet=wallet, accounts=accounts, tx_hash=unhexlify(txid)[::-1], position=nout - ) - elif claim_id is not None: - supports = await self.ledger.get_supports( - wallet=wallet, accounts=accounts, claim_id=claim_id + if abandon_dict['txid']: + supports = await wallet.supports.list( + wallet=wallet, tx_hash=unhexlify(abandon_dict['txid'])[::-1], position=abandon_dict['nout'] ) + elif abandon_dict['claim_id'] is not None: + supports = await wallet.supports.list(wallet=wallet, claim_id=abandon_dict['claim_id']) else: raise Exception('Must specify claim_id, or txid and nout') @@ -2523,22 +2530,14 @@ class API: raise Exception('No supports found for the specified claim_id or txid:nout') if keep is not None: - keep = self.get_dewies_or_error('keep', keep) + keep = self.ledger.get_dewies_or_error('keep', keep, positive_value=True) else: keep = 0 - outputs = [] - if keep > 0: - outputs = [ - Output.pay_support_pubkey_hash( - keep, supports[0].claim_name, supports[0].claim_id, supports[0].pubkey_hash - ) - ] - - tx = await Transaction.create( - [Input.spend(txo) for txo in supports], outputs, accounts, account + tx = await wallet.supports.delete( + supports=supports, keep=keep, funding_accounts=funding_accounts, change_account=change_account ) - await self.service.maybe_broadcast_or_release(tx, blocking, preview) + await self.service.maybe_broadcast_or_release(tx, **tx_dict) return tx BLOCK_DOC = """ diff --git a/lbry/service/base.py b/lbry/service/base.py index b447e4822..868a14a5d 100644 --- a/lbry/service/base.py +++ b/lbry/service/base.py @@ -203,14 +203,14 @@ class Service: async def resolve(self, urls, **kwargs): raise NotImplementedError - async def search_claims(self, accounts, **kwargs) -> Tuple[List[Output], Optional[int], Censor]: + async def search_claims(self, accounts, **kwargs) -> Result[Output]: raise NotImplementedError - async def search_supports(self, accounts, **kwargs) -> Tuple[List[Output], Optional[int]]: + async def search_supports(self, accounts, **kwargs) -> Result[Output]: raise NotImplementedError - async def get_claim_by_claim_id(self, accounts, claim_id, **kwargs) -> Output: - for claim in (await self.search_claims(accounts, claim_id=claim_id, **kwargs))[0]: + async def get_claim_by_claim_id(self, accounts, claim_id, **kwargs) -> Optional[Output]: + for claim in await self.search_claims(accounts, claim_id=claim_id, **kwargs): return claim @staticmethod diff --git a/lbry/testcase.py b/lbry/testcase.py index 07d76b54e..0e3b181cb 100644 --- a/lbry/testcase.py +++ b/lbry/testcase.py @@ -654,6 +654,9 @@ class CommandTestCase(IntegrationTestCase): async def wallet_remove(self, *args, **kwargs): return await self.out(self.api.wallet_remove(*args, **kwargs)) + async def wallet_balance(self, *args, **kwargs): + return await self.out(self.api.wallet_balance(*args, **kwargs)) + async def account_list(self, *args, **kwargs): return (await self.out(self.api.account_list(*args, **kwargs)))['items'] @@ -669,6 +672,15 @@ class CommandTestCase(IntegrationTestCase): async def account_remove(self, *args, **kwargs): return await self.out(self.api.account_remove(*args, **kwargs)) + async def account_send(self, *args, **kwargs): + return await self.out(self.api.account_send(*args, **kwargs)) + + async def account_balance(self, *args, **kwargs): + return await self.out(self.api.account_balance(*args, **kwargs)) + + async def address_unused(self, *args, **kwargs): + return await self.out(self.api.address_unused(*args, **kwargs)) + def create_upload_file(self, data, prefix=None, suffix=None): file_path = tempfile.mktemp( prefix=prefix or "tmp", suffix=suffix or "", dir=self.ledger.conf.upload_dir @@ -762,8 +774,6 @@ class CommandTestCase(IntegrationTestCase): ) async def support_abandon(self, *args, confirm=True, **kwargs): - if 'blocking' not in kwargs: - kwargs['blocking'] = False return await self.confirm_and_render( self.api.support_abandon(*args, **kwargs), confirm ) diff --git a/lbry/wallet/wallet.py b/lbry/wallet/wallet.py index ba2c82161..268d405a6 100644 --- a/lbry/wallet/wallet.py +++ b/lbry/wallet/wallet.py @@ -665,9 +665,8 @@ class StreamListManager(ClaimListManager): async def create( self, name: str, amount: int, file_path: str, create_file_stream: Callable[[str], Awaitable[ManagedStream]], - holding_address: str, funding_accounts: List[Account], - signing_channel: Optional[Output] = None, - preview=False, **kwargs + holding_address: str, funding_accounts: List[Account], change_account: Account, + signing_channel: Optional[Output] = None, preview=False, **kwargs ) -> Tuple[Transaction, ManagedStream]: claim = Claim() @@ -676,7 +675,7 @@ class StreamListManager(ClaimListManager): # before creating file stream, create TX to ensure we have enough LBC tx = await self._create( name, claim, amount, holding_address, - funding_accounts, funding_accounts[0], + funding_accounts, change_account, signing_channel ) txo = tx.outputs[0] @@ -705,7 +704,7 @@ class StreamListManager(ClaimListManager): async def update( self, old: Output, amount: int, file_path: str, create_file_stream: Callable[[str], Awaitable[ManagedStream]], - holding_address: str, funding_accounts: List[Account], + holding_address: str, funding_accounts: List[Account], change_account: Account, signing_channel: Optional[Output] = None, preview=False, replace=False, **kwargs ) -> Tuple[Transaction, ManagedStream]: @@ -726,7 +725,7 @@ class StreamListManager(ClaimListManager): # before creating file stream, create TX to ensure we have enough LBC tx = await super().update( old, claim, amount, holding_address, - funding_accounts, funding_accounts[0], + funding_accounts, change_account, signing_channel ) txo = tx.outputs[0] @@ -785,9 +784,27 @@ class SupportListManager(BaseListManager): support_output = Output.pay_support_pubkey_hash( amount, name, claim_id, self.wallet.ledger.address_to_hash160(holding_address) ) - return await self.wallet.create_transaction( + tx = await self.wallet.create_transaction( [], [support_output], funding_accounts, change_account ) + await self.wallet.sign(tx) + return tx + + async def delete(self, supports, keep=0, funding_accounts=None, change_account=None): + outputs = [] + if keep > 0: + outputs = [ + Output.pay_support_pubkey_hash( + keep, supports[0].claim_name, supports[0].claim_id, supports[0].pubkey_hash + ) + ] + tx = await self.wallet.create_transaction( + [Input.spend(txo) for txo in supports], outputs, + funding_accounts or self.wallet._accounts, + change_account or self.wallet._accounts[0] + ) + await self.wallet.sign(tx) + return tx async def list(self, **constraints) -> Result[Output]: return await self.wallet.db.get_supports(**constraints) diff --git a/tests/integration/commands/test_account.py b/tests/integration/commands/test_account.py index b29f54f72..5617e8254 100644 --- a/tests/integration/commands/test_account.py +++ b/tests/integration/commands/test_account.py @@ -7,57 +7,63 @@ def extract(d, keys): class AccountManagement(CommandTestCase): + async def test_account_list_set_create_remove_add(self): # check initial account - accounts = await self.daemon.jsonrpc_account_list() - self.assertItemCount(accounts, 1) + self.assertEqual(len(await self.account_list()), 1) + + # create another account + await self.account_create('second account') + accounts = await self.account_list() + self.assertEqual(len(accounts), 2) + account = accounts[1] + self.assertEqual(account['name'], 'second account') + self.assertEqual(account['address_generator'], { + 'name': 'deterministic-chain', + 'receiving': {'gap': 20, 'maximum_uses_per_address': 1}, + 'change': {'gap': 6, 'maximum_uses_per_address': 1}, + }) # change account name and gap - account_id = accounts['items'][0]['id'] - self.daemon.jsonrpc_account_set( - account_id=account_id, new_name='test account', + await self.account_set( + account_id=account['id'], new_name='test account', receiving_gap=95, receiving_max_uses=96, change_gap=97, change_max_uses=98 ) - accounts = (await self.daemon.jsonrpc_account_list())['items'][0] - self.assertEqual(accounts['name'], 'test account') - self.assertEqual( - accounts['address_generator']['receiving'], - {'gap': 95, 'maximum_uses_per_address': 96} - ) - self.assertEqual( - accounts['address_generator']['change'], - {'gap': 97, 'maximum_uses_per_address': 98} - ) - - # create another account - await self.daemon.jsonrpc_account_create('second account') - accounts = await self.daemon.jsonrpc_account_list() - self.assertItemCount(accounts, 2) - self.assertEqual(accounts['items'][1]['name'], 'second account') - account_id2 = accounts['items'][1]['id'] + account = (await self.account_list())[1] + self.assertEqual(account['name'], 'test account') + self.assertEqual(account['address_generator'], { + 'name': 'deterministic-chain', + 'receiving': {'gap': 95, 'maximum_uses_per_address': 96}, + 'change': {'gap': 97, 'maximum_uses_per_address': 98}, + }) # make new account the default - self.daemon.jsonrpc_account_set(account_id=account_id2, default=True) - accounts = await self.daemon.jsonrpc_account_list(show_seed=True) - self.assertEqual(accounts['items'][0]['name'], 'second account') + await self.account_set(account_id=account['id'], default=True) + actual = (await self.account_list())[0] + self.assertNotEqual(account['modified_on'], actual['modified_on']) + del account['modified_on'] + del actual['modified_on'] + self.assertEqual(account, actual) - account_seed = accounts['items'][1]['seed'] + account_seed, account_pubkey = account['seed'], account['public_key'] # remove account - self.daemon.jsonrpc_account_remove(accounts['items'][1]['id']) - accounts = await self.daemon.jsonrpc_account_list() - self.assertItemCount(accounts, 1) + await self.account_remove(account['id']) + self.assertEqual(len(await self.account_list()), 1) # add account - await self.daemon.jsonrpc_account_add('recreated account', seed=account_seed) - accounts = await self.daemon.jsonrpc_account_list() - self.assertItemCount(accounts, 2) - self.assertEqual(accounts['items'][1]['name'], 'recreated account') + await self.account_add('recreated account', seed=account_seed) + accounts = await self.account_list() + self.assertEqual(len(accounts), 2) + account = accounts[1] + self.assertEqual(account['name'], 'recreated account') + self.assertEqual(account['public_key'], account_pubkey) # list specific account - accounts = await self.daemon.jsonrpc_account_list(account_id, include_claims=True) - self.assertEqual(accounts['items'][0]['name'], 'recreated account') + accounts = await self.account_list(account['id']) + self.assertEqual(len(accounts), 1) + self.assertEqual(accounts[0]['name'], 'recreated account') async def test_wallet_migration(self): # null certificates should get deleted diff --git a/tests/integration/commands/test_wallet.py b/tests/integration/commands/test_wallet.py index 30415bfd7..82defd8be 100644 --- a/tests/integration/commands/test_wallet.py +++ b/tests/integration/commands/test_wallet.py @@ -1,50 +1,37 @@ -import asyncio import json +import asyncio +from unittest import skip from sqlalchemy import event -from lbry.wallet import ENCRYPT_ON_DISK +from lbry.wallet.wallet import ENCRYPT_ON_DISK from lbry.error import InvalidPasswordError from lbry.testcase import CommandTestCase -from lbry.wallet.dewies import dict_values_to_lbc +from lbry.blockchain.dewies import dict_values_to_lbc class WalletCommands(CommandTestCase): - async def test_wallet_create_and_add_subscribe(self): - session = next(iter(self.conductor.spv_node.server.session_mgr.sessions)) - self.assertEqual(len(session.hashX_subs), 27) - wallet = await self.daemon.jsonrpc_wallet_create('foo', create_account=True, single_key=True) - self.assertEqual(len(session.hashX_subs), 28) - await self.daemon.jsonrpc_wallet_remove(wallet.id) - self.assertEqual(len(session.hashX_subs), 27) - await self.daemon.jsonrpc_wallet_add(wallet.id) - self.assertEqual(len(session.hashX_subs), 28) - - async def test_wallet_syncing_status(self): - address = await self.daemon.jsonrpc_address_unused() - self.assertFalse(self.daemon.jsonrpc_wallet_status()['is_syncing']) - await self.blockchain.send_to_address(address, 1) - await self.ledger._update_tasks.started.wait() - self.assertTrue(self.daemon.jsonrpc_wallet_status()['is_syncing']) - await self.ledger._update_tasks.done.wait() - self.assertFalse(self.daemon.jsonrpc_wallet_status()['is_syncing']) - - wallet = self.daemon.component_manager.get_actual_component('wallet') - wallet_manager = wallet.wallet_manager - # when component manager hasn't started yet - wallet.wallet_manager = None + async def test_list_create_add_and_remove(self): + self.assertEqual(await self.wallet_list(), [{'id': 'default_wallet', 'name': 'Wallet'}]) self.assertEqual( - {'is_encrypted': None, 'is_syncing': None, 'is_locked': None}, - self.daemon.jsonrpc_wallet_status() - ) - wallet.wallet_manager = wallet_manager - self.assertEqual( - {'is_encrypted': False, 'is_syncing': False, 'is_locked': False}, - self.daemon.jsonrpc_wallet_status() + await self.wallet_create('another', "Another"), + {'id': 'another', 'name': 'Another'} ) + self.assertEqual(await self.wallet_list(), [ + {'id': 'default_wallet', 'name': 'Wallet'}, + {'id': 'another', 'name': 'Another'} + ]) + self.assertEqual(await self.wallet_remove('another'), {'id': 'another', 'name': 'Another'}) + self.assertEqual(await self.wallet_list(), [{'id': 'default_wallet', 'name': 'Wallet'}]) + self.assertEqual(await self.wallet_add('another'), {'id': 'another', 'name': 'Another'}) + self.assertEqual(await self.wallet_list(), [ + {'id': 'default_wallet', 'name': 'Wallet'}, + {'id': 'another', 'name': 'Another'} + ]) - async def test_wallet_reconnect(self): + @skip + async def test_reconnect(self): await self.conductor.spv_node.stop(True) self.conductor.spv_node.port = 54320 await self.conductor.spv_node.start(self.conductor.blockchain_node) @@ -57,160 +44,107 @@ class WalletCommands(CommandTestCase): self.assertEqual(len(status['wallet']['servers']), 1) self.assertEqual(status['wallet']['servers'][0]['port'], 54320) - async def test_balance_caching(self): - account2 = await self.daemon.jsonrpc_account_create("Tip-er") - address2 = await self.daemon.jsonrpc_address_unused(account2.id) - sendtxid = await self.blockchain.send_to_address(address2, 10) - await self.confirm_tx(sendtxid) - await self.generate(1) - - wallet_balance = self.daemon.jsonrpc_wallet_balance - ledger = self.ledger - - query_count = 0 - - def catch_queries(*args, **kwargs): - nonlocal query_count - query_count += 1 - - event.listen(self.ledger.db.engine, "before_cursor_execute", catch_queries) - - expected = { - 'total': '20.0', - 'available': '20.0', - 'reserved': '0.0', - 'reserved_subtotals': {'claims': '0.0', 'supports': '0.0', 'tips': '0.0'} - } - self.assertIsNone(ledger._balance_cache.get(self.account.id)) - - self.assertEqual(await wallet_balance(), expected) - self.assertEqual(query_count, 6) - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(self.account.id))['total'], '10.0') - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(account2.id))['total'], '10.0') - - # calling again uses cache - self.assertEqual(await wallet_balance(), expected) - self.assertEqual(query_count, 6) - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(self.account.id))['total'], '10.0') - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(account2.id))['total'], '10.0') - - await self.stream_create() - await self.generate(1) - - expected = { - 'total': '19.979893', - 'available': '18.979893', - 'reserved': '1.0', - 'reserved_subtotals': {'claims': '1.0', 'supports': '0.0', 'tips': '0.0'} - } - # on_transaction event reset balance cache - query_count = 0 - self.assertEqual(await wallet_balance(), expected) - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(self.account.id))['total'], '9.979893') - self.assertEqual(dict_values_to_lbc(ledger._balance_cache.get(account2.id))['total'], '10.0') - self.assertEqual(query_count, 3) # only one of the accounts changed - async def test_granular_balances(self): - account2 = await self.daemon.jsonrpc_account_create("Tip-er") - wallet2 = await self.daemon.jsonrpc_wallet_create('foo', create_account=True) - account3 = wallet2.default_account - address3 = await self.daemon.jsonrpc_address_unused(account3.id, wallet2.id) - await self.confirm_tx(await self.blockchain.send_to_address(address3, 1)) + account2 = (await self.account_create("Tip-er"))["id"] + wallet2 = (await self.wallet_create("foo", create_account=True))["id"] + account3 = (await self.account_list(wallet_id=wallet2))[0]["id"] + address3 = await self.address_unused(account3, wallet2) + await self.chain.send_to_address(address3, 1) await self.generate(1) - account_balance = self.daemon.jsonrpc_account_balance - wallet_balance = self.daemon.jsonrpc_wallet_balance - expected = { 'total': '10.0', 'available': '10.0', 'reserved': '0.0', 'reserved_subtotals': {'claims': '0.0', 'supports': '0.0', 'tips': '0.0'} } - self.assertEqual(await account_balance(), expected) - self.assertEqual(await wallet_balance(), expected) + self.assertEqual(await self.account_balance(), expected) + self.assertEqual(await self.wallet_balance(), expected) # claim with update + supporting our own claim stream1 = await self.stream_create('granularity', '3.0') + await self.generate(1) await self.stream_update(self.get_claim_id(stream1), data=b'news', bid='1.0') + await self.generate(1) await self.support_create(self.get_claim_id(stream1), '2.0') expected = { - 'total': '9.977534', - 'available': '6.977534', + 'total': '9.977558', + 'available': '6.977558', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} } - self.assertEqual(await account_balance(), expected) - self.assertEqual(await wallet_balance(), expected) + self.assertEqual(await self.account_balance(), expected) + self.assertEqual(await self.wallet_balance(), expected) - address2 = await self.daemon.jsonrpc_address_unused(account2.id) + address2 = await self.address_unused(account2) # send lbc to someone else - tx = await self.daemon.jsonrpc_account_send('1.0', address2) - await self.confirm_tx(tx.id) - self.assertEqual(await account_balance(), { - 'total': '8.97741', - 'available': '5.97741', + tx = await self.wallet_send('1.0', address2, fund_account_id=self.account.id) + await self.generate(1) + self.assertEqual(await self.account_balance(), { + 'total': '8.977434', + 'available': '5.977434', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) - self.assertEqual(await wallet_balance(), { - 'total': '9.97741', - 'available': '6.97741', + self.assertEqual(await self.wallet_balance(), { + 'total': '9.977434', + 'available': '6.977434', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) # tip received support1 = await self.support_create( - self.get_claim_id(stream1), '0.3', tip=True, wallet_id=wallet2.id + self.get_claim_id(stream1), '0.3', tip=True, wallet_id=wallet2 ) - self.assertEqual(await account_balance(), { - 'total': '9.27741', - 'available': '5.97741', + self.assertEqual(await self.account_balance(), { + 'total': '9.277434', + 'available': '5.977434', 'reserved': '3.3', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} }) - self.assertEqual(await wallet_balance(), { - 'total': '10.27741', - 'available': '6.97741', + self.assertEqual(await self.wallet_balance(), { + 'total': '10.277434', + 'available': '6.977434', 'reserved': '3.3', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} }) # tip claimed - tx = await self.daemon.jsonrpc_support_abandon(txid=support1['txid'], nout=0) - await self.confirm_tx(tx.id) - self.assertEqual(await account_balance(), { - 'total': '9.277303', - 'available': '6.277303', + tx = await self.support_abandon(txid=support1['txid']) + await self.generate(1) + self.assertEqual(await self.account_balance(), { + 'total': '9.277327', + 'available': '6.277327', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) - self.assertEqual(await wallet_balance(), { - 'total': '10.277303', - 'available': '7.277303', + self.assertEqual(await self.wallet_balance(), { + 'total': '10.277327', + 'available': '7.277327', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) stream2 = await self.stream_create( - 'granularity-is-cool', '0.1', account_id=account2.id, funding_account_ids=[account2.id] + 'granularity-is-cool', '0.1', + account_id=account2, fund_account_id=[account2], change_account_id=account2 ) # tip another claim await self.support_create( - self.get_claim_id(stream2), '0.2', tip=True, wallet_id=wallet2.id + self.get_claim_id(stream2), '0.2', tip=True, wallet_id=wallet2 ) - self.assertEqual(await account_balance(), { - 'total': '9.277303', - 'available': '6.277303', + self.assertEqual(await self.account_balance(), { + 'total': '9.277327', + 'available': '6.277327', 'reserved': '3.0', 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} }) - self.assertEqual(await wallet_balance(), { - 'total': '10.439196', - 'available': '7.139196', + self.assertEqual(await self.wallet_balance(), { + 'total': '10.43922', + 'available': '7.13922', 'reserved': '3.3', 'reserved_subtotals': {'claims': '1.1', 'supports': '2.0', 'tips': '0.2'} }) @@ -225,7 +159,7 @@ class WalletEncryptionAndSynchronization(CommandTestCase): async def asyncSetUp(self): await super().asyncSetUp() - self.daemon2 = await self.add_daemon( + self.daemon2 = await self.add_full_node( seed="chest sword toast envelope bottom stomach absent " "carbon smart garage balance margin twelve" )