This commit is contained in:
Lex Berezhny 2019-03-24 16:55:04 -04:00
parent 8fc4f4aa73
commit d47575e8e0
19 changed files with 1360 additions and 1177 deletions

View file

@ -21,10 +21,6 @@ CURRENCIES = {
'LBC': {'type': 'crypto'}, 'LBC': {'type': 'crypto'},
'USD': {'type': 'fiat'}, 'USD': {'type': 'fiat'},
} }
SLACK_WEBHOOK = (
'nUE0pUZ6Yl9bo29epl5moTSwnl5wo20ip2IlqzywMKZiIQSFZR5'
'AHx4mY0VmF0WQZ1ESEP9kMHZlp1WzJwWOoKN3ImR1M2yUAaMyqGZ='
)
HEADERS_FILE_SHA256_CHECKSUM = ( HEADERS_FILE_SHA256_CHECKSUM = (
366295, 'b0c8197153a33ccbc52fb81a279588b6015b68b7726f73f6a2b81f7e25bfe4b9' 366295, 'b0c8197153a33ccbc52fb81a279588b6015b68b7726f73f6a2b81f7e25bfe4b9'
) )

View file

@ -265,10 +265,7 @@ class WalletComponent(Component):
async def start(self): async def start(self):
log.info("Starting torba wallet") log.info("Starting torba wallet")
storage = self.component_manager.get_component(DATABASE_COMPONENT) self.wallet_manager = await LbryWalletManager.from_lbrynet_config(self.conf)
lbrynet.schema.BLOCKCHAIN_NAME = self.conf.blockchain_name
self.wallet_manager = await LbryWalletManager.from_lbrynet_config(self.conf, storage)
self.wallet_manager.old_db = storage
await self.wallet_manager.start() await self.wallet_manager.start()
async def stop(self): async def stop(self):

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,13 @@
import os.path
from typing import List, Tuple from typing import List, Tuple
from decimal import Decimal from decimal import Decimal
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from google.protobuf.json_format import MessageToDict from google.protobuf.json_format import MessageToDict
from google.protobuf.message import DecodeError from google.protobuf.message import DecodeError
from hachoir.parser import createParser as binary_file_parser
from hachoir.metadata import extractMetadata as binary_file_metadata
from hachoir.core.log import log as hachoir_log
from torba.client.hash import Base58 from torba.client.hash import Base58
from torba.client.constants import COIN from torba.client.constants import COIN
@ -11,6 +15,10 @@ from torba.client.constants import COIN
from lbrynet.schema.types.v2.claim_pb2 import Claim as ClaimMessage, Fee as FeeMessage from lbrynet.schema.types.v2.claim_pb2 import Claim as ClaimMessage, Fee as FeeMessage
from lbrynet.schema import compat from lbrynet.schema import compat
from lbrynet.schema.base import Signable from lbrynet.schema.base import Signable
from lbrynet.schema.mime_types import guess_media_type
hachoir_log.use_print = False
class Claim(Signable): class Claim(Signable):
@ -54,6 +62,9 @@ class Claim(Signable):
def channel(self) -> 'Channel': def channel(self) -> 'Channel':
return Channel(self) return Channel(self)
def to_dict(self):
return MessageToDict(self.message, preserving_proto_field_name=True)
@classmethod @classmethod
def from_bytes(cls, data: bytes) -> 'Claim': def from_bytes(cls, data: bytes) -> 'Claim':
try: try:
@ -208,225 +219,243 @@ class Fee:
self._fee.currency = FeeMessage.USD self._fee.currency = FeeMessage.USD
class Channel: class BaseClaimSubType:
__slots__ = '_claim', '_channel' __slots__ = 'claim', 'message'
def __init__(self, claim: Claim = None): def __init__(self, claim: Claim):
self._claim = claim or Claim() self.claim = claim or Claim()
self._channel = self._claim.channel_message
def to_dict(self):
return MessageToDict(self._channel)
@property @property
def claim(self) -> Claim: def title(self) -> str:
return self._claim return self.message.title
@title.setter
def title(self, title: str):
self.message.title = title
@property
def description(self) -> str:
return self.message.description
@description.setter
def description(self, description: str):
self.message.description = description
@property
def language(self) -> str:
return self.message.language
@language.setter
def language(self, language: str):
self.message.language = language
@property
def thumbnail_url(self) -> str:
return self.message.thumbnail_url
@thumbnail_url.setter
def thumbnail_url(self, thumbnail_url: str):
self.message.thumbnail_url = thumbnail_url
@property @property
def tags(self) -> List: def tags(self) -> List:
return self._channel.tags return self.message.tags
def to_dict(self):
return MessageToDict(self.message, preserving_proto_field_name=True)
def update(self, tags=None, clear_tags=False, **kwargs):
if clear_tags:
self.message.ClearField('tags')
if tags is not None:
if isinstance(tags, str):
self.tags.append(tags)
elif isinstance(tags, list):
self.tags.extend(tags)
else:
raise ValueError(f"Unknown tag type: {tags}")
for key, value in kwargs.items():
setattr(self, key, value)
class Channel(BaseClaimSubType):
__slots__ = ()
def __init__(self, claim: Claim = None):
super().__init__(claim)
self.message = self.claim.channel_message
@property @property
def public_key(self) -> str: def public_key(self) -> str:
return hexlify(self._channel.public_key).decode() return hexlify(self.message.public_key).decode()
@public_key.setter @public_key.setter
def public_key(self, sd_public_key: str): def public_key(self, sd_public_key: str):
self._channel.public_key = unhexlify(sd_public_key.encode()) self.message.public_key = unhexlify(sd_public_key.encode())
@property @property
def public_key_bytes(self) -> bytes: def public_key_bytes(self) -> bytes:
return self._channel.public_key return self.message.public_key
@public_key_bytes.setter @public_key_bytes.setter
def public_key_bytes(self, public_key: bytes): def public_key_bytes(self, public_key: bytes):
self._channel.public_key = public_key self.message.public_key = public_key
@property
def language(self) -> str:
return self._channel.language
@language.setter
def language(self, language: str):
self._channel.language = language
@property
def title(self) -> str:
return self._channel.title
@title.setter
def title(self, title: str):
self._channel.title = title
@property
def description(self) -> str:
return self._channel.description
@description.setter
def description(self, description: str):
self._channel.description = description
@property @property
def contact_email(self) -> str: def contact_email(self) -> str:
return self._channel.contact_email return self.message.contact_email
@contact_email.setter @contact_email.setter
def contact_email(self, contact_email: str): def contact_email(self, contact_email: str):
self._channel.contact_email = contact_email self.message.contact_email = contact_email
@property @property
def homepage_url(self) -> str: def homepage_url(self) -> str:
return self._channel.homepage_url return self.message.homepage_url
@homepage_url.setter @homepage_url.setter
def homepage_url(self, homepage_url: str): def homepage_url(self, homepage_url: str):
self._channel.homepage_url = homepage_url self.message.homepage_url = homepage_url
@property
def thumbnail_url(self) -> str:
return self._channel.thumbnail_url
@thumbnail_url.setter
def thumbnail_url(self, thumbnail_url: str):
self._channel.thumbnail_url = thumbnail_url
@property @property
def cover_url(self) -> str: def cover_url(self) -> str:
return self._channel.cover_url return self.message.cover_url
@cover_url.setter @cover_url.setter
def cover_url(self, cover_url: str): def cover_url(self, cover_url: str):
self._channel.cover_url = cover_url self.message.cover_url = cover_url
class Stream: class Stream(BaseClaimSubType):
__slots__ = '_claim', '_stream' __slots__ = ()
def __init__(self, claim: Claim = None): def __init__(self, claim: Claim = None):
self._claim = claim or Claim() super().__init__(claim)
self._stream = self._claim.stream_message self.message = self.claim.stream_message
def to_dict(self): def update(
return MessageToDict(self._stream) self, file_path=None, duration=None,
fee_currency=None, fee_amount=None, fee_address=None,
video_height=None, video_width=None,
**kwargs):
@property super().update(**kwargs)
def claim(self) -> Claim:
return self._claim if video_height is not None:
self.video.height = video_height
if video_width is not None:
self.video.width = video_width
if file_path is not None:
self.media_type = guess_media_type(file_path)
if not os.path.isfile(file_path):
raise Exception(f"File does not exist: {file_path}")
self.file.size = os.path.getsize(file_path)
if self.file.size == 0:
raise Exception(f"Cannot publish empty file: {file_path}")
if fee_amount and fee_currency:
if fee_address:
self.fee.address = fee_address
if fee_currency.lower() == 'lbc':
self.fee.lbc = Decimal(fee_amount)
elif fee_currency.lower() == 'usd':
self.fee.usd = Decimal(fee_amount)
else:
raise Exception(f'Unknown currency type: {fee_currency}')
if duration is not None:
self.duration = duration
elif file_path is not None:
try:
file_metadata = binary_file_metadata(binary_file_parser(file_path))
self.duration = file_metadata.getValues('duration')[0].seconds
except:
pass
@property @property
def video(self) -> Video: def video(self) -> Video:
return Video(self._stream.video) return Video(self.message.video)
@property @property
def file(self) -> File: def file(self) -> File:
return File(self._stream.file) return File(self.message.file)
@property @property
def fee(self) -> Fee: def fee(self) -> Fee:
return Fee(self._stream.fee) return Fee(self.message.fee)
@property @property
def has_fee(self) -> bool: def has_fee(self) -> bool:
return self._stream.HasField('fee') return self.message.HasField('fee')
@property
def tags(self) -> List:
return self._stream.tags
@property @property
def hash(self) -> str: def hash(self) -> str:
return hexlify(self._stream.hash).decode() return hexlify(self.message.hash).decode()
@hash.setter @hash.setter
def hash(self, sd_hash: str): def hash(self, sd_hash: str):
self._stream.hash = unhexlify(sd_hash.encode()) self.message.hash = unhexlify(sd_hash.encode())
@property @property
def hash_bytes(self) -> bytes: def hash_bytes(self) -> bytes:
return self._stream.hash return self.message.hash
@hash_bytes.setter @hash_bytes.setter
def hash_bytes(self, hash: bytes): def hash_bytes(self, hash: bytes):
self._stream.hash = hash self.message.hash = hash
@property
def language(self) -> str:
return self._stream.language
@language.setter
def language(self, language: str):
self._stream.language = language
@property
def title(self) -> str:
return self._stream.title
@title.setter
def title(self, title: str):
self._stream.title = title
@property @property
def author(self) -> str: def author(self) -> str:
return self._stream.author return self.message.author
@author.setter @author.setter
def author(self, author: str): def author(self, author: str):
self._stream.author = author self.message.author = author
@property
def description(self) -> str:
return self._stream.description
@description.setter
def description(self, description: str):
self._stream.description = description
@property @property
def media_type(self) -> str: def media_type(self) -> str:
return self._stream.media_type return self.message.media_type
@media_type.setter @media_type.setter
def media_type(self, media_type: str): def media_type(self, media_type: str):
self._stream.media_type = media_type self.message.media_type = media_type
@property @property
def license(self) -> str: def license(self) -> str:
return self._stream.license return self.message.license
@license.setter @license.setter
def license(self, license: str): def license(self, license: str):
self._stream.license = license self.message.license = license
@property @property
def license_url(self) -> str: def license_url(self) -> str:
return self._stream.license_url return self.message.license_url
@license_url.setter @license_url.setter
def license_url(self, license_url: str): def license_url(self, license_url: str):
self._stream.license_url = license_url self.message.license_url = license_url
@property
def thumbnail_url(self) -> str:
return self._stream.thumbnail_url
@thumbnail_url.setter
def thumbnail_url(self, thumbnail_url: str):
self._stream.thumbnail_url = thumbnail_url
@property @property
def duration(self) -> int: def duration(self) -> int:
return self._stream.duration return self.message.duration
@duration.setter @duration.setter
def duration(self, duration: int): def duration(self, duration: int):
self._stream.duration = duration self.message.duration = duration
@property @property
def release_time(self) -> int: def release_time(self) -> int:
return self._stream.release_time return self.message.release_time
@release_time.setter @release_time.setter
def release_time(self, release_time: int): def release_time(self, release_time: int):
self._stream.release_time = release_time self.message.release_time = release_time

View file

@ -4,7 +4,7 @@ import typing
import logging import logging
import binascii import binascii
from lbrynet.utils import generate_id from lbrynet.utils import generate_id
from lbrynet.extras.daemon.mime_types import guess_media_type from lbrynet.schema.mime_types import guess_media_type
from lbrynet.stream.downloader import StreamDownloader from lbrynet.stream.downloader import StreamDownloader
from lbrynet.stream.descriptor import StreamDescriptor from lbrynet.stream.descriptor import StreamDescriptor
from lbrynet.stream.reflector.client import StreamReflectorClient from lbrynet.stream.reflector.client import StreamReflectorClient

View file

@ -1,12 +1,16 @@
import json import json
import logging import logging
import binascii import binascii
import typing
from hashlib import sha256 from hashlib import sha256
from string import hexdigits from string import hexdigits
from torba.client.baseaccount import BaseAccount from torba.client.baseaccount import BaseAccount
from torba.client.basetransaction import TXORef from torba.client.basetransaction import TXORef
if typing.TYPE_CHECKING:
from lbrynet.wallet import ledger
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -21,31 +25,32 @@ def validate_claim_id(claim_id):
class Account(BaseAccount): class Account(BaseAccount):
ledger: 'ledger.MainNetLedger'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.certificates = {} self.channel_keys = {}
@property @property
def hash(self) -> bytes: def hash(self) -> bytes:
h = sha256(json.dumps(self.to_dict(False)).encode()) h = sha256(json.dumps(self.to_dict(False)).encode())
for cert in sorted(self.certificates.keys()): for cert in sorted(self.channel_keys.keys()):
h.update(cert.encode()) h.update(cert.encode())
return h.digest() return h.digest()
def apply(self, d: dict): def apply(self, d: dict):
super().apply(d) super().apply(d)
self.certificates.update(d.get('certificates', {})) self.channel_keys.update(d.get('certificates', {}))
def add_certificate_private_key(self, ref: TXORef, private_key): def add_channel_private_key(self, ref: TXORef, private_key):
assert ref.id not in self.certificates, 'Trying to add a duplicate certificate.' assert ref.id not in self.channel_keys, 'Trying to add a duplicate channel private key.'
self.certificates[ref.id] = private_key self.channel_keys[ref.id] = private_key
def get_certificate_private_key(self, ref: TXORef): def get_channel_private_key(self, ref: TXORef):
return self.certificates.get(ref.id) return self.channel_keys.get(ref.id)
async def maybe_migrate_certificates(self): async def maybe_migrate_certificates(self):
if not self.certificates: if not self.channel_keys:
return return
addresses = {} addresses = {}
@ -59,7 +64,7 @@ class Account(BaseAccount):
} }
double_hex_encoded_to_pop = [] double_hex_encoded_to_pop = []
for maybe_claim_id in list(self.certificates): for maybe_claim_id in list(self.channel_keys):
if ':' not in maybe_claim_id: if ':' not in maybe_claim_id:
try: try:
validate_claim_id(maybe_claim_id) validate_claim_id(maybe_claim_id)
@ -71,7 +76,7 @@ class Account(BaseAccount):
maybe_claim_id_bytes = maybe_claim_id_bytes.encode() maybe_claim_id_bytes = maybe_claim_id_bytes.encode()
decoded_double_hex = binascii.unhexlify(maybe_claim_id_bytes).decode() decoded_double_hex = binascii.unhexlify(maybe_claim_id_bytes).decode()
validate_claim_id(decoded_double_hex) validate_claim_id(decoded_double_hex)
if decoded_double_hex in self.certificates: if decoded_double_hex in self.channel_keys:
log.warning("don't know how to migrate certificate %s", decoded_double_hex) log.warning("don't know how to migrate certificate %s", decoded_double_hex)
else: else:
log.info("claim id was double hex encoded, fixing it") log.info("claim id was double hex encoded, fixing it")
@ -80,9 +85,9 @@ class Account(BaseAccount):
continue continue
for double_encoded_claim_id, correct_claim_id in double_hex_encoded_to_pop: for double_encoded_claim_id, correct_claim_id in double_hex_encoded_to_pop:
self.certificates[correct_claim_id] = self.certificates.pop(double_encoded_claim_id) self.channel_keys[correct_claim_id] = self.channel_keys.pop(double_encoded_claim_id)
for maybe_claim_id in list(self.certificates): for maybe_claim_id in list(self.channel_keys):
results['total'] += 1 results['total'] += 1
if ':' not in maybe_claim_id: if ':' not in maybe_claim_id:
try: try:
@ -117,8 +122,8 @@ class Account(BaseAccount):
.format(maybe_claim_id) .format(maybe_claim_id)
) )
tx_nout = '{txid}:{nout}'.format(**claim) tx_nout = '{txid}:{nout}'.format(**claim)
self.certificates[tx_nout] = self.certificates[maybe_claim_id] self.channel_keys[tx_nout] = self.channel_keys[maybe_claim_id]
del self.certificates[maybe_claim_id] del self.channel_keys[maybe_claim_id]
log.info( log.info(
"Migrated certificate with claim_id '%s' ('%s') to a new look up key %s.", "Migrated certificate with claim_id '%s' ('%s') to a new look up key %s.",
maybe_claim_id, txo.script.values['claim_name'], tx_nout maybe_claim_id, txo.script.values['claim_name'], tx_nout
@ -186,18 +191,18 @@ class Account(BaseAccount):
@classmethod @classmethod
def from_dict(cls, ledger, wallet, d: dict) -> 'Account': def from_dict(cls, ledger, wallet, d: dict) -> 'Account':
account = super().from_dict(ledger, wallet, d) account = super().from_dict(ledger, wallet, d)
account.certificates = d.get('certificates', {}) account.channel_keys = d.get('certificates', {})
return account return account
def to_dict(self, include_certificates=True): def to_dict(self, include_channel_keys=True):
d = super().to_dict() d = super().to_dict()
if include_certificates: if include_channel_keys:
d['certificates'] = self.certificates d['certificates'] = self.channel_keys
return d return d
async def get_details(self, **kwargs): async def get_details(self, **kwargs):
details = await super().get_details(**kwargs) details = await super().get_details(**kwargs)
details['certificates'] = len(self.certificates) details['certificates'] = len(self.channel_keys)
return details return details
def get_claim(self, claim_id=None, txid=None, nout=None): def get_claim(self, claim_id=None, txid=None, nout=None):
@ -207,15 +212,15 @@ class Account(BaseAccount):
return self.ledger.db.get_claims(**{'account': self, 'txo.txid': txid, 'txo.position': nout}) return self.ledger.db.get_claims(**{'account': self, 'txo.txid': txid, 'txo.position': nout})
@staticmethod @staticmethod
def constraint_utxos_sans_claims(constraints): def constraint_spending_utxos(constraints):
constraints.update({'is_claim': 0, 'is_update': 0, 'is_support': 0}) constraints.update({'is_claim': 0, 'is_update': 0, 'is_support': 0})
def get_utxos(self, **constraints): def get_utxos(self, **constraints):
self.constraint_utxos_sans_claims(constraints) self.constraint_spending_utxos(constraints)
return super().get_utxos(**constraints) return super().get_utxos(**constraints)
def get_utxo_count(self, **constraints): def get_utxo_count(self, **constraints):
self.constraint_utxos_sans_claims(constraints) self.constraint_spending_utxos(constraints)
return super().get_utxo_count(**constraints) return super().get_utxo_count(**constraints)
def get_claims(self, **constraints): def get_claims(self, **constraints):
@ -230,6 +235,12 @@ class Account(BaseAccount):
def get_channel_count(self, **constraints): def get_channel_count(self, **constraints):
return self.ledger.db.get_channel_count(account=self, **constraints) return self.ledger.db.get_channel_count(account=self, **constraints)
def get_supports(self, **constraints):
return self.ledger.db.get_supports(account=self, **constraints)
def get_support_count(self, **constraints):
return self.ledger.db.get_support_count(account=self, **constraints)
async def send_to_addresses(self, amount, addresses, broadcast=False): async def send_to_addresses(self, amount, addresses, broadcast=False):
tx_class = self.ledger.transaction_class tx_class = self.ledger.transaction_class
tx = await tx_class.create( tx = await tx_class.create(

View file

@ -1,5 +1,9 @@
from typing import List
from torba.client.basedatabase import BaseDatabase from torba.client.basedatabase import BaseDatabase
from lbrynet.wallet.transaction import Output
class WalletDatabase(BaseDatabase): class WalletDatabase(BaseDatabase):
@ -48,7 +52,7 @@ class WalletDatabase(BaseDatabase):
row['claim_name'] = txo.claim_name row['claim_name'] = txo.claim_name
return row return row
async def get_txos(self, **constraints): async def get_txos(self, **constraints) -> List[Output]:
my_account = constraints.get('my_account', constraints.get('account', None)) my_account = constraints.get('my_account', constraints.get('account', None))
txos = await super().get_txos(**constraints) txos = await super().get_txos(**constraints)
@ -58,8 +62,8 @@ class WalletDatabase(BaseDatabase):
if txo.script.is_claim_name or txo.script.is_update_claim: if txo.script.is_claim_name or txo.script.is_update_claim:
if txo.claim.is_signed: if txo.claim.is_signed:
channel_ids.add(txo.claim.signing_channel_id) channel_ids.add(txo.claim.signing_channel_id)
if txo.claim_name.startswith('@') and my_account is not None: if txo.claim.is_channel and my_account is not None:
txo.private_key = my_account.get_certificate_private_key(txo.ref) txo.private_key = my_account.get_channel_private_key(txo.ref)
if channel_ids: if channel_ids:
channels = { channels = {
@ -77,11 +81,11 @@ class WalletDatabase(BaseDatabase):
@staticmethod @staticmethod
def constrain_claims(constraints): def constrain_claims(constraints):
constraints['claim_type__any'] = {'is_claim': 1, 'is_update': 1, 'is_support': 1} constraints['claim_type__any'] = {'is_claim': 1, 'is_update': 1}
def get_claims(self, **constraints): async def get_claims(self, **constraints) -> List[Output]:
self.constrain_claims(constraints) self.constrain_claims(constraints)
return self.get_utxos(**constraints) return await self.get_utxos(**constraints)
def get_claim_count(self, **constraints): def get_claim_count(self, **constraints):
self.constrain_claims(constraints) self.constrain_claims(constraints)
@ -100,22 +104,17 @@ class WalletDatabase(BaseDatabase):
self.constrain_channels(constraints) self.constrain_channels(constraints)
return self.get_claim_count(**constraints) return self.get_claim_count(**constraints)
async def get_certificates(self, private_key_accounts, exclude_without_key=False, **constraints): @staticmethod
channels = await self.get_channels(**constraints) def constrain_supports(constraints):
certificates = [] constraints['is_support'] = 1
if private_key_accounts is not None:
for channel in channels: def get_supports(self, **constraints):
if not channel.has_private_key: self.constrain_supports(constraints)
private_key = None return self.get_utxos(**constraints)
for account in private_key_accounts:
private_key = account.get_certificate_private_key(channel.ref) def get_support_count(self, **constraints):
if private_key is not None: self.constrain_supports(constraints)
break return self.get_utxo_count(**constraints)
if private_key is None and exclude_without_key:
continue
channel.private_key = private_key
certificates.append(channel)
return certificates
async def release_all_outputs(self, account): async def release_all_outputs(self, account):
await self.db.execute( await self.db.execute(

View file

@ -29,6 +29,8 @@ class MainNetLedger(BaseLedger):
network_class = Network network_class = Network
transaction_class = Transaction transaction_class = Transaction
db: WalletDatabase
secret_prefix = bytes((0x1c,)) secret_prefix = bytes((0x1c,))
pubkey_address_prefix = bytes((0x55,)) pubkey_address_prefix = bytes((0x55,))
script_address_prefix = bytes((0x7a,)) script_address_prefix = bytes((0x7a,))
@ -97,7 +99,7 @@ class MainNetLedger(BaseLedger):
log.info("Loaded account %s with %s LBC, %d receiving addresses (gap: %d), " log.info("Loaded account %s with %s LBC, %d receiving addresses (gap: %d), "
"%d change addresses (gap: %d), %d channels, %d certificates and %d claims. ", "%d change addresses (gap: %d), %d channels, %d certificates and %d claims. ",
account.id, balance, total_receiving, account.receiving.gap, total_change, account.change.gap, account.id, balance, total_receiving, account.receiving.gap, total_change, account.change.gap,
channel_count, len(account.certificates), claim_count) channel_count, len(account.channel_keys), claim_count)
class TestNetLedger(MainNetLedger): class TestNetLedger(MainNetLedger):

View file

@ -155,7 +155,7 @@ class LbryWalletManager(BaseWalletManager):
return receiving_addresses, change_addresses return receiving_addresses, change_addresses
@classmethod @classmethod
async def from_lbrynet_config(cls, settings, db): async def from_lbrynet_config(cls, settings):
ledger_id = { ledger_id = {
'lbrycrd_main': 'lbc_mainnet', 'lbrycrd_main': 'lbc_mainnet',
@ -233,24 +233,6 @@ class LbryWalletManager(BaseWalletManager):
await account.ledger.broadcast(tx) await account.ledger.broadcast(tx)
return tx return tx
async def send_claim_to_address(self, claim_id: str, destination_address: str, amount: Optional[int],
account=None):
account = account or self.default_account
claims = await account.get_claims(
claim_name_type__any={'is_claim': 1, 'is_update': 1}, # exclude is_supports
claim_id=claim_id
)
if not claims:
raise NameError(f"Claim not found: {claim_id}")
if not amount:
amount = claims[0].get_estimator(self.ledger).effective_amount
tx = await Transaction.update(
claims[0], claims[0].claim, amount,
destination_address.encode(), [account], account
)
await self.ledger.broadcast(tx)
return tx
def send_points_to_address(self, reserved: ReservedPoints, amount: int, account=None): def send_points_to_address(self, reserved: ReservedPoints, amount: int, account=None):
destination_address: bytes = reserved.identifier.encode('latin1') destination_address: bytes = reserved.identifier.encode('latin1')
return self.send_amount_to_address(amount, destination_address, account) return self.send_amount_to_address(amount, destination_address, account)
@ -392,44 +374,6 @@ 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, account, name, amount, claim: Claim, certificate=None, claim_address=None):
claim_address = claim_address or await account.receiving.get_or_create_usable_address()
existing_claims = await account.get_claims(
claim_name_type__any={'is_claim': 1, 'is_update': 1}, # exclude is_supports
claim_name=name
)
inputs = []
if len(existing_claims) == 0:
claim_output = Output.pay_claim_name_pubkey_hash(
amount, name, claim, account.ledger.address_to_hash160(claim_address)
)
elif len(existing_claims) == 1:
previous_claim = existing_claims[0]
claim_output = Output.pay_update_claim_pubkey_hash(
amount, previous_claim.claim_name, previous_claim.claim_id,
claim, account.ledger.address_to_hash160(claim_address)
)
inputs = [Input.spend(previous_claim)]
else:
raise NameError(f"More than one other claim exists with the name '{name}'.")
if certificate:
claim_output.sign(certificate, first_input_id=b'placeholder')
tx = await Transaction.create(inputs, [claim_output], [account], account)
if certificate:
claim_output.sign(certificate)
await tx.sign([account])
await account.ledger.broadcast(tx)
await self.old_db.save_claims([self._old_get_temp_claim_info(
tx, tx.outputs[0], claim_address, claim, name, dewies_to_lbc(amount)
)])
# TODO: release reserved tx outputs in case anything fails by this point
return tx
async def support_claim(self, claim_name, claim_id, amount, account): async def support_claim(self, claim_name, claim_id, amount, account):
holding_address = await account.receiving.get_or_create_usable_address() holding_address = await account.receiving.get_or_create_usable_address()
tx = await Transaction.support(claim_name, claim_id, amount, holding_address, [account], account) tx = await Transaction.support(claim_name, claim_id, amount, holding_address, [account], account)
@ -468,46 +412,6 @@ 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, account):
address = await account.receiving.get_or_create_usable_address()
claim = Claim()
claim_output = Output.pay_claim_name_pubkey_hash(
amount, channel_name, claim, account.ledger.address_to_hash160(address)
)
key = claim_output.generate_channel_private_key()
claim_output.script.generate()
tx = await Transaction.create([], [claim_output], [account], account)
await account.ledger.broadcast(tx)
account.add_certificate_private_key(tx.outputs[0].ref, key.decode())
# TODO: release reserved tx outputs in case anything fails by this point
await self.old_db.save_claims([self._old_get_temp_claim_info(
tx, tx.outputs[0], address, claim, channel_name, dewies_to_lbc(amount)
)])
return tx
def _old_get_temp_claim_info(self, tx, txo, address, claim_dict, name, bid):
return {
"claim_id": txo.claim_id,
"name": name,
"amount": bid,
"address": address,
"txid": tx.id,
"nout": txo.position,
"value": claim_dict,
"height": -1,
"claim_sequence": -1,
}
def get_certificates(self, private_key_accounts, exclude_without_key=True, **constraints):
return self.db.get_certificates(
private_key_accounts=private_key_accounts,
exclude_without_key=exclude_without_key,
**constraints
)
def update_peer_address(self, peer, address): def update_peer_address(self, peer, address):
pass # TODO: Data payments is disabled pass # TODO: Data payments is disabled

View file

@ -11,7 +11,7 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from ecdsa.util import sigencode_der from ecdsa.util import sigencode_der
from torba.client.basetransaction import BaseTransaction, BaseInput, BaseOutput from torba.client.basetransaction import BaseTransaction, BaseInput, BaseOutput, ReadOnlyList
from torba.client.hash import hash160, sha256, Base58 from torba.client.hash import hash160, sha256, Base58
from lbrynet.schema.claim import Claim from lbrynet.schema.claim import Claim
from lbrynet.wallet.account import Account from lbrynet.wallet.account import Account
@ -117,6 +117,7 @@ class Output(BaseOutput):
return True return True
def sign(self, channel: 'Output', first_input_id=None): def sign(self, channel: 'Output', first_input_id=None):
self.channel = channel
self.claim.signing_channel_hash = channel.claim_hash self.claim.signing_channel_hash = channel.claim_hash
digest = sha256(b''.join([ digest = sha256(b''.join([
first_input_id or self.tx_ref.tx.inputs[0].txo_ref.id.encode(), first_input_id or self.tx_ref.tx.inputs[0].txo_ref.id.encode(),
@ -129,8 +130,9 @@ class Output(BaseOutput):
def generate_channel_private_key(self): def generate_channel_private_key(self):
private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256) private_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256)
self.private_key = private_key.to_pem() self.private_key = private_key.to_pem().decode()
self.claim.channel.public_key_bytes = private_key.get_verifying_key().to_der() self.claim.channel.public_key_bytes = private_key.get_verifying_key().to_der()
self.script.generate()
return self.private_key return self.private_key
def is_channel_private_key(self, private_key_pem): def is_channel_private_key(self, private_key_pem):
@ -169,6 +171,9 @@ class Transaction(BaseTransaction):
input_class = Input input_class = Input
output_class = Output output_class = Output
outputs: ReadOnlyList[Output]
inputs: ReadOnlyList[Input]
@classmethod @classmethod
def pay(cls, amount: int, address: bytes, funding_accounts: List[Account], change_account: Account): def pay(cls, amount: int, address: bytes, funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account) ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
@ -176,13 +181,42 @@ class Transaction(BaseTransaction):
return cls.create([], [output], funding_accounts, change_account) return cls.create([], [output], funding_accounts, change_account)
@classmethod @classmethod
def claim(cls, name: str, claim: Claim, amount: int, holding_address: bytes, def claim_create(
funding_accounts: List[Account], change_account: Account): cls, name: str, claim: Claim, amount: int, holding_address: str,
funding_accounts: List[Account], change_account: Account, signing_channel: Output = None):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account) ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
claim_output = Output.pay_claim_name_pubkey_hash( claim_output = Output.pay_claim_name_pubkey_hash(
amount, name, claim, ledger.address_to_hash160(holding_address) amount, name, claim, ledger.address_to_hash160(holding_address)
) )
return cls.create([], [claim_output], funding_accounts, change_account) if signing_channel is not None:
claim_output.sign(signing_channel, b'placeholder txid:nout')
return cls.create([], [claim_output], funding_accounts, change_account, sign=False)
@classmethod
def claim_update(
cls, previous_claim: Output, amount: int, holding_address: str,
funding_accounts: List[Account], change_account: Account, signing_channel: Output = None):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
updated_claim = Output.pay_update_claim_pubkey_hash(
amount, previous_claim.claim_name, previous_claim.claim_id,
previous_claim.claim, ledger.address_to_hash160(holding_address)
)
if signing_channel is not None:
updated_claim.sign(signing_channel, b'placeholder txid:nout')
return cls.create(
[Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account, sign=False
)
@classmethod
def support(cls, claim_name: str, claim_id: str, amount: int, holding_address: str,
funding_accounts: List[Account], change_account: Account, signing_channel: Output = None):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
support_output = Output.pay_support_pubkey_hash(
amount, claim_name, claim_id, ledger.address_to_hash160(holding_address)
)
if signing_channel is not None:
support_output.sign(signing_channel, b'placeholder txid:nout')
return cls.create([], [support_output], funding_accounts, change_account, sign=False)
@classmethod @classmethod
def purchase(cls, claim: Output, amount: int, merchant_address: bytes, def purchase(cls, claim: Output, amount: int, merchant_address: bytes,
@ -193,25 +227,6 @@ class Transaction(BaseTransaction):
) )
return cls.create([], [claim_output], funding_accounts, change_account) return cls.create([], [claim_output], funding_accounts, change_account)
@classmethod
def update(cls, previous_claim: Output, claim: Claim, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
updated_claim = Output.pay_update_claim_pubkey_hash(
amount, previous_claim.claim_name, previous_claim.claim_id,
claim, ledger.address_to_hash160(holding_address)
)
return cls.create([Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account)
@classmethod
def support(cls, claim_name: str, claim_id: str, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
output = Output.pay_support_pubkey_hash(
amount, claim_name, claim_id, ledger.address_to_hash160(holding_address)
)
return cls.create([], [output], funding_accounts, change_account)
@classmethod @classmethod
def abandon(cls, claims: Iterable[Output], funding_accounts: Iterable[Account], change_account: Account): def abandon(cls, claims: Iterable[Output], funding_accounts: Iterable[Account], change_account: Account):
return cls.create([Input.spend(txo) for txo in claims], [], funding_accounts, change_account) return cls.create([Input.spend(txo) for txo in claims], [], funding_accounts, change_account)

View file

@ -38,5 +38,6 @@ setup(
'torba', 'torba',
'pyyaml==3.13', 'pyyaml==3.13',
'docopt==0.6.2', 'docopt==0.6.2',
'hachoir',
], ],
) )

View file

@ -19,9 +19,8 @@ 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!
channel = await self.out(self.daemon.jsonrpc_channel_new('@spam', "1.0")) tx = await self.create_channel('@spam', '1.0')
self.assertTrue(channel['success']) channel_id = tx['outputs'][0]['claim_id']
await self.confirm_tx(channel['tx']['txid'])
# Do we have it locally? # Do we have it locally?
channels = await self.out(self.daemon.jsonrpc_channel_list()) channels = await self.out(self.daemon.jsonrpc_channel_list())
@ -52,17 +51,12 @@ 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.
with tempfile.NamedTemporaryFile() as file: tx = await self.create_claim(
file.write(b'blah blah blah...') 'hovercraft', '1.0',
file.write(b'[insert long story about eels driving hovercraft]') data=b'[insert long story about eels driving hovercraft]',
file.write(b'yada yada yada!') channel_id=channel_id
file.write(b'the end') )
file.flush() claim_id = tx['outputs'][0]['claim_id']
claim1 = await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '1.0', file_path=file.name, channel_id=channel['claim_id']
))
self.assertTrue(claim1['success'])
await self.confirm_tx(claim1['tx']['txid'])
# He quickly checks the unconfirmed balance to make sure everything looks # He quickly checks the unconfirmed balance to make sure everything looks
# correct. # correct.
@ -84,21 +78,11 @@ 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.
with tempfile.NamedTemporaryFile() as file: await self.update_claim(claim_id, data=b'[typo fixing sounds being made]')
file.write(b'blah blah blah...')
file.write(b'[typo fixing sounds being made]')
file.write(b'yada yada yada!')
file.flush()
claim2 = await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '1.0', file_path=file.name, channel_name='@spam'
))
self.assertTrue(claim2['success'])
self.assertEqual(claim2['claim_id'], claim1['claim_id'])
await self.confirm_tx(claim2['tx']['txid'])
# 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.
abandon = await self.out(self.daemon.jsonrpc_claim_abandon(claim1['claim_id'], blocking=False)) abandon = await self.out(self.daemon.jsonrpc_claim_abandon(claim_id, blocking=False))
self.assertTrue(abandon['success']) self.assertTrue(abandon['success'])
await self.confirm_tx(abandon['tx']['txid']) await self.confirm_tx(abandon['tx']['txid'])
@ -134,17 +118,10 @@ 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:
with tempfile.NamedTemporaryFile() as file: tx = await self.create_claim(
file.write(b'Amazingly Original First Line') 'fresh-start', '1.0', data=b'Amazingly Original First Line', channel_id=channel_id
file.write(b'Super plot for the grand novel') )
file.write(b'Totally un-cliched ending') claim_id2 = tx['outputs'][0]['claim_id']
file.write(b'**Audience Gasps**')
file.flush()
claim3 = await self.out(self.daemon.jsonrpc_publish(
'fresh-start', '1.0', file_path=file.name, channel_name='@spam'
))
self.assertTrue(claim3['success'])
await self.confirm_tx(claim3['tx']['txid'])
await self.generate(5) await self.generate(5)
@ -154,7 +131,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# And voila, and bravo and encore! His Best Friend Ramsey read the story and immediately knew this was a hit # And voila, and bravo and encore! His Best Friend Ramsey read the story and immediately knew this was a hit
# Now to keep this claim winning on the lbry blockchain he immediately supports the claim # Now to keep this claim winning on the lbry blockchain he immediately supports the claim
tx = await self.out(self.daemon.jsonrpc_claim_new_support( tx = await self.out(self.daemon.jsonrpc_claim_new_support(
'fresh-start', claim3['claim_id'], '0.2', account_id=ramsey_account_id 'fresh-start', claim_id2, '0.2', account_id=ramsey_account_id
)) ))
await self.confirm_tx(tx['txid']) await self.confirm_tx(tx['txid'])
@ -170,7 +147,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# Now he also wanted to support the original creator of the Award Winning Novel # Now he also wanted to support the original creator of the Award Winning Novel
# So he quickly decides to send a tip to him # So he quickly decides to send a tip to him
tx = await self.out( tx = await self.out(
self.daemon.jsonrpc_claim_tip(claim3['claim_id'], '0.3', account_id=ramsey_account_id)) self.daemon.jsonrpc_claim_tip(claim_id2, '0.3', account_id=ramsey_account_id))
await self.confirm_tx(tx['txid']) await self.confirm_tx(tx['txid'])
# And again checks if it went to the just right place # And again checks if it went to the just right place
@ -181,7 +158,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
await self.generate(5) await self.generate(5)
# Seeing the ravishing success of his novel Chris adds support to his claim too # Seeing the ravishing success of his novel Chris adds support to his claim too
tx = await self.out(self.daemon.jsonrpc_claim_new_support('fresh-start', claim3['claim_id'], '0.4')) tx = await self.out(self.daemon.jsonrpc_claim_new_support('fresh-start', claim_id2, '0.4'))
await self.confirm_tx(tx['txid']) await self.confirm_tx(tx['txid'])
# And check if his support showed up # And check if his support showed up
@ -197,16 +174,9 @@ 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
with tempfile.NamedTemporaryFile() as file: tx = await self.out(self.daemon.jsonrpc_publish(
file.write(b'The Whale amd The Bookmark') 'hit-song', '1.0', data=b'The Whale and The Bookmark', channel_id=channel_id
file.write(b'I know right? Totally a hit song')
file.write(b'That\'s what goes around for songs these days anyways')
file.flush()
claim4 = await self.out(self.daemon.jsonrpc_publish(
'hit-song', '1.0', file_path=file.name, channel_id=channel['claim_id']
)) ))
self.assertTrue(claim4['success'])
await self.confirm_tx(claim4['tx']['txid'])
await self.generate(5) await self.generate(5)
@ -215,7 +185,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# But sadly Ramsey wasn't so pleased. It was hard for him to tell Chris... # But sadly Ramsey wasn't so pleased. It was hard for him to tell Chris...
# Chris, though a bit heartbroken, abandoned the claim for now, but instantly started working on new hit lyrics # Chris, though a bit heartbroken, abandoned the claim for now, but instantly started working on new hit lyrics
abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=claim4['tx']['txid'], nout=0, blocking=False)) abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=tx['txid'], nout=0, blocking=False))
self.assertTrue(abandon['success']) self.assertTrue(abandon['success'])
await self.confirm_tx(abandon['tx']['txid']) await self.confirm_tx(abandon['tx']['txid'])

View file

@ -1,79 +1,224 @@
import hashlib import hashlib
import tempfile
from binascii import unhexlify from binascii import unhexlify
import ecdsa import ecdsa
from lbrynet.wallet.transaction import Transaction, Output from lbrynet.wallet.transaction import Transaction, Output
from lbrynet.error import InsufficientFundsError from torba.client.errors import InsufficientFundsError
from lbrynet.schema.claim import Claim
from lbrynet.schema.compat import OldClaimMessage from lbrynet.schema.compat import OldClaimMessage
from integration.testcase import CommandTestCase from integration.testcase import CommandTestCase
from torba.client.hash import sha256, Base58 from torba.client.hash import sha256, Base58
class ChannelCommands(CommandTestCase):
async def test_create_channel_names(self):
# claim new name
await self.create_channel('@foo')
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '8.991893')
# fail to claim duplicate
with self.assertRaisesRegex(Exception, "You already have a channel under the name '@foo'."):
await self.create_channel('@foo')
# fail to claim invalid name
with self.assertRaisesRegex(Exception, "Cannot make a new channel for a non channel name"):
await self.create_channel('foo')
# nothing's changed after failed attempts
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '8.991893')
# succeed overriding duplicate restriction
await self.create_channel('@foo', allow_duplicate_name=True)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
await self.assertBalance(self.account, '7.983786')
async def test_channel_bids(self):
# enough funds
tx = await self.create_channel('@foo', '5.0')
claim_id = tx['outputs'][0]['claim_id']
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '4.991893')
# bid preserved on update
tx = await self.update_channel(claim_id)
self.assertEqual(tx['outputs'][0]['amount'], '5.0')
# bid changed on update
tx = await self.update_channel(claim_id, bid='4.0')
self.assertEqual(tx['outputs'][0]['amount'], '4.0')
await self.assertBalance(self.account, '5.991447')
# not enough funds
with self.assertRaisesRegex(
InsufficientFundsError, "Not enough funds to cover this transaction."):
await self.create_channel('@foo2', '9.0')
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 1)
await self.assertBalance(self.account, '5.991447')
# spend exactly amount available, no change
tx = await self.create_channel('@foo3', '5.981266')
await self.assertBalance(self.account, '0.0')
self.assertEqual(len(tx['outputs']), 1) # no change
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
async def test_setting_channel_fields(self):
values = {
'title': "Cool Channel",
'description': "Best channel on LBRY.",
'contact_email': "human@email.com",
'tags': ["cool", "awesome"],
'cover_url': "https://co.ol/cover.png",
'homepage_url': "https://co.ol",
'thumbnail_url': "https://co.ol/thumbnail.png",
'language': "en"
}
# create new channel with all fields set
tx = await self.out(self.create_channel('@bigchannel', **values))
txo = tx['outputs'][0]
self.assertEqual(
txo['value']['channel'],
{'public_key': txo['value']['channel']['public_key'], **values}
)
# create channel with nothing set
tx = await self.out(self.create_channel('@lightchannel'))
txo = tx['outputs'][0]
self.assertEqual(
txo['value']['channel'],
{'public_key': txo['value']['channel']['public_key']}
)
# create channel with just some tags
tx = await self.out(self.create_channel('@updatedchannel', tags='blah'))
txo = tx['outputs'][0]
claim_id = txo['claim_id']
public_key = txo['value']['channel']['public_key']
self.assertEqual(
txo['value']['channel'],
{'public_key': public_key, 'tags': ['blah']}
)
# update channel setting all fields
tx = await self.out(self.update_channel(claim_id, **values))
txo = tx['outputs'][0]
values['public_key'] = public_key
values['tags'].insert(0, 'blah') # existing tag
self.assertEqual(
txo['value']['channel'],
values
)
# clearing and settings tags
tx = await self.out(self.update_channel(claim_id, tags='single', clear_tags=True))
txo = tx['outputs'][0]
values['tags'] = ['single']
self.assertEqual(
txo['value']['channel'],
values
)
# reset signing key
tx = await self.out(self.update_channel(claim_id, new_signing_key=True))
txo = tx['outputs'][0]
self.assertNotEqual(
txo['value']['channel']['public_key'],
values['public_key']
)
# send channel to someone else
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'])
# before sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 3)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0)
other_address = await account2.receiving.get_or_create_usable_address()
tx = await self.out(self.update_channel(claim_id, claim_address=other_address))
# after sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 1)
# shoud not have private key
txo = (await account2.get_channels())[0]
self.assertIsNone(txo.private_key)
# send the private key too
txoid = f"{tx['outputs'][0]['txid']}:{tx['outputs'][0]['nout']}"
account2.channel_keys[txoid] = self.account.channel_keys[txoid]
# now should have private key
txo = (await account2.get_channels())[0]
self.assertIsNotNone(txo.private_key)
class ClaimCommands(CommandTestCase): class ClaimCommands(CommandTestCase):
async def test_create_update_and_abandon_claim(self): async def test_create_claim_names(self):
await self.assertBalance(self.account, '10.0') # claim new name
await self.create_claim('foo')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '8.993893')
claim = await self.make_claim(amount='2.5') # creates new claim # fail to claim duplicate
txs = await self.out(self.daemon.jsonrpc_transaction_list()) with self.assertRaisesRegex(Exception, "You already have a claim published under the name 'foo'."):
self.assertEqual(len(txs[0]['claim_info']), 1) await self.create_claim('foo')
self.assertEqual(txs[0]['confirmations'], 1)
self.assertEqual(txs[0]['claim_info'][0]['balance_delta'], '-2.5')
self.assertEqual(txs[0]['claim_info'][0]['claim_id'], claim['claim_id'])
self.assertEqual(txs[0]['value'], '0.0')
self.assertEqual(txs[0]['fee'], '-0.020107')
await self.assertBalance(self.account, '7.479893')
await self.make_claim(amount='1.0') # updates previous claim # fail claim starting with @
txs = await self.out(self.daemon.jsonrpc_transaction_list()) with self.assertRaisesRegex(Exception, "Claim names cannot start with @ symbol."):
self.assertEqual(len(txs[0]['update_info']), 1) await self.create_claim('@foo')
self.assertEqual(txs[0]['update_info'][0]['balance_delta'], '1.5')
self.assertEqual(txs[0]['update_info'][0]['claim_id'], claim['claim_id'])
self.assertEqual(txs[0]['value'], '0.0')
self.assertEqual(txs[0]['fee'], '-0.000182')
await self.assertBalance(self.account, '8.979711')
await self.out(self.daemon.jsonrpc_claim_abandon(claim['claim_id'])) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
txs = await self.out(self.daemon.jsonrpc_transaction_list()) await self.assertBalance(self.account, '8.993893')
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]['claim_id'], claim['claim_id'])
self.assertEqual(txs[0]['value'], '0.0')
self.assertEqual(txs[0]['fee'], '-0.000107')
await self.assertBalance(self.account, '9.979604')
async def test_update_claim_holding_address(self): # succeed overriding duplicate restriction
other_account_id = (await self.daemon.jsonrpc_account_create('second account'))['id'] await self.create_claim('foo', allow_duplicate_name=True)
other_account = self.daemon.get_account_or_error(other_account_id) self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
other_address = await other_account.receiving.get_or_create_usable_address() await self.assertBalance(self.account, '7.987786')
await self.assertBalance(self.account, '10.0') async def test_bids(self):
# enough funds
tx = await self.create_claim('foo', '2.0')
claim_id = tx['outputs'][0]['claim_id']
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '7.993893')
# create the initial name claim # bid preserved on update
claim = await self.make_claim() tx = await self.update_claim(claim_id)
self.assertEqual(tx['outputs'][0]['amount'], '2.0')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list_mine()), 1) # bid changed on update
self.assertEqual(len(await self.daemon.jsonrpc_claim_list_mine(account_id=other_account_id)), 0) tx = await self.update_claim(claim_id, bid='3.0')
tx = await self.daemon.jsonrpc_claim_send_to_address( self.assertEqual(tx['outputs'][0]['amount'], '3.0')
claim['claim_id'], other_address
)
await self.ledger.wait(tx)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list_mine()), 0)
self.assertEqual(len(await self.daemon.jsonrpc_claim_list_mine(account_id=other_account_id)), 1)
async def test_publishing_checks_all_accounts_for_certificate(self): await self.assertBalance(self.account, '6.993384')
# not enough funds
with self.assertRaisesRegex(
InsufficientFundsError, "Not enough funds to cover this transaction."):
await self.create_claim('foo2', '9.0')
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
await self.assertBalance(self.account, '6.993384')
# spend exactly amount available, no change
tx = await self.create_claim('foo3', '6.98527700')
await self.assertBalance(self.account, '0.0')
self.assertEqual(len(tx['outputs']), 1) # no change
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
async def test_publishing_checks_all_accounts_for_channel(self):
account1_id, account1 = self.account.id, self.account account1_id, account1 = self.account.id, self.account
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'])
spam_channel = await self.out(self.daemon.jsonrpc_channel_new('@spam', '1.0')) await self.out(self.create_channel('@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()) self.assertEqual('8.989893', await self.daemon.jsonrpc_account_balance())
result = await self.out(self.daemon.jsonrpc_wallet_send( result = await self.out(self.daemon.jsonrpc_wallet_send(
@ -84,9 +229,8 @@ 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_channel = await self.out(self.daemon.jsonrpc_channel_new('@baz', '1.0', account2_id)) baz_tx = await self.out(self.create_channel('@baz', '1.0', account_id=account2_id))
self.assertTrue(baz_channel['success']) baz_id = baz_tx['outputs'][0]['claim_id']
await self.confirm_tx(baz_channel['tx']['txid'])
channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id)) channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id))
self.assertEqual(len(channels), 1) self.assertEqual(len(channels), 1)
@ -98,84 +242,158 @@ 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
with tempfile.NamedTemporaryFile() as file: await self.create_claim('hovercraft1', channel_id=baz_id)
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 # uses only the specific accounts which contains the channel
with tempfile.NamedTemporaryFile() as file: await self.create_claim('hovercraft2', channel_id=baz_id, channel_account_id=[account2_id])
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 # fails when specifying account which does not contain channel
with tempfile.NamedTemporaryFile() as file: with self.assertRaisesRegex(ValueError, "Couldn't find channel with channel_id"):
file.write(b'hi!') await self.create_claim(
file.flush() 'hovercraft3', channel_id=baz_id, channel_account_id=[account1_id]
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]
))
async def test_updating_claim_includes_claim_value_in_balance_check(self): async def test_setting_claim_fields(self):
values = {
'title': "Cool Channel",
'description': "Best channel on LBRY.",
'contact_email': "human@email.com",
'tags': ["cool", "awesome"],
'cover_url': "https://co.ol/cover.png",
'homepage_url': "https://co.ol",
'thumbnail_url': "https://co.ol/thumbnail.png",
'language': "en"
}
# create new channel with all fields set
tx = await self.out(self.create_channel('@bigchannel', **values))
txo = tx['outputs'][0]
self.assertEqual(
txo['value']['channel'],
{'public_key': txo['value']['channel']['public_key'], **values}
)
# create channel with nothing set
tx = await self.out(self.create_channel('@lightchannel'))
txo = tx['outputs'][0]
self.assertEqual(
txo['value']['channel'],
{'public_key': txo['value']['channel']['public_key']}
)
# create channel with just some tags
tx = await self.out(self.create_channel('@updatedchannel', tags='blah'))
txo = tx['outputs'][0]
claim_id = txo['claim_id']
public_key = txo['value']['channel']['public_key']
self.assertEqual(
txo['value']['channel'],
{'public_key': public_key, 'tags': ['blah']}
)
# update channel setting all fields
tx = await self.out(self.update_channel(claim_id, **values))
txo = tx['outputs'][0]
values['public_key'] = public_key
values['tags'].insert(0, 'blah') # existing tag
self.assertEqual(
txo['value']['channel'],
values
)
# clearing and settings tags
tx = await self.out(self.update_channel(claim_id, tags='single', clear_tags=True))
txo = tx['outputs'][0]
values['tags'] = ['single']
self.assertEqual(
txo['value']['channel'],
values
)
# reset signing key
tx = await self.out(self.update_channel(claim_id, new_signing_key=True))
txo = tx['outputs'][0]
self.assertNotEqual(
txo['value']['channel']['public_key'],
values['public_key']
)
# send channel to someone else
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'])
# before sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 3)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 0)
other_address = await account2.receiving.get_or_create_usable_address()
tx = await self.out(self.update_channel(claim_id, claim_address=other_address))
# after sending
self.assertEqual(len(await self.daemon.jsonrpc_channel_list()), 2)
self.assertEqual(len(await self.daemon.jsonrpc_channel_list(account_id=account2_id)), 1)
# shoud not have private key
txo = (await account2.get_channels())[0]
self.assertIsNone(txo.private_key)
# send the private key too
txoid = f"{tx['outputs'][0]['txid']}:{tx['outputs'][0]['nout']}"
account2.channel_keys[txoid] = self.account.channel_keys[txoid]
# now should have private key
txo = (await account2.get_channels())[0]
self.assertIsNotNone(txo.private_key)
async def test_create_update_and_abandon_claim(self):
await self.assertBalance(self.account, '10.0') await self.assertBalance(self.account, '10.0')
await self.make_claim(amount='9.0') tx = await self.create_claim(bid='2.5') # creates new claim
await self.assertBalance(self.account, '0.979893') claim_id = tx['outputs'][0]['claim_id']
txs = await self.out(self.daemon.jsonrpc_transaction_list())
self.assertEqual(len(txs[0]['claim_info']), 1)
self.assertEqual(txs[0]['confirmations'], 1)
self.assertEqual(txs[0]['claim_info'][0]['balance_delta'], '-2.5')
self.assertEqual(txs[0]['claim_info'][0]['claim_id'], claim_id)
self.assertEqual(txs[0]['value'], '0.0')
self.assertEqual(txs[0]['fee'], '-0.020107')
await self.assertBalance(self.account, '7.479893')
# update the same claim await self.update_claim(claim_id, bid='1.0') # updates previous claim
await self.make_claim(amount='9.0') txs = await self.out(self.daemon.jsonrpc_transaction_list())
await self.assertBalance(self.account, '0.979637') 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]['claim_id'], claim_id)
self.assertEqual(txs[0]['value'], '0.0')
self.assertEqual(txs[0]['fee'], '-0.000184')
await self.assertBalance(self.account, '8.979709')
# update the claim a second time but use even more funds await self.out(self.daemon.jsonrpc_claim_abandon(claim_id))
await self.make_claim(amount='9.97') txs = await self.out(self.daemon.jsonrpc_transaction_list())
await self.assertBalance(self.account, '0.009381') self.assertEqual(len(txs[0]['abandon_info']), 1)
self.assertEqual(txs[0]['abandon_info'][0]['balance_delta'], '1.0')
# fails when specifying more than available self.assertEqual(txs[0]['abandon_info'][0]['claim_id'], claim_id)
with tempfile.NamedTemporaryFile() as file: self.assertEqual(txs[0]['value'], '0.0')
file.write(b'hi!') self.assertEqual(txs[0]['fee'], '-0.000107')
file.flush() await self.assertBalance(self.account, '9.979602')
with self.assertRaisesRegex(
InsufficientFundsError,
"Please lower the bid value, the maximum amount"
" you can specify for this claim is 9.979307."
):
await self.out(self.daemon.jsonrpc_publish(
'hovercraft', '9.98', file_path=file.name
))
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')
claim = await self.make_claim(amount='0.0001') tx = await self.create_claim(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(claim['claim_id'])) await self.out(self.daemon.jsonrpc_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): async def test_claim_show(self):
channel = await self.out(self.daemon.jsonrpc_channel_new('@abc', "1.0")) channel = await self.create_channel('@abc', '1.0')
self.assertTrue(channel['success'])
await self.confirm_tx(channel['tx']['txid'])
channel_from_claim_show = await self.out( channel_from_claim_show = await self.out(
self.daemon.jsonrpc_claim_show(txid=channel['tx']['txid'], nout=channel['output']['nout']) self.daemon.jsonrpc_claim_show(txid=channel['txid'], nout=0)
) )
self.assertEqual(channel_from_claim_show['value'], channel['output']['value']) self.assertEqual(channel_from_claim_show['value'], channel['outputs'][0]['value'])
channel_from_claim_show = await self.out( channel_from_claim_show = await self.out(
self.daemon.jsonrpc_claim_show(claim_id=channel['claim_id']) self.daemon.jsonrpc_claim_show(claim_id=channel['outputs'][0]['claim_id'])
) )
self.assertEqual(channel_from_claim_show['value'], channel['output']['value']) self.assertEqual(channel_from_claim_show['value'], channel['outputs'][0]['value'])
abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=channel['tx']['txid'], nout=0, blocking=False)) abandon = await self.out(self.daemon.jsonrpc_claim_abandon(txid=channel['txid'], nout=0, blocking=False))
self.assertTrue(abandon['success']) self.assertTrue(abandon['success'])
await self.confirm_tx(abandon['tx']['txid']) await self.confirm_tx(abandon['tx']['txid'])
not_a_claim = await self.out( not_a_claim = await self.out(
@ -184,13 +402,10 @@ class ClaimCommands(CommandTestCase):
self.assertEqual(not_a_claim, 'claim not found') self.assertEqual(not_a_claim, 'claim not found')
async def test_claim_list(self): async def test_claim_list(self):
channel = await self.out(self.daemon.jsonrpc_channel_new('@abc', "1.0")) channel = await self.create_channel('@abc', '1.0')
self.assertTrue(channel['success']) channel_id = channel['outputs'][0]['claim_id']
await self.confirm_tx(channel['tx']['txid']) claim = await self.create_claim('on-channel-claim', '0.0001', channel_id=channel_id)
claim = await self.make_claim(amount='0.0001', name='on-channel-claim', channel_name='@abc') unsigned_claim = await self.create_claim('unsigned', '0.0001')
self.assertTrue(claim['success'])
unsigned_claim = await self.make_claim(amount='0.0001', name='unsigned')
self.assertTrue(claim['success'])
channel_from_claim_list = await self.out(self.daemon.jsonrpc_claim_list('@abc')) channel_from_claim_list = await self.out(self.daemon.jsonrpc_claim_list('@abc'))
self.assertEqual(channel_from_claim_list['claims'][0]['value'], channel['output']['value']) self.assertEqual(channel_from_claim_list['claims'][0]['value'], channel['output']['value'])
@ -373,8 +588,8 @@ class ClaimCommands(CommandTestCase):
# 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.make_claim('ΣίσυφοςfiÆ', '0.1') c1 = await self.create_claim('ΣίσυφοςfiÆ', '0.1')
c2 = await self.make_claim('ΣΊΣΥΦΟσFIæ', '0.2') c2 = await self.create_claim('ΣΊΣΥΦΟσ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æ')

View file

@ -4,7 +4,6 @@ import os
from integration.testcase import CommandTestCase from integration.testcase import CommandTestCase
from lbrynet.blob_exchange.downloader import BlobDownloader from lbrynet.blob_exchange.downloader import BlobDownloader
from lbrynet.error import InsufficientFundsError
class FileCommands(CommandTestCase): class FileCommands(CommandTestCase):
@ -12,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.make_claim('foo', '0.01') await self.create_claim('foo', '0.01')
await self.make_claim('foo2', '0.01') await self.create_claim('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')
@ -28,8 +27,8 @@ 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):
claim = await self.make_claim('foo', '0.01') tx = await self.create_claim('foo', '0.01')
sd_hash = claim['output']['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 = [
blob_hash for blob_hash in self.server.blob_manager.completed_blob_hashes if blob_hash != sd_hash blob_hash for blob_hash in self.server.blob_manager.completed_blob_hashes if blob_hash != sd_hash
@ -49,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.make_claim('foo', '0.01', data=bytes([0]*(1<<23))) await self.create_claim('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')
@ -70,8 +69,8 @@ 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):
claim = await self.make_claim('foo', '0.01') tx = await self.create_claim('foo', '0.01')
sd_hash = claim['output']['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')
blobs = await self.server_storage.get_blobs_for_stream( blobs = await self.server_storage.get_blobs_for_stream(
@ -89,8 +88,8 @@ 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):
claim = await self.make_claim('foo', '0.01') tx = await self.create_claim('foo', '0.01')
sd_hash = claim['output']['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(
await self.server_storage.get_stream_hash_for_sd_hash(sd_hash) await self.server_storage.get_stream_hash_for_sd_hash(sd_hash)
@ -129,8 +128,8 @@ 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
claim = await self.make_claim('foo', '0.01', data=bytes([0]*(1<<23))) tx = await self.create_claim('foo', '0.01', data=bytes([0]*(1<<23)))
sd_hash = claim['output']['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')
# backup blob # backup blob
@ -151,27 +150,30 @@ 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.make_claim( await self.create_claim(
'expensive', '0.01', data=b'pay me if you can', 'expensive', '0.01', data=b'pay me if you can',
fee={'currency': 'LBC', 'amount': 11.0, 'address': target_address}) fee_currency='LBC', fee_amount='11.0', fee_address=target_address
)
await self.daemon.jsonrpc_file_delete(claim_name='expensive') await self.daemon.jsonrpc_file_delete(claim_name='expensive')
response = await self.daemon.jsonrpc_get('lbry://expensive') response = await self.daemon.jsonrpc_get('lbry://expensive')
self.assertEqual(response['error'], 'fee of 11.00000 exceeds max available balance') self.assertEqual(response['error'], 'fee of 11.00000 exceeds max available balance')
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.make_claim( await self.create_claim(
'maxkey', '0.01', data=b'no pay me, no', 'maxkey', '0.01', data=b'no pay me, no',
fee={'currency': 'LBC', 'amount': 111.0, 'address': target_address}) fee_currency='LBC', fee_amount='111.0', fee_address=target_address
)
await self.daemon.jsonrpc_file_delete(claim_name='maxkey') await self.daemon.jsonrpc_file_delete(claim_name='maxkey')
response = await self.daemon.jsonrpc_get('lbry://maxkey') response = await self.daemon.jsonrpc_get('lbry://maxkey')
self.assertEqual(len(self.daemon.jsonrpc_file_list()), 0) self.assertEqual(len(self.daemon.jsonrpc_file_list()), 0)
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.make_claim( await self.create_claim(
'icanpay', '0.01', data=b'I got the power!', 'icanpay', '0.01', data=b'I got the power!',
fee={'currency': 'LBC', 'amount': 1.0, 'address': target_address}) fee_currency='LBC', fee_amount='1.0', fee_address=target_address
)
await self.daemon.jsonrpc_file_delete(claim_name='icanpay') await self.daemon.jsonrpc_file_delete(claim_name='icanpay')
await self.assertBalance(self.account, '9.925679') await self.assertBalance(self.account, '9.925679')
response = await self.daemon.jsonrpc_get('lbry://icanpay') response = await self.daemon.jsonrpc_get('lbry://icanpay')

View file

@ -4,7 +4,8 @@ from integration.testcase import CommandTestCase
class ResolveCommand(CommandTestCase): class ResolveCommand(CommandTestCase):
async def test_resolve(self): async def test_resolve(self):
await self.make_channel('@abc', '0.01') tx = await self.create_channel('@abc', '0.01')
channel_id = tx['outputs'][0]['claim_id']
# resolving a channel @abc # resolving a channel @abc
response = await self.resolve('lbry://@abc') response = await self.resolve('lbry://@abc')
@ -14,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.make_claim('foo', '0.01', channel_name='@abc') await self.create_claim('foo', '0.01', channel_id=channel_id)
await self.make_claim('foo2', '0.01', channel_name='@abc') await self.create_claim('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

@ -62,7 +62,7 @@ class AccountSynchronization(AsyncioTestCase):
self.account.modified_on = 123.456 self.account.modified_on = 123.456
self.assertEqual(self.daemon.jsonrpc_sync_hash(), starting_hash) self.assertEqual(self.daemon.jsonrpc_sync_hash(), starting_hash)
self.assertEqual(self.daemon.jsonrpc_sync_apply('password')['hash'], starting_hash) self.assertEqual(self.daemon.jsonrpc_sync_apply('password')['hash'], starting_hash)
self.assertFalse(self.account.certificates) self.assertFalse(self.account.channel_keys)
hash_w_cert = '974721f42dab42657b5911b7caf4af98ce4d3879eea6ac23d50c1d79bc5020ef' hash_w_cert = '974721f42dab42657b5911b7caf4af98ce4d3879eea6ac23d50c1d79bc5020ef'
add_cert = ( add_cert = (
@ -78,9 +78,9 @@ class AccountSynchronization(AsyncioTestCase):
) )
self.daemon.jsonrpc_sync_apply('password', data=add_cert) self.daemon.jsonrpc_sync_apply('password', data=add_cert)
self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert) self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert)
self.assertEqual(self.account.certificates, {'abcdefg1234:0': '---PRIVATE KEY---'}) self.assertEqual(self.account.channel_keys, {'abcdefg1234:0': '---PRIVATE KEY---'})
# applying the same diff is idempotent # applying the same diff is idempotent
self.daemon.jsonrpc_sync_apply('password', data=add_cert) self.daemon.jsonrpc_sync_apply('password', data=add_cert)
self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert) self.assertEqual(self.daemon.jsonrpc_sync_hash(), hash_w_cert)
self.assertEqual(self.account.certificates, {'abcdefg1234:0': '---PRIVATE KEY---'}) self.assertEqual(self.account.channel_keys, {'abcdefg1234:0': '---PRIVATE KEY---'})

View file

@ -155,29 +155,53 @@ 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 make_claim(self, name='hovercraft', amount='1.0', data=b'hi!', async def create_claim(self, name='hovercraft', bid='1.0', data=b'hi!', confirm=True, **kwargs):
channel_name=None, confirm=True, account_id=None, fee=None):
with tempfile.NamedTemporaryFile() as file: with tempfile.NamedTemporaryFile() as file:
file.write(data) file.write(data)
file.flush() file.flush()
claim = await self.out(self.daemon.jsonrpc_publish( claim = await self.out(
name, amount, file_path=file.name, channel_name=channel_name, account_id=account_id, self.daemon.jsonrpc_publish(name, bid, file_path=file.name, **kwargs)
fee=fee )
)) self.assertEqual(claim['outputs'][0]['name'], name)
self.assertTrue(claim['success'])
if confirm: if confirm:
await self.on_transaction_dict(claim['tx']) await self.on_transaction_dict(claim)
await self.generate(1) await self.generate(1)
await self.on_transaction_dict(claim['tx']) await self.on_transaction_dict(claim)
return claim return claim
async def make_channel(self, name='@arena', amount='1.0', confirm=True, account_id=None): async def update_claim(self, claim_id, data=None, confirm=True, **kwargs):
channel = await self.out(self.daemon.jsonrpc_channel_new(name, amount, account_id)) if data:
self.assertTrue(channel['success']) with tempfile.NamedTemporaryFile() as file:
file.write(data)
file.flush()
claim = await self.out(
self.daemon.jsonrpc_claim_update(claim_id, file_path=file.name, **kwargs)
)
else:
claim = await self.out(self.daemon.jsonrpc_claim_update(claim_id, **kwargs))
self.assertIsNotNone(claim['outputs'][0]['name'])
if confirm: if confirm:
await self.on_transaction_dict(channel['tx']) await self.on_transaction_dict(claim)
await self.generate(1) await self.generate(1)
await self.on_transaction_dict(channel['tx']) await self.on_transaction_dict(claim)
return claim
async def create_channel(self, name='@arena', bid='1.0', confirm=True, **kwargs):
channel = await self.out(self.daemon.jsonrpc_channel_create(name, bid, **kwargs))
self.assertEqual(channel['outputs'][0]['name'], name)
if confirm:
await self.on_transaction_dict(channel)
await self.generate(1)
await self.on_transaction_dict(channel)
return channel
async def update_channel(self, claim_id, confirm=True, **kwargs):
channel = await self.out(self.daemon.jsonrpc_channel_update(claim_id, **kwargs))
self.assertTrue(channel['outputs'][0]['name'].startswith('@'))
if confirm:
await self.on_transaction_dict(channel)
await self.generate(1)
await self.on_transaction_dict(channel)
return channel return channel
async def resolve(self, uri): async def resolve(self, uri):

View file

@ -1,5 +1,5 @@
import unittest import unittest
from lbrynet.extras.daemon import mime_types from lbrynet.schema import mime_types
class TestMimeTypes(unittest.TestCase): class TestMimeTypes(unittest.TestCase):