publish command supports custom account list to lookup channels

This commit is contained in:
Lex Berezhny 2018-10-17 19:07:17 -04:00
parent 8754c6fabe
commit 2ddf1a08f6
5 changed files with 117 additions and 28 deletions

View file

@ -6,7 +6,7 @@ import urllib
import json
import textwrap
from typing import Callable, Optional
from typing import Callable, Optional, List
from operator import itemgetter
from binascii import hexlify, unhexlify
from copy import deepcopy
@ -400,10 +400,10 @@ class Daemon(AuthJSONRPCServer):
del self.streams[sd_hash]
defer.returnValue(result)
async def _publish_stream(self, name, bid, claim_dict, file_path=None, certificate=None,
async def _publish_stream(self, account, name, bid, claim_dict, file_path=None, certificate=None,
claim_address=None, change_address=None):
publisher = Publisher(
self.blob_manager, self.payment_rate_manager, self.storage,
account, self.blob_manager, self.payment_rate_manager, self.storage,
self.file_manager, self.wallet_manager, certificate
)
parse_lbry_uri(name)
@ -1975,17 +1975,19 @@ class Daemon(AuthJSONRPCServer):
return self.get_est_cost(uri, size)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_channel_new(self, channel_name, amount):
async def jsonrpc_channel_new(self, channel_name, amount, account_id=None):
"""
Generate a publisher key and create a new '@' prefixed certificate claim
Usage:
channel_new (<channel_name> | --channel_name=<channel_name>)
(<amount> | --amount=<amount>)
[--account_id=<account_id>]
Options:
--channel_name=<channel_name> : (str) name of the channel prefixed with '@'
--amount=<amount> : (decimal) bid amount on the channel
--account_id=<account_id> : (str) id of the account to store channel
Returns:
(dict) Dictionary containing result of the claim
@ -2007,10 +2009,12 @@ class Daemon(AuthJSONRPCServer):
raise Exception("Invalid channel name")
amount = self.get_dewies_or_error("amount", amount)
if amount <= 0:
raise Exception("Invalid amount")
tx = await self.wallet_manager.claim_new_channel(channel_name, amount)
tx = await self.wallet_manager.claim_new_channel(
channel_name, amount, self.get_account_or_default(account_id)
)
self.default_wallet.save()
self.analytics_manager.send_new_channel()
nout = 0
@ -2030,7 +2034,7 @@ class Daemon(AuthJSONRPCServer):
Get certificate claim infos for channels that can be published to
Usage:
channel_list [<account_id> | --account_id=<account_id> ]
channel_list [<account_id> | --account_id=<account_id>]
[--page=<page>] [--page_size=<page_size>]
Options:
@ -2089,11 +2093,12 @@ class Daemon(AuthJSONRPCServer):
@requires(WALLET_COMPONENT, FILE_MANAGER_COMPONENT, BLOB_COMPONENT, PAYMENT_RATE_COMPONENT, DATABASE_COMPONENT,
conditions=[WALLET_IS_UNLOCKED])
async def jsonrpc_publish(self, name, bid, metadata=None, file_path=None, fee=None, title=None,
description=None, author=None, language=None, license=None,
license_url=None, thumbnail=None, preview=None, nsfw=None, sources=None,
channel_name=None, channel_id=None,
claim_address=None, change_address=None):
async def jsonrpc_publish(
self, name, bid, metadata=None, file_path=None, fee=None, title=None,
description=None, author=None, language=None, license=None,
license_url=None, thumbnail=None, preview=None, nsfw=None, sources=None,
channel_name=None, channel_id=None, channel_account_id=None, account_id=None,
claim_address=None, change_address=None):
"""
Make a new name claim and publish associated data to lbrynet,
update over existing claim if user already has a claim for name.
@ -2117,6 +2122,7 @@ class Daemon(AuthJSONRPCServer):
[--license=<license>] [--license_url=<license_url>] [--thumbnail=<thumbnail>]
[--preview=<preview>] [--nsfw=<nsfw>] [--sources=<sources>]
[--channel_name=<channel_name>] [--channel_id=<channel_id>]
[--channel_account_id=<channel_account_id>...] [--account_id=<account_id>]
[--claim_address=<claim_address>] [--change_address=<change_address>]
Options:
@ -2156,8 +2162,11 @@ class Daemon(AuthJSONRPCServer):
for channel claim being in the wallet. This allows
publishing to a channel where only the certificate
private key is in the wallet.
--channel_account_id=<channel_id>: (str) one or more account ids for accounts to look in
for channel certificates, defaults to all accounts.
--account_id=<account_id> : (str) account to use for funding the transaction
--claim_address=<claim_address> : (str) address where the claim is sent to, if not specified
new address wil automatically be created
new address will automatically be created
Returns:
(dict) Dictionary containing result of the claim
@ -2184,7 +2193,9 @@ class Daemon(AuthJSONRPCServer):
# raises an error if the address is invalid
decode_address(address)
available = await self.default_account.get_balance()
account = self.get_account_or_default(account_id)
available = await account.get_balance()
if amount >= available:
# TODO: add check for existing claim balance
#balance = yield self.wallet.get_max_usable_balance_for_claim(name)
@ -2236,7 +2247,7 @@ class Daemon(AuthJSONRPCServer):
log.warning("Stripping empty fee from published metadata")
del metadata['fee']
elif 'address' not in metadata['fee']:
address = await self.default_account.receiving.get_or_create_usable_address()
address = await account.receiving.get_or_create_usable_address()
metadata['fee']['address'] = address
if 'fee' in metadata and 'version' not in metadata['fee']:
metadata['fee']['version'] = '_0_0_1'
@ -2279,7 +2290,9 @@ class Daemon(AuthJSONRPCServer):
certificate = None
if channel_id or channel_name:
certificate = await self.get_channel_or_error(channel_id, channel_name)
certificate = await self.get_channel_or_error(
self.get_accounts_or_all(channel_account_id), channel_id, channel_name
)
log.info("Publish: %s", {
'name': name,
@ -2293,8 +2306,8 @@ class Daemon(AuthJSONRPCServer):
})
return await self._publish_stream(
name, amount, claim_dict, file_path, certificate,
claim_address, change_address
account, name, amount, claim_dict, file_path,
certificate, claim_address, change_address
)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@ -3248,16 +3261,17 @@ class Daemon(AuthJSONRPCServer):
response['head_blob_availability'].get('is_available')
defer.returnValue(response)
async def get_channel_or_error(self, channel_id: str = None, channel_name: str = None):
async def get_channel_or_error(
self, accounts: List[LBCAccount], channel_id: str = None, channel_name: str = None):
if channel_id is not None:
certificates = await self.wallet_manager.get_certificates(
private_key_accounts=[self.default_account], claim_id=channel_id)
private_key_accounts=accounts, claim_id=channel_id)
if not certificates:
raise ValueError("Couldn't find channel with claim_id '{}'." .format(channel_id))
return certificates[0]
if channel_name is not None:
certificates = await self.wallet_manager.get_certificates(
private_key_accounts=[self.default_account], claim_name=channel_name)
private_key_accounts=accounts, claim_name=channel_name)
if not certificates:
raise ValueError("Couldn't find channel with name '{}'.".format(channel_name))
return certificates[0]
@ -3268,6 +3282,12 @@ class Daemon(AuthJSONRPCServer):
return self.default_account
return self.get_account_or_error(account_id, argument_name, lbc_only)
def get_accounts_or_all(self, account_ids: List[str]):
return [
self.get_account_or_error(account_id)
for account_id in account_ids
] if account_ids else self.default_wallet.accounts
def get_account_or_error(self, account_id: str, argument_name: str = "account", lbc_only=True):
for account in self.default_wallet.accounts:
if account.id == account_id:

View file

@ -13,7 +13,9 @@ def d2f(d):
class Publisher:
def __init__(self, blob_manager, payment_rate_manager, storage, lbry_file_manager, wallet, certificate):
def __init__(self, account, blob_manager, payment_rate_manager, storage,
lbry_file_manager, wallet, certificate):
self.account = account
self.blob_manager = blob_manager
self.payment_rate_manager = payment_rate_manager
self.storage = storage
@ -44,7 +46,7 @@ class Publisher:
claim_dict['stream']['source']['contentType'] = get_content_type(file_path)
claim_dict['stream']['source']['version'] = "_0_0_1" # need current version here
tx = await self.wallet.claim_name(
name, bid, claim_dict, self.certificate, holding_address
self.account, name, bid, claim_dict, self.certificate, holding_address
)
# check if we have a file already for this claim (if this is a publish update with a new stream)
@ -65,7 +67,7 @@ class Publisher:
async def publish_stream(self, name, bid, claim_dict, stream_hash, holding_address=None):
"""Make a claim without creating a lbry file"""
tx = await self.wallet.claim_name(
name, bid, claim_dict, self.certificate, holding_address
self.account, name, bid, claim_dict, self.certificate, holding_address
)
if stream_hash: # the stream_hash returned from the db will be None if this isn't a stream we have
await d2f(self.storage.save_content_claim(

View file

@ -341,8 +341,7 @@ class LbryWalletManager(BaseWalletManager):
def get_utxos(account: BaseAccount):
return account.get_utxos()
async def claim_name(self, name, amount, claim_dict, certificate=None, claim_address=None):
account = self.default_account
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None):
claim = ClaimDict.load_dict(claim_dict)
if not claim_address:
claim_address = await account.receiving.get_or_create_usable_address()
@ -405,8 +404,7 @@ class LbryWalletManager(BaseWalletManager):
# TODO: release reserved tx outputs in case anything fails by this point
return tx
async def claim_new_channel(self, channel_name, amount):
account = self.default_account
async def claim_new_channel(self, channel_name, amount, account):
address = await account.receiving.get_or_create_usable_address()
cert, key = generate_certificate()
tx = await Transaction.claim(channel_name, cert, amount, address, [account], account)

View file

@ -462,3 +462,71 @@ class AccountManagement(CommandTestCase):
# list specific account
response = await self.daemon.jsonrpc_account_list(account_id, include_claims=True)
self.assertEqual(response['name'], 'recreated account')
class PublishCommand(CommandTestCase):
VERBOSE = False
async def test_publishing_checks_all_accounts_for_certificate(self):
account1_id, account1 = self.account.id, self.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'])
spam_channel = await self.out(self.daemon.jsonrpc_channel_new('@spam', '1.0'))
self.assertTrue(spam_channel['success'])
await self.confirm_tx(spam_channel['tx']['txid'])
self.assertEqual('8.989893', await self.daemon.jsonrpc_account_balance())
result = await self.out(self.daemon.jsonrpc_wallet_send(
'5.0', await self.daemon.jsonrpc_address_unused(account2_id)
))
await self.confirm_tx(result['txid'])
self.assertEqual('3.989769', await self.daemon.jsonrpc_account_balance())
self.assertEqual('5.0', await self.daemon.jsonrpc_account_balance(account2_id))
baz_channel = await self.out(self.daemon.jsonrpc_channel_new('@baz', '1.0', account2_id))
self.assertTrue(baz_channel['success'])
await self.confirm_tx(baz_channel['tx']['txid'])
channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id))
self.assertEqual(len(channels), 1)
self.assertEqual(channels[0]['name'], '@spam')
self.assertEqual(channels, await self.out(self.daemon.jsonrpc_channel_list()))
channels = await self.out(self.daemon.jsonrpc_channel_list(account2_id))
self.assertEqual(len(channels), 1)
self.assertEqual(channels[0]['name'], '@baz')
# defaults to using all accounts to lookup channel
with tempfile.NamedTemporaryFile() as file:
file.write(b'hi!')
file.flush()
claim1 = await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '1.0', file_path=file.name, channel_name='@baz'
))
self.assertTrue(claim1['success'])
await self.confirm_tx(claim1['tx']['txid'])
# uses only the specific accounts which contains the channel
with tempfile.NamedTemporaryFile() as file:
file.write(b'hi!')
file.flush()
claim1 = await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '1.0', file_path=file.name,
channel_name='@baz', channel_account_id=[account2_id]
))
self.assertTrue(claim1['success'])
await self.confirm_tx(claim1['tx']['txid'])
# fails when specifying account which does not contain channel
with tempfile.NamedTemporaryFile() as file:
file.write(b'hi!')
file.flush()
with self.assertRaisesRegex(ValueError, "Couldn't find channel with name '@baz'."):
await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '1.0', file_path=file.name,
channel_name='@baz', channel_account_id=[account1_id]
))

View file

@ -19,4 +19,5 @@ commands =
coverage run -p --source={envsitepackagesdir}/lbrynet -m unittest integration.wallet.test_transactions.BasicTransactionTest
coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.cli
coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_commands.AccountManagement
coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_commands.PublishCommand
coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_commands.EpicAdventuresOfChris45