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

View file

@ -13,7 +13,9 @@ def d2f(d):
class Publisher: 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.blob_manager = blob_manager
self.payment_rate_manager = payment_rate_manager self.payment_rate_manager = payment_rate_manager
self.storage = storage self.storage = storage
@ -44,7 +46,7 @@ class Publisher:
claim_dict['stream']['source']['contentType'] = get_content_type(file_path) claim_dict['stream']['source']['contentType'] = get_content_type(file_path)
claim_dict['stream']['source']['version'] = "_0_0_1" # need current version here claim_dict['stream']['source']['version'] = "_0_0_1" # need current version here
tx = await self.wallet.claim_name( 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) # 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): async def publish_stream(self, name, bid, claim_dict, stream_hash, holding_address=None):
"""Make a claim without creating a lbry file""" """Make a claim without creating a lbry file"""
tx = await self.wallet.claim_name( 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 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( await d2f(self.storage.save_content_claim(

View file

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

View file

@ -462,3 +462,71 @@ class AccountManagement(CommandTestCase):
# list specific account # list specific account
response = await self.daemon.jsonrpc_account_list(account_id, include_claims=True) response = await self.daemon.jsonrpc_account_list(account_id, include_claims=True)
self.assertEqual(response['name'], 'recreated account') 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 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.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.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 coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_commands.EpicAdventuresOfChris45