claim search

This commit is contained in:
Lex Berezhny 2019-03-25 22:06:36 -04:00
parent 8087b457c6
commit 183e3ac1c7
8 changed files with 308 additions and 357 deletions

View file

@ -122,7 +122,6 @@ async def maybe_paginate(get_records: Callable, get_record_count: Callable,
def sort_claim_results(claims): def sort_claim_results(claims):
claims.sort(key=lambda d: (d['height'], d['name'], d['claim_id'], d['txid'], d['nout'])) claims.sort(key=lambda d: (d['height'], d['name'], d['claim_id'], d['txid'], d['nout']))
return claims
DHT_HAS_CONTACTS = "dht_has_contacts" DHT_HAS_CONTACTS = "dht_has_contacts"
@ -912,7 +911,7 @@ class Daemon(metaclass=JSONRPCServerType):
return {key: cleaned} return {key: cleaned}
ACCOUNT_DOC = """ ACCOUNT_DOC = """
Account management. Create, modify and inspect wallet accounts.
""" """
@requires("wallet") @requires("wallet")
@ -1376,7 +1375,7 @@ class Daemon(metaclass=JSONRPCServerType):
} }
ADDRESS_DOC = """ ADDRESS_DOC = """
Address management. List, generate and verify addresses.
""" """
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
@ -1623,7 +1622,7 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
def jsonrpc_claim_list(self, account_id=None, page=None, page_size=None): def jsonrpc_claim_list(self, account_id=None, page=None, page_size=None):
""" """
List my name claims List my stream and channel claims.
Usage: Usage:
claim_list [<account_id> | --account_id=<account_id>] claim_list [<account_id> | --account_id=<account_id>]
@ -1642,134 +1641,65 @@ class Daemon(metaclass=JSONRPCServerType):
) )
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
async def jsonrpc_claim_search(self, name, channel_id=None, winning=False): async def jsonrpc_claim_search(
self, name=None, claim_id=None, txid=None, nout=None,
channel_id=None, winning=False, page=1, page_size=10):
""" """
Search for claims on the blockchain. Search for stream and channel claims on the blockchain.
Use --channel_id=<channel_id> to list all stream claims in a channel.
Usage: Usage:
claim_search (<name> | --name=<name>) [--channel_id=<channel_id>] [--winning] claim_search ([<name> | --name=<name>] | --claim_id=<claim_id> | --txid=<txid> --nout=<nout>)
[--channel_id=<channel_id>] [--winning] [--page=<page>] [--page_size=<page_size>]
Options: Options:
--name=<name> : (str) name of the claim to list info about --name=<name> : (str) find claims with this name
--channel_id=<channel_id> : (str) limit search to specific channel --claim_id=<claim_id> : (str) find a claim with this claim_id
--txid=<txid> : (str) find a claim with this txid:nout
--nout=<nout> : (str) find a claim with this txid:nout
--channel_id=<channel_id> : (str) limit search to specific channel (returns stream claims)
--winning : (bool) limit to winning claims --winning : (bool) limit to winning claims
--page=<page> : (int) page to return during paginating
--page_size=<page_size> : (int) number of items on page during pagination
""" """
response = await self.wallet_manager.ledger.network.get_claims_for_name(name) claims = []
resolutions = await self.resolve(*(f"{claim['name']}#{claim['claim_id']}" for claim in response['claims'])) if name is not None:
response['claims'] = [value.get('claim', value.get('certificate')) for value in resolutions.values()] claims = await self.ledger.network.get_claims_for_name(name)
response['claims'] = sort_claim_results(response['claims']) elif claim_id is not None:
return response claim = await self.wallet_manager.get_claim_by_claim_id(claim_id)
""" if claim and claim != 'claim not found':
Resolve claim info from txid/nout or with claim ID claims = {'claims': [claim]}
elif txid is not None and nout is not None:
Usage: claim = await self.wallet_manager.get_claim_by_outpoint(txid, int(nout))
claim_show [<txid> | --txid=<txid>] [<nout> | --nout=<nout>] if claim and claim != 'claim not found':
[<claim_id> | --claim_id=<claim_id>] claims = {'claims': [claim]}
elif channel_id is not None:
Options: claim = await self.wallet_manager.get_claim_by_claim_id(channel_id)
--txid=<txid> : (str) look for claim with this txid, nout must if claim and claim != 'claim not found':
also be specified channel_url = f"{claim['name']}#{claim['claim_id']}"
--nout=<nout> : (int) look for claim with this nout, txid must resolve = await self.resolve(f"{claim['name']}#{claim['claim_id']}", page=page, page_size=page_size)
also be specified resolve = resolve.get(channel_url, {})
--claim_id=<claim_id> : (str) look for claim with this claim id claims = resolve.get('claims_in_channel', []) or []
""" total_pages = 0
if claim_id is not None and txid is None and nout is None: if claims:
claim_results = await self.wallet_manager.get_claim_by_claim_id(claim_id) total_pages = int((resolve['total_claims'] + (page_size-1)) / page_size)
elif txid is not None and nout is not None and claim_id is None: #sort_claim_results(claims)
claim_results = await self.wallet_manager.get_claim_by_outpoint(txid, int(nout)) return {"items": claims, "total_pages": total_pages, "page": page, "page_size": page_size}
else: else:
raise Exception("Must specify either txid/nout, or claim_id") raise Exception("Must specify either name, claimd_id, or txid:nout.")
return claim_results if claims:
resolutions = await self.resolve(*(f"{claim['name']}#{claim['claim_id']}" for claim in claims['claims']))
async def jsonrpc_claim_list_by_channel(self, page=0, page_size=10, uri=None, uris=[]): claims = [value.get('claim', value.get('certificate')) for value in resolutions.values()]
""" sort_claim_results(claims)
Get paginated claims in a channel specified by a channel uri return {"items": claims, "total_pages": 1, "page": 1, "page_size": len(claims)}
Usage:
claim_list_by_channel (<uri> | --uri=<uri>) [<uris>...] [--page=<page>]
[--page_size=<page_size>]
Options:
--uri=<uri> : (str) uri of the channel
--uris=<uris> : (list) uris of the channel
--page=<page> : (int) which page of results to return where page 1 is the first
page, defaults to no pages
--page_size=<page_size> : (int) number of results in a page, default of 10
Returns:
{
resolved channel uri: {
If there was an error:
'error': (str) error message
'claims_in_channel': the total number of results for the channel,
If a page of results was requested:
'returned_page': page number returned,
'claims_in_channel': [
{
'absolute_channel_position': (int) claim index number in sorted list of
claims which assert to be part of the
channel
'address': (str) claim address,
'amount': (float) claim amount,
'effective_amount': (float) claim amount including supports,
'claim_id': (str) claim id,
'decoded_claim': (bool) whether or not the claim value was decoded,
'height': (int) claim height,
'depth': (int) claim depth,
'has_signature': (bool) included if decoded_claim
'name': (str) claim name,
'supports: (list) list of supports [{'txid': (str) txid,
'nout': (int) nout,
'amount': (float) amount}],
'txid': (str) claim txid,
'nout': (str) claim nout,
'signature_is_valid': (bool), included if has_signature,
'value': ClaimDict if decoded, otherwise hex string
}
],
}
}
"""
uris = tuple(uris)
page = int(page)
page_size = int(page_size)
if uri is not None:
uris += (uri,)
results = {}
valid_uris = tuple()
for chan_uri in uris:
try:
parsed = parse_lbry_uri(chan_uri)
if not parsed.contains_channel:
results[chan_uri] = {"error": "%s is not a channel uri" % parsed.name}
elif parsed.path:
results[chan_uri] = {"error": "%s is a claim in a channel" % parsed.path}
else:
valid_uris += (chan_uri,)
except URIParseError:
results[chan_uri] = {"error": "%s is not a valid uri" % chan_uri}
resolved = await self.resolve(*valid_uris, page=page, page_size=page_size)
if 'error' in resolved:
return {'error': resolved['error']}
for u in resolved:
if 'error' in resolved[u]:
results[u] = resolved[u]
else:
results[u] = {
'claims_in_channel': resolved[u]['claims_in_channel']
}
if page:
results[u]['returned_page'] = page
results[u]['claims_in_channel'] = resolved[u].get('claims_in_channel', [])
return results
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED]) @requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_claim_abandon( async def jsonrpc_claim_abandon(
self, claim_id=None, txid=None, nout=None, account_id=None, self, claim_id=None, txid=None, nout=None, account_id=None,
preview=False, blocking=True): preview=False, blocking=True):
""" """
Abandon a name and reclaim credits from the claim Abandon one of my channel or stream claims.
Usage: Usage:
claim_abandon [<claim_id> | --claim_id=<claim_id>] claim_abandon [<claim_id> | --claim_id=<claim_id>]
@ -1787,9 +1717,9 @@ class Daemon(metaclass=JSONRPCServerType):
""" """
account = self.get_account_or_default(account_id) account = self.get_account_or_default(account_id)
if txid and nout: if txid is not None and nout is not None:
claims = await account.get_claims(**{'txo.txid': txid, 'txo.position': nout}) claims = await account.get_claims(**{'txo.txid': txid, 'txo.position': nout})
elif claim_id: elif claim_id is not None:
claims = await account.get_claims(claim_id=claim_id) claims = await account.get_claims(claim_id=claim_id)
else: else:
raise Exception('Must specify claim_id, or txid and nout') raise Exception('Must specify claim_id, or txid and nout')
@ -1812,7 +1742,7 @@ class Daemon(metaclass=JSONRPCServerType):
return tx return tx
CHANNEL_DOC = """ CHANNEL_DOC = """
Channel management. Create, update and list your channel claims.
""" """
@deprecated('channel_create') @deprecated('channel_create')
@ -1823,7 +1753,7 @@ class Daemon(metaclass=JSONRPCServerType):
async def jsonrpc_channel_create( async def jsonrpc_channel_create(
self, name, bid, allow_duplicate_name=False, account_id=None, claim_address=None, preview=False, **kwargs): self, name, bid, allow_duplicate_name=False, account_id=None, claim_address=None, preview=False, **kwargs):
""" """
Generate a publisher key and create a new '@' prefixed channel claim. Create a new channel by generating a channel private key and establishing an '@' prefixed claim.
Usage: Usage:
channel_create (<name> | --name=<name>) (<bid> | --bid=<bid>) channel_create (<name> | --name=<name>) (<bid> | --bid=<bid>)
@ -1852,7 +1782,7 @@ class Daemon(metaclass=JSONRPCServerType):
--preview : (bool) do not broadcast the transaction --preview : (bool) do not broadcast the transaction
""" """
account = self.get_account_or_default(account_id) account = self.get_account_or_default(account_id)
name = self.get_channel_name_or_error(name) self.valid_channel_name_or_error(name)
amount = self.get_dewies_or_error('bid', bid, positive_value=True) amount = self.get_dewies_or_error('bid', bid, positive_value=True)
claim_address = await self.get_receiving_address(claim_address, account) claim_address = await self.get_receiving_address(claim_address, account)
@ -1891,7 +1821,7 @@ class Daemon(metaclass=JSONRPCServerType):
self, claim_id, bid=None, account_id=None, claim_address=None, self, claim_id, bid=None, account_id=None, claim_address=None,
new_signing_key=False, preview=False, **kwargs): new_signing_key=False, preview=False, **kwargs):
""" """
Update attributes of a channel. Update an existing channel claim.
Usage: Usage:
channel_update (<claim_id> | --claim_id=<claim_id>) [<bid> | --bid=<bid>] channel_update (<claim_id> | --claim_id=<claim_id>) [<bid> | --bid=<bid>]
@ -1970,7 +1900,7 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
def jsonrpc_channel_list(self, account_id=None, page=None, page_size=None): def jsonrpc_channel_list(self, account_id=None, page=None, page_size=None):
""" """
Get certificate claim infos for channels that can be published to List my channel claims.
Usage: Usage:
channel_list [<account_id> | --account_id=<account_id>] channel_list [<account_id> | --account_id=<account_id>]
@ -2027,7 +1957,7 @@ class Daemon(metaclass=JSONRPCServerType):
return await self.wallet_manager.import_certificate_info(serialized_certificate_info) return await self.wallet_manager.import_certificate_info(serialized_certificate_info)
STREAM_DOC = """ STREAM_DOC = """
Create, update, list and inspect stream claims. Create, update, list and inspect your stream claims.
""" """
@requires(WALLET_COMPONENT, STREAM_MANAGER_COMPONENT, BLOB_COMPONENT, DATABASE_COMPONENT, @requires(WALLET_COMPONENT, STREAM_MANAGER_COMPONENT, BLOB_COMPONENT, DATABASE_COMPONENT,
@ -2418,7 +2348,7 @@ class Daemon(metaclass=JSONRPCServerType):
@requires(WALLET_COMPONENT) @requires(WALLET_COMPONENT)
def jsonrpc_support_list(self, account_id=None, page=None, page_size=None): def jsonrpc_support_list(self, account_id=None, page=None, page_size=None):
""" """
List supports and tips. List supports and tips in my control.
Usage: Usage:
support_list [<account_id> | --account_id=<account_id>] support_list [<account_id> | --account_id=<account_id>]
@ -2441,7 +2371,7 @@ class Daemon(metaclass=JSONRPCServerType):
self, claim_id=None, txid=None, nout=None, keep=None, self, claim_id=None, txid=None, nout=None, keep=None,
account_id=None, preview=False, blocking=True): account_id=None, preview=False, blocking=True):
""" """
Abandon supports, including tips, to a specific claim, optionally Abandon supports, including tips, of a specific claim, optionally
keeping some amount as supports. keeping some amount as supports.
Usage: Usage:
@ -2459,9 +2389,9 @@ class Daemon(metaclass=JSONRPCServerType):
""" """
account = self.get_account_or_default(account_id) account = self.get_account_or_default(account_id)
if txid and nout: if txid is not None and nout is not None:
supports = await account.get_supports(**{'txo.txid': txid, 'txo.position': nout}) supports = await account.get_supports(**{'txo.txid': txid, 'txo.position': nout})
elif claim_id: elif claim_id is not None:
supports = await account.get_supports(claim_id=claim_id) supports = await account.get_supports(claim_id=claim_id)
else: else:
raise Exception('Must specify claim_id, or txid and nout') raise Exception('Must specify claim_id, or txid and nout')

View file

@ -141,6 +141,7 @@ class Resolver:
claims_in_channel, upper_bound = channel_info claims_in_channel, upper_bound = channel_info
if claims_in_channel: if claims_in_channel:
result['total_claims'] = upper_bound
result['claims_in_channel'] = claims_in_channel result['claims_in_channel'] = claims_in_channel
elif 'error' not in result: elif 'error' not in result:
return {'error': 'claim not found', 'success': False, 'uri': str(parsed_uri)} return {'error': 'claim not found', 'success': False, 'uri': str(parsed_uri)}

View file

@ -18,7 +18,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# While making the spamdwich he wonders... has anyone on LBRY # While making the spamdwich he wonders... has anyone on LBRY
# registered the @spam channel yet? "I should do that!" he # registered the @spam channel yet? "I should do that!" he
# exclaims and goes back to his computer to do just that! # exclaims and goes back to his computer to do just that!
tx = await self.create_channel('@spam', '1.0') tx = await self.channel_create('@spam', '1.0')
channel_id = tx['outputs'][0]['claim_id'] channel_id = tx['outputs'][0]['claim_id']
# Do we have it locally? # Do we have it locally?
@ -50,7 +50,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# And so, many hours later, Chris is finished writing his epic story # And so, many hours later, Chris is finished writing his epic story
# about eels driving a hovercraft across the wetlands while eating spam # about eels driving a hovercraft across the wetlands while eating spam
# and decides it's time to publish it to the @spam channel. # and decides it's time to publish it to the @spam channel.
tx = await self.create_claim( tx = await self.stream_create(
'hovercraft', '1.0', 'hovercraft', '1.0',
data=b'[insert long story about eels driving hovercraft]', data=b'[insert long story about eels driving hovercraft]',
channel_id=channel_id channel_id=channel_id
@ -77,7 +77,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# As people start reading his story they discover some typos and notify # As people start reading his story they discover some typos and notify
# Chris who explains in despair "Oh! Noooooos!" but then remembers # Chris who explains in despair "Oh! Noooooos!" but then remembers
# "No big deal! I can update my claim." And so he updates his claim. # "No big deal! I can update my claim." And so he updates his claim.
await self.update_claim(claim_id, data=b'[typo fixing sounds being made]') await self.stream_update(claim_id, data=b'[typo fixing sounds being made]')
# After some soul searching Chris decides that his story needs more # After some soul searching Chris decides that his story needs more
# heart and a better ending. He takes down the story and begins the rewrite. # heart and a better ending. He takes down the story and begins the rewrite.
@ -116,7 +116,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# After Chris is done with all the "helping other people" stuff he decides that it's time to # After Chris is done with all the "helping other people" stuff he decides that it's time to
# write a new story and publish it to lbry. All he needed was a fresh start and he came up with: # write a new story and publish it to lbry. All he needed was a fresh start and he came up with:
tx = await self.create_claim( tx = await self.stream_create(
'fresh-start', '1.0', data=b'Amazingly Original First Line', channel_id=channel_id 'fresh-start', '1.0', data=b'Amazingly Original First Line', channel_id=channel_id
) )
claim_id2 = tx['outputs'][0]['claim_id'] claim_id2 = tx['outputs'][0]['claim_id']
@ -173,7 +173,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# his song, seeing as his novel had smashed all the records, he was the perfect candidate! # his song, seeing as his novel had smashed all the records, he was the perfect candidate!
# ....... # .......
# Chris agrees.. 17 hours 43 minutes and 14 seconds later, he makes his publish # Chris agrees.. 17 hours 43 minutes and 14 seconds later, he makes his publish
tx = await self.create_claim( tx = await self.stream_create(
'hit-song', '1.0', data=b'The Whale and The Bookmark', channel_id=channel_id 'hit-song', '1.0', data=b'The Whale and The Bookmark', channel_id=channel_id
) )
await self.generate(5) await self.generate(5)

View file

@ -16,40 +16,40 @@ class ChannelCommands(CommandTestCase):
async def test_create_channel_names(self): async def test_create_channel_names(self):
# claim new name # claim new name
await self.create_channel('@foo') await self.channel_create('@foo')
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '8.991893') await self.assertBalance(self.account, '8.991893')
# fail to claim duplicate # fail to claim duplicate
with self.assertRaisesRegex(Exception, "You already have a channel under the name '@foo'."): with self.assertRaisesRegex(Exception, "You already have a channel under the name '@foo'."):
await self.create_channel('@foo') await self.channel_create('@foo')
# fail to claim invalid name # fail to claim invalid name
with self.assertRaisesRegex(Exception, "Cannot make a new channel for a non channel name"): with self.assertRaisesRegex(Exception, "Channel names must start with '@' symbol."):
await self.create_channel('foo') await self.channel_create('foo')
# nothing's changed after failed attempts # nothing's changed after failed attempts
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '8.991893') await self.assertBalance(self.account, '8.991893')
# succeed overriding duplicate restriction # succeed overriding duplicate restriction
await self.create_channel('@foo', allow_duplicate_name=True) await self.channel_create('@foo', allow_duplicate_name=True)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
await self.assertBalance(self.account, '7.983786') await self.assertBalance(self.account, '7.983786')
async def test_channel_bids(self): async def test_channel_bids(self):
# enough funds # enough funds
tx = await self.create_channel('@foo', '5.0') tx = await self.channel_create('@foo', '5.0')
claim_id = tx['outputs'][0]['claim_id'] claim_id = tx['outputs'][0]['claim_id']
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '4.991893') await self.assertBalance(self.account, '4.991893')
# bid preserved on update # bid preserved on update
tx = await self.update_channel(claim_id) tx = await self.channel_update(claim_id)
self.assertEqual(tx['outputs'][0]['amount'], '5.0') self.assertEqual(tx['outputs'][0]['amount'], '5.0')
# bid changed on update # bid changed on update
tx = await self.update_channel(claim_id, bid='4.0') tx = await self.channel_update(claim_id, bid='4.0')
self.assertEqual(tx['outputs'][0]['amount'], '4.0') self.assertEqual(tx['outputs'][0]['amount'], '4.0')
await self.assertBalance(self.account, '5.991447') await self.assertBalance(self.account, '5.991447')
@ -57,12 +57,12 @@ class ChannelCommands(CommandTestCase):
# not enough funds # not enough funds
with self.assertRaisesRegex( with self.assertRaisesRegex(
InsufficientFundsError, "Not enough funds to cover this transaction."): InsufficientFundsError, "Not enough funds to cover this transaction."):
await self.create_channel('@foo2', '9.0') await self.channel_create('@foo2', '9.0')
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '5.991447') await self.assertBalance(self.account, '5.991447')
# spend exactly amount available, no change # spend exactly amount available, no change
tx = await self.create_channel('@foo3', '5.981266') tx = await self.channel_create('@foo3', '5.981266')
await self.assertBalance(self.account, '0.0') await self.assertBalance(self.account, '0.0')
self.assertEqual(len(tx['outputs']), 1) # no change self.assertEqual(len(tx['outputs']), 1) # no change
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
@ -80,7 +80,7 @@ class ChannelCommands(CommandTestCase):
} }
# create new channel with all fields set # create new channel with all fields set
tx = await self.out(self.create_channel('@bigchannel', **values)) tx = await self.out(self.channel_create('@bigchannel', **values))
txo = tx['outputs'][0] txo = tx['outputs'][0]
self.assertEqual( self.assertEqual(
txo['value']['channel'], txo['value']['channel'],
@ -88,7 +88,7 @@ class ChannelCommands(CommandTestCase):
) )
# create channel with nothing set # create channel with nothing set
tx = await self.out(self.create_channel('@lightchannel')) tx = await self.out(self.channel_create('@lightchannel'))
txo = tx['outputs'][0] txo = tx['outputs'][0]
self.assertEqual( self.assertEqual(
txo['value']['channel'], txo['value']['channel'],
@ -96,7 +96,7 @@ class ChannelCommands(CommandTestCase):
) )
# create channel with just some tags # create channel with just some tags
tx = await self.out(self.create_channel('@updatedchannel', tags='blah')) tx = await self.out(self.channel_create('@updatedchannel', tags='blah'))
txo = tx['outputs'][0] txo = tx['outputs'][0]
claim_id = txo['claim_id'] claim_id = txo['claim_id']
public_key = txo['value']['channel']['public_key'] public_key = txo['value']['channel']['public_key']
@ -106,7 +106,7 @@ class ChannelCommands(CommandTestCase):
) )
# update channel setting all fields # update channel setting all fields
tx = await self.out(self.update_channel(claim_id, **values)) tx = await self.out(self.channel_update(claim_id, **values))
txo = tx['outputs'][0] txo = tx['outputs'][0]
values['public_key'] = public_key values['public_key'] = public_key
values['tags'].insert(0, 'blah') # existing tag values['tags'].insert(0, 'blah') # existing tag
@ -116,7 +116,7 @@ class ChannelCommands(CommandTestCase):
) )
# clearing and settings tags # clearing and settings tags
tx = await self.out(self.update_channel(claim_id, tags='single', clear_tags=True)) tx = await self.out(self.channel_update(claim_id, tags='single', clear_tags=True))
txo = tx['outputs'][0] txo = tx['outputs'][0]
values['tags'] = ['single'] values['tags'] = ['single']
self.assertEqual( self.assertEqual(
@ -125,7 +125,7 @@ class ChannelCommands(CommandTestCase):
) )
# reset signing key # reset signing key
tx = await self.out(self.update_channel(claim_id, new_signing_key=True)) tx = await self.out(self.channel_update(claim_id, new_signing_key=True))
txo = tx['outputs'][0] txo = tx['outputs'][0]
self.assertNotEqual( self.assertNotEqual(
txo['value']['channel']['public_key'], txo['value']['channel']['public_key'],
@ -141,7 +141,7 @@ class ChannelCommands(CommandTestCase):
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0) self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0)
other_address = await account2.receiving.get_or_create_usable_address() other_address = await account2.receiving.get_or_create_usable_address()
tx = await self.out(self.update_channel(claim_id, claim_address=other_address)) tx = await self.out(self.channel_update(claim_id, claim_address=other_address))
# after sending # after sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
@ -160,122 +160,45 @@ class ChannelCommands(CommandTestCase):
self.assertIsNotNone(txo.private_key) self.assertIsNotNone(txo.private_key)
class SupportCommands(CommandTestCase): class StreamCommands(CommandTestCase):
async def test_regular_supports_and_tip_supports(self): async def test_create_stream_names(self):
# account2 will be used to send tips and supports to account1
account2_id = (await self.daemon.jsonrpc_account_create('second account'))['id']
account2 = self.daemon.get_account_or_error(account2_id)
# send account2 5 LBC out of the 10 LBC in account1
result = await self.out(self.daemon.jsonrpc_account_send(
'5.0', await self.daemon.jsonrpc_address_unused(account2_id)
))
await self.on_transaction_dict(result)
# account1 and account2 balances:
await self.assertBalance(self.account, '4.999876')
await self.assertBalance(account2, '5.0')
# create the claim we'll be tipping and supporting
tx = await self.create_claim()
claim_id = tx['outputs'][0]['claim_id']
# account1 and account2 balances:
await self.assertBalance(self.account, '3.979769')
await self.assertBalance(account2, '5.0')
# send a tip to the claim using account2
tip = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '1.0', True, account2_id)
)
await self.on_transaction_dict(tip)
await self.generate(1)
await self.on_transaction_dict(tip)
# 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())
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)
self.assertEqual(txs[0]['support_info'][0]['is_tip'], True)
self.assertEqual(txs[0]['value'], '1.0')
self.assertEqual(txs[0]['fee'], '0.0')
# verify that the outgoing tip is marked correctly as is_tip=True in account2
txs2 = await self.out(
self.daemon.jsonrpc_transaction_list(account2_id)
)
self.assertEqual(len(txs2[0]['support_info']), 1)
self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-1.0')
self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id)
self.assertEqual(txs2[0]['support_info'][0]['is_tip'], True)
self.assertEqual(txs2[0]['value'], '-1.0')
self.assertEqual(txs2[0]['fee'], '-0.0001415')
# send a support to the claim using account2
support = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '2.0', False, account2_id)
)
await self.on_transaction_dict(support)
await self.generate(1)
await self.on_transaction_dict(support)
# account2 balance went down ~2
await self.assertBalance(self.account, '3.979769')
await self.assertBalance(account2, '1.999717')
# verify that the outgoing support is marked correctly as is_tip=False in account2
txs2 = await self.out(self.daemon.jsonrpc_transaction_list(account2_id))
self.assertEqual(len(txs2[0]['support_info']), 1)
self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-2.0')
self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id)
self.assertEqual(txs2[0]['support_info'][0]['is_tip'], False)
self.assertEqual(txs2[0]['value'], '0.0')
self.assertEqual(txs2[0]['fee'], '-0.0001415')
class ClaimCommands(CommandTestCase):
async def test_create_claim_names(self):
# claim new name # claim new name
await self.create_claim('foo') await self.stream_create('foo')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '8.993893') await self.assertBalance(self.account, '8.993893')
# fail to claim duplicate # fail to claim duplicate
with self.assertRaisesRegex(Exception, "You already have a claim published under the name 'foo'."): with self.assertRaisesRegex(
await self.create_claim('foo') Exception, "You already have a stream claim published under the name 'foo'."):
await self.stream_create('foo')
# fail claim starting with @ # fail claim starting with @
with self.assertRaisesRegex(Exception, "Claim names cannot start with @ symbol."): with self.assertRaisesRegex(
await self.create_claim('@foo') Exception, "Stream names cannot start with '@' symbol."):
await self.stream_create('@foo')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '8.993893') await self.assertBalance(self.account, '8.993893')
# succeed overriding duplicate restriction # succeed overriding duplicate restriction
await self.create_claim('foo', allow_duplicate_name=True) await self.stream_create('foo', allow_duplicate_name=True)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
await self.assertBalance(self.account, '7.987786') await self.assertBalance(self.account, '7.987786')
async def test_bids(self): async def test_stream_bids(self):
# enough funds # enough funds
tx = await self.create_claim('foo', '2.0') tx = await self.stream_create('foo', '2.0')
claim_id = tx['outputs'][0]['claim_id'] claim_id = tx['outputs'][0]['claim_id']
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '7.993893') await self.assertBalance(self.account, '7.993893')
# bid preserved on update # bid preserved on update
tx = await self.update_claim(claim_id) tx = await self.stream_update(claim_id)
self.assertEqual(tx['outputs'][0]['amount'], '2.0') self.assertEqual(tx['outputs'][0]['amount'], '2.0')
# bid changed on update # bid changed on update
tx = await self.update_claim(claim_id, bid='3.0') tx = await self.stream_update(claim_id, bid='3.0')
self.assertEqual(tx['outputs'][0]['amount'], '3.0') self.assertEqual(tx['outputs'][0]['amount'], '3.0')
await self.assertBalance(self.account, '6.993384') await self.assertBalance(self.account, '6.993384')
@ -283,12 +206,12 @@ class ClaimCommands(CommandTestCase):
# not enough funds # not enough funds
with self.assertRaisesRegex( with self.assertRaisesRegex(
InsufficientFundsError, "Not enough funds to cover this transaction."): InsufficientFundsError, "Not enough funds to cover this transaction."):
await self.create_claim('foo2', '9.0') await self.stream_create('foo2', '9.0')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '6.993384') await self.assertBalance(self.account, '6.993384')
# spend exactly amount available, no change # spend exactly amount available, no change
tx = await self.create_claim('foo3', '6.98527700') tx = await self.stream_create('foo3', '6.98527700')
await self.assertBalance(self.account, '0.0') await self.assertBalance(self.account, '0.0')
self.assertEqual(len(tx['outputs']), 1) # no change self.assertEqual(len(tx['outputs']), 1) # no change
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
@ -298,7 +221,7 @@ class ClaimCommands(CommandTestCase):
new_account = await self.daemon.jsonrpc_account_create('second account') new_account = await self.daemon.jsonrpc_account_create('second account')
account2_id, account2 = new_account['id'], self.daemon.get_account_or_error(new_account['id']) account2_id, account2 = new_account['id'], self.daemon.get_account_or_error(new_account['id'])
await self.out(self.create_channel('@spam', '1.0')) await self.out(self.channel_create('@spam', '1.0'))
self.assertEqual('8.989893', await self.daemon.jsonrpc_account_balance()) self.assertEqual('8.989893', await self.daemon.jsonrpc_account_balance())
result = await self.out(self.daemon.jsonrpc_account_send( result = await self.out(self.daemon.jsonrpc_account_send(
@ -309,7 +232,7 @@ class ClaimCommands(CommandTestCase):
self.assertEqual('3.989769', await self.daemon.jsonrpc_account_balance()) self.assertEqual('3.989769', await self.daemon.jsonrpc_account_balance())
self.assertEqual('5.0', await self.daemon.jsonrpc_account_balance(account2_id)) self.assertEqual('5.0', await self.daemon.jsonrpc_account_balance(account2_id))
baz_tx = await self.out(self.create_channel('@baz', '1.0', account_id=account2_id)) baz_tx = await self.out(self.channel_create('@baz', '1.0', account_id=account2_id))
baz_id = baz_tx['outputs'][0]['claim_id'] baz_id = baz_tx['outputs'][0]['claim_id']
channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id)) channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id))
@ -322,12 +245,12 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(channels[0]['name'], '@baz') self.assertEqual(channels[0]['name'], '@baz')
# defaults to using all accounts to lookup channel # defaults to using all accounts to lookup channel
await self.create_claim('hovercraft1', channel_id=baz_id) await self.stream_create('hovercraft1', channel_id=baz_id)
# uses only the specific accounts which contains the channel # uses only the specific accounts which contains the channel
await self.create_claim('hovercraft2', channel_id=baz_id, channel_account_id=[account2_id]) await self.stream_create('hovercraft2', channel_id=baz_id, channel_account_id=[account2_id])
# fails when specifying account which does not contain channel # fails when specifying account which does not contain channel
with self.assertRaisesRegex(ValueError, "Couldn't find channel with channel_id"): with self.assertRaisesRegex(ValueError, "Couldn't find channel with channel_id"):
await self.create_claim( await self.stream_create(
'hovercraft3', channel_id=baz_id, channel_account_id=[account1_id] 'hovercraft3', channel_id=baz_id, channel_account_id=[account1_id]
) )
@ -353,7 +276,7 @@ class ClaimCommands(CommandTestCase):
} }
# create new channel with all fields set # create new channel with all fields set
tx = await self.out(self.create_claim('big', **values)) tx = await self.out(self.stream_create('big', **values))
txo = tx['outputs'][0] txo = tx['outputs'][0]
stream = txo['value']['stream'] stream = txo['value']['stream']
fixed_values = values.copy() fixed_values = values.copy()
@ -373,7 +296,7 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(stream, fixed_values) self.assertEqual(stream, fixed_values)
# create channel with nothing set # create channel with nothing set
tx = await self.out(self.create_claim('light')) tx = await self.out(self.stream_create('light'))
txo = tx['outputs'][0] txo = tx['outputs'][0]
self.assertEqual( self.assertEqual(
txo['value']['stream'], { txo['value']['stream'], {
@ -384,7 +307,7 @@ class ClaimCommands(CommandTestCase):
) )
# create channel with just some tags # create channel with just some tags
tx = await self.out(self.create_claim('updated', tags='blah')) tx = await self.out(self.stream_create('updated', tags='blah'))
txo = tx['outputs'][0] txo = tx['outputs'][0]
claim_id = txo['claim_id'] claim_id = txo['claim_id']
fixed_values['hash'] = txo['value']['stream']['hash'] fixed_values['hash'] = txo['value']['stream']['hash']
@ -398,13 +321,13 @@ class ClaimCommands(CommandTestCase):
) )
# update channel setting all fields # update channel setting all fields
tx = await self.out(self.update_claim(claim_id, **values)) tx = await self.out(self.stream_update(claim_id, **values))
txo = tx['outputs'][0] txo = tx['outputs'][0]
fixed_values['tags'].insert(0, 'blah') # existing tag fixed_values['tags'].insert(0, 'blah') # existing tag
self.assertEqual(txo['value']['stream'], fixed_values) self.assertEqual(txo['value']['stream'], fixed_values)
# clearing and settings tags # clearing and settings tags
tx = await self.out(self.update_claim(claim_id, tags='single', clear_tags=True)) tx = await self.out(self.stream_update(claim_id, tags='single', clear_tags=True))
txo = tx['outputs'][0] txo = tx['outputs'][0]
fixed_values['tags'] = ['single'] fixed_values['tags'] = ['single']
self.assertEqual(txo['value']['stream'], fixed_values) self.assertEqual(txo['value']['stream'], fixed_values)
@ -418,7 +341,7 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=account2_id)), 0) self.assertEqual(len(await self.daemon.jsonrpc_claim_list(account_id=account2_id)), 0)
other_address = await account2.receiving.get_or_create_usable_address() other_address = await account2.receiving.get_or_create_usable_address()
tx = await self.out(self.update_claim(claim_id, claim_address=other_address)) tx = await self.out(self.stream_update(claim_id, claim_address=other_address))
# after sending # after sending
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
@ -427,7 +350,7 @@ class ClaimCommands(CommandTestCase):
async def test_create_update_and_abandon_claim(self): async def test_create_update_and_abandon_claim(self):
await self.assertBalance(self.account, '10.0') await self.assertBalance(self.account, '10.0')
tx = await self.create_claim(bid='2.5') # creates new claim tx = await self.stream_create(bid='2.5') # creates new claim
claim_id = tx['outputs'][0]['claim_id'] claim_id = tx['outputs'][0]['claim_id']
txs = await self.out(self.daemon.jsonrpc_transaction_list()) txs = await self.out(self.daemon.jsonrpc_transaction_list())
self.assertEqual(len(txs[0]['claim_info']), 1) self.assertEqual(len(txs[0]['claim_info']), 1)
@ -438,7 +361,7 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(txs[0]['fee'], '-0.020107') self.assertEqual(txs[0]['fee'], '-0.020107')
await self.assertBalance(self.account, '7.479893') await self.assertBalance(self.account, '7.479893')
await self.update_claim(claim_id, bid='1.0') # updates previous claim await self.stream_update(claim_id, bid='1.0') # updates previous claim
txs = await self.out(self.daemon.jsonrpc_transaction_list()) txs = await self.out(self.daemon.jsonrpc_transaction_list())
self.assertEqual(len(txs[0]['update_info']), 1) self.assertEqual(len(txs[0]['update_info']), 1)
self.assertEqual(txs[0]['update_info'][0]['balance_delta'], '1.5') self.assertEqual(txs[0]['update_info'][0]['balance_delta'], '1.5')
@ -447,7 +370,7 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(txs[0]['fee'], '-0.000184') self.assertEqual(txs[0]['fee'], '-0.000184')
await self.assertBalance(self.account, '8.979709') await self.assertBalance(self.account, '8.979709')
await self.out(self.daemon.jsonrpc_claim_abandon(claim_id)) await self.claim_abandon(claim_id)
txs = await self.out(self.daemon.jsonrpc_transaction_list()) txs = await self.out(self.daemon.jsonrpc_transaction_list())
self.assertEqual(len(txs[0]['abandon_info']), 1) self.assertEqual(len(txs[0]['abandon_info']), 1)
self.assertEqual(txs[0]['abandon_info'][0]['balance_delta'], '1.0') self.assertEqual(txs[0]['abandon_info'][0]['balance_delta'], '1.0')
@ -458,57 +381,88 @@ class ClaimCommands(CommandTestCase):
async def test_abandoning_claim_at_loss(self): async def test_abandoning_claim_at_loss(self):
await self.assertBalance(self.account, '10.0') await self.assertBalance(self.account, '10.0')
tx = await self.create_claim(bid='0.0001') tx = await self.stream_create(bid='0.0001')
await self.assertBalance(self.account, '9.979793') await self.assertBalance(self.account, '9.979793')
await self.out(self.daemon.jsonrpc_claim_abandon(tx['outputs'][0]['claim_id'])) await self.claim_abandon(tx['outputs'][0]['claim_id'])
await self.assertBalance(self.account, '9.97968399') await self.assertBalance(self.account, '9.97968399')
async def test_claim_show(self):
channel = await self.create_channel('@abc', '1.0')
channel_from_claim_show = await self.out(
self.daemon.jsonrpc_claim_show(txid=channel['txid'], nout=0)
)
self.assertEqual(channel_from_claim_show['value'], channel['outputs'][0]['value'])
channel_from_claim_show = await self.out(
self.daemon.jsonrpc_claim_show(claim_id=channel['outputs'][0]['claim_id'])
)
self.assertEqual(channel_from_claim_show['value'], channel['outputs'][0]['value'])
abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=channel['txid'], nout=0, blocking=False))
self.assertEqual(abandon['inputs'][0]['txid'], channel['txid'])
await self.confirm_tx(abandon['txid'])
not_a_claim = await self.out(
self.daemon.jsonrpc_claim_show(txid=abandon['txid'], nout=0)
)
self.assertEqual(not_a_claim, 'claim not found')
async def test_claim_search(self): async def test_claim_search(self):
channel = await self.create_channel('@abc', '1.0') # search for channel claim
channel_id = channel['outputs'][0]['claim_id'] channel = await self.channel_create('@abc', '1.0')
claim = await self.create_claim('on-channel-claim', '0.0001', channel_id=channel_id) channel_id, txid = channel['outputs'][0]['claim_id'], channel['txid']
unsigned_claim = await self.create_claim('unsigned', '0.0001') value = channel['outputs'][0]['value']
channel_from_claim_list = await self.out(self.daemon.jsonrpc_claim_search('@abc')) claims = await self.claim_search('@abc')
self.assertEqual(channel_from_claim_list['claims'][0]['value'], channel['outputs'][0]['value']) self.assertEqual(claims[0]['value'], value)
signed_claim_from_claim_list = await self.out(self.daemon.jsonrpc_claim_search('on-channel-claim'))
self.assertEqual(signed_claim_from_claim_list['claims'][0]['value'], claim['outputs'][0]['value'])
unsigned_claim_from_claim_list = await self.out(self.daemon.jsonrpc_claim_search('unsigned'))
self.assertEqual(unsigned_claim_from_claim_list['claims'][0]['value'], unsigned_claim['outputs'][0]['value'])
abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=channel['txid'], nout=0, blocking=False)) claims = await self.claim_search(txid=txid, nout=0)
self.assertTrue(abandon['outputs'][0]['txid'], channel['txid']) self.assertEqual(claims[0]['value'], value)
await self.on_transaction_dict(abandon)
await self.generate(1)
await self.on_transaction_dict(abandon)
empty = await self.out(self.daemon.jsonrpc_claim_search('@abc')) claims = await self.claim_search(claim_id=channel_id)
self.assertEqual(len(empty['claims']), 0) self.assertEqual(claims[0]['value'], value)
await self.claim_abandon(txid=txid, nout=0)
self.assertEqual(len(await self.claim_search(txid=txid, nout=0)), 0)
# search stream claims
channel = await self.channel_create('@abc', '1.0')
channel_id, txid = channel['outputs'][0]['claim_id'], channel['txid']
signed = await self.stream_create('on-channel-claim', '0.0001', channel_id=channel_id)
unsigned = await self.stream_create('unsigned', '0.0001')
claims = await self.claim_search('on-channel-claim')
self.assertEqual(claims[0]['value'], signed['outputs'][0]['value'])
claims = await self.claim_search('unsigned')
self.assertEqual(claims[0]['value'], unsigned['outputs'][0]['value'])
# list streams in a channel
await self.stream_create('on-channel-claim-2', '0.0001', channel_id=channel_id)
await self.stream_create('on-channel-claim-3', '0.0001', channel_id=channel_id)
claims = await self.claim_search(channel_id=channel_id)
self.assertEqual(len(claims), 3)
await self.claim_abandon(claim_id=claims[0]['claim_id'])
await self.claim_abandon(claim_id=claims[1]['claim_id'])
await self.claim_abandon(claim_id=claims[2]['claim_id'])
claims = await self.claim_search(channel_id=channel_id)
self.assertEqual(len(claims), 0)
tx = await self.daemon.jsonrpc_account_fund(None, None, '0.001', outputs=100, broadcast=True)
await self.confirm_tx(tx.id)
# 4 claims per block, 3 blocks. Sorted by height (descending) then claim_id (ascending).
claims = []
for j in range(3):
same_height_claims = []
for k in range(3):
claim_tx = await self.stream_create(f'c{j}-{k}', '0.000001', channel_id=channel_id, confirm=False)
same_height_claims.append(claim_tx['outputs'][0]['claim_id'])
await self.on_transaction_dict(claim_tx)
claim_tx = await self.stream_create(f'c{j}-4', '0.000001', channel_id=channel_id, confirm=True)
same_height_claims.append(claim_tx['outputs'][0]['claim_id'])
same_height_claims.sort(key=lambda x: int(x, 16))
claims = same_height_claims + claims
page = await self.claim_search(page_size=20, channel_id=channel_id)
page_claim_ids = [item['claim_id'] for item in page]
self.assertEqual(page_claim_ids, claims)
page = await self.claim_search(page_size=6, channel_id=channel_id)
page_claim_ids = [item['claim_id'] for item in page]
self.assertEqual(page_claim_ids, claims[:6])
out_of_bounds = await self.claim_search(page=2, page_size=20, channel_id=channel_id)
self.assertEqual(out_of_bounds, [])
async def test_abandoned_channel_with_signed_claims(self): async def test_abandoned_channel_with_signed_claims(self):
channel = (await self.create_channel('@abc', '1.0'))['outputs'][0] channel = (await self.channel_create('@abc', '1.0'))['outputs'][0]
orphan_claim = await self.create_claim('on-channel-claim', '0.0001', channel_id=channel['claim_id']) orphan_claim = await self.stream_create('on-channel-claim', '0.0001', channel_id=channel['claim_id'])
await self.out(self.daemon.jsonrpc_claim_abandon(txid=channel['txid'], nout=0)) await self.claim_abandon(txid=channel['txid'], nout=0)
channel = (await self.create_channel('@abc', '1.0'))['outputs'][0] channel = (await self.channel_create('@abc', '1.0'))['outputs'][0]
orphan_claim_id = orphan_claim['outputs'][0]['claim_id'] orphan_claim_id = orphan_claim['outputs'][0]['claim_id']
# Original channel doesnt exists anymore, so the signature is invalid. For invalid signatures, resolution is # Original channel doesnt exists anymore, so the signature is invalid. For invalid signatures, resolution is
@ -520,17 +474,17 @@ class ClaimCommands(CommandTestCase):
direct_uri = 'lbry://on-channel-claim#' + orphan_claim_id direct_uri = 'lbry://on-channel-claim#' + orphan_claim_id
response = await self.resolve(direct_uri) response = await self.resolve(direct_uri)
self.assertFalse(response[direct_uri]['claim']['signature_is_valid']) self.assertFalse(response[direct_uri]['claim']['signature_is_valid'])
await self.daemon.jsonrpc_claim_abandon(claim_id=orphan_claim_id) await self.claim_abandon(claim_id=orphan_claim_id)
uri = 'lbry://@abc/on-channel-claim' uri = 'lbry://@abc/on-channel-claim'
# now, claim something on this channel (it will update the invalid claim, but we save and forcefully restore) # now, claim something on this channel (it will update the invalid claim, but we save and forcefully restore)
valid_claim = await self.create_claim('on-channel-claim', '0.00000001', channel_id=channel['claim_id']) valid_claim = await self.stream_create('on-channel-claim', '0.00000001', channel_id=channel['claim_id'])
# resolves normally # resolves normally
response = await self.resolve(uri) response = await self.resolve(uri)
self.assertTrue(response[uri]['claim']['signature_is_valid']) self.assertTrue(response[uri]['claim']['signature_is_valid'])
# ooops! claimed a valid conflict! (this happens on the wild, mostly by accident or race condition) # ooops! claimed a valid conflict! (this happens on the wild, mostly by accident or race condition)
await self.create_claim( await self.stream_create(
'on-channel-claim', '0.00000001', channel_id=channel['claim_id'], allow_duplicate_name=True 'on-channel-claim', '0.00000001', channel_id=channel['claim_id'], allow_duplicate_name=True
) )
@ -538,45 +492,17 @@ class ClaimCommands(CommandTestCase):
response = await self.resolve(uri) response = await self.resolve(uri)
self.assertTrue(response[uri]['claim']['signature_is_valid']) self.assertTrue(response[uri]['claim']['signature_is_valid'])
self.assertEqual(response[uri]['claim']['txid'], valid_claim['txid']) self.assertEqual(response[uri]['claim']['txid'], valid_claim['txid'])
claims = (await self.daemon.jsonrpc_claim_search('on-channel-claim'))['claims'] claims = (await self.daemon.jsonrpc_claim_search('on-channel-claim'))['items']
self.assertEqual(2, len(claims)) self.assertEqual(2, len(claims))
signer_ids = set([claim['value'].signing_channel_id for claim in claims]) signer_ids = set([claim['value'].signing_channel_id for claim in claims])
self.assertEqual({channel['claim_id']}, signer_ids) self.assertEqual({channel['claim_id']}, signer_ids)
async def test_claim_list_by_channel(self):
self.maxDiff = None
tx = await self.daemon.jsonrpc_account_fund(None, None, '0.001', outputs=100, broadcast=True)
await self.confirm_tx(tx.id)
channel_id = (await self.create_channel('@abc', "0.0001"))['outputs'][0]['claim_id']
# 4 claims per block, 3 blocks. Sorted by height (descending) then claim_id (ascending).
claims = []
for j in range(3):
same_height_claims = []
for k in range(3):
claim_tx = await self.create_claim(f'c{j}-{k}', '0.000001', channel_id=channel_id, confirm=False)
same_height_claims.append(claim_tx['outputs'][0]['claim_id'])
await self.on_transaction_dict(claim_tx)
claim_tx = await self.create_claim(f'c{j}-4', '0.000001', channel_id=channel_id, confirm=True)
same_height_claims.append(claim_tx['outputs'][0]['claim_id'])
same_height_claims.sort(key=lambda x: int(x, 16))
claims = same_height_claims + claims
page = await self.out(self.daemon.jsonrpc_claim_list_by_channel(1, page_size=20, uri='@abc'))
page_claim_ids = [item['claim_id'] for item in page['@abc']['claims_in_channel']]
self.assertEqual(page_claim_ids, claims)
page = await self.out(self.daemon.jsonrpc_claim_list_by_channel(1, page_size=6, uri='@abc'))
page_claim_ids = [item['claim_id'] for item in page['@abc']['claims_in_channel']]
self.assertEqual(page_claim_ids, claims[:6])
out_of_bounds = await self.out(self.daemon.jsonrpc_claim_list_by_channel(2, page_size=20, uri='@abc'))
self.assertEqual(out_of_bounds['error'], 'claim 20 greater than max 12')
async def test_normalization_resolution(self): async def test_normalization_resolution(self):
# this test assumes that the lbrycrd forks normalization at height == 250 on regtest # this test assumes that the lbrycrd forks normalization at height == 250 on regtest
c1 = await self.create_claim('ΣίσυφοςfiÆ', '0.1') c1 = await self.stream_create('ΣίσυφοςfiÆ', '0.1')
c2 = await self.create_claim('ΣΊΣΥΦΟσFIæ', '0.2') c2 = await self.stream_create('ΣΊΣΥΦΟσFIæ', '0.2')
r1 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣίσυφοςfiÆ') r1 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣίσυφοςfiÆ')
r2 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣΊΣΥΦΟσFIæ') r2 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣΊΣΥΦΟσFIæ')
@ -666,3 +592,84 @@ def generate_signed_legacy(name: str, address: bytes, output: Output):
claim.publisherSignature.signature = signature claim.publisherSignature.signature = signature
claim.publisherSignature.certificateId = output.claim_hash claim.publisherSignature.certificateId = output.claim_hash
return claim return claim
class SupportCommands(CommandTestCase):
async def test_regular_supports_and_tip_supports(self):
# account2 will be used to send tips and supports to account1
account2_id = (await self.daemon.jsonrpc_account_create('second account'))['id']
account2 = self.daemon.get_account_or_error(account2_id)
# send account2 5 LBC out of the 10 LBC in account1
result = await self.out(self.daemon.jsonrpc_account_send(
'5.0', await self.daemon.jsonrpc_address_unused(account2_id)
))
await self.on_transaction_dict(result)
# account1 and account2 balances:
await self.assertBalance(self.account, '4.999876')
await self.assertBalance(account2, '5.0')
# create the claim we'll be tipping and supporting
tx = await self.stream_create()
claim_id = tx['outputs'][0]['claim_id']
# account1 and account2 balances:
await self.assertBalance(self.account, '3.979769')
await self.assertBalance(account2, '5.0')
# send a tip to the claim using account2
tip = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '1.0', True, account2_id)
)
await self.on_transaction_dict(tip)
await self.generate(1)
await self.on_transaction_dict(tip)
# 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())
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)
self.assertEqual(txs[0]['support_info'][0]['is_tip'], True)
self.assertEqual(txs[0]['value'], '1.0')
self.assertEqual(txs[0]['fee'], '0.0')
# verify that the outgoing tip is marked correctly as is_tip=True in account2
txs2 = await self.out(
self.daemon.jsonrpc_transaction_list(account2_id)
)
self.assertEqual(len(txs2[0]['support_info']), 1)
self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-1.0')
self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id)
self.assertEqual(txs2[0]['support_info'][0]['is_tip'], True)
self.assertEqual(txs2[0]['value'], '-1.0')
self.assertEqual(txs2[0]['fee'], '-0.0001415')
# send a support to the claim using account2
support = await self.out(
self.daemon.jsonrpc_support_create(claim_id, '2.0', False, account2_id)
)
await self.on_transaction_dict(support)
await self.generate(1)
await self.on_transaction_dict(support)
# account2 balance went down ~2
await self.assertBalance(self.account, '3.979769')
await self.assertBalance(account2, '1.999717')
# verify that the outgoing support is marked correctly as is_tip=False in account2
txs2 = await self.out(self.daemon.jsonrpc_transaction_list(account2_id))
self.assertEqual(len(txs2[0]['support_info']), 1)
self.assertEqual(txs2[0]['support_info'][0]['balance_delta'], '-2.0')
self.assertEqual(txs2[0]['support_info'][0]['claim_id'], claim_id)
self.assertEqual(txs2[0]['support_info'][0]['is_tip'], False)
self.assertEqual(txs2[0]['value'], '0.0')
self.assertEqual(txs2[0]['fee'], '-0.0001415')

View file

@ -11,8 +11,8 @@ class FileCommands(CommandTestCase):
VERBOSITY = logging.WARN VERBOSITY = logging.WARN
async def test_file_management(self): async def test_file_management(self):
await self.create_claim('foo', '0.01') await self.stream_create('foo', '0.01')
await self.create_claim('foo2', '0.01') await self.stream_create('foo2', '0.01')
file1, file2 = self.daemon.jsonrpc_file_list('claim_name') file1, file2 = self.daemon.jsonrpc_file_list('claim_name')
self.assertEqual(file1['claim_name'], 'foo') self.assertEqual(file1['claim_name'], 'foo')
@ -27,7 +27,7 @@ class FileCommands(CommandTestCase):
self.assertEqual(len(self.daemon.jsonrpc_file_list()), 1) self.assertEqual(len(self.daemon.jsonrpc_file_list()), 1)
async def test_download_different_timeouts(self): async def test_download_different_timeouts(self):
tx = await self.create_claim('foo', '0.01') tx = await self.stream_create('foo', '0.01')
sd_hash = tx['outputs'][0]['value']['stream']['hash'] sd_hash = tx['outputs'][0]['value']['stream']['hash']
await self.daemon.jsonrpc_file_delete(claim_name='foo') await self.daemon.jsonrpc_file_delete(claim_name='foo')
all_except_sd = [ all_except_sd = [
@ -48,7 +48,7 @@ class FileCommands(CommandTestCase):
await asyncio.sleep(0.01) await asyncio.sleep(0.01)
async def test_filename_conflicts_management_on_resume_download(self): async def test_filename_conflicts_management_on_resume_download(self):
await self.create_claim('foo', '0.01', data=bytes([0]*(1<<23))) await self.stream_create('foo', '0.01', data=bytes([0] * (1 << 23)))
file_info = self.daemon.jsonrpc_file_list()[0] file_info = self.daemon.jsonrpc_file_list()[0]
original_path = os.path.join(self.daemon.conf.download_dir, file_info['file_name']) original_path = os.path.join(self.daemon.conf.download_dir, file_info['file_name'])
await self.daemon.jsonrpc_file_delete(claim_name='foo') await self.daemon.jsonrpc_file_delete(claim_name='foo')
@ -69,7 +69,7 @@ class FileCommands(CommandTestCase):
# this used to be inconsistent, if it becomes again it would create weird bugs, so worth checking # this used to be inconsistent, if it becomes again it would create weird bugs, so worth checking
async def test_incomplete_downloads_erases_output_file_on_stop(self): async def test_incomplete_downloads_erases_output_file_on_stop(self):
tx = await self.create_claim('foo', '0.01') tx = await self.stream_create('foo', '0.01')
sd_hash = tx['outputs'][0]['value']['stream']['hash'] sd_hash = tx['outputs'][0]['value']['stream']['hash']
file_info = self.daemon.jsonrpc_file_list()[0] file_info = self.daemon.jsonrpc_file_list()[0]
await self.daemon.jsonrpc_file_delete(claim_name='foo') await self.daemon.jsonrpc_file_delete(claim_name='foo')
@ -88,7 +88,7 @@ class FileCommands(CommandTestCase):
self.assertFalse(os.path.isfile(os.path.join(self.daemon.conf.download_dir, file_info['file_name']))) self.assertFalse(os.path.isfile(os.path.join(self.daemon.conf.download_dir, file_info['file_name'])))
async def test_incomplete_downloads_retry(self): async def test_incomplete_downloads_retry(self):
tx = await self.create_claim('foo', '0.01') tx = await self.stream_create('foo', '0.01')
sd_hash = tx['outputs'][0]['value']['stream']['hash'] sd_hash = tx['outputs'][0]['value']['stream']['hash']
await self.daemon.jsonrpc_file_delete(claim_name='foo') await self.daemon.jsonrpc_file_delete(claim_name='foo')
blobs = await self.server_storage.get_blobs_for_stream( blobs = await self.server_storage.get_blobs_for_stream(
@ -128,7 +128,7 @@ class FileCommands(CommandTestCase):
async def test_unban_recovers_stream(self): async def test_unban_recovers_stream(self):
BlobDownloader.BAN_TIME = .5 # fixme: temporary field, will move to connection manager or a conf BlobDownloader.BAN_TIME = .5 # fixme: temporary field, will move to connection manager or a conf
tx = await self.create_claim('foo', '0.01', data=bytes([0]*(1<<23))) tx = await self.stream_create('foo', '0.01', data=bytes([0] * (1 << 23)))
sd_hash = tx['outputs'][0]['value']['stream']['hash'] sd_hash = tx['outputs'][0]['value']['stream']['hash']
missing_blob_hash = (await self.daemon.jsonrpc_blob_list(sd_hash=sd_hash))[-2] missing_blob_hash = (await self.daemon.jsonrpc_blob_list(sd_hash=sd_hash))[-2]
await self.daemon.jsonrpc_file_delete(claim_name='foo') await self.daemon.jsonrpc_file_delete(claim_name='foo')
@ -150,7 +150,7 @@ class FileCommands(CommandTestCase):
target_address = await self.blockchain.get_raw_change_address() target_address = await self.blockchain.get_raw_change_address()
# FAIL: beyond available balance # FAIL: beyond available balance
await self.create_claim( await self.stream_create(
'expensive', '0.01', data=b'pay me if you can', 'expensive', '0.01', data=b'pay me if you can',
fee_currency='LBC', fee_amount='11.0', fee_address=target_address fee_currency='LBC', fee_amount='11.0', fee_address=target_address
) )
@ -160,7 +160,7 @@ class FileCommands(CommandTestCase):
self.assertEqual(len(self.daemon.jsonrpc_file_list()), 0) self.assertEqual(len(self.daemon.jsonrpc_file_list()), 0)
# FAIL: beyond maximum key fee # FAIL: beyond maximum key fee
await self.create_claim( await self.stream_create(
'maxkey', '0.01', data=b'no pay me, no', 'maxkey', '0.01', data=b'no pay me, no',
fee_currency='LBC', fee_amount='111.0', fee_address=target_address fee_currency='LBC', fee_amount='111.0', fee_address=target_address
) )
@ -170,7 +170,7 @@ class FileCommands(CommandTestCase):
self.assertEqual(response['error'], 'fee of 111.00000 exceeds max configured to allow of 50.00000') self.assertEqual(response['error'], 'fee of 111.00000 exceeds max configured to allow of 50.00000')
# PASS: purchase is successful # PASS: purchase is successful
await self.create_claim( await self.stream_create(
'icanpay', '0.01', data=b'I got the power!', 'icanpay', '0.01', data=b'I got the power!',
fee_currency='LBC', fee_amount='1.0', fee_address=target_address fee_currency='LBC', fee_amount='1.0', fee_address=target_address
) )

View file

@ -4,7 +4,7 @@ from integration.testcase import CommandTestCase
class ResolveCommand(CommandTestCase): class ResolveCommand(CommandTestCase):
async def test_resolve(self): async def test_resolve(self):
tx = await self.create_channel('@abc', '0.01') tx = await self.channel_create('@abc', '0.01')
channel_id = tx['outputs'][0]['claim_id'] channel_id = tx['outputs'][0]['claim_id']
# resolving a channel @abc # resolving a channel @abc
@ -15,8 +15,8 @@ class ResolveCommand(CommandTestCase):
self.assertEqual(response['lbry://@abc']['certificate']['name'], '@abc') self.assertEqual(response['lbry://@abc']['certificate']['name'], '@abc')
self.assertEqual(response['lbry://@abc']['claims_in_channel'], 0) self.assertEqual(response['lbry://@abc']['claims_in_channel'], 0)
await self.create_claim('foo', '0.01', channel_id=channel_id) await self.stream_create('foo', '0.01', channel_id=channel_id)
await self.create_claim('foo2', '0.01', channel_id=channel_id) await self.stream_create('foo2', '0.01', channel_id=channel_id)
# resolving a channel @abc with some claims in it # resolving a channel @abc with some claims in it
response = await self.resolve('lbry://@abc') response = await self.resolve('lbry://@abc')

View file

@ -155,12 +155,12 @@ class CommandTestCase(IntegrationTestCase):
to JSON and then back to a dictionary. """ to JSON and then back to a dictionary. """
return json.loads(jsonrpc_dumps_pretty(await awaitable, ledger=self.ledger))['result'] return json.loads(jsonrpc_dumps_pretty(await awaitable, ledger=self.ledger))['result']
async def create_claim(self, name='hovercraft', bid='1.0', data=b'hi!', confirm=True, **kwargs): async def stream_create(self, name='hovercraft', bid='1.0', data=b'hi!', confirm=True, **kwargs):
with tempfile.NamedTemporaryFile() as file: with tempfile.NamedTemporaryFile() as file:
file.write(data) file.write(data)
file.flush() file.flush()
claim = await self.out( claim = await self.out(
self.daemon.jsonrpc_publish(name, bid, file_path=file.name, **kwargs) self.daemon.jsonrpc_stream_create(name, bid, file_path=file.name, **kwargs)
) )
self.assertEqual(claim['outputs'][0]['name'], name) self.assertEqual(claim['outputs'][0]['name'], name)
if confirm: if confirm:
@ -169,16 +169,16 @@ class CommandTestCase(IntegrationTestCase):
await self.on_transaction_dict(claim) await self.on_transaction_dict(claim)
return claim return claim
async def update_claim(self, claim_id, data=None, confirm=True, **kwargs): async def stream_update(self, claim_id, data=None, confirm=True, **kwargs):
if data: if data:
with tempfile.NamedTemporaryFile() as file: with tempfile.NamedTemporaryFile() as file:
file.write(data) file.write(data)
file.flush() file.flush()
claim = await self.out( claim = await self.out(
self.daemon.jsonrpc_claim_update(claim_id, file_path=file.name, **kwargs) self.daemon.jsonrpc_stream_update(claim_id, file_path=file.name, **kwargs)
) )
else: else:
claim = await self.out(self.daemon.jsonrpc_claim_update(claim_id, **kwargs)) claim = await self.out(self.daemon.jsonrpc_stream_update(claim_id, **kwargs))
self.assertIsNotNone(claim['outputs'][0]['name']) self.assertIsNotNone(claim['outputs'][0]['name'])
if confirm: if confirm:
await self.on_transaction_dict(claim) await self.on_transaction_dict(claim)
@ -186,7 +186,7 @@ class CommandTestCase(IntegrationTestCase):
await self.on_transaction_dict(claim) await self.on_transaction_dict(claim)
return claim return claim
async def create_channel(self, name='@arena', bid='1.0', confirm=True, **kwargs): async def channel_create(self, name='@arena', bid='1.0', confirm=True, **kwargs):
channel = await self.out(self.daemon.jsonrpc_channel_create(name, bid, **kwargs)) channel = await self.out(self.daemon.jsonrpc_channel_create(name, bid, **kwargs))
self.assertEqual(channel['outputs'][0]['name'], name) self.assertEqual(channel['outputs'][0]['name'], name)
if confirm: if confirm:
@ -195,7 +195,7 @@ class CommandTestCase(IntegrationTestCase):
await self.on_transaction_dict(channel) await self.on_transaction_dict(channel)
return channel return channel
async def update_channel(self, claim_id, confirm=True, **kwargs): async def channel_update(self, claim_id, confirm=True, **kwargs):
channel = await self.out(self.daemon.jsonrpc_channel_update(claim_id, **kwargs)) channel = await self.out(self.daemon.jsonrpc_channel_update(claim_id, **kwargs))
self.assertTrue(channel['outputs'][0]['name'].startswith('@')) self.assertTrue(channel['outputs'][0]['name'].startswith('@'))
if confirm: if confirm:
@ -204,5 +204,18 @@ class CommandTestCase(IntegrationTestCase):
await self.on_transaction_dict(channel) await self.on_transaction_dict(channel)
return channel return channel
async def claim_abandon(self, *args, confirm=True, **kwargs):
if 'blocking' not in kwargs:
kwargs['blocking'] = False
tx = await self.out(self.daemon.jsonrpc_claim_abandon(*args, **kwargs))
if confirm:
await self.on_transaction_dict(tx)
await self.generate(1)
await self.on_transaction_dict(tx)
return tx
async def resolve(self, uri): async def resolve(self, uri):
return await self.out(self.daemon.jsonrpc_resolve(uri)) return await self.out(self.daemon.jsonrpc_resolve(uri))
async def claim_search(self, *args, **kwargs):
return (await self.out(self.daemon.jsonrpc_claim_search(*args, **kwargs)))['items']

View file

@ -61,7 +61,7 @@ class CLITest(AsyncioTestCase):
# publish is ungrouped command, returns usage only implicitly # publish is ungrouped command, returns usage only implicitly
self.assertIn('publish (<name> | --name=<name>)', self.shell(['publish'])) self.assertIn('publish (<name> | --name=<name>)', self.shell(['publish']))
# publish is ungrouped command, with explicit --help # publish is ungrouped command, with explicit --help
self.assertIn('Make a new name claim and publish', self.shell(['publish', '--help'])) self.assertIn('Create or update a stream claim at a given name', self.shell(['publish', '--help']))
# account is a group, returns help implicitly # account is a group, returns help implicitly
self.assertIn('Return the balance of an account', self.shell(['account'])) self.assertIn('Return the balance of an account', self.shell(['account']))
# account is a group, with explicit --help # account is a group, with explicit --help