added flags to resolve: --include_purchase_receipt, --include_is_my_output, --include_my_supports, --include_my_tips
This commit is contained in:
parent
1c05295e89
commit
9749da46ae
7 changed files with 119 additions and 21 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue