forked from LBRYCommunity/lbry-sdk
publish command supports custom account list to lookup channels
This commit is contained in:
parent
8754c6fabe
commit
2ddf1a08f6
5 changed files with 117 additions and 28 deletions
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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]
|
||||||
|
))
|
||||||
|
|
1
tox.ini
1
tox.ini
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue