progress
This commit is contained in:
parent
8fc4f4aa73
commit
d47575e8e0
19 changed files with 1360 additions and 1177 deletions
|
@ -21,10 +21,6 @@ CURRENCIES = {
|
|||
'LBC': {'type': 'crypto'},
|
||||
'USD': {'type': 'fiat'},
|
||||
}
|
||||
SLACK_WEBHOOK = (
|
||||
'nUE0pUZ6Yl9bo29epl5moTSwnl5wo20ip2IlqzywMKZiIQSFZR5'
|
||||
'AHx4mY0VmF0WQZ1ESEP9kMHZlp1WzJwWOoKN3ImR1M2yUAaMyqGZ='
|
||||
)
|
||||
HEADERS_FILE_SHA256_CHECKSUM = (
|
||||
366295, 'b0c8197153a33ccbc52fb81a279588b6015b68b7726f73f6a2b81f7e25bfe4b9'
|
||||
)
|
||||
|
|
|
@ -265,10 +265,7 @@ class WalletComponent(Component):
|
|||
|
||||
async def start(self):
|
||||
log.info("Starting torba wallet")
|
||||
storage = self.component_manager.get_component(DATABASE_COMPONENT)
|
||||
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
|
||||
self.wallet_manager = await LbryWalletManager.from_lbrynet_config(self.conf)
|
||||
await self.wallet_manager.start()
|
||||
|
||||
async def stop(self):
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,13 @@
|
|||
import os.path
|
||||
from typing import List, Tuple
|
||||
from decimal import Decimal
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from google.protobuf.json_format import MessageToDict
|
||||
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.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 import compat
|
||||
from lbrynet.schema.base import Signable
|
||||
from lbrynet.schema.mime_types import guess_media_type
|
||||
|
||||
|
||||
hachoir_log.use_print = False
|
||||
|
||||
|
||||
class Claim(Signable):
|
||||
|
@ -54,6 +62,9 @@ class Claim(Signable):
|
|||
def channel(self) -> 'Channel':
|
||||
return Channel(self)
|
||||
|
||||
def to_dict(self):
|
||||
return MessageToDict(self.message, preserving_proto_field_name=True)
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes) -> 'Claim':
|
||||
try:
|
||||
|
@ -208,225 +219,243 @@ class Fee:
|
|||
self._fee.currency = FeeMessage.USD
|
||||
|
||||
|
||||
class Channel:
|
||||
class BaseClaimSubType:
|
||||
|
||||
__slots__ = '_claim', '_channel'
|
||||
__slots__ = 'claim', 'message'
|
||||
|
||||
def __init__(self, claim: Claim = None):
|
||||
self._claim = claim or Claim()
|
||||
self._channel = self._claim.channel_message
|
||||
|
||||
def to_dict(self):
|
||||
return MessageToDict(self._channel)
|
||||
def __init__(self, claim: Claim):
|
||||
self.claim = claim or Claim()
|
||||
|
||||
@property
|
||||
def claim(self) -> Claim:
|
||||
return self._claim
|
||||
def title(self) -> str:
|
||||
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
|
||||
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
|
||||
def public_key(self) -> str:
|
||||
return hexlify(self._channel.public_key).decode()
|
||||
return hexlify(self.message.public_key).decode()
|
||||
|
||||
@public_key.setter
|
||||
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
|
||||
def public_key_bytes(self) -> bytes:
|
||||
return self._channel.public_key
|
||||
return self.message.public_key
|
||||
|
||||
@public_key_bytes.setter
|
||||
def public_key_bytes(self, public_key: bytes):
|
||||
self._channel.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
|
||||
self.message.public_key = public_key
|
||||
|
||||
@property
|
||||
def contact_email(self) -> str:
|
||||
return self._channel.contact_email
|
||||
return self.message.contact_email
|
||||
|
||||
@contact_email.setter
|
||||
def contact_email(self, contact_email: str):
|
||||
self._channel.contact_email = contact_email
|
||||
self.message.contact_email = contact_email
|
||||
|
||||
@property
|
||||
def homepage_url(self) -> str:
|
||||
return self._channel.homepage_url
|
||||
return self.message.homepage_url
|
||||
|
||||
@homepage_url.setter
|
||||
def homepage_url(self, homepage_url: str):
|
||||
self._channel.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
|
||||
self.message.homepage_url = homepage_url
|
||||
|
||||
@property
|
||||
def cover_url(self) -> str:
|
||||
return self._channel.cover_url
|
||||
return self.message.cover_url
|
||||
|
||||
@cover_url.setter
|
||||
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):
|
||||
self._claim = claim or Claim()
|
||||
self._stream = self._claim.stream_message
|
||||
super().__init__(claim)
|
||||
self.message = self.claim.stream_message
|
||||
|
||||
def to_dict(self):
|
||||
return MessageToDict(self._stream)
|
||||
def update(
|
||||
self, file_path=None, duration=None,
|
||||
fee_currency=None, fee_amount=None, fee_address=None,
|
||||
video_height=None, video_width=None,
|
||||
**kwargs):
|
||||
|
||||
@property
|
||||
def claim(self) -> Claim:
|
||||
return self._claim
|
||||
super().update(**kwargs)
|
||||
|
||||
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
|
||||
def video(self) -> Video:
|
||||
return Video(self._stream.video)
|
||||
return Video(self.message.video)
|
||||
|
||||
@property
|
||||
def file(self) -> File:
|
||||
return File(self._stream.file)
|
||||
return File(self.message.file)
|
||||
|
||||
@property
|
||||
def fee(self) -> Fee:
|
||||
return Fee(self._stream.fee)
|
||||
return Fee(self.message.fee)
|
||||
|
||||
@property
|
||||
def has_fee(self) -> bool:
|
||||
return self._stream.HasField('fee')
|
||||
|
||||
@property
|
||||
def tags(self) -> List:
|
||||
return self._stream.tags
|
||||
return self.message.HasField('fee')
|
||||
|
||||
@property
|
||||
def hash(self) -> str:
|
||||
return hexlify(self._stream.hash).decode()
|
||||
return hexlify(self.message.hash).decode()
|
||||
|
||||
@hash.setter
|
||||
def hash(self, sd_hash: str):
|
||||
self._stream.hash = unhexlify(sd_hash.encode())
|
||||
self.message.hash = unhexlify(sd_hash.encode())
|
||||
|
||||
@property
|
||||
def hash_bytes(self) -> bytes:
|
||||
return self._stream.hash
|
||||
return self.message.hash
|
||||
|
||||
@hash_bytes.setter
|
||||
def hash_bytes(self, hash: bytes):
|
||||
self._stream.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
|
||||
self.message.hash = hash
|
||||
|
||||
@property
|
||||
def author(self) -> str:
|
||||
return self._stream.author
|
||||
return self.message.author
|
||||
|
||||
@author.setter
|
||||
def author(self, author: str):
|
||||
self._stream.author = author
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._stream.description
|
||||
|
||||
@description.setter
|
||||
def description(self, description: str):
|
||||
self._stream.description = description
|
||||
self.message.author = author
|
||||
|
||||
@property
|
||||
def media_type(self) -> str:
|
||||
return self._stream.media_type
|
||||
return self.message.media_type
|
||||
|
||||
@media_type.setter
|
||||
def media_type(self, media_type: str):
|
||||
self._stream.media_type = media_type
|
||||
self.message.media_type = media_type
|
||||
|
||||
@property
|
||||
def license(self) -> str:
|
||||
return self._stream.license
|
||||
return self.message.license
|
||||
|
||||
@license.setter
|
||||
def license(self, license: str):
|
||||
self._stream.license = license
|
||||
self.message.license = license
|
||||
|
||||
@property
|
||||
def license_url(self) -> str:
|
||||
return self._stream.license_url
|
||||
return self.message.license_url
|
||||
|
||||
@license_url.setter
|
||||
def license_url(self, license_url: str):
|
||||
self._stream.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
|
||||
self.message.license_url = license_url
|
||||
|
||||
@property
|
||||
def duration(self) -> int:
|
||||
return self._stream.duration
|
||||
return self.message.duration
|
||||
|
||||
@duration.setter
|
||||
def duration(self, duration: int):
|
||||
self._stream.duration = duration
|
||||
self.message.duration = duration
|
||||
|
||||
@property
|
||||
def release_time(self) -> int:
|
||||
return self._stream.release_time
|
||||
return self.message.release_time
|
||||
|
||||
@release_time.setter
|
||||
def release_time(self, release_time: int):
|
||||
self._stream.release_time = release_time
|
||||
self.message.release_time = release_time
|
||||
|
|
|
@ -4,7 +4,7 @@ import typing
|
|||
import logging
|
||||
import binascii
|
||||
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.descriptor import StreamDescriptor
|
||||
from lbrynet.stream.reflector.client import StreamReflectorClient
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import json
|
||||
import logging
|
||||
import binascii
|
||||
import typing
|
||||
from hashlib import sha256
|
||||
from string import hexdigits
|
||||
|
||||
from torba.client.baseaccount import BaseAccount
|
||||
from torba.client.basetransaction import TXORef
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from lbrynet.wallet import ledger
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -21,31 +25,32 @@ def validate_claim_id(claim_id):
|
|||
|
||||
|
||||
class Account(BaseAccount):
|
||||
ledger: 'ledger.MainNetLedger'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.certificates = {}
|
||||
self.channel_keys = {}
|
||||
|
||||
@property
|
||||
def hash(self) -> bytes:
|
||||
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())
|
||||
return h.digest()
|
||||
|
||||
def apply(self, d: dict):
|
||||
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):
|
||||
assert ref.id not in self.certificates, 'Trying to add a duplicate certificate.'
|
||||
self.certificates[ref.id] = private_key
|
||||
def add_channel_private_key(self, ref: TXORef, private_key):
|
||||
assert ref.id not in self.channel_keys, 'Trying to add a duplicate channel private key.'
|
||||
self.channel_keys[ref.id] = private_key
|
||||
|
||||
def get_certificate_private_key(self, ref: TXORef):
|
||||
return self.certificates.get(ref.id)
|
||||
def get_channel_private_key(self, ref: TXORef):
|
||||
return self.channel_keys.get(ref.id)
|
||||
|
||||
async def maybe_migrate_certificates(self):
|
||||
if not self.certificates:
|
||||
if not self.channel_keys:
|
||||
return
|
||||
|
||||
addresses = {}
|
||||
|
@ -59,7 +64,7 @@ class Account(BaseAccount):
|
|||
}
|
||||
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:
|
||||
try:
|
||||
validate_claim_id(maybe_claim_id)
|
||||
|
@ -71,7 +76,7 @@ class Account(BaseAccount):
|
|||
maybe_claim_id_bytes = maybe_claim_id_bytes.encode()
|
||||
decoded_double_hex = binascii.unhexlify(maybe_claim_id_bytes).decode()
|
||||
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)
|
||||
else:
|
||||
log.info("claim id was double hex encoded, fixing it")
|
||||
|
@ -80,9 +85,9 @@ class Account(BaseAccount):
|
|||
continue
|
||||
|
||||
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
|
||||
if ':' not in maybe_claim_id:
|
||||
try:
|
||||
|
@ -117,8 +122,8 @@ class Account(BaseAccount):
|
|||
.format(maybe_claim_id)
|
||||
)
|
||||
tx_nout = '{txid}:{nout}'.format(**claim)
|
||||
self.certificates[tx_nout] = self.certificates[maybe_claim_id]
|
||||
del self.certificates[maybe_claim_id]
|
||||
self.channel_keys[tx_nout] = self.channel_keys[maybe_claim_id]
|
||||
del self.channel_keys[maybe_claim_id]
|
||||
log.info(
|
||||
"Migrated certificate with claim_id '%s' ('%s') to a new look up key %s.",
|
||||
maybe_claim_id, txo.script.values['claim_name'], tx_nout
|
||||
|
@ -186,18 +191,18 @@ class Account(BaseAccount):
|
|||
@classmethod
|
||||
def from_dict(cls, ledger, wallet, d: dict) -> 'Account':
|
||||
account = super().from_dict(ledger, wallet, d)
|
||||
account.certificates = d.get('certificates', {})
|
||||
account.channel_keys = d.get('certificates', {})
|
||||
return account
|
||||
|
||||
def to_dict(self, include_certificates=True):
|
||||
def to_dict(self, include_channel_keys=True):
|
||||
d = super().to_dict()
|
||||
if include_certificates:
|
||||
d['certificates'] = self.certificates
|
||||
if include_channel_keys:
|
||||
d['certificates'] = self.channel_keys
|
||||
return d
|
||||
|
||||
async def get_details(self, **kwargs):
|
||||
details = await super().get_details(**kwargs)
|
||||
details['certificates'] = len(self.certificates)
|
||||
details['certificates'] = len(self.channel_keys)
|
||||
return details
|
||||
|
||||
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})
|
||||
|
||||
@staticmethod
|
||||
def constraint_utxos_sans_claims(constraints):
|
||||
def constraint_spending_utxos(constraints):
|
||||
constraints.update({'is_claim': 0, 'is_update': 0, 'is_support': 0})
|
||||
|
||||
def get_utxos(self, **constraints):
|
||||
self.constraint_utxos_sans_claims(constraints)
|
||||
self.constraint_spending_utxos(constraints)
|
||||
return super().get_utxos(**constraints)
|
||||
|
||||
def get_utxo_count(self, **constraints):
|
||||
self.constraint_utxos_sans_claims(constraints)
|
||||
self.constraint_spending_utxos(constraints)
|
||||
return super().get_utxo_count(**constraints)
|
||||
|
||||
def get_claims(self, **constraints):
|
||||
|
@ -230,6 +235,12 @@ class Account(BaseAccount):
|
|||
def get_channel_count(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):
|
||||
tx_class = self.ledger.transaction_class
|
||||
tx = await tx_class.create(
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from typing import List
|
||||
|
||||
from torba.client.basedatabase import BaseDatabase
|
||||
|
||||
from lbrynet.wallet.transaction import Output
|
||||
|
||||
|
||||
class WalletDatabase(BaseDatabase):
|
||||
|
||||
|
@ -48,7 +52,7 @@ class WalletDatabase(BaseDatabase):
|
|||
row['claim_name'] = txo.claim_name
|
||||
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))
|
||||
|
||||
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.claim.is_signed:
|
||||
channel_ids.add(txo.claim.signing_channel_id)
|
||||
if txo.claim_name.startswith('@') and my_account is not None:
|
||||
txo.private_key = my_account.get_certificate_private_key(txo.ref)
|
||||
if txo.claim.is_channel and my_account is not None:
|
||||
txo.private_key = my_account.get_channel_private_key(txo.ref)
|
||||
|
||||
if channel_ids:
|
||||
channels = {
|
||||
|
@ -77,11 +81,11 @@ class WalletDatabase(BaseDatabase):
|
|||
|
||||
@staticmethod
|
||||
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)
|
||||
return self.get_utxos(**constraints)
|
||||
return await self.get_utxos(**constraints)
|
||||
|
||||
def get_claim_count(self, **constraints):
|
||||
self.constrain_claims(constraints)
|
||||
|
@ -100,22 +104,17 @@ class WalletDatabase(BaseDatabase):
|
|||
self.constrain_channels(constraints)
|
||||
return self.get_claim_count(**constraints)
|
||||
|
||||
async def get_certificates(self, private_key_accounts, exclude_without_key=False, **constraints):
|
||||
channels = await self.get_channels(**constraints)
|
||||
certificates = []
|
||||
if private_key_accounts is not None:
|
||||
for channel in channels:
|
||||
if not channel.has_private_key:
|
||||
private_key = None
|
||||
for account in private_key_accounts:
|
||||
private_key = account.get_certificate_private_key(channel.ref)
|
||||
if private_key is not None:
|
||||
break
|
||||
if private_key is None and exclude_without_key:
|
||||
continue
|
||||
channel.private_key = private_key
|
||||
certificates.append(channel)
|
||||
return certificates
|
||||
@staticmethod
|
||||
def constrain_supports(constraints):
|
||||
constraints['is_support'] = 1
|
||||
|
||||
def get_supports(self, **constraints):
|
||||
self.constrain_supports(constraints)
|
||||
return self.get_utxos(**constraints)
|
||||
|
||||
def get_support_count(self, **constraints):
|
||||
self.constrain_supports(constraints)
|
||||
return self.get_utxo_count(**constraints)
|
||||
|
||||
async def release_all_outputs(self, account):
|
||||
await self.db.execute(
|
||||
|
|
|
@ -29,6 +29,8 @@ class MainNetLedger(BaseLedger):
|
|||
network_class = Network
|
||||
transaction_class = Transaction
|
||||
|
||||
db: WalletDatabase
|
||||
|
||||
secret_prefix = bytes((0x1c,))
|
||||
pubkey_address_prefix = bytes((0x55,))
|
||||
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), "
|
||||
"%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,
|
||||
channel_count, len(account.certificates), claim_count)
|
||||
channel_count, len(account.channel_keys), claim_count)
|
||||
|
||||
|
||||
class TestNetLedger(MainNetLedger):
|
||||
|
|
|
@ -155,7 +155,7 @@ class LbryWalletManager(BaseWalletManager):
|
|||
return receiving_addresses, change_addresses
|
||||
|
||||
@classmethod
|
||||
async def from_lbrynet_config(cls, settings, db):
|
||||
async def from_lbrynet_config(cls, settings):
|
||||
|
||||
ledger_id = {
|
||||
'lbrycrd_main': 'lbc_mainnet',
|
||||
|
@ -233,24 +233,6 @@ class LbryWalletManager(BaseWalletManager):
|
|||
await account.ledger.broadcast(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):
|
||||
destination_address: bytes = reserved.identifier.encode('latin1')
|
||||
return self.send_amount_to_address(amount, destination_address, account)
|
||||
|
@ -392,44 +374,6 @@ class LbryWalletManager(BaseWalletManager):
|
|||
def get_utxos(account: BaseAccount):
|
||||
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):
|
||||
holding_address = await account.receiving.get_or_create_usable_address()
|
||||
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
|
||||
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):
|
||||
pass # TODO: Data payments is disabled
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from cryptography.hazmat.primitives.asymmetric import ec
|
|||
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
|
||||
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 lbrynet.schema.claim import Claim
|
||||
from lbrynet.wallet.account import Account
|
||||
|
@ -117,6 +117,7 @@ class Output(BaseOutput):
|
|||
return True
|
||||
|
||||
def sign(self, channel: 'Output', first_input_id=None):
|
||||
self.channel = channel
|
||||
self.claim.signing_channel_hash = channel.claim_hash
|
||||
digest = sha256(b''.join([
|
||||
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):
|
||||
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.script.generate()
|
||||
return self.private_key
|
||||
|
||||
def is_channel_private_key(self, private_key_pem):
|
||||
|
@ -169,6 +171,9 @@ class Transaction(BaseTransaction):
|
|||
input_class = Input
|
||||
output_class = Output
|
||||
|
||||
outputs: ReadOnlyList[Output]
|
||||
inputs: ReadOnlyList[Input]
|
||||
|
||||
@classmethod
|
||||
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)
|
||||
|
@ -176,42 +181,52 @@ class Transaction(BaseTransaction):
|
|||
return cls.create([], [output], funding_accounts, change_account)
|
||||
|
||||
@classmethod
|
||||
def claim(cls, name: str, claim: Claim, amount: int, holding_address: bytes,
|
||||
funding_accounts: List[Account], change_account: Account):
|
||||
def claim_create(
|
||||
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)
|
||||
claim_output = Output.pay_claim_name_pubkey_hash(
|
||||
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
|
||||
def purchase(cls, claim: Output, amount: int, merchant_address: bytes,
|
||||
funding_accounts: List[Account], change_account: Account):
|
||||
funding_accounts: List[Account], change_account: Account):
|
||||
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
|
||||
claim_output = Output.purchase_claim_pubkey_hash(
|
||||
amount, claim.claim_id, ledger.address_to_hash160(merchant_address)
|
||||
)
|
||||
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
|
||||
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)
|
||||
|
|
1
setup.py
1
setup.py
|
@ -38,5 +38,6 @@ setup(
|
|||
'torba',
|
||||
'pyyaml==3.13',
|
||||
'docopt==0.6.2',
|
||||
'hachoir',
|
||||
],
|
||||
)
|
||||
|
|
|
@ -19,9 +19,8 @@ class EpicAdventuresOfChris45(CommandTestCase):
|
|||
# While making the spamdwich he wonders... has anyone on LBRY
|
||||
# registered the @spam channel yet? "I should do that!" he
|
||||
# exclaims and goes back to his computer to do just that!
|
||||
channel = await self.out(self.daemon.jsonrpc_channel_new('@spam', "1.0"))
|
||||
self.assertTrue(channel['success'])
|
||||
await self.confirm_tx(channel['tx']['txid'])
|
||||
tx = await self.create_channel('@spam', '1.0')
|
||||
channel_id = tx['outputs'][0]['claim_id']
|
||||
|
||||
# Do we have it locally?
|
||||
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
|
||||
# about eels driving a hovercraft across the wetlands while eating spam
|
||||
# and decides it's time to publish it to the @spam channel.
|
||||
with tempfile.NamedTemporaryFile() as file:
|
||||
file.write(b'blah blah blah...')
|
||||
file.write(b'[insert long story about eels driving hovercraft]')
|
||||
file.write(b'yada yada yada!')
|
||||
file.write(b'the end')
|
||||
file.flush()
|
||||
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'])
|
||||
tx = await self.create_claim(
|
||||
'hovercraft', '1.0',
|
||||
data=b'[insert long story about eels driving hovercraft]',
|
||||
channel_id=channel_id
|
||||
)
|
||||
claim_id = tx['outputs'][0]['claim_id']
|
||||
|
||||
# He quickly checks the unconfirmed balance to make sure everything looks
|
||||
# correct.
|
||||
|
@ -84,21 +78,11 @@ class EpicAdventuresOfChris45(CommandTestCase):
|
|||
# As people start reading his story they discover some typos and notify
|
||||
# Chris who explains in despair "Oh! Noooooos!" but then remembers
|
||||
# "No big deal! I can update my claim." And so he updates his claim.
|
||||
with tempfile.NamedTemporaryFile() as file:
|
||||
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'])
|
||||
await self.update_claim(claim_id, data=b'[typo fixing sounds being made]')
|
||||
|
||||
# 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.
|
||||
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'])
|
||||
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
|
||||
# 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:
|
||||
file.write(b'Amazingly Original First Line')
|
||||
file.write(b'Super plot for the grand novel')
|
||||
file.write(b'Totally un-cliched ending')
|
||||
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'])
|
||||
tx = await self.create_claim(
|
||||
'fresh-start', '1.0', data=b'Amazingly Original First Line', channel_id=channel_id
|
||||
)
|
||||
claim_id2 = tx['outputs'][0]['claim_id']
|
||||
|
||||
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
|
||||
# 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(
|
||||
'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'])
|
||||
|
||||
|
@ -170,7 +147,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
|
|||
# Now he also wanted to support the original creator of the Award Winning Novel
|
||||
# So he quickly decides to send a tip to him
|
||||
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'])
|
||||
|
||||
# And again checks if it went to the just right place
|
||||
|
@ -181,7 +158,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
|
|||
await self.generate(5)
|
||||
|
||||
# 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'])
|
||||
|
||||
# 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!
|
||||
# .......
|
||||
# Chris agrees.. 17 hours 43 minutes and 14 seconds later, he makes his publish
|
||||
with tempfile.NamedTemporaryFile() as file:
|
||||
file.write(b'The Whale amd The Bookmark')
|
||||
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'])
|
||||
tx = await self.out(self.daemon.jsonrpc_publish(
|
||||
'hit-song', '1.0', data=b'The Whale and The Bookmark', channel_id=channel_id
|
||||
))
|
||||
|
||||
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...
|
||||
# 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'])
|
||||
await self.confirm_tx(abandon['tx']['txid'])
|
||||
|
||||
|
|
|
@ -1,79 +1,224 @@
|
|||
import hashlib
|
||||
import tempfile
|
||||
from binascii import unhexlify
|
||||
|
||||
import ecdsa
|
||||
|
||||
from lbrynet.wallet.transaction import Transaction, Output
|
||||
from lbrynet.error import InsufficientFundsError
|
||||
from lbrynet.schema.claim import Claim
|
||||
from torba.client.errors import InsufficientFundsError
|
||||
from lbrynet.schema.compat import OldClaimMessage
|
||||
|
||||
from integration.testcase import CommandTestCase
|
||||
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):
|
||||
|
||||
async def test_create_update_and_abandon_claim(self):
|
||||
await self.assertBalance(self.account, '10.0')
|
||||
async def test_create_claim_names(self):
|
||||
# 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
|
||||
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['claim_id'])
|
||||
self.assertEqual(txs[0]['value'], '0.0')
|
||||
self.assertEqual(txs[0]['fee'], '-0.020107')
|
||||
await self.assertBalance(self.account, '7.479893')
|
||||
# fail to claim duplicate
|
||||
with self.assertRaisesRegex(Exception, "You already have a claim published under the name 'foo'."):
|
||||
await self.create_claim('foo')
|
||||
|
||||
await self.make_claim(amount='1.0') # updates previous claim
|
||||
txs = await self.out(self.daemon.jsonrpc_transaction_list())
|
||||
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['claim_id'])
|
||||
self.assertEqual(txs[0]['value'], '0.0')
|
||||
self.assertEqual(txs[0]['fee'], '-0.000182')
|
||||
await self.assertBalance(self.account, '8.979711')
|
||||
# fail claim starting with @
|
||||
with self.assertRaisesRegex(Exception, "Claim names cannot start with @ symbol."):
|
||||
await self.create_claim('@foo')
|
||||
|
||||
await self.out(self.daemon.jsonrpc_claim_abandon(claim['claim_id']))
|
||||
txs = await self.out(self.daemon.jsonrpc_transaction_list())
|
||||
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')
|
||||
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 1)
|
||||
await self.assertBalance(self.account, '8.993893')
|
||||
|
||||
async def test_update_claim_holding_address(self):
|
||||
other_account_id = (await self.daemon.jsonrpc_account_create('second account'))['id']
|
||||
other_account = self.daemon.get_account_or_error(other_account_id)
|
||||
other_address = await other_account.receiving.get_or_create_usable_address()
|
||||
# succeed overriding duplicate restriction
|
||||
await self.create_claim('foo', allow_duplicate_name=True)
|
||||
self.assertEqual(len(await self.daemon.jsonrpc_claim_list()), 2)
|
||||
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
|
||||
claim = await self.make_claim()
|
||||
# bid preserved on update
|
||||
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)
|
||||
self.assertEqual(len(await self.daemon.jsonrpc_claim_list_mine(account_id=other_account_id)), 0)
|
||||
tx = await self.daemon.jsonrpc_claim_send_to_address(
|
||||
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)
|
||||
# bid changed on update
|
||||
tx = await self.update_claim(claim_id, bid='3.0')
|
||||
self.assertEqual(tx['outputs'][0]['amount'], '3.0')
|
||||
|
||||
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
|
||||
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'])
|
||||
|
||||
await self.out(self.create_channel('@spam', '1.0'))
|
||||
self.assertEqual('8.989893', await self.daemon.jsonrpc_account_balance())
|
||||
|
||||
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('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'])
|
||||
baz_tx = await self.out(self.create_channel('@baz', '1.0', account_id=account2_id))
|
||||
baz_id = baz_tx['outputs'][0]['claim_id']
|
||||
|
||||
channels = await self.out(self.daemon.jsonrpc_channel_list(account1_id))
|
||||
self.assertEqual(len(channels), 1)
|
||||
|
@ -98,84 +242,158 @@ class ClaimCommands(CommandTestCase):
|
|||
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'])
|
||||
|
||||
await self.create_claim('hovercraft1', channel_id=baz_id)
|
||||
# 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'])
|
||||
|
||||
await self.create_claim('hovercraft2', channel_id=baz_id, channel_account_id=[account2_id])
|
||||
# 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]
|
||||
))
|
||||
with self.assertRaisesRegex(ValueError, "Couldn't find channel with channel_id"):
|
||||
await self.create_claim(
|
||||
'hovercraft3', channel_id=baz_id, 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.make_claim(amount='9.0')
|
||||
await self.assertBalance(self.account, '0.979893')
|
||||
tx = await self.create_claim(bid='2.5') # creates new claim
|
||||
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.make_claim(amount='9.0')
|
||||
await self.assertBalance(self.account, '0.979637')
|
||||
await self.update_claim(claim_id, bid='1.0') # updates previous claim
|
||||
txs = await self.out(self.daemon.jsonrpc_transaction_list())
|
||||
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.make_claim(amount='9.97')
|
||||
await self.assertBalance(self.account, '0.009381')
|
||||
|
||||
# fails when specifying more than available
|
||||
with tempfile.NamedTemporaryFile() as file:
|
||||
file.write(b'hi!')
|
||||
file.flush()
|
||||
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
|
||||
))
|
||||
await self.out(self.daemon.jsonrpc_claim_abandon(claim_id))
|
||||
txs = await self.out(self.daemon.jsonrpc_transaction_list())
|
||||
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_id)
|
||||
self.assertEqual(txs[0]['value'], '0.0')
|
||||
self.assertEqual(txs[0]['fee'], '-0.000107')
|
||||
await self.assertBalance(self.account, '9.979602')
|
||||
|
||||
async def test_abandoning_claim_at_loss(self):
|
||||
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.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')
|
||||
|
||||
async def test_claim_show(self):
|
||||
channel = await self.out(self.daemon.jsonrpc_channel_new('@abc', "1.0"))
|
||||
self.assertTrue(channel['success'])
|
||||
await self.confirm_tx(channel['tx']['txid'])
|
||||
channel = await self.create_channel('@abc', '1.0')
|
||||
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(
|
||||
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'])
|
||||
await self.confirm_tx(abandon['tx']['txid'])
|
||||
not_a_claim = await self.out(
|
||||
|
@ -184,13 +402,10 @@ class ClaimCommands(CommandTestCase):
|
|||
self.assertEqual(not_a_claim, 'claim not found')
|
||||
|
||||
async def test_claim_list(self):
|
||||
channel = await self.out(self.daemon.jsonrpc_channel_new('@abc', "1.0"))
|
||||
self.assertTrue(channel['success'])
|
||||
await self.confirm_tx(channel['tx']['txid'])
|
||||
claim = await self.make_claim(amount='0.0001', name='on-channel-claim', channel_name='@abc')
|
||||
self.assertTrue(claim['success'])
|
||||
unsigned_claim = await self.make_claim(amount='0.0001', name='unsigned')
|
||||
self.assertTrue(claim['success'])
|
||||
channel = await self.create_channel('@abc', '1.0')
|
||||
channel_id = channel['outputs'][0]['claim_id']
|
||||
claim = await self.create_claim('on-channel-claim', '0.0001', channel_id=channel_id)
|
||||
unsigned_claim = await self.create_claim('unsigned', '0.0001')
|
||||
|
||||
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'])
|
||||
|
@ -373,8 +588,8 @@ class ClaimCommands(CommandTestCase):
|
|||
|
||||
# this test assumes that the lbrycrd forks normalization at height == 250 on regtest
|
||||
|
||||
c1 = await self.make_claim('ΣίσυφοςfiÆ', '0.1')
|
||||
c2 = await self.make_claim('ΣΊΣΥΦΟσFIæ', '0.2')
|
||||
c1 = await self.create_claim('ΣίσυφοςfiÆ', '0.1')
|
||||
c2 = await self.create_claim('ΣΊΣΥΦΟσFIæ', '0.2')
|
||||
|
||||
r1 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣίσυφοςfiÆ')
|
||||
r2 = await self.daemon.jsonrpc_resolve(urls='lbry://ΣΊΣΥΦΟσFIæ')
|
||||
|
|
|
@ -4,7 +4,6 @@ import os
|
|||
|
||||
from integration.testcase import CommandTestCase
|
||||
from lbrynet.blob_exchange.downloader import BlobDownloader
|
||||
from lbrynet.error import InsufficientFundsError
|
||||
|
||||
|
||||
class FileCommands(CommandTestCase):
|
||||
|
@ -12,8 +11,8 @@ class FileCommands(CommandTestCase):
|
|||
VERBOSITY = logging.WARN
|
||||
|
||||
async def test_file_management(self):
|
||||
await self.make_claim('foo', '0.01')
|
||||
await self.make_claim('foo2', '0.01')
|
||||
await self.create_claim('foo', '0.01')
|
||||
await self.create_claim('foo2', '0.01')
|
||||
|
||||
file1, file2 = self.daemon.jsonrpc_file_list('claim_name')
|
||||
self.assertEqual(file1['claim_name'], 'foo')
|
||||
|
@ -28,8 +27,8 @@ class FileCommands(CommandTestCase):
|
|||
self.assertEqual(len(self.daemon.jsonrpc_file_list()), 1)
|
||||
|
||||
async def test_download_different_timeouts(self):
|
||||
claim = await self.make_claim('foo', '0.01')
|
||||
sd_hash = claim['output']['value']['stream']['hash']
|
||||
tx = await self.create_claim('foo', '0.01')
|
||||
sd_hash = tx['outputs'][0]['value']['stream']['hash']
|
||||
await self.daemon.jsonrpc_file_delete(claim_name='foo')
|
||||
all_except_sd = [
|
||||
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)
|
||||
|
||||
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]
|
||||
original_path = os.path.join(self.daemon.conf.download_dir, file_info['file_name'])
|
||||
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
|
||||
|
||||
async def test_incomplete_downloads_erases_output_file_on_stop(self):
|
||||
claim = await self.make_claim('foo', '0.01')
|
||||
sd_hash = claim['output']['value']['stream']['hash']
|
||||
tx = await self.create_claim('foo', '0.01')
|
||||
sd_hash = tx['outputs'][0]['value']['stream']['hash']
|
||||
file_info = self.daemon.jsonrpc_file_list()[0]
|
||||
await self.daemon.jsonrpc_file_delete(claim_name='foo')
|
||||
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'])))
|
||||
|
||||
async def test_incomplete_downloads_retry(self):
|
||||
claim = await self.make_claim('foo', '0.01')
|
||||
sd_hash = claim['output']['value']['stream']['hash']
|
||||
tx = await self.create_claim('foo', '0.01')
|
||||
sd_hash = tx['outputs'][0]['value']['stream']['hash']
|
||||
await self.daemon.jsonrpc_file_delete(claim_name='foo')
|
||||
blobs = await self.server_storage.get_blobs_for_stream(
|
||||
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):
|
||||
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)))
|
||||
sd_hash = claim['output']['value']['stream']['hash']
|
||||
tx = await self.create_claim('foo', '0.01', data=bytes([0]*(1<<23)))
|
||||
sd_hash = tx['outputs'][0]['value']['stream']['hash']
|
||||
missing_blob_hash = (await self.daemon.jsonrpc_blob_list(sd_hash=sd_hash))[-2]
|
||||
await self.daemon.jsonrpc_file_delete(claim_name='foo')
|
||||
# backup blob
|
||||
|
@ -151,27 +150,30 @@ class FileCommands(CommandTestCase):
|
|||
target_address = await self.blockchain.get_raw_change_address()
|
||||
|
||||
# FAIL: beyond available balance
|
||||
await self.make_claim(
|
||||
await self.create_claim(
|
||||
'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')
|
||||
response = await self.daemon.jsonrpc_get('lbry://expensive')
|
||||
self.assertEqual(response['error'], 'fee of 11.00000 exceeds max available balance')
|
||||
self.assertEqual(len(self.daemon.jsonrpc_file_list()), 0)
|
||||
|
||||
# FAIL: beyond maximum key fee
|
||||
await self.make_claim(
|
||||
await self.create_claim(
|
||||
'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')
|
||||
response = await self.daemon.jsonrpc_get('lbry://maxkey')
|
||||
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')
|
||||
|
||||
# PASS: purchase is successful
|
||||
await self.make_claim(
|
||||
await self.create_claim(
|
||||
'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.assertBalance(self.account, '9.925679')
|
||||
response = await self.daemon.jsonrpc_get('lbry://icanpay')
|
||||
|
|
|
@ -4,7 +4,8 @@ from integration.testcase import CommandTestCase
|
|||
class ResolveCommand(CommandTestCase):
|
||||
|
||||
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
|
||||
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']['claims_in_channel'], 0)
|
||||
|
||||
await self.make_claim('foo', '0.01', channel_name='@abc')
|
||||
await self.make_claim('foo2', '0.01', channel_name='@abc')
|
||||
await self.create_claim('foo', '0.01', channel_id=channel_id)
|
||||
await self.create_claim('foo2', '0.01', channel_id=channel_id)
|
||||
|
||||
# resolving a channel @abc with some claims in it
|
||||
response = await self.resolve('lbry://@abc')
|
||||
|
|
|
@ -62,7 +62,7 @@ class AccountSynchronization(AsyncioTestCase):
|
|||
self.account.modified_on = 123.456
|
||||
self.assertEqual(self.daemon.jsonrpc_sync_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'
|
||||
add_cert = (
|
||||
|
@ -78,9 +78,9 @@ class AccountSynchronization(AsyncioTestCase):
|
|||
)
|
||||
self.daemon.jsonrpc_sync_apply('password', data=add_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
|
||||
self.daemon.jsonrpc_sync_apply('password', data=add_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---'})
|
||||
|
|
|
@ -155,29 +155,53 @@ class CommandTestCase(IntegrationTestCase):
|
|||
to JSON and then back to a dictionary. """
|
||||
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!',
|
||||
channel_name=None, confirm=True, account_id=None, fee=None):
|
||||
async def create_claim(self, name='hovercraft', bid='1.0', data=b'hi!', confirm=True, **kwargs):
|
||||
with tempfile.NamedTemporaryFile() as file:
|
||||
file.write(data)
|
||||
file.flush()
|
||||
claim = await self.out(self.daemon.jsonrpc_publish(
|
||||
name, amount, file_path=file.name, channel_name=channel_name, account_id=account_id,
|
||||
fee=fee
|
||||
))
|
||||
self.assertTrue(claim['success'])
|
||||
claim = await self.out(
|
||||
self.daemon.jsonrpc_publish(name, bid, file_path=file.name, **kwargs)
|
||||
)
|
||||
self.assertEqual(claim['outputs'][0]['name'], name)
|
||||
if confirm:
|
||||
await self.on_transaction_dict(claim['tx'])
|
||||
await self.on_transaction_dict(claim)
|
||||
await self.generate(1)
|
||||
await self.on_transaction_dict(claim['tx'])
|
||||
await self.on_transaction_dict(claim)
|
||||
return claim
|
||||
|
||||
async def make_channel(self, name='@arena', amount='1.0', confirm=True, account_id=None):
|
||||
channel = await self.out(self.daemon.jsonrpc_channel_new(name, amount, account_id))
|
||||
self.assertTrue(channel['success'])
|
||||
async def update_claim(self, claim_id, data=None, confirm=True, **kwargs):
|
||||
if data:
|
||||
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:
|
||||
await self.on_transaction_dict(channel['tx'])
|
||||
await self.on_transaction_dict(claim)
|
||||
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
|
||||
|
||||
async def resolve(self, uri):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import unittest
|
||||
from lbrynet.extras.daemon import mime_types
|
||||
from lbrynet.schema import mime_types
|
||||
|
||||
|
||||
class TestMimeTypes(unittest.TestCase):
|
||||
|
|
Loading…
Reference in a new issue