most commands now work across all accounts

This commit is contained in:
Lex Berezhny 2019-08-12 00:40:05 -04:00
parent e2d618f472
commit 98d4d00f96
10 changed files with 426 additions and 223 deletions

View file

@ -1703,12 +1703,14 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Paginated[Output]}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
account.get_claims,
account.get_claim_count,
page, page_size
)
if account_id:
account = self.get_account_or_error(account_id)
claims = account.get_claims
claim_count = account.get_claim_count
else:
claims = self.ledger.get_claims
claim_count = self.ledger.get_claim_count
return maybe_paginate(claims, claim_count, page, page_size)
@requires(WALLET_COMPONENT)
async def jsonrpc_claim_search(self, **kwargs):
@ -1859,7 +1861,7 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_channel_create(
self, name, bid, allow_duplicate_name=False, account_id=None, claim_address=None,
preview=False, blocking=False, **kwargs):
funding_account_ids=None, preview=False, blocking=False, **kwargs):
"""
Create a new channel by generating a channel private key and establishing an '@' prefixed claim.
@ -1871,6 +1873,7 @@ class Daemon(metaclass=JSONRPCServerType):
[--tags=<tags>...] [--languages=<languages>...] [--locations=<locations>...]
[--thumbnail_url=<thumbnail_url>] [--cover_url=<cover_url>]
[--account_id=<account_id>] [--claim_address=<claim_address>]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking]
Options:
@ -1924,7 +1927,8 @@ class Daemon(metaclass=JSONRPCServerType):
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
--cover_url=<cover_url> : (str) url of cover image
--account_id=<account_id> : (str) id of the account to store channel
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the channel is sent to, if not specified
it will be determined automatically from the account
--preview : (bool) do not broadcast the transaction
@ -1933,6 +1937,7 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Transaction}
"""
account = self.get_account_or_default(account_id)
funding_accounts = self.get_accounts_or_all(funding_account_ids)
self.valid_channel_name_or_error(name)
amount = self.get_dewies_or_error('bid', bid, positive_value=True)
claim_address = await self.get_receiving_address(claim_address, account)
@ -1948,7 +1953,7 @@ class Daemon(metaclass=JSONRPCServerType):
claim = Claim()
claim.channel.update(**kwargs)
tx = await Transaction.claim_create(
name, claim, amount, claim_address, [account], account
name, claim, amount, claim_address, funding_accounts, funding_accounts[0]
)
txo = tx.outputs[0]
txo.generate_channel_private_key()
@ -1970,7 +1975,8 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_channel_update(
self, claim_id, bid=None, account_id=None, claim_address=None,
new_signing_key=False, preview=False, blocking=False, replace=False, **kwargs):
funding_account_ids=None, new_signing_key=False, preview=False,
blocking=False, replace=False, **kwargs):
"""
Update an existing channel claim.
@ -1984,6 +1990,7 @@ class Daemon(metaclass=JSONRPCServerType):
[--locations=<locations>...] [--clear_locations]
[--thumbnail_url=<thumbnail_url>] [--cover_url=<cover_url>]
[--account_id=<account_id>] [--claim_address=<claim_address>] [--new_signing_key]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking] [--replace]
Options:
@ -2039,7 +2046,8 @@ class Daemon(metaclass=JSONRPCServerType):
--clear_locations : (bool) clear existing locations (prior to adding new ones)
--thumbnail_url=<thumbnail_url>: (str) thumbnail url
--cover_url=<cover_url> : (str) url of cover image
--account_id=<account_id> : (str) id of the account to store channel
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the channel is sent
--new_signing_key : (bool) generate a new signing key, will invalidate all previous publishes
--preview : (bool) do not broadcast the transaction
@ -2052,6 +2060,7 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Transaction}
"""
account = self.get_account_or_default(account_id)
funding_accounts = self.get_accounts_or_all(funding_account_ids)
existing_channels = await account.get_claims(claim_id=claim_id)
if len(existing_channels) != 1:
@ -2081,7 +2090,7 @@ class Daemon(metaclass=JSONRPCServerType):
claim = Claim.from_bytes(old_txo.claim.to_bytes())
claim.channel.update(**kwargs)
tx = await Transaction.claim_update(
old_txo, claim, amount, claim_address, [account], account
old_txo, claim, amount, claim_address, funding_accounts, funding_accounts[0]
)
new_txo = tx.outputs[0]
@ -2169,12 +2178,14 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Paginated[Output]}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
account.get_channels,
account.get_channel_count,
page, page_size
)
if account_id:
account = self.get_account_or_error(account_id)
channels = account.get_channels
channel_count = account.get_channel_count
else:
channels = self.ledger.get_channels
channel_count = self.ledger.get_channel_count
return maybe_paginate(channels, channel_count, page, page_size)
@requires(WALLET_COMPONENT)
async def jsonrpc_channel_export(self, channel_id=None, channel_name=None, account_id=None):
@ -2261,6 +2272,7 @@ class Daemon(metaclass=JSONRPCServerType):
[--channel_id=<channel_id> | --channel_name=<channel_name>]
[--channel_account_id=<channel_account_id>...]
[--account_id=<account_id>] [--claim_address=<claim_address>]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking]
Options:
@ -2328,7 +2340,8 @@ class Daemon(metaclass=JSONRPCServerType):
--channel_name=<channel_name> : (str) name of publisher channel
--channel_account_id=<channel_account_id>: (str) one or more account ids for accounts to look in
for channel certificates, defaults to all accounts.
--account_id=<account_id> : (str) account to use for funding the transaction
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the claim is sent to, if not specified
it will be determined automatically from the account
--preview : (bool) do not broadcast the transaction
@ -2359,8 +2372,8 @@ class Daemon(metaclass=JSONRPCServerType):
async def jsonrpc_stream_create(
self, name, bid, file_path, allow_duplicate_name=False,
channel_id=None, channel_name=None, channel_account_id=None,
account_id=None, claim_address=None, preview=False, blocking=False,
**kwargs):
account_id=None, claim_address=None, funding_account_ids=None,
preview=False, blocking=False, **kwargs):
"""
Make a new stream claim and announce the associated file to lbrynet.
@ -2375,6 +2388,7 @@ class Daemon(metaclass=JSONRPCServerType):
[--channel_id=<channel_id> | --channel_name=<channel_name>]
[--channel_account_id=<channel_account_id>...]
[--account_id=<account_id>] [--claim_address=<claim_address>]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking]
Options:
@ -2444,7 +2458,8 @@ class Daemon(metaclass=JSONRPCServerType):
--channel_name=<channel_name> : (str) name of the publisher channel
--channel_account_id=<channel_account_id>: (str) one or more account ids for accounts to look in
for channel certificates, defaults to all accounts.
--account_id=<account_id> : (str) account to use for funding the transaction
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the claim is sent to, if not specified
it will be determined automatically from the account
--preview : (bool) do not broadcast the transaction
@ -2454,6 +2469,7 @@ class Daemon(metaclass=JSONRPCServerType):
"""
self.valid_stream_name_or_error(name)
account = self.get_account_or_default(account_id)
funding_accounts = self.get_accounts_or_all(funding_account_ids)
channel = await self.get_channel_or_none(channel_account_id, channel_id, channel_name, for_signing=True)
amount = self.get_dewies_or_error('bid', bid, positive_value=True)
claim_address = await self.get_receiving_address(claim_address, account)
@ -2470,7 +2486,7 @@ class Daemon(metaclass=JSONRPCServerType):
claim = Claim()
claim.stream.update(file_path=file_path, sd_hash='0'*96, **kwargs)
tx = await Transaction.claim_create(
name, claim, amount, claim_address, [account], account, channel
name, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel
)
new_txo = tx.outputs[0]
@ -2501,7 +2517,7 @@ class Daemon(metaclass=JSONRPCServerType):
async def jsonrpc_stream_update(
self, claim_id, bid=None, file_path=None,
channel_id=None, channel_name=None, channel_account_id=None, clear_channel=False,
account_id=None, claim_address=None,
account_id=None, claim_address=None, funding_account_ids=None,
preview=False, blocking=False, replace=False, **kwargs):
"""
Update an existing stream claim and if a new file is provided announce it to lbrynet.
@ -2520,6 +2536,7 @@ class Daemon(metaclass=JSONRPCServerType):
[--channel_id=<channel_id> | --channel_name=<channel_name> | --clear_channel]
[--channel_account_id=<channel_account_id>...]
[--account_id=<account_id>] [--claim_address=<claim_address>]
[--funding_account_ids=<funding_account_ids>...]
[--preview] [--blocking] [--replace]
Options:
@ -2595,7 +2612,8 @@ class Daemon(metaclass=JSONRPCServerType):
--clear_channel : (bool) remove channel signature
--channel_account_id=<channel_account_id>: (str) one or more account ids for accounts to look in
for channel certificates, defaults to all accounts.
--account_id=<account_id> : (str) account to use for funding the transaction
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--claim_address=<claim_address>: (str) address where the claim is sent to, if not specified
it will be determined automatically from the account
--preview : (bool) do not broadcast the transaction
@ -2608,6 +2626,7 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Transaction}
"""
account = self.get_account_or_default(account_id)
funding_accounts = self.get_accounts_or_all(funding_account_ids)
existing_claims = await account.get_claims(claim_id=claim_id)
if len(existing_claims) != 1:
@ -2655,7 +2674,7 @@ class Daemon(metaclass=JSONRPCServerType):
claim = Claim.from_bytes(old_txo.claim.to_bytes())
claim.stream.update(file_path=file_path, **kwargs)
tx = await Transaction.claim_update(
old_txo, claim, amount, claim_address, [account], account, channel
old_txo, claim, amount, claim_address, funding_accounts, funding_accounts[0], channel
)
new_txo = tx.outputs[0]
@ -2753,12 +2772,14 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Paginated[Output]}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
account.get_streams,
account.get_stream_count,
page, page_size
)
if account_id:
account = self.get_account_or_error(account_id)
streams = account.get_streams
stream_count = account.get_stream_count
else:
streams = self.ledger.get_streams
stream_count = self.ledger.get_stream_count
return maybe_paginate(streams, stream_count, page, page_size)
@requires(WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT,
DHT_COMPONENT, DATABASE_COMPONENT,
@ -2785,7 +2806,7 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_support_create(
self, claim_id, amount, tip=False, account_id=None,
self, claim_id, amount, tip=False, account_id=None, funding_account_ids=None,
preview=False, blocking=False):
"""
Create a support or a tip for name claim.
@ -2793,18 +2814,21 @@ class Daemon(metaclass=JSONRPCServerType):
Usage:
support_create (<claim_id> | --claim_id=<claim_id>) (<amount> | --amount=<amount>)
[--tip] [--account_id=<account_id>] [--preview] [--blocking]
[--funding_account_ids=<funding_account_ids>...]
Options:
--claim_id=<claim_id> : (str) claim_id of the claim to support
--amount=<amount> : (decimal) amount of support
--tip : (bool) send support to claim owner, default: false.
--account_id=<account_id> : (str) id of the account to use
--account_id=<account_id> : (str) account to use for holding the transaction
--funding_account_ids=<funding_account_ids>: (list) ids of accounts to fund this transaction
--preview : (bool) do not broadcast the transaction
--blocking : (bool) wait until transaction is in mempool
Returns: {Transaction}
"""
account = self.get_account_or_default(account_id)
funding_accounts = self.get_accounts_or_all(funding_account_ids)
amount = self.get_dewies_or_error("amount", amount)
claim = await self.ledger.get_claim_by_claim_id(claim_id)
claim_address = claim.get_address(self.ledger)
@ -2812,7 +2836,7 @@ class Daemon(metaclass=JSONRPCServerType):
claim_address = await account.receiving.get_or_create_usable_address()
tx = await Transaction.support(
claim.claim_name, claim_id, amount, claim_address, [account], account
claim.claim_name, claim_id, amount, claim_address, funding_accounts, funding_accounts[0]
)
if not preview:
@ -2847,12 +2871,14 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Paginated[Output]}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
account.get_supports,
account.get_support_count,
page, page_size
)
if account_id:
account = self.get_account_or_error(account_id)
supports = account.get_supports
support_count = account.get_support_count
else:
supports = self.ledger.get_supports
support_count = self.ledger.get_support_count
return maybe_paginate(supports, support_count, page, page_size)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_support_abandon(
@ -2978,12 +3004,14 @@ class Daemon(metaclass=JSONRPCServerType):
}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
self.wallet_manager.get_history,
self.ledger.db.get_transaction_count,
page, page_size, account=account
)
if account_id:
account = self.get_account_or_error(account_id)
transactions = account.get_transaction_history
transaction_count = account.get_transaction_history_count
else:
transactions = self.ledger.get_transaction_history
transaction_count = self.ledger.get_transaction_history_count
return maybe_paginate(transactions, transaction_count, page, page_size)
@requires(WALLET_COMPONENT)
def jsonrpc_transaction_show(self, txid):
@ -3020,12 +3048,14 @@ class Daemon(metaclass=JSONRPCServerType):
Returns: {Paginated[Output]}
"""
account = self.get_account_or_default(account_id)
return maybe_paginate(
account.get_utxos,
account.get_utxo_count,
page, page_size
)
if account_id:
account = self.get_account_or_error(account_id)
utxos = account.get_utxos
utxo_count = account.get_utxo_count
else:
utxos = self.ledger.get_utxos
utxo_count = self.ledger.get_utxo_count
return maybe_paginate(utxos, utxo_count, page, page_size)
@requires(WALLET_COMPONENT)
def jsonrpc_utxo_release(self, account_id=None):

View file

@ -131,41 +131,35 @@ class Account(BaseAccount):
details['certificates'] = len(self.channel_keys)
return details
@staticmethod
def constraint_spending_utxos(constraints):
constraints.update({'is_claim': 0, 'is_update': 0, 'is_support': 0})
def get_transaction_history(self, **constraints):
return self.ledger.get_transaction_history(account=self, **constraints)
def get_utxos(self, **constraints):
self.constraint_spending_utxos(constraints)
return super().get_utxos(**constraints)
def get_utxo_count(self, **constraints):
self.constraint_spending_utxos(constraints)
return super().get_utxo_count(**constraints)
def get_transaction_history_count(self, **constraints):
return self.ledger.get_transaction_history_count(account=self, **constraints)
def get_claims(self, **constraints):
return self.ledger.db.get_claims(account=self, **constraints)
return self.ledger.get_claims(account=self, **constraints)
def get_claim_count(self, **constraints):
return self.ledger.db.get_claim_count(account=self, **constraints)
return self.ledger.get_claim_count(account=self, **constraints)
def get_streams(self, **constraints):
return self.ledger.db.get_streams(account=self, **constraints)
return self.ledger.get_streams(account=self, **constraints)
def get_stream_count(self, **constraints):
return self.ledger.db.get_stream_count(account=self, **constraints)
return self.ledger.get_stream_count(account=self, **constraints)
def get_channels(self, **constraints):
return self.ledger.db.get_channels(account=self, **constraints)
return self.ledger.get_channels(account=self, **constraints)
def get_channel_count(self, **constraints):
return self.ledger.db.get_channel_count(account=self, **constraints)
return self.ledger.get_channel_count(account=self, **constraints)
def get_supports(self, **constraints):
return self.ledger.db.get_supports(account=self, **constraints)
return self.ledger.get_supports(account=self, **constraints)
def get_support_count(self, **constraints):
return self.ledger.db.get_support_count(account=self, **constraints)
return self.ledger.get_support_count(account=self, **constraints)
def get_support_summary(self):
return self.ledger.db.get_supports_summary(account_id=self.id)

View file

@ -53,7 +53,7 @@ class WalletDatabase(BaseDatabase):
return row
async def get_txos(self, **constraints) -> List[Output]:
my_account = constraints.get('my_account', constraints.get('account', None))
my_accounts = constraints.get('my_accounts', constraints.get('accounts', []))
txos = await super().get_txos(**constraints)
@ -62,16 +62,20 @@ class WalletDatabase(BaseDatabase):
if txo.is_claim and txo.can_decode_claim:
if txo.claim.is_signed:
channel_ids.add(txo.claim.signing_channel_id)
if txo.claim.is_channel and my_account is not None:
txo.private_key = my_account.get_channel_private_key(
txo.claim.channel.public_key_bytes
)
if txo.claim.is_channel and my_accounts:
for account in my_accounts:
private_key = account.get_channel_private_key(
txo.claim.channel.public_key_bytes
)
if private_key:
txo.private_key = private_key
break
if channel_ids:
channels = {
txo.claim_id: txo for txo in
(await self.get_claims(
my_account=my_account,
my_accounts=my_accounts,
claim_id__in=channel_ids
))
}

View file

@ -1,7 +1,8 @@
import asyncio
import logging
from binascii import unhexlify
from typing import Tuple, List, Dict
from typing import Tuple, List
from datetime import datetime
from torba.client.baseledger import BaseLedger
from torba.client.baseaccount import SingleKey
@ -74,10 +75,10 @@ class MainNetLedger(BaseLedger):
result[url] = {'error': f'{url} did not resolve to a claim'}
return result
async def claim_search(self, **kwargs) -> Tuple[List, int, int]:
async def claim_search(self, **kwargs) -> Tuple[List[Output], int, int]:
return await self._inflate_outputs(self.network.claim_search(**kwargs))
async def get_claim_by_claim_id(self, claim_id) -> Dict[str, Output]:
async def get_claim_by_claim_id(self, claim_id) -> Output:
for claim in (await self.claim_search(claim_id=claim_id))[0]:
return claim
@ -109,6 +110,157 @@ class MainNetLedger(BaseLedger):
'Failed to display wallet state, please file issue '
'for this bug along with the traceback you see below:')
def constraint_account_or_all(self, constraints):
account = constraints.pop('account', None)
if account:
constraints['accounts'] = [account]
else:
constraints['accounts'] = self.accounts
def constraint_spending_utxos(self, constraints):
self.constraint_account_or_all(constraints)
constraints.update({'is_claim': 0, 'is_update': 0, 'is_support': 0})
def get_utxos(self, **constraints):
self.constraint_spending_utxos(constraints)
return super().get_utxos(**constraints)
def get_utxo_count(self, **constraints):
self.constraint_spending_utxos(constraints)
return super().get_utxo_count(**constraints)
def get_claims(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_claims(**constraints)
def get_claim_count(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_claim_count(**constraints)
def get_streams(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_streams(**constraints)
def get_stream_count(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_stream_count(**constraints)
def get_channels(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_channels(**constraints)
def get_channel_count(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_channel_count(**constraints)
def get_supports(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_supports(**constraints)
def get_support_count(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_support_count(**constraints)
async def get_transaction_history(self, **constraints):
self.constraint_account_or_all(constraints)
txs = await self.db.get_transactions(**constraints)
headers = self.headers
history = []
for tx in txs:
ts = headers[tx.height]['timestamp'] if tx.height > 0 else None
item = {
'txid': tx.id,
'timestamp': ts,
'date': datetime.fromtimestamp(ts).isoformat(' ')[:-3] if tx.height > 0 else None,
'confirmations': (headers.height+1) - tx.height if tx.height > 0 else 0,
'claim_info': [],
'update_info': [],
'support_info': [],
'abandon_info': []
}
is_my_inputs = all([txi.is_my_account for txi in tx.inputs])
if is_my_inputs:
# fees only matter if we are the ones paying them
item['value'] = dewies_to_lbc(tx.net_account_balance+tx.fee)
item['fee'] = dewies_to_lbc(-tx.fee)
else:
# someone else paid the fees
item['value'] = dewies_to_lbc(tx.net_account_balance)
item['fee'] = '0.0'
for txo in tx.my_claim_outputs:
item['claim_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
for txo in tx.my_update_outputs:
if is_my_inputs: # updating my own claim
previous = None
for txi in tx.inputs:
if txi.txo_ref.txo is not None:
other_txo = txi.txo_ref.txo
if (other_txo.is_claim or other_txo.script.is_support_claim) \
and other_txo.claim_id == txo.claim_id:
previous = other_txo
break
if previous is not None:
item['update_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(previous.amount-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
else: # someone sent us their claim
item['update_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(0),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
for txo in tx.my_support_outputs:
item['support_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(txo.amount if not is_my_inputs else -txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'is_tip': not is_my_inputs,
'nout': txo.position
})
if is_my_inputs:
for txo in tx.other_support_outputs:
item['support_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'is_tip': is_my_inputs,
'nout': txo.position
})
for txo in tx.my_abandon_outputs:
item['abandon_info'].append({
'address': txo.get_address(self),
'balance_delta': dewies_to_lbc(txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
history.append(item)
return history
def get_transaction_history_count(self, **constraints):
self.constraint_account_or_all(constraints)
return self.db.get_transaction_count(**constraints)
class TestNetLedger(MainNetLedger):
network_name = 'testnet'

View file

@ -3,16 +3,13 @@ import json
import logging
from binascii import unhexlify
from datetime import datetime
from torba.client.basemanager import BaseWalletManager
from torba.rpc.jsonrpc import CodeMessageError
from lbry.wallet.ledger import MainNetLedger
from lbry.wallet.account import BaseAccount
from lbry.wallet.transaction import Transaction
from lbry.wallet.database import WalletDatabase
from lbry.wallet.dewies import dewies_to_lbc
from lbry.conf import Config
@ -209,102 +206,6 @@ class LbryWalletManager(BaseWalletManager):
await self.ledger.maybe_verify_transaction(tx, height)
return tx
@staticmethod
async def get_history(account: BaseAccount, **constraints):
headers = account.ledger.headers
txs = await account.get_transactions(**constraints)
history = []
for tx in txs:
ts = headers[tx.height]['timestamp'] if tx.height > 0 else None
item = {
'txid': tx.id,
'timestamp': ts,
'date': datetime.fromtimestamp(ts).isoformat(' ')[:-3] if tx.height > 0 else None,
'confirmations': (headers.height+1) - tx.height if tx.height > 0 else 0,
'claim_info': [],
'update_info': [],
'support_info': [],
'abandon_info': []
}
is_my_inputs = all([txi.is_my_account for txi in tx.inputs])
if is_my_inputs:
# fees only matter if we are the ones paying them
item['value'] = dewies_to_lbc(tx.net_account_balance+tx.fee)
item['fee'] = dewies_to_lbc(-tx.fee)
else:
# someone else paid the fees
item['value'] = dewies_to_lbc(tx.net_account_balance)
item['fee'] = '0.0'
for txo in tx.my_claim_outputs:
item['claim_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
for txo in tx.my_update_outputs:
if is_my_inputs: # updating my own claim
previous = None
for txi in tx.inputs:
if txi.txo_ref.txo is not None:
other_txo = txi.txo_ref.txo
if (other_txo.is_claim or other_txo.script.is_support_claim) \
and other_txo.claim_id == txo.claim_id:
previous = other_txo
break
if previous is not None:
item['update_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(previous.amount-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
else: # someone sent us their claim
item['update_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(0),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
for txo in tx.my_support_outputs:
item['support_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(txo.amount if not is_my_inputs else -txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'is_tip': not is_my_inputs,
'nout': txo.position
})
if is_my_inputs:
for txo in tx.other_support_outputs:
item['support_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(-txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'is_tip': is_my_inputs,
'nout': txo.position
})
for txo in tx.my_abandon_outputs:
item['abandon_info'].append({
'address': txo.get_address(account.ledger),
'balance_delta': dewies_to_lbc(txo.amount),
'amount': dewies_to_lbc(txo.amount),
'claim_id': txo.claim_id,
'claim_name': txo.claim_name,
'nout': txo.position
})
history.append(item)
return history
def save(self):
for wallet in self.wallets:
wallet.save()

View file

@ -1,4 +1,9 @@
from lbry.testcase import CommandTestCase
from lbry.wallet.dewies import dewies_to_lbc
def extract(d, keys):
return dict((k, d[k]) for k in keys)
class AccountManagement(CommandTestCase):
@ -65,3 +70,101 @@ class AccountManagement(CommandTestCase):
self.account.channel_keys[keys[1]] = "some invalid junk"
await self.account.maybe_migrate_certificates()
self.assertEqual(list(self.account.channel_keys.keys()), [keys[2]])
async def assertFindsClaims(self, claim_names, awaitable):
self.assertEqual(claim_names, [txo.claim_name for txo in await awaitable])
async def assertOutputAmount(self, amounts, awaitable):
self.assertEqual(amounts, [dewies_to_lbc(txo.amount) for txo in await awaitable])
async def test_commands_across_accounts(self):
channel_list = self.daemon.jsonrpc_channel_list
stream_list = self.daemon.jsonrpc_stream_list
support_list = self.daemon.jsonrpc_support_list
utxo_list = self.daemon.jsonrpc_utxo_list
default_account = self.daemon.default_account
second_account = await self.daemon.jsonrpc_account_create('second account')
tx = await self.daemon.jsonrpc_account_send(
'0.05', await self.daemon.jsonrpc_address_unused(account_id=second_account.id)
)
await self.confirm_tx(tx.id)
await self.assertOutputAmount(['0.05', '9.949876'], utxo_list())
await self.assertOutputAmount(['0.05'], utxo_list(account_id=second_account.id))
await self.assertOutputAmount(['9.949876'], utxo_list(account_id=default_account.id))
channel1 = await self.channel_create('@channel-in-account1', '0.01')
channel2 = await self.channel_create(
'@channel-in-account2', '0.01', account_id=second_account.id, funding_account_ids=[default_account.id]
)
await self.assertFindsClaims(['@channel-in-account2', '@channel-in-account1'], channel_list())
await self.assertFindsClaims(['@channel-in-account1'], channel_list(account_id=default_account.id))
await self.assertFindsClaims(['@channel-in-account2'], channel_list(account_id=second_account.id))
stream1 = await self.stream_create('stream-in-account1', '0.01', channel_id=self.get_claim_id(channel1))
stream2 = await self.stream_create(
'stream-in-account2', '0.01', channel_id=self.get_claim_id(channel2),
account_id=second_account.id, funding_account_ids=[default_account.id]
)
await self.assertFindsClaims(['stream-in-account2', 'stream-in-account1'], stream_list())
await self.assertFindsClaims(['stream-in-account1'], stream_list(account_id=default_account.id))
await self.assertFindsClaims(['stream-in-account2'], stream_list(account_id=second_account.id))
await self.assertFindsClaims(
['stream-in-account2', 'stream-in-account1', '@channel-in-account2', '@channel-in-account1'],
self.daemon.jsonrpc_claim_list()
)
await self.assertFindsClaims(
['stream-in-account1', '@channel-in-account1'],
self.daemon.jsonrpc_claim_list(account_id=default_account.id)
)
await self.assertFindsClaims(
['stream-in-account2', '@channel-in-account2'],
self.daemon.jsonrpc_claim_list(account_id=second_account.id)
)
support1 = await self.support_create(self.get_claim_id(stream1), '0.01')
support2 = await self.support_create(
self.get_claim_id(stream2), '0.01', account_id=second_account.id, funding_account_ids=[default_account.id]
)
self.assertEqual([support2['txid'], support1['txid']], [txo.tx_ref.id for txo in await support_list()])
self.assertEqual([support1['txid']], [txo.tx_ref.id for txo in await support_list(account_id=default_account.id)])
self.assertEqual([support2['txid']], [txo.tx_ref.id for txo in await support_list(account_id=second_account.id)])
history = await self.daemon.jsonrpc_transaction_list()
self.assertEqual(len(history), 8)
self.assertEqual(extract(history[0]['support_info'][0], ['claim_name', 'is_tip', 'amount', 'balance_delta']), {
'claim_name': 'stream-in-account2',
'is_tip': False,
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(extract(history[1]['support_info'][0], ['claim_name', 'is_tip', 'amount', 'balance_delta']), {
'claim_name': 'stream-in-account1',
'is_tip': False,
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(extract(history[2]['claim_info'][0], ['claim_name', 'amount', 'balance_delta']), {
'claim_name': 'stream-in-account2',
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(extract(history[3]['claim_info'][0], ['claim_name', 'amount', 'balance_delta']), {
'claim_name': 'stream-in-account1',
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(extract(history[4]['claim_info'][0], ['claim_name', 'amount', 'balance_delta']), {
'claim_name': '@channel-in-account2',
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(extract(history[5]['claim_info'][0], ['claim_name', 'amount', 'balance_delta']), {
'claim_name': '@channel-in-account1',
'amount': '0.01',
'balance_delta': '-0.01'
})
self.assertEqual(history[6]['value'], '0.0')
self.assertEqual(history[7]['value'], '10.0')

View file

@ -478,7 +478,8 @@ class ChannelCommands(CommandTestCase):
tx = await self.out(self.channel_update(claim_id, claim_address=other_address))
# after sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 3)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=self.account.id)), 2)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 1)
# shoud not have private key
@ -618,12 +619,17 @@ class StreamCommands(ClaimTestCase):
channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id))
self.assertEqual(len(channels), 1)
self.assertEqual(channels[0]['name'], '@spam')
self.assertEqual(channels, await self.out(self.daemon.jsonrpc_channel_list()))
self.assertEqual(channels, await self.out(self.daemon.jsonrpc_channel_list(account1_id)))
channels = await self.out(self.daemon.jsonrpc_channel_list(account2_id))
self.assertEqual(len(channels), 1)
self.assertEqual(channels[0]['name'], '@baz')
channels = await self.out(self.daemon.jsonrpc_channel_list())
self.assertEqual(len(channels), 2)
self.assertEqual(channels[0]['name'], '@baz')
self.assertEqual(channels[1]['name'], '@spam')
# defaults to using all accounts to lookup channel
await self.stream_create('hovercraft1', '0.1', channel_id=baz_id)
self.assertEqual((await self.claim_search(name='hovercraft1'))[0]['signing_channel']['name'], '@baz')
@ -806,13 +812,15 @@ class StreamCommands(ClaimTestCase):
# before sending
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 4)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=self.account.id)), 4)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=account2_id)), 0)
other_address = await account2.receiving.get_or_create_usable_address()
tx = await self.out(self.stream_update(claim_id, claim_address=other_address))
# after sending
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 3)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 4)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=self.account.id)), 3)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=account2_id)), 1)
async def test_setting_fee_fields(self):
@ -1112,8 +1120,7 @@ class SupportCommands(CommandTestCase):
await self.assertBalance(account2, '5.0')
# create the claim we'll be tipping and supporting
tx = await self.stream_create()
claim_id = self.get_claim_id(tx)
claim_id = self.get_claim_id(await self.stream_create())
# account1 and account2 balances:
await self.assertBalance(self.account, '3.979769')
@ -1121,18 +1128,17 @@ class SupportCommands(CommandTestCase):
# send a tip to the claim using account2
tip = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '1.0', True, account2_id)
self.daemon.jsonrpc_support_create(
claim_id, '1.0', True, account2_id, funding_account_ids=[account2_id])
)
await self.on_transaction_dict(tip)
await self.generate(1)
await self.on_transaction_dict(tip)
await self.confirm_tx(tip['txid'])
# tips don't affect balance so account1 balance is same but account2 balance went down
await self.assertBalance(self.account, '3.979769')
await self.assertBalance(account2, '3.9998585')
# verify that the incoming tip is marked correctly as is_tip=True in account1
txs = await self.out(self.daemon.jsonrpc_transaction_list())
txs = await self.out(self.daemon.jsonrpc_transaction_list(self.account.id))
self.assertEqual(len(txs[0]['support_info']), 1)
self.assertEqual(txs[0]['support_info'][0]['balance_delta'], '1.0')
self.assertEqual(txs[0]['support_info'][0]['claim_id'], claim_id)
@ -1153,11 +1159,10 @@ class SupportCommands(CommandTestCase):
# send a support to the claim using account2
support = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '2.0', False, account2_id)
self.daemon.jsonrpc_support_create(
claim_id, '2.0', False, account2_id, funding_account_ids=[account2_id])
)
await self.on_transaction_dict(support)
await self.generate(1)
await self.on_transaction_dict(support)
await self.confirm_tx(support['txid'])
# account2 balance went down ~2
await self.assertBalance(self.account, '3.979769')

View file

@ -50,7 +50,7 @@ class AddressManager:
def _query_addresses(self, **constraints):
return self.account.ledger.db.get_addresses(
account=self.account,
accounts=[self.account],
chain=self.chain_number,
**constraints
)
@ -406,7 +406,7 @@ class BaseAccount:
if confirmations > 0:
height = self.ledger.headers.height - (confirmations-1)
constraints.update({'height__lte': height, 'height__gt': 0})
return self.ledger.db.get_balance(account=self, **constraints)
return self.ledger.db.get_balance(accounts=[self], **constraints)
async def get_max_gap(self):
change_gap = await self.change.get_max_gap()
@ -417,16 +417,16 @@ class BaseAccount:
}
def get_utxos(self, **constraints):
return self.ledger.db.get_utxos(account=self, **constraints)
return self.ledger.get_utxos(account=self, **constraints)
def get_utxo_count(self, **constraints):
return self.ledger.db.get_utxo_count(account=self, **constraints)
return self.ledger.get_utxo_count(account=self, **constraints)
def get_transactions(self, **constraints):
return self.ledger.db.get_transactions(account=self, **constraints)
return self.ledger.get_transactions(account=self, **constraints)
def get_transaction_count(self, **constraints):
return self.ledger.db.get_transaction_count(account=self, **constraints)
return self.ledger.get_transaction_count(account=self, **constraints)
async def fund(self, to_account, amount=None, everything=False,
outputs=1, broadcast=False, **constraints):

View file

@ -160,14 +160,10 @@ def query(select, **constraints):
offset = constraints.pop('offset', None)
order_by = constraints.pop('order_by', None)
constraints.pop('my_account', None)
account = constraints.pop('account', None)
if account is not None:
if not isinstance(account, list):
account = [account]
constraints['account__in'] = [
(a.public_key.address if isinstance(a, BaseAccount) else a) for a in account
]
constraints.pop('my_accounts', None)
accounts = constraints.pop('accounts', None)
if accounts is not None:
constraints['account__in'] = [a.public_key.address for a in accounts]
where, values = constraints_to_sql(constraints)
if where:
@ -395,22 +391,26 @@ class BaseDatabase(SQLiteMixin):
# 2. update address histories removing deleted TXs
return True
async def select_transactions(self, cols, account=None, **constraints):
if 'txid' not in constraints and account is not None:
constraints['$account'] = account.public_key.address
constraints['txid__in'] = """
async def select_transactions(self, cols, accounts=None, **constraints):
if 'txid' not in constraints:
assert accounts is not None, "'accounts' argument required when no 'txid' constraint"
constraints.update({
f'$account{i}': a.public_key.address for i, a in enumerate(accounts)
})
account_values = ', '.join([f':$account{i}' for i in range(len(accounts))])
constraints['txid__in'] = f"""
SELECT txo.txid FROM txo
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :$account
INNER JOIN pubkey_address USING (address) WHERE pubkey_address.account IN ({account_values})
UNION
SELECT txi.txid FROM txi
JOIN pubkey_address USING (address) WHERE pubkey_address.account = :$account
INNER JOIN pubkey_address USING (address) WHERE pubkey_address.account IN ({account_values})
"""
return await self.db.execute_fetchall(
*query("SELECT {} FROM tx".format(cols), **constraints)
)
async def get_transactions(self, my_account=None, **constraints):
my_account = my_account or constraints.get('account', None)
async def get_transactions(self, **constraints):
accounts = constraints.get('accounts', None)
tx_rows = await self.select_transactions(
'txid, raw, height, position, is_verified',
@ -436,7 +436,7 @@ class BaseDatabase(SQLiteMixin):
annotated_txos.update({
txo.id: txo for txo in
(await self.get_txos(
my_account=my_account,
my_accounts=accounts,
txid__in=txids[offset:offset+step],
))
})
@ -446,7 +446,7 @@ class BaseDatabase(SQLiteMixin):
referenced_txos.update({
txo.id: txo for txo in
(await self.get_txos(
my_account=my_account,
my_accounts=accounts,
txoid__in=txi_txoids[offset:offset+step],
))
})
@ -484,12 +484,14 @@ class BaseDatabase(SQLiteMixin):
" JOIN tx USING (txid)".format(cols), **constraints
))
async def get_txos(self, my_account=None, no_tx=False, **constraints):
my_account = my_account or constraints.get('account', None)
if isinstance(my_account, BaseAccount):
my_account = my_account.public_key.address
async def get_txos(self, my_accounts=None, no_tx=False, **constraints):
my_accounts = [
(a.public_key.address if isinstance(a, BaseAccount) else a)
for a in (my_accounts or constraints.get('accounts', []))
]
if 'order_by' not in constraints:
constraints['order_by'] = ["tx.height=0 DESC", "tx.height DESC", "tx.position DESC"]
constraints['order_by'] = [
"tx.height=0 DESC", "tx.height DESC", "tx.position DESC", "txo.position"]
rows = await self.select_txos(
"tx.txid, raw, tx.height, tx.position, tx.is_verified, "
"txo.position, chain, account, amount, script",
@ -513,7 +515,7 @@ class BaseDatabase(SQLiteMixin):
)
txo = txs[row[0]].outputs[row[5]]
txo.is_change = row[6] == 1
txo.is_my_account = row[7] == my_account
txo.is_my_account = row[7] in my_accounts
txos.append(txo)
return txos

View file

@ -227,6 +227,18 @@ class BaseLedger(metaclass=LedgerRegistry):
def release_tx(self, tx):
return self.release_outputs([txi.txo_ref.txo for txi in tx.inputs])
def get_utxos(self, **constraints):
return self.db.get_utxos(**constraints)
def get_utxo_count(self, **constraints):
return self.db.get_utxo_count(**constraints)
def get_transactions(self, **constraints):
return self.db.get_transactions(**constraints)
def get_transaction_count(self, **constraints):
return self.db.get_transaction_count(**constraints)
async def get_local_status_and_history(self, address):
address_details = await self.db.get_address(address=address)
history = address_details['history'] or ''