wallet commands progress

This commit is contained in:
Lex Berezhny 2020-10-30 10:25:52 -04:00
parent d8f5b11158
commit d459d6a26b
9 changed files with 199 additions and 232 deletions

View file

@ -38,7 +38,7 @@ def clean_wallet_account_ids(constraints):
async def add_channel_keys_to_txo_results(accounts: List, txos: Iterable[Output]): async def add_channel_keys_to_txo_results(accounts: List, txos: Iterable[Output]):
sub_channels = set() sub_channels = set()
for txo in txos: for txo in txos:
if txo.claim.is_channel: if txo.is_claim and txo.claim.is_channel:
for account in accounts: for account in accounts:
private_key = await account.get_channel_private_key( private_key = await account.get_channel_private_key(
txo.claim.channel.public_key_bytes txo.claim.channel.public_key_bytes

View file

@ -380,8 +380,8 @@ def select_txos(
(TXI.c.address.notin_(my_addresses)) (TXI.c.address.notin_(my_addresses))
) )
joins = TXO.join(TX) joins = TXO.join(TX)
#if constraints.get('is_spent', None) is False: if constraints.pop('is_spent', None) is False:
# s = s.where((TXO.c.is_spent == False) & (TXO.c.is_reserved == False)) s = s.where((TXO.c.spent_height == 0) & (TXO.c.is_reserved == False))
if include_is_my_input: if include_is_my_input:
joins = joins.join(TXI, (TXI.c.position == 0) & (TXI.c.tx_hash == TXO.c.tx_hash), isouter=True) 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: if claim_id_not_in_claim_table:

View file

@ -203,6 +203,7 @@ txi_join_account = TXI.join(AccountAddress, TXI.columns.address == AccountAddres
pg_add_txi_constraints_and_indexes = [ pg_add_txi_constraints_and_indexes = [
"ALTER TABLE txi ADD PRIMARY KEY (txo_hash);", "ALTER TABLE txi ADD PRIMARY KEY (txo_hash);",
"CREATE INDEX txi_height ON txi (height);", "CREATE INDEX txi_height ON txi (height);",
"CREATE INDEX txi_first_address ON txi (tx_hash) INCLUDE (address) WHERE position = 0;",
] ]

View file

@ -859,10 +859,11 @@ class API:
{kwargs} {kwargs}
""" """
args = transaction(**transaction_kwargs) tx_dict, kwargs = pop_kwargs('tx', extract_tx(**tx_kwargs))
wallet = self.wallets.get_or_default_for_spending(args['wallet_id']) assert_consumed_kwargs(kwargs)
account = wallet.accounts.get_or_default(args['change_account_id']) wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id'))
accounts = wallet.accounts.get_or_all(args['funding_account_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) amount = self.ledger.get_dewies_or_error("amount", amount)
if addresses and not isinstance(addresses, list): if addresses and not isinstance(addresses, list):
addresses = [addresses] addresses = [addresses]
@ -876,7 +877,7 @@ class API:
) )
tx = await wallet.create_transaction([], outputs, accounts, account) tx = await wallet.create_transaction([], outputs, accounts, account)
await wallet.sign(tx) 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 return tx
ACCOUNT_DOC = """ ACCOUNT_DOC = """
@ -1474,7 +1475,7 @@ class API:
tx = await self.wallets.create_purchase_transaction( tx = await self.wallets.create_purchase_transaction(
accounts, txo, self.exchange_rate_manager, override_max_key_fee 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 return tx
CLAIM_DOC = """ CLAIM_DOC = """
@ -1635,7 +1636,7 @@ class API:
name=name, amount=amount, holding_account=holding_account, funding_accounts=funding_accounts, name=name, amount=amount, holding_account=holding_account, funding_accounts=funding_accounts,
save_key=not tx_dict['preview'], **remove_nulls(channel_dict) 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 return tx
async def channel_update( async def channel_update(
@ -1678,7 +1679,7 @@ class API:
save_key=not tx_dict['preview'], **remove_nulls(channel_edit_dict) 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 return tx
@ -1719,7 +1720,7 @@ class API:
tx = await Transaction.create( tx = await Transaction.create(
[Input.spend(txo) for txo in claims], [], [account], account [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 return tx
async def channel_list( async def channel_list(
@ -1922,7 +1923,7 @@ class API:
new_txo.sign(channel) new_txo.sign(channel)
await tx.sign(funding_accounts) 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 return tx
async def stream_create( async def stream_create(
@ -1948,6 +1949,7 @@ class API:
amount = self.ledger.get_dewies_or_error('bid', bid, positive_value=True) amount = self.ledger.get_dewies_or_error('bid', bid, positive_value=True)
holding_account = wallet.accounts.get_or_default(stream_dict.pop('account_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')) 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 signing_channel = None
channel_id = stream_dict.pop('channel_id') channel_id = stream_dict.pop('channel_id')
channel_name = stream_dict.pop('channel_name') channel_name = stream_dict.pop('channel_name')
@ -1977,9 +1979,10 @@ class API:
name=name, amount=amount, file_path=stream_dict.pop('file_path'), name=name, amount=amount, file_path=stream_dict.pop('file_path'),
create_file_stream=create_file_stream, create_file_stream=create_file_stream,
holding_address=holding_address, funding_accounts=funding_accounts, 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 return tx
async def stream_update( async def stream_update(
@ -2002,6 +2005,7 @@ class API:
wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) 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')) 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')) 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') replace = stream_dict.pop('replace')
old = await wallet.claims.get(claim_id=claim_id) 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'), old=old, amount=amount, file_path=stream_dict.pop('file_path'),
create_file_stream=create_file_stream, replace=replace, create_file_stream=create_file_stream, replace=replace,
holding_address=holding_address, funding_accounts=funding_accounts, 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 return tx
async def stream_abandon( async def stream_abandon(
@ -2093,7 +2098,7 @@ class API:
[Input.spend(txo) for txo in claims], [], accounts, account [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 return tx
async def stream_list( async def stream_list(
@ -2179,7 +2184,7 @@ class API:
new_txo.sign(channel) new_txo.sign(channel)
await tx.sign(funding_accounts) 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 return tx
async def collection_update( async def collection_update(
@ -2259,7 +2264,7 @@ class API:
new_txo.sign(channel) new_txo.sign(channel)
await tx.sign(funding_accounts) 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 return tx
async def collection_abandon( async def collection_abandon(
@ -2365,16 +2370,22 @@ class API:
wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id')) 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) 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')) 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) claim_address = claim.get_address(self.ledger)
if not tip: if not tip:
holding_account = wallet.accounts.get_or_default(account_id) holding_account = wallet.accounts.get_or_default(account_id)
claim_address = await holding_account.receiving.get_or_create_usable_address() claim_address = await holding_account.receiving.get_or_create_usable_address()
tx = await wallet.supports.create( 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 return tx
async def support_list( async def support_list(
@ -2499,23 +2510,19 @@ class API:
{kwargs} {kwargs}
""" """
wallet = self.wallets.get_or_default(wallet_id) abandon_dict, kwargs = pop_kwargs('abandon', extract_abandon(**abandon_and_tx_kwargs))
assert not wallet.is_locked, "Cannot spend funds with locked wallet, unlock first." tx_dict, kwargs = pop_kwargs('tx', extract_tx(**kwargs))
if account_id: assert_consumed_kwargs(kwargs)
account = wallet.get_account_or_error(account_id) wallet = self.wallets.get_or_default_for_spending(tx_dict.pop('wallet_id'))
accounts = [account] funding_accounts = wallet.accounts.get_or_all(tx_dict.pop('fund_account_id'))
else: change_account = wallet.accounts.get_or_default(tx_dict.pop('change_account_id'))
account = wallet.default_account
accounts = wallet.accounts
if txid is not None and nout is not None: if abandon_dict['txid']:
supports = await self.ledger.get_supports( supports = await wallet.supports.list(
wallet=wallet, accounts=accounts, tx_hash=unhexlify(txid)[::-1], position=nout wallet=wallet, tx_hash=unhexlify(abandon_dict['txid'])[::-1], position=abandon_dict['nout']
)
elif claim_id is not None:
supports = await self.ledger.get_supports(
wallet=wallet, accounts=accounts, claim_id=claim_id
) )
elif abandon_dict['claim_id'] is not None:
supports = await wallet.supports.list(wallet=wallet, claim_id=abandon_dict['claim_id'])
else: else:
raise Exception('Must specify claim_id, or txid and nout') 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') raise Exception('No supports found for the specified claim_id or txid:nout')
if keep is not None: 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: else:
keep = 0 keep = 0
outputs = [] tx = await wallet.supports.delete(
if keep > 0: supports=supports, keep=keep, funding_accounts=funding_accounts, change_account=change_account
outputs = [
Output.pay_support_pubkey_hash(
keep, supports[0].claim_name, supports[0].claim_id, supports[0].pubkey_hash
) )
] await self.service.maybe_broadcast_or_release(tx, **tx_dict)
tx = await Transaction.create(
[Input.spend(txo) for txo in supports], outputs, accounts, account
)
await self.service.maybe_broadcast_or_release(tx, blocking, preview)
return tx return tx
BLOCK_DOC = """ BLOCK_DOC = """

View file

@ -203,14 +203,14 @@ class Service:
async def resolve(self, urls, **kwargs): async def resolve(self, urls, **kwargs):
raise NotImplementedError 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 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 raise NotImplementedError
async def get_claim_by_claim_id(self, accounts, claim_id, **kwargs) -> Output: 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))[0]: for claim in await self.search_claims(accounts, claim_id=claim_id, **kwargs):
return claim return claim
@staticmethod @staticmethod

View file

@ -654,6 +654,9 @@ class CommandTestCase(IntegrationTestCase):
async def wallet_remove(self, *args, **kwargs): async def wallet_remove(self, *args, **kwargs):
return await self.out(self.api.wallet_remove(*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): async def account_list(self, *args, **kwargs):
return (await self.out(self.api.account_list(*args, **kwargs)))['items'] 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): async def account_remove(self, *args, **kwargs):
return await self.out(self.api.account_remove(*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): def create_upload_file(self, data, prefix=None, suffix=None):
file_path = tempfile.mktemp( file_path = tempfile.mktemp(
prefix=prefix or "tmp", suffix=suffix or "", dir=self.ledger.conf.upload_dir 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): async def support_abandon(self, *args, confirm=True, **kwargs):
if 'blocking' not in kwargs:
kwargs['blocking'] = False
return await self.confirm_and_render( return await self.confirm_and_render(
self.api.support_abandon(*args, **kwargs), confirm self.api.support_abandon(*args, **kwargs), confirm
) )

View file

@ -665,9 +665,8 @@ class StreamListManager(ClaimListManager):
async def create( async def create(
self, name: str, amount: int, file_path: str, self, name: str, amount: int, file_path: str,
create_file_stream: Callable[[str], Awaitable[ManagedStream]], 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, signing_channel: Optional[Output] = None, preview=False, **kwargs
preview=False, **kwargs
) -> Tuple[Transaction, ManagedStream]: ) -> Tuple[Transaction, ManagedStream]:
claim = Claim() claim = Claim()
@ -676,7 +675,7 @@ class StreamListManager(ClaimListManager):
# before creating file stream, create TX to ensure we have enough LBC # before creating file stream, create TX to ensure we have enough LBC
tx = await self._create( tx = await self._create(
name, claim, amount, holding_address, name, claim, amount, holding_address,
funding_accounts, funding_accounts[0], funding_accounts, change_account,
signing_channel signing_channel
) )
txo = tx.outputs[0] txo = tx.outputs[0]
@ -705,7 +704,7 @@ class StreamListManager(ClaimListManager):
async def update( async def update(
self, old: Output, amount: int, file_path: str, self, old: Output, amount: int, file_path: str,
create_file_stream: Callable[[str], Awaitable[ManagedStream]], 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, signing_channel: Optional[Output] = None,
preview=False, replace=False, **kwargs preview=False, replace=False, **kwargs
) -> Tuple[Transaction, ManagedStream]: ) -> Tuple[Transaction, ManagedStream]:
@ -726,7 +725,7 @@ class StreamListManager(ClaimListManager):
# before creating file stream, create TX to ensure we have enough LBC # before creating file stream, create TX to ensure we have enough LBC
tx = await super().update( tx = await super().update(
old, claim, amount, holding_address, old, claim, amount, holding_address,
funding_accounts, funding_accounts[0], funding_accounts, change_account,
signing_channel signing_channel
) )
txo = tx.outputs[0] txo = tx.outputs[0]
@ -785,9 +784,27 @@ class SupportListManager(BaseListManager):
support_output = Output.pay_support_pubkey_hash( support_output = Output.pay_support_pubkey_hash(
amount, name, claim_id, self.wallet.ledger.address_to_hash160(holding_address) 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 [], [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]: async def list(self, **constraints) -> Result[Output]:
return await self.wallet.db.get_supports(**constraints) return await self.wallet.db.get_supports(**constraints)

View file

@ -7,57 +7,63 @@ def extract(d, keys):
class AccountManagement(CommandTestCase): class AccountManagement(CommandTestCase):
async def test_account_list_set_create_remove_add(self): async def test_account_list_set_create_remove_add(self):
# check initial account # check initial account
accounts = await self.daemon.jsonrpc_account_list() self.assertEqual(len(await self.account_list()), 1)
self.assertItemCount(accounts, 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 # change account name and gap
account_id = accounts['items'][0]['id'] await self.account_set(
self.daemon.jsonrpc_account_set( account_id=account['id'], new_name='test account',
account_id=account_id, new_name='test account',
receiving_gap=95, receiving_max_uses=96, receiving_gap=95, receiving_max_uses=96,
change_gap=97, change_max_uses=98 change_gap=97, change_max_uses=98
) )
accounts = (await self.daemon.jsonrpc_account_list())['items'][0] account = (await self.account_list())[1]
self.assertEqual(accounts['name'], 'test account') self.assertEqual(account['name'], 'test account')
self.assertEqual( self.assertEqual(account['address_generator'], {
accounts['address_generator']['receiving'], 'name': 'deterministic-chain',
{'gap': 95, 'maximum_uses_per_address': 96} 'receiving': {'gap': 95, 'maximum_uses_per_address': 96},
) 'change': {'gap': 97, 'maximum_uses_per_address': 98},
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']
# make new account the default # make new account the default
self.daemon.jsonrpc_account_set(account_id=account_id2, default=True) await self.account_set(account_id=account['id'], default=True)
accounts = await self.daemon.jsonrpc_account_list(show_seed=True) actual = (await self.account_list())[0]
self.assertEqual(accounts['items'][0]['name'], 'second account') 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 # remove account
self.daemon.jsonrpc_account_remove(accounts['items'][1]['id']) await self.account_remove(account['id'])
accounts = await self.daemon.jsonrpc_account_list() self.assertEqual(len(await self.account_list()), 1)
self.assertItemCount(accounts, 1)
# add account # add account
await self.daemon.jsonrpc_account_add('recreated account', seed=account_seed) await self.account_add('recreated account', seed=account_seed)
accounts = await self.daemon.jsonrpc_account_list() accounts = await self.account_list()
self.assertItemCount(accounts, 2) self.assertEqual(len(accounts), 2)
self.assertEqual(accounts['items'][1]['name'], 'recreated account') account = accounts[1]
self.assertEqual(account['name'], 'recreated account')
self.assertEqual(account['public_key'], account_pubkey)
# list specific account # list specific account
accounts = await self.daemon.jsonrpc_account_list(account_id, include_claims=True) accounts = await self.account_list(account['id'])
self.assertEqual(accounts['items'][0]['name'], 'recreated account') self.assertEqual(len(accounts), 1)
self.assertEqual(accounts[0]['name'], 'recreated account')
async def test_wallet_migration(self): async def test_wallet_migration(self):
# null certificates should get deleted # null certificates should get deleted

View file

@ -1,50 +1,37 @@
import asyncio
import json import json
import asyncio
from unittest import skip
from sqlalchemy import event 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.error import InvalidPasswordError
from lbry.testcase import CommandTestCase 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): class WalletCommands(CommandTestCase):
async def test_wallet_create_and_add_subscribe(self): async def test_list_create_add_and_remove(self):
session = next(iter(self.conductor.spv_node.server.session_mgr.sessions)) self.assertEqual(await self.wallet_list(), [{'id': 'default_wallet', 'name': 'Wallet'}])
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
self.assertEqual( self.assertEqual(
{'is_encrypted': None, 'is_syncing': None, 'is_locked': None}, await self.wallet_create('another', "Another"),
self.daemon.jsonrpc_wallet_status() {'id': 'another', 'name': 'Another'}
)
wallet.wallet_manager = wallet_manager
self.assertEqual(
{'is_encrypted': False, 'is_syncing': False, 'is_locked': False},
self.daemon.jsonrpc_wallet_status()
) )
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) await self.conductor.spv_node.stop(True)
self.conductor.spv_node.port = 54320 self.conductor.spv_node.port = 54320
await self.conductor.spv_node.start(self.conductor.blockchain_node) 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(len(status['wallet']['servers']), 1)
self.assertEqual(status['wallet']['servers'][0]['port'], 54320) 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): async def test_granular_balances(self):
account2 = await self.daemon.jsonrpc_account_create("Tip-er") account2 = (await self.account_create("Tip-er"))["id"]
wallet2 = await self.daemon.jsonrpc_wallet_create('foo', create_account=True) wallet2 = (await self.wallet_create("foo", create_account=True))["id"]
account3 = wallet2.default_account account3 = (await self.account_list(wallet_id=wallet2))[0]["id"]
address3 = await self.daemon.jsonrpc_address_unused(account3.id, wallet2.id) address3 = await self.address_unused(account3, wallet2)
await self.confirm_tx(await self.blockchain.send_to_address(address3, 1)) await self.chain.send_to_address(address3, 1)
await self.generate(1) await self.generate(1)
account_balance = self.daemon.jsonrpc_account_balance
wallet_balance = self.daemon.jsonrpc_wallet_balance
expected = { expected = {
'total': '10.0', 'total': '10.0',
'available': '10.0', 'available': '10.0',
'reserved': '0.0', 'reserved': '0.0',
'reserved_subtotals': {'claims': '0.0', 'supports': '0.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '0.0', 'supports': '0.0', 'tips': '0.0'}
} }
self.assertEqual(await account_balance(), expected) self.assertEqual(await self.account_balance(), expected)
self.assertEqual(await wallet_balance(), expected) self.assertEqual(await self.wallet_balance(), expected)
# claim with update + supporting our own claim # claim with update + supporting our own claim
stream1 = await self.stream_create('granularity', '3.0') 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.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') await self.support_create(self.get_claim_id(stream1), '2.0')
expected = { expected = {
'total': '9.977534', 'total': '9.977558',
'available': '6.977534', 'available': '6.977558',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
} }
self.assertEqual(await account_balance(), expected) self.assertEqual(await self.account_balance(), expected)
self.assertEqual(await wallet_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 # send lbc to someone else
tx = await self.daemon.jsonrpc_account_send('1.0', address2) tx = await self.wallet_send('1.0', address2, fund_account_id=self.account.id)
await self.confirm_tx(tx.id) await self.generate(1)
self.assertEqual(await account_balance(), { self.assertEqual(await self.account_balance(), {
'total': '8.97741', 'total': '8.977434',
'available': '5.97741', 'available': '5.977434',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
}) })
self.assertEqual(await wallet_balance(), { self.assertEqual(await self.wallet_balance(), {
'total': '9.97741', 'total': '9.977434',
'available': '6.97741', 'available': '6.977434',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
}) })
# tip received # tip received
support1 = await self.support_create( 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(), { self.assertEqual(await self.account_balance(), {
'total': '9.27741', 'total': '9.277434',
'available': '5.97741', 'available': '5.977434',
'reserved': '3.3', 'reserved': '3.3',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'}
}) })
self.assertEqual(await wallet_balance(), { self.assertEqual(await self.wallet_balance(), {
'total': '10.27741', 'total': '10.277434',
'available': '6.97741', 'available': '6.977434',
'reserved': '3.3', 'reserved': '3.3',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.3'}
}) })
# tip claimed # tip claimed
tx = await self.daemon.jsonrpc_support_abandon(txid=support1['txid'], nout=0) tx = await self.support_abandon(txid=support1['txid'])
await self.confirm_tx(tx.id) await self.generate(1)
self.assertEqual(await account_balance(), { self.assertEqual(await self.account_balance(), {
'total': '9.277303', 'total': '9.277327',
'available': '6.277303', 'available': '6.277327',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
}) })
self.assertEqual(await wallet_balance(), { self.assertEqual(await self.wallet_balance(), {
'total': '10.277303', 'total': '10.277327',
'available': '7.277303', 'available': '7.277327',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
}) })
stream2 = await self.stream_create( 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 # tip another claim
await self.support_create( 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(), { self.assertEqual(await self.account_balance(), {
'total': '9.277303', 'total': '9.277327',
'available': '6.277303', 'available': '6.277327',
'reserved': '3.0', 'reserved': '3.0',
'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'} 'reserved_subtotals': {'claims': '1.0', 'supports': '2.0', 'tips': '0.0'}
}) })
self.assertEqual(await wallet_balance(), { self.assertEqual(await self.wallet_balance(), {
'total': '10.439196', 'total': '10.43922',
'available': '7.139196', 'available': '7.13922',
'reserved': '3.3', 'reserved': '3.3',
'reserved_subtotals': {'claims': '1.1', 'supports': '2.0', 'tips': '0.2'} 'reserved_subtotals': {'claims': '1.1', 'supports': '2.0', 'tips': '0.2'}
}) })
@ -225,7 +159,7 @@ class WalletEncryptionAndSynchronization(CommandTestCase):
async def asyncSetUp(self): async def asyncSetUp(self):
await super().asyncSetUp() await super().asyncSetUp()
self.daemon2 = await self.add_daemon( self.daemon2 = await self.add_full_node(
seed="chest sword toast envelope bottom stomach absent " seed="chest sword toast envelope bottom stomach absent "
"carbon smart garage balance margin twelve" "carbon smart garage balance margin twelve"
) )