added flags to resolve: --include_purchase_receipt, --include_is_my_output, --include_my_supports, --include_my_tips

This commit is contained in:
Lex Berezhny 2020-03-21 23:44:57 -04:00
parent 1c05295e89
commit 9749da46ae
7 changed files with 119 additions and 21 deletions

View file

@ -920,16 +920,28 @@ class Daemon(metaclass=JSONRPCServerType):
return self.platform_info return self.platform_info
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
async def jsonrpc_resolve(self, urls: typing.Union[str, list], wallet_id=None): async def jsonrpc_resolve(self, urls: typing.Union[str, list], wallet_id=None, **kwargs):
""" """
Get the claim that a URL refers to. Get the claim that a URL refers to.
Usage: Usage:
resolve <urls>... [--wallet_id=<wallet_id>] resolve <urls>... [--wallet_id=<wallet_id>]
[--include_purchase_receipt]
[--include_is_my_output]
[--include_my_supports]
[--include_my_tips]
Options: Options:
--urls=<urls> : (str, list) one or more urls to resolve --urls=<urls> : (str, list) one or more urls to resolve
--wallet_id=<wallet_id> : (str) wallet to check for claim purchase reciepts --wallet_id=<wallet_id> : (str) wallet to check for claim purchase reciepts
--include_purchase_receipt : (bool) lookup and include a receipt if this wallet
has purchased the claim being resolved
--include_is_my_output : (bool) lookup and include a boolean indicating
if claim being resolved is yours
--include_my_supports : (bool) lookup and sum the total amount
of supports you've made to this claim
--include_my_tips : (bool) lookup and sum the total amount
of tips you've made to this claim
Returns: Returns:
Dictionary of results, keyed by url Dictionary of results, keyed by url
@ -1002,7 +1014,7 @@ class Daemon(metaclass=JSONRPCServerType):
except ValueError: except ValueError:
results[url] = {"error": f"{url} is not a valid url"} results[url] = {"error": f"{url} is not a valid url"}
resolved = await self.resolve(wallet.accounts, list(valid_urls)) resolved = await self.resolve(wallet.accounts, list(valid_urls), **kwargs)
for resolved_uri in resolved: for resolved_uri in resolved:
results[resolved_uri] = resolved[resolved_uri] if resolved[resolved_uri] is not None else \ results[resolved_uri] = resolved[resolved_uri] if resolved[resolved_uri] is not None else \
@ -5100,8 +5112,8 @@ class Daemon(metaclass=JSONRPCServerType):
except ValueError as e: except ValueError as e:
raise ValueError(f"Invalid value for '{argument}': {e.args[0]}") raise ValueError(f"Invalid value for '{argument}': {e.args[0]}")
async def resolve(self, accounts, urls): async def resolve(self, accounts, urls, **kwargs):
results = await self.ledger.resolve(accounts, urls) results = await self.ledger.resolve(accounts, urls, **kwargs)
if self.conf.save_resolved_claims and results: if self.conf.save_resolved_claims and results:
try: try:
claims = self.stream_manager._convert_to_old_resolve_output(self.wallet_manager, results) claims = self.stream_manager._convert_to_old_resolve_output(self.wallet_manager, results)

View file

@ -174,6 +174,10 @@ class JSONResponseEncoder(JSONEncoder):
output['is_my_output'] = txo.is_my_output output['is_my_output'] = txo.is_my_output
if txo.is_my_input is not None: if txo.is_my_input is not None:
output['is_my_input'] = txo.is_my_input output['is_my_input'] = txo.is_my_input
if txo.my_supports is not None:
output['my_supports'] = dewies_to_lbc(txo.my_supports)
if txo.my_tips is not None:
output['my_tips'] = dewies_to_lbc(txo.my_tips)
if txo.is_internal_transfer is not None: if txo.is_internal_transfer is not None:
output['is_internal_transfer'] = txo.is_internal_transfer output['is_internal_transfer'] = txo.is_internal_transfer

View file

@ -490,8 +490,8 @@ class CommandTestCase(IntegrationTestCase):
self.daemon.jsonrpc_stream_update(claim_id, **kwargs), confirm self.daemon.jsonrpc_stream_update(claim_id, **kwargs), confirm
) )
def stream_repost(self, claim_id, name='repost', bid='1.0', confirm=True, **kwargs): async def stream_repost(self, claim_id, name='repost', bid='1.0', confirm=True, **kwargs):
return self.confirm_and_render( return await self.confirm_and_render(
self.daemon.jsonrpc_stream_repost(claim_id=claim_id, name=name, bid=bid, **kwargs), confirm self.daemon.jsonrpc_stream_repost(claim_id=claim_id, name=name, bid=bid, **kwargs), confirm
) )
@ -502,6 +502,11 @@ class CommandTestCase(IntegrationTestCase):
self.daemon.jsonrpc_stream_abandon(*args, **kwargs), confirm self.daemon.jsonrpc_stream_abandon(*args, **kwargs), confirm
) )
async def purchase_create(self, *args, confirm=True, **kwargs):
return await self.confirm_and_render(
self.daemon.jsonrpc_purchase_create(*args, **kwargs), confirm
)
async def publish(self, name, *args, confirm=True, **kwargs): async def publish(self, name, *args, confirm=True, **kwargs):
return await self.confirm_and_render( return await self.confirm_and_render(
self.daemon.jsonrpc_publish(name, *args, **kwargs), confirm self.daemon.jsonrpc_publish(name, *args, **kwargs), confirm
@ -560,8 +565,8 @@ class CommandTestCase(IntegrationTestCase):
self.daemon.jsonrpc_wallet_send(*args, **kwargs), confirm self.daemon.jsonrpc_wallet_send(*args, **kwargs), confirm
) )
async def resolve(self, uri): async def resolve(self, uri, **kwargs):
return (await self.out(self.daemon.jsonrpc_resolve(uri)))[uri] return (await self.out(self.daemon.jsonrpc_resolve(uri, **kwargs)))[uri]
async def claim_search(self, **kwargs): async def claim_search(self, **kwargs):
return (await self.out(self.daemon.jsonrpc_claim_search(**kwargs)))['items'] return (await self.out(self.daemon.jsonrpc_claim_search(**kwargs)))['items']

View file

@ -446,7 +446,7 @@ class Database(SQLiteMixin):
); );
create index if not exists txo_txid_idx on txo (txid); create index if not exists txo_txid_idx on txo (txid);
create index if not exists txo_address_idx on txo (address); create index if not exists txo_address_idx on txo (address);
create index if not exists txo_claim_id_idx on txo (claim_id); create index if not exists txo_claim_id_idx on txo (claim_id, txo_type);
create index if not exists txo_claim_name_idx on txo (claim_name); create index if not exists txo_claim_name_idx on txo (claim_name);
create index if not exists txo_txo_type_idx on txo (txo_type); create index if not exists txo_txo_type_idx on txo (txo_type);
create index if not exists txo_channel_id_idx on txo (channel_id); create index if not exists txo_channel_id_idx on txo (channel_id);
@ -676,7 +676,7 @@ class Database(SQLiteMixin):
constraints.pop('limit', None) constraints.pop('limit', None)
constraints.pop('order_by', None) constraints.pop('order_by', None)
count = await self.select_transactions('COUNT(*) as total', **constraints) count = await self.select_transactions('COUNT(*) as total', **constraints)
return count[0]['total'] return count[0]['total'] or 0
async def get_transaction(self, **constraints): async def get_transaction(self, **constraints):
txs = await self.get_transactions(limit=1, **constraints) txs = await self.get_transactions(limit=1, **constraints)
@ -865,12 +865,12 @@ class Database(SQLiteMixin):
async def get_txo_count(self, unspent=False, **constraints): async def get_txo_count(self, unspent=False, **constraints):
self._clean_txo_constraints_for_aggregation(unspent, constraints) self._clean_txo_constraints_for_aggregation(unspent, constraints)
count = await self.select_txos('COUNT(*) as total', **constraints) count = await self.select_txos('COUNT(*) as total', **constraints)
return count[0]['total'] return count[0]['total'] or 0
async def get_txo_sum(self, unspent=False, **constraints): async def get_txo_sum(self, unspent=False, **constraints):
self._clean_txo_constraints_for_aggregation(unspent, constraints) self._clean_txo_constraints_for_aggregation(unspent, constraints)
result = await self.select_txos('SUM(amount) as total', **constraints) result = await self.select_txos('SUM(amount) as total', **constraints)
return result[0]['total'] return result[0]['total'] or 0
def get_utxos(self, read_only=False, **constraints): def get_utxos(self, read_only=False, **constraints):
return self.get_txos(unspent=True, read_only=read_only, **constraints) return self.get_txos(unspent=True, read_only=read_only, **constraints)
@ -908,7 +908,7 @@ class Database(SQLiteMixin):
async def get_address_count(self, cols=None, read_only=False, **constraints): async def get_address_count(self, cols=None, read_only=False, **constraints):
count = await self.select_addresses('COUNT(*) as total', read_only=read_only, **constraints) count = await self.select_addresses('COUNT(*) as total', read_only=read_only, **constraints)
return count[0]['total'] return count[0]['total'] or 0
async def get_address(self, read_only=False, **constraints): async def get_address(self, read_only=False, **constraints):
addresses = await self.get_addresses(read_only=read_only, limit=1, **constraints) addresses = await self.get_addresses(read_only=read_only, limit=1, **constraints)

View file

@ -25,7 +25,7 @@ from .account import Account, AddressManager, SingleKey
from .network import Network from .network import Network
from .transaction import Transaction, Output from .transaction import Transaction, Output
from .header import Headers, UnvalidatedHeaders from .header import Headers, UnvalidatedHeaders
from .constants import TXO_TYPES, COIN, NULL_HASH32 from .constants import TXO_TYPES, CLAIM_TYPES, COIN, NULL_HASH32
from .bip32 import PubKey, PrivateKey from .bip32 import PubKey, PrivateKey
from .coinselection import CoinSelector from .coinselection import CoinSelector
@ -646,7 +646,13 @@ class Ledger(metaclass=LedgerRegistry):
print(record['history'], addresses, tx.id) print(record['history'], addresses, tx.id)
raise asyncio.TimeoutError('Timed out waiting for transaction.') raise asyncio.TimeoutError('Timed out waiting for transaction.')
async def _inflate_outputs(self, query, accounts) -> Tuple[List[Output], dict, int, int]: async def _inflate_outputs(
self, query, accounts,
include_purchase_receipt=False,
include_is_my_output=False,
include_my_supports=False,
include_my_tips=False
) -> Tuple[List[Output], dict, int, int]:
encoded_outputs = await query encoded_outputs = await query
outputs = Outputs.from_base64(encoded_outputs or b'') # TODO: why is the server returning None? outputs = Outputs.from_base64(encoded_outputs or b'') # TODO: why is the server returning None?
txs = [] txs = []
@ -654,7 +660,7 @@ class Ledger(metaclass=LedgerRegistry):
txs: List[Transaction] = await asyncio.gather(*( txs: List[Transaction] = await asyncio.gather(*(
self.cache_transaction(*tx) for tx in outputs.txs self.cache_transaction(*tx) for tx in outputs.txs
)) ))
if accounts: if include_purchase_receipt and accounts:
priced_claims = [] priced_claims = []
for tx in txs: for tx in txs:
for txo in tx.outputs: for txo in tx.outputs:
@ -671,11 +677,36 @@ class Ledger(metaclass=LedgerRegistry):
for txo in priced_claims: for txo in priced_claims:
txo.purchase_receipt = receipts.get(txo.claim_id) txo.purchase_receipt = receipts.get(txo.claim_id)
txos, blocked = outputs.inflate(txs) txos, blocked = outputs.inflate(txs)
if any((include_is_my_output, include_my_supports, include_my_tips)):
for txo in txos:
if isinstance(txo, Output) and txo.can_decode_claim:
if include_is_my_output:
mine = await self.db.get_txo_count(
claim_id=txo.claim_id, txo_type__in=CLAIM_TYPES, is_my_output=True,
unspent=True, accounts=accounts
)
if mine:
txo.is_my_output = True
else:
txo.is_my_output = False
if include_my_supports:
supports = await self.db.get_txo_sum(
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
is_my_input=True, is_my_output=True,
unspent=True, accounts=accounts
)
txo.my_supports = supports
if include_my_tips:
tips = await self.db.get_txo_sum(
claim_id=txo.claim_id, txo_type=TXO_TYPES['support'],
is_my_input=True, is_my_output=False, accounts=accounts
)
txo.my_tips = tips
return txos, blocked, outputs.offset, outputs.total return txos, blocked, outputs.offset, outputs.total
async def resolve(self, accounts, urls): async def resolve(self, accounts, urls, **kwargs):
resolve = partial(self.network.retriable_call, self.network.resolve) resolve = partial(self.network.retriable_call, self.network.resolve)
txos = (await self._inflate_outputs(resolve(urls), accounts))[0] txos = (await self._inflate_outputs(resolve(urls), accounts, **kwargs))[0]
assert len(urls) == len(txos), "Mismatch between urls requested for resolve and responses received." assert len(urls) == len(txos), "Mismatch between urls requested for resolve and responses received."
result = {} result = {}
for url, txo in zip(urls, txos): for url, txo in zip(urls, txos):

View file

@ -208,7 +208,7 @@ class Output(InputOutput):
__slots__ = ( __slots__ = (
'amount', 'script', 'is_internal_transfer', 'is_spent', 'is_my_output', 'is_my_input', 'amount', 'script', 'is_internal_transfer', 'is_spent', 'is_my_output', 'is_my_input',
'channel', 'private_key', 'meta', 'channel', 'private_key', 'meta', 'my_supports', 'my_tips',
'purchase', 'purchased_claim', 'purchase_receipt', 'purchase', 'purchased_claim', 'purchase_receipt',
'reposted_claim', 'claims', 'reposted_claim', 'claims',
) )
@ -217,6 +217,7 @@ class Output(InputOutput):
tx_ref: TXRef = None, position: int = None, tx_ref: TXRef = None, position: int = None,
is_internal_transfer: Optional[bool] = None, is_spent: Optional[bool] = None, is_internal_transfer: Optional[bool] = None, is_spent: Optional[bool] = None,
is_my_output: Optional[bool] = None, is_my_input: Optional[bool] = None, is_my_output: Optional[bool] = None, is_my_input: Optional[bool] = None,
my_supports: Optional[int] = None, my_tips: Optional[int] = None,
channel: Optional['Output'] = None, private_key: Optional[str] = None channel: Optional['Output'] = None, private_key: Optional[str] = None
) -> None: ) -> None:
super().__init__(tx_ref, position) super().__init__(tx_ref, position)
@ -226,6 +227,8 @@ class Output(InputOutput):
self.is_spent = is_spent self.is_spent = is_spent
self.is_my_output = is_my_output self.is_my_output = is_my_output
self.is_my_input = is_my_input self.is_my_input = is_my_input
self.my_supports = my_supports
self.my_tips = my_tips
self.channel = channel self.channel = channel
self.private_key = private_key self.private_key = private_key
self.purchase: 'Output' = None # txo containing purchase metadata self.purchase: 'Output' = None # txo containing purchase metadata

View file

@ -263,6 +263,49 @@ class ResolveCommand(BaseResolveTestCase):
await self.resolve('@olds/bad_example') await self.resolve('@olds/bad_example')
) )
async def test_resolve_with_includes(self):
wallet2 = await self.daemon.jsonrpc_wallet_create('wallet2', create_account=True)
address2 = await self.daemon.jsonrpc_address_unused(wallet_id=wallet2.id)
await self.wallet_send('1.0', address2)
stream = await self.stream_create(
'priced', '0.1', wallet_id=wallet2.id,
fee_amount='0.5', fee_currency='LBC', fee_address=address2
)
stream_id = self.get_claim_id(stream)
resolve = await self.resolve('priced')
self.assertNotIn('is_my_output', resolve)
self.assertNotIn('purchase_receipt', resolve)
self.assertNotIn('my_supports', resolve)
self.assertNotIn('my_tips', resolve)
# is_my_output
resolve = await self.resolve('priced', include_is_my_output=True)
self.assertFalse(resolve['is_my_output'])
resolve = await self.resolve('priced', wallet_id=wallet2.id, include_is_my_output=True)
self.assertTrue(resolve['is_my_output'])
# purchase receipt
resolve = await self.resolve('priced', include_purchase_receipt=True)
self.assertNotIn('purchase_receipt', resolve)
await self.purchase_create(stream_id)
resolve = await self.resolve('priced', include_purchase_receipt=True)
self.assertEqual('0.5', resolve['purchase_receipt']['amount'])
# my supports and my tips
resolve = await self.resolve('priced', include_my_supports=True, include_my_tips=True)
self.assertEqual('0.0', resolve['my_supports'])
self.assertEqual('0.0', resolve['my_tips'])
await self.support_create(stream_id, '0.3')
await self.support_create(stream_id, '0.2')
await self.support_create(stream_id, '0.4', tip=True)
await self.support_create(stream_id, '0.5', tip=True)
resolve = await self.resolve('priced', include_my_supports=True, include_my_tips=True)
self.assertEqual('0.5', resolve['my_supports'])
self.assertEqual('0.9', resolve['my_tips'])
class ResolveAfterReorg(BaseResolveTestCase): class ResolveAfterReorg(BaseResolveTestCase):