Esempio n. 1
0
 def encode_transaction(self, tx):
     return {
         'txid': tx.id,
         'height': tx.height,
         'inputs': [self.encode_input(txo) for txo in tx.inputs],
         'outputs': [self.encode_output(txo) for txo in tx.outputs],
         'total_input': dewies_to_lbc(tx.input_sum),
         'total_output': dewies_to_lbc(tx.input_sum - tx.fee),
         'total_fee': dewies_to_lbc(tx.fee),
         'hex': hexlify(tx.raw).decode(),
     }
Esempio n. 2
0
 async def _report_state(self):
     try:
         for account in self.accounts:
             balance = dewies_to_lbc(await account.get_balance())
             channel_count = await account.get_channel_count()
             claim_count = await account.get_claim_count()
             if isinstance(account.receiving, SingleKey):
                 log.info(
                     "Loaded single key account %s with %s LBC. "
                     "%d channels, %d certificates and %d claims",
                     account.id, balance, channel_count,
                     len(account.channel_keys), claim_count)
             else:
                 total_receiving = len((await
                                        account.receiving.get_addresses()))
                 total_change = len((await account.change.get_addresses()))
                 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.channel_keys), claim_count)
     except:
         log.exception(
             'Failed to display wallet state, please file issue '
             'for this bug along with the traceback you see below:')
    async def assertStreamPurchased(self, stream: Transaction, operation):

        await self.account.release_all_outputs()
        buyer_balance = await self.account.get_balance()
        merchant_balance = lbc_to_dewies(str(await self.blockchain.get_balance()))
        pre_purchase_count = (await self.daemon.jsonrpc_purchase_list())['total_items']
        purchase = await operation()
        stream_txo, purchase_txo = stream.outputs[0], purchase.outputs[0]
        stream_fee = stream_txo.claim.stream.fee
        self.assertEqual(stream_fee.dewies, purchase_txo.amount)
        self.assertEqual(stream_fee.address, purchase_txo.get_address(self.ledger))

        await self.ledger.wait(purchase)
        await self.generate(1)
        merchant_balance += lbc_to_dewies('1.0')  # block reward
        await self.ledger.wait(purchase)

        self.assertEqual(
            await self.account.get_balance(), buyer_balance - (purchase.input_sum-purchase.outputs[2].amount))
        self.assertEqual(
            str(await self.blockchain.get_balance()), dewies_to_lbc(merchant_balance + purchase_txo.amount))

        purchases = await self.daemon.jsonrpc_purchase_list()
        self.assertEqual(purchases['total_items'], pre_purchase_count+1)

        tx = purchases['items'][0].tx_ref.tx
        self.assertEqual(len(tx.outputs), 3)  # purchase txo, purchase data, change

        txo0 = tx.outputs[0]
        txo1 = tx.outputs[1]
        self.assertEqual(txo0.purchase, txo1)  # purchase txo has reference to purchase data
        self.assertTrue(txo1.is_purchase_data)
        self.assertTrue(txo1.can_decode_purchase_data)
        self.assertIsInstance(txo1.purchase_data, Purchase)
        self.assertEqual(txo1.purchase_data.claim_id, stream_txo.claim_id)
Esempio n. 4
0
 def _convert_to_old_resolve_output(wallet_manager, resolves):
     result = {}
     for url, txo in resolves.items():
         if isinstance(txo, Output):
             tx_height = txo.tx_ref.height
             best_height = wallet_manager.ledger.headers.height
             result[url] = {
                 'name': txo.claim_name,
                 'value': txo.claim,
                 'protobuf': binascii.hexlify(txo.claim.to_bytes()),
                 'claim_id': txo.claim_id,
                 'txid': txo.tx_ref.id,
                 'nout': txo.position,
                 'amount': dewies_to_lbc(txo.amount),
                 'effective_amount': txo.meta.get('effective_amount', 0),
                 'height': tx_height,
                 'confirmations': (best_height+1) - tx_height if tx_height > 0 else tx_height,
                 'claim_sequence': -1,
                 'address': txo.get_address(wallet_manager.ledger),
                 'valid_at_height': txo.meta.get('activation_height', None),
                 'timestamp': wallet_manager.ledger.headers[tx_height]['timestamp'],
                 'supports': []
             }
         else:
             result[url] = txo
     return result
Esempio n. 5
0
 def _format_support(outpoint, supported_id, amount, address):
     return {
         "txid": outpoint.split(":")[0],
         "nout": int(outpoint.split(":")[1]),
         "claim_id": supported_id,
         "amount": dewies_to_lbc(amount),
         "address": address,
     }
Esempio n. 6
0
 def encode_claim_meta(self, meta):
     for key, value in meta.items():
         if key.endswith('_amount'):
             if isinstance(value, int):
                 meta[key] = dewies_to_lbc(value)
     if 0 < meta.get('creation_height', 0) <= self.ledger.headers.height:
         meta['creation_timestamp'] = self.ledger.headers.estimated_timestamp(meta['creation_height'])
     return meta
Esempio n. 7
0
    def encode_output(self, txo, check_signature=True):
        tx_height = txo.tx_ref.height
        best_height = self.ledger.headers.height
        output = {
            'txid': txo.tx_ref.id,
            'nout': txo.position,
            'height': tx_height,
            'amount': dewies_to_lbc(txo.amount),
            'address': txo.get_address(self.ledger),
            'confirmations': (best_height+1) - tx_height if tx_height > 0 else tx_height,
            'timestamp': self.ledger.headers[tx_height]['timestamp'] if 0 < tx_height <= best_height else None
        }
        if txo.is_change is not None:
            output['is_change'] = txo.is_change
        if txo.is_my_account is not None:
            output['is_mine'] = txo.is_my_account

        if txo.script.is_claim_name:
            output['type'] = 'claim'
            output['claim_op'] = 'create'
        elif txo.script.is_update_claim:
            output['type'] = 'claim'
            output['claim_op'] = 'update'
        elif txo.script.is_support_claim:
            output['type'] = 'support'
        else:
            output['type'] = 'payment'

        if txo.script.is_claim_involved:
            output.update({
                'name': txo.claim_name,
                'normalized_name': txo.normalized_name,
                'claim_id': txo.claim_id,
                'permanent_url': txo.permanent_url,
                'meta': self.encode_claim_meta(txo.meta.copy())
            })
            if 'short_url' in output['meta']:
                output['short_url'] = output['meta'].pop('short_url')
            if 'canonical_url' in output['meta']:
                output['canonical_url'] = output['meta'].pop('canonical_url')
            if txo.script.is_claim_name or txo.script.is_update_claim:
                try:
                    output['value'] = txo.claim
                    output['value_type'] = txo.claim.claim_type
                    if self.include_protobuf:
                        output['protobuf'] = hexlify(txo.claim.to_bytes())
                    if txo.claim.is_channel:
                        output['has_signing_key'] = txo.has_private_key
                    if check_signature and txo.claim.is_signed:
                        if txo.channel is not None:
                            output['signing_channel'] = self.encode_output(txo.channel)
                            output['is_channel_signature_valid'] = txo.is_signed_by(txo.channel, self.ledger)
                        else:
                            output['signing_channel'] = {'channel_id': txo.claim.signing_channel_id}
                            output['is_channel_signature_valid'] = False
                except DecodeError:
                    pass
        return output
Esempio n. 8
0
 def save_claim_from_output(self, ledger, *outputs: Output):
     return self.save_claims([{
         "claim_id": output.claim_id,
         "name": output.claim_name,
         "amount": dewies_to_lbc(output.amount),
         "address": output.get_address(ledger),
         "txid": output.tx_ref.id,
         "nout": output.position,
         "value": output.claim,
         "height": output.tx_ref.height,
         "claim_sequence": -1,
     } for output in outputs])
Esempio n. 9
0
 async def get_granular_balances(self, confirmations=0, reserved_subtotals=False):
     tips_balance, supports_balance, claims_balance = 0, 0, 0
     get_total_balance = partial(self.get_balance, confirmations=confirmations, include_claims=True)
     total = await get_total_balance()
     if reserved_subtotals:
         claims_balance = await get_total_balance(txo_type__in=[TXO_TYPES['stream'], TXO_TYPES['channel']])
         for amount, spent, from_me, to_me, height in await self.get_support_summary():
             if confirmations > 0 and not 0 < height <= self.ledger.headers.height - (confirmations - 1):
                 continue
             if not spent and to_me:
                 if from_me:
                     supports_balance += amount
                 else:
                     tips_balance += amount
         reserved = claims_balance + supports_balance + tips_balance
     else:
         reserved = await self.get_balance(
             confirmations=confirmations, include_claims=True, txo_type__gt=0
         )
     return {
         'total': dewies_to_lbc(total),
         'available': dewies_to_lbc(total - reserved),
         'reserved': dewies_to_lbc(reserved),
         'reserved_subtotals': {
             'claims': dewies_to_lbc(claims_balance),
             'supports': dewies_to_lbc(supports_balance),
             'tips': dewies_to_lbc(tips_balance)
         } if reserved_subtotals else None
     }
Esempio n. 10
0
 def as_dict(self) -> typing.Dict:
     return {
         "name": self.claim_name,
         "claim_id": self.claim_id,
         "address": self.claim_address,
         "claim_sequence": self.claim_sequence,
         "value": self.claim,
         "height": self.height,
         "amount": dewies_to_lbc(self.amount),
         "nout": self.nout,
         "txid": self.txid,
         "channel_claim_id": self.channel_claim_id,
         "channel_name": self.channel_name
     }
Esempio n. 11
0
 async def get_transaction_history(self, **constraints):
     txs = await self.db.get_transactions(**constraints)
     headers = self.headers
     history = []
     for tx in txs:
         ts = headers[tx.height]['timestamp'] if tx.height > 0 else None
         item = {
             'txid':
             tx.id,
             'timestamp':
             ts,
             'date':
             datetime.fromtimestamp(ts).isoformat(' ')[:-3]
             if tx.height > 0 else None,
             'confirmations':
             (headers.height + 1) - tx.height if tx.height > 0 else 0,
             'claim_info': [],
             'update_info': [],
             'support_info': [],
             'abandon_info': []
         }
         is_my_inputs = all([txi.is_my_account for txi in tx.inputs])
         if is_my_inputs:
             # fees only matter if we are the ones paying them
             item['value'] = dewies_to_lbc(tx.net_account_balance + tx.fee)
             item['fee'] = dewies_to_lbc(-tx.fee)
         else:
             # someone else paid the fees
             item['value'] = dewies_to_lbc(tx.net_account_balance)
             item['fee'] = '0.0'
         for txo in tx.my_claim_outputs:
             item['claim_info'].append({
                 'address':
                 txo.get_address(self),
                 'balance_delta':
                 dewies_to_lbc(-txo.amount),
                 'amount':
                 dewies_to_lbc(txo.amount),
                 'claim_id':
                 txo.claim_id,
                 'claim_name':
                 txo.claim_name,
                 'nout':
                 txo.position
             })
         for txo in tx.my_update_outputs:
             if is_my_inputs:  # updating my own claim
                 previous = None
                 for txi in tx.inputs:
                     if txi.txo_ref.txo is not None:
                         other_txo = txi.txo_ref.txo
                         if (other_txo.is_claim or other_txo.script.is_support_claim) \
                                 and other_txo.claim_id == txo.claim_id:
                             previous = other_txo
                             break
                 if previous is not None:
                     item['update_info'].append({
                         'address':
                         txo.get_address(self),
                         'balance_delta':
                         dewies_to_lbc(previous.amount - txo.amount),
                         'amount':
                         dewies_to_lbc(txo.amount),
                         'claim_id':
                         txo.claim_id,
                         'claim_name':
                         txo.claim_name,
                         'nout':
                         txo.position
                     })
             else:  # someone sent us their claim
                 item['update_info'].append({
                     'address':
                     txo.get_address(self),
                     'balance_delta':
                     dewies_to_lbc(0),
                     'amount':
                     dewies_to_lbc(txo.amount),
                     'claim_id':
                     txo.claim_id,
                     'claim_name':
                     txo.claim_name,
                     'nout':
                     txo.position
                 })
         for txo in tx.my_support_outputs:
             item['support_info'].append({
                 'address':
                 txo.get_address(self),
                 'balance_delta':
                 dewies_to_lbc(
                     txo.amount if not is_my_inputs else -txo.amount),
                 'amount':
                 dewies_to_lbc(txo.amount),
                 'claim_id':
                 txo.claim_id,
                 'claim_name':
                 txo.claim_name,
                 'is_tip':
                 not is_my_inputs,
                 'nout':
                 txo.position
             })
         if is_my_inputs:
             for txo in tx.other_support_outputs:
                 item['support_info'].append({
                     'address':
                     txo.get_address(self),
                     'balance_delta':
                     dewies_to_lbc(-txo.amount),
                     'amount':
                     dewies_to_lbc(txo.amount),
                     'claim_id':
                     txo.claim_id,
                     'claim_name':
                     txo.claim_name,
                     'is_tip':
                     is_my_inputs,
                     'nout':
                     txo.position
                 })
         for txo in tx.my_abandon_outputs:
             item['abandon_info'].append({
                 'address':
                 txo.get_address(self),
                 'balance_delta':
                 dewies_to_lbc(txo.amount),
                 'amount':
                 dewies_to_lbc(txo.amount),
                 'claim_id':
                 txo.claim_id,
                 'claim_name':
                 txo.claim_name,
                 'nout':
                 txo.position
             })
         history.append(item)
     return history
Esempio n. 12
0
def calculate_effective_amount(amount: str, supports: typing.Optional[typing.List[typing.Dict]] = None) -> str:
    return dewies_to_lbc(
        lbc_to_dewies(amount) + sum([lbc_to_dewies(support['amount']) for support in supports])
    )
Esempio n. 13
0
    async def parse_and_validate_claim_result(self,
                                              claim_result,
                                              certificate=None):
        if not claim_result or 'value' not in claim_result:
            return claim_result
        claim_result = _decode_claim_result(claim_result)
        if claim_result.get('height'):
            claim_result['timestamp'] = self.ledger.headers[
                claim_result['height']]['timestamp']
        if claim_result.get('depth'):
            claim_result['confirmations'] = claim_result.pop('depth')

        if claim_result['value']:
            claim_result['has_signature'] = False
            if claim_result['value'].is_signed:
                claim_result['has_signature'] = True
                claim_tx = await self._fetch_tx(claim_result['txid'])
                if certificate is None:
                    log.info("fetching certificate to check claim signature")
                    channel_id = claim_result['value'].signing_channel_id
                    certificate = (await self.network.get_claims_by_ids(
                        [channel_id])).get(channel_id)
                    if not certificate:
                        log.warning('Certificate %s not found', channel_id)
                claim_result['channel_name'] = certificate[
                    'name'] if certificate else None
                cert_tx = await self._fetch_tx(certificate['txid']
                                               ) if certificate else None
                claim_result[
                    'signature_is_valid'] = validate_claim_signature_and_get_channel_name(
                        claim_result,
                        certificate,
                        self.ledger,
                        claim_tx=claim_tx,
                        cert_tx=cert_tx)
                # fixme: workaround while json encoder isnt used here
                if cert_tx:
                    channel_txo = cert_tx.outputs[certificate['nout']]
                    claim_result['signing_channel'] = {
                        'name': channel_txo.claim_name,
                        'claim_id': channel_txo.claim_id,
                        'value': channel_txo.claim
                    }
                    claim_result['is_channel_signature_valid'] = claim_result[
                        'signature_is_valid']

        if 'amount' in claim_result:
            claim_result['amount'] = dewies_to_lbc(claim_result['amount'])
            claim_result['effective_amount'] = dewies_to_lbc(
                claim_result['effective_amount'])
            claim_result['supports'] = [{
                'txid': txid,
                'nout': nout,
                'amount': dewies_to_lbc(amount)
            } for (txid, nout, amount) in claim_result['supports']]

        claim_result['height'] = claim_result.get('height', -1) or -1
        claim_result[
            'permanent_url'] = f"lbry://{claim_result['name']}#{claim_result['claim_id']}"

        return claim_result
Esempio n. 14
0
    async def download_from_uri(self, uri, exchange_rate_manager: 'ExchangeRateManager',
                                timeout: Optional[float] = None, file_name: Optional[str] = None,
                                download_directory: Optional[str] = None,
                                save_file: Optional[bool] = None, resolve_timeout: float = 3.0,
                                wallet: Optional['Wallet'] = None) -> ManagedDownloadSource:

        wallet = wallet or self.wallet_manager.default_wallet
        timeout = timeout or self.config.download_timeout
        start_time = self.loop.time()
        resolved_time = None
        stream = None
        claim = None
        error = None
        outpoint = None
        if save_file is None:
            save_file = self.config.save_files
        if file_name and not save_file:
            save_file = True
        if save_file:
            download_directory = download_directory or self.config.download_dir
        else:
            download_directory = None

        payment = None
        try:
            # resolve the claim
            if not URL.parse(uri).has_stream:
                raise ResolveError("cannot download a channel claim, specify a /path")
            try:
                resolved_result = await asyncio.wait_for(
                    self.wallet_manager.ledger.resolve(
                        wallet.accounts, [uri],
                        include_purchase_receipt=True,
                        include_is_my_output=True
                    ), resolve_timeout
                )
            except asyncio.TimeoutError:
                raise ResolveTimeoutError(uri)
            except Exception as err:
                if isinstance(err, asyncio.CancelledError):
                    raise
                log.exception("Unexpected error resolving stream:")
                raise ResolveError(f"Unexpected error resolving stream: {str(err)}")
            if 'error' in resolved_result:
                raise ResolveError(f"Unexpected error resolving uri for download: {resolved_result['error']}")
            if not resolved_result or uri not in resolved_result:
                raise ResolveError(f"Failed to resolve stream at '{uri}'")
            txo = resolved_result[uri]
            if isinstance(txo, dict):
                raise ResolveError(f"Failed to resolve stream at '{uri}': {txo}")
            claim = txo.claim
            outpoint = f"{txo.tx_ref.id}:{txo.position}"
            resolved_time = self.loop.time() - start_time
            await self.storage.save_claim_from_output(self.wallet_manager.ledger, txo)

            ####################
            # update or replace
            ####################

            if claim.stream.source.bt_infohash:
                source_manager = self.source_managers['torrent']
                existing = source_manager.get_filtered(bt_infohash=claim.stream.source.bt_infohash)
            else:
                source_manager = self.source_managers['stream']
                existing = source_manager.get_filtered(sd_hash=claim.stream.source.sd_hash)

            # resume or update an existing stream, if the stream changed: download it and delete the old one after
            to_replace, updated_stream = None, None
            if existing and existing[0].claim_id != txo.claim_id:
                raise ResolveError(f"stream for {existing[0].claim_id} collides with existing download {txo.claim_id}")
            if existing:
                log.info("claim contains a metadata only update to a stream we have")
                if claim.stream.source.bt_infohash:
                    await self.storage.save_torrent_content_claim(
                        existing[0].identifier, outpoint, existing[0].torrent_length, existing[0].torrent_name
                    )
                    claim_info = await self.storage.get_content_claim_for_torrent(existing[0].identifier)
                    existing[0].set_claim(claim_info, claim)
                else:
                    await self.storage.save_content_claim(
                        existing[0].stream_hash, outpoint
                    )
                    await source_manager._update_content_claim(existing[0])
                updated_stream = existing[0]
            else:
                existing_for_claim_id = self.get_filtered(claim_id=txo.claim_id)
                if existing_for_claim_id:
                    log.info("claim contains an update to a stream we have, downloading it")
                    if save_file and existing_for_claim_id[0].output_file_exists:
                        save_file = False
                    if not claim.stream.source.bt_infohash:
                        existing_for_claim_id[0].downloader.node = source_manager.node
                    await existing_for_claim_id[0].start(timeout=timeout, save_now=save_file)
                    if not existing_for_claim_id[0].output_file_exists and (
                            save_file or file_name or download_directory):
                        await existing_for_claim_id[0].save_file(
                            file_name=file_name, download_directory=download_directory
                        )
                    to_replace = existing_for_claim_id[0]

            # resume or update an existing stream, if the stream changed: download it and delete the old one after
            if updated_stream:
                log.info("already have stream for %s", uri)
                if save_file and updated_stream.output_file_exists:
                    save_file = False
                if not claim.stream.source.bt_infohash:
                    updated_stream.downloader.node = source_manager.node
                await updated_stream.start(timeout=timeout, save_now=save_file)
                if not updated_stream.output_file_exists and (save_file or file_name or download_directory):
                    await updated_stream.save_file(
                        file_name=file_name, download_directory=download_directory
                    )
                return updated_stream

            ####################
            # pay fee
            ####################

            needs_purchasing = (
                not to_replace and
                not txo.is_my_output and
                txo.has_price and
                not txo.purchase_receipt
            )

            if needs_purchasing:
                payment = await self.wallet_manager.create_purchase_transaction(
                    wallet.accounts, txo, exchange_rate_manager
                )

            ####################
            # make downloader and wait for start
            ####################

            if not claim.stream.source.bt_infohash:
                # fixme: this shouldnt be here
                stream = ManagedStream(
                    self.loop, self.config, source_manager.blob_manager, claim.stream.source.sd_hash,
                    download_directory, file_name, ManagedStream.STATUS_RUNNING, content_fee=payment,
                    analytics_manager=self.analytics_manager
                )
                stream.downloader.node = source_manager.node
            else:
                stream = TorrentSource(
                    self.loop, self.config, self.storage, identifier=claim.stream.source.bt_infohash,
                    file_name=file_name, download_directory=download_directory or self.config.download_dir,
                    status=ManagedStream.STATUS_RUNNING,
                    analytics_manager=self.analytics_manager,
                    torrent_session=source_manager.torrent_session
                )
            log.info("starting download for %s", uri)

            before_download = self.loop.time()
            await stream.start(timeout, save_file)

            ####################
            # success case: delete to_replace if applicable, broadcast fee payment
            ####################

            if to_replace:  # delete old stream now that the replacement has started downloading
                await source_manager.delete(to_replace)

            if payment is not None:
                await self.wallet_manager.broadcast_or_release(payment)
                payment = None  # to avoid releasing in `finally` later
                log.info("paid fee of %s for %s", dewies_to_lbc(stream.content_fee.outputs[0].amount), uri)
                await self.storage.save_content_fee(stream.stream_hash, stream.content_fee)

            source_manager.add(stream)

            if not claim.stream.source.bt_infohash:
                await self.storage.save_content_claim(stream.stream_hash, outpoint)
            else:
                await self.storage.save_torrent_content_claim(
                    stream.identifier, outpoint, stream.torrent_length, stream.torrent_name
                )
                claim_info = await self.storage.get_content_claim_for_torrent(stream.identifier)
                stream.set_claim(claim_info, claim)
            if save_file:
                await asyncio.wait_for(stream.save_file(), timeout - (self.loop.time() - before_download),
                                       loop=self.loop)
            return stream
        except asyncio.TimeoutError:
            error = DownloadDataTimeoutError(stream.sd_hash)
            raise error
        except Exception as err:  # forgive data timeout, don't delete stream
            expected = (DownloadSDTimeoutError, DownloadDataTimeoutError, InsufficientFundsError,
                        KeyFeeAboveMaxAllowedError)
            if isinstance(err, expected):
                log.warning("Failed to download %s: %s", uri, str(err))
            elif isinstance(err, asyncio.CancelledError):
                pass
            else:
                log.exception("Unexpected error downloading stream:")
            error = err
            raise
        finally:
            if payment is not None:
                # payment is set to None after broadcasting, if we're here an exception probably happened
                await self.wallet_manager.ledger.release_tx(payment)
            if self.analytics_manager and claim and claim.stream.source.bt_infohash:
                # TODO: analytics for torrents
                pass
            elif self.analytics_manager and (error or (stream and (stream.downloader.time_to_descriptor or
                                                                   stream.downloader.time_to_first_bytes))):
                server = self.wallet_manager.ledger.network.client.server
                self.loop.create_task(
                    self.analytics_manager.send_time_to_first_bytes(
                        resolved_time, self.loop.time() - start_time, None if not stream else stream.download_id,
                        uri, outpoint,
                        None if not stream else len(stream.downloader.blob_downloader.active_connections),
                        None if not stream else len(stream.downloader.blob_downloader.scores),
                        None if not stream else len(stream.downloader.blob_downloader.connection_failures),
                        False if not stream else stream.downloader.added_fixed_peers,
                        self.config.fixed_peer_delay if not stream else stream.downloader.fixed_peers_delay,
                        None if not stream else stream.sd_hash,
                        None if not stream else stream.downloader.time_to_descriptor,
                        None if not (stream and stream.descriptor) else stream.descriptor.blobs[0].blob_hash,
                        None if not (stream and stream.descriptor) else stream.descriptor.blobs[0].length,
                        None if not stream else stream.downloader.time_to_first_bytes,
                        None if not error else error.__class__.__name__,
                        None if not error else str(error),
                        None if not server else f"{server[0]}:{server[1]}"
                    )
                )
Esempio n. 15
0
    def encode_output(self, txo, check_signature=True):
        if not txo:
            return
        tx_height = txo.tx_ref.height
        best_height = self.ledger.headers.height
        output = {
            'txid': txo.tx_ref.id,
            'nout': txo.position,
            'height': tx_height,
            'amount': dewies_to_lbc(txo.amount),
            'address':
            txo.get_address(self.ledger) if txo.has_address else None,
            'confirmations':
            (best_height + 1) - tx_height if tx_height > 0 else tx_height,
            'timestamp': self.ledger.headers.estimated_timestamp(tx_height)
        }
        if txo.is_spent is not None:
            output['is_spent'] = txo.is_spent
        if txo.is_my_output is not None:
            output['is_my_output'] = txo.is_my_output
        if txo.is_my_input is not None:
            output['is_my_input'] = txo.is_my_input
        if txo.sent_supports is not None:
            output['sent_supports'] = dewies_to_lbc(txo.sent_supports)
        if txo.sent_tips is not None:
            output['sent_tips'] = dewies_to_lbc(txo.sent_tips)
        if txo.received_tips is not None:
            output['received_tips'] = dewies_to_lbc(txo.received_tips)
        if txo.is_internal_transfer is not None:
            output['is_internal_transfer'] = txo.is_internal_transfer

        if txo.script.is_claim_name:
            output['type'] = 'claim'
            output['claim_op'] = 'create'
        elif txo.script.is_update_claim:
            output['type'] = 'claim'
            output['claim_op'] = 'update'
        elif txo.script.is_support_claim:
            output['type'] = 'support'
        elif txo.script.is_return_data:
            output['type'] = 'data'
        elif txo.purchase is not None:
            output['type'] = 'purchase'
            output['claim_id'] = txo.purchased_claim_id
            if txo.purchased_claim is not None:
                output['claim'] = self.encode_output(txo.purchased_claim)
        else:
            output['type'] = 'payment'

        if txo.script.is_claim_involved:
            output.update({
                'name': txo.claim_name,
                'normalized_name': txo.normalized_name,
                'claim_id': txo.claim_id,
                'permanent_url': txo.permanent_url,
                'meta': self.encode_claim_meta(txo.meta.copy())
            })
            if 'short_url' in output['meta']:
                output['short_url'] = output['meta'].pop('short_url')
            if 'canonical_url' in output['meta']:
                output['canonical_url'] = output['meta'].pop('canonical_url')
            if txo.claims is not None:
                output['claims'] = [self.encode_output(o) for o in txo.claims]
            if txo.reposted_claim is not None:
                output['reposted_claim'] = self.encode_output(
                    txo.reposted_claim)
        if txo.script.is_claim_name or txo.script.is_update_claim or txo.script.is_support_claim_data:
            try:
                output['value'] = txo.signable
                if self.include_protobuf:
                    output['protobuf'] = hexlify(txo.signable.to_bytes())
                if txo.purchase_receipt is not None:
                    output['purchase_receipt'] = self.encode_output(
                        txo.purchase_receipt)
                if txo.script.is_claim_name or txo.script.is_update_claim:
                    output['value_type'] = txo.claim.claim_type
                    if txo.claim.is_channel:
                        output['has_signing_key'] = txo.has_private_key
                if check_signature and txo.signable.is_signed:
                    if txo.channel is not None:
                        output['signing_channel'] = self.encode_output(
                            txo.channel)
                        output[
                            'is_channel_signature_valid'] = txo.is_signed_by(
                                txo.channel, self.ledger)
                    else:
                        output['signing_channel'] = {
                            'channel_id': txo.signable.signing_channel_id
                        }
                        output['is_channel_signature_valid'] = False
            except DecodeError:
                pass
        return output
Esempio n. 16
0
    async def download_stream_from_uri(self, uri, exchange_rate_manager: 'ExchangeRateManager',
                                       timeout: Optional[float] = None,
                                       file_name: Optional[str] = None,
                                       download_directory: Optional[str] = None,
                                       save_file: Optional[bool] = None,
                                       resolve_timeout: float = 3.0,
                                       wallet: Optional['Wallet'] = None) -> ManagedStream:
        manager = self.wallet_manager
        wallet = wallet or manager.default_wallet
        timeout = timeout or self.config.download_timeout
        start_time = self.loop.time()
        resolved_time = None
        stream = None
        txo: Optional[Output] = None
        error = None
        outpoint = None
        if save_file is None:
            save_file = self.config.save_files
        if file_name and not save_file:
            save_file = True
        if save_file:
            download_directory = download_directory or self.config.download_dir
        else:
            download_directory = None

        payment = None
        try:
            # resolve the claim
            if not URL.parse(uri).has_stream:
                raise ResolveError("cannot download a channel claim, specify a /path")
            try:
                response = await asyncio.wait_for(
                    manager.ledger.resolve(wallet.accounts, [uri]),
                    resolve_timeout
                )
                resolved_result = self._convert_to_old_resolve_output(manager, response)
            except asyncio.TimeoutError:
                raise ResolveTimeoutError(uri)
            except Exception as err:
                if isinstance(err, asyncio.CancelledError):  # TODO: remove when updated to 3.8
                    raise
                log.exception("Unexpected error resolving stream:")
                raise ResolveError(f"Unexpected error resolving stream: {str(err)}")
            await self.storage.save_claims_for_resolve([
                value for value in resolved_result.values() if 'error' not in value
            ])
            resolved = resolved_result.get(uri, {})
            resolved = resolved if 'value' in resolved else resolved.get('claim')
            if not resolved:
                raise ResolveError(f"Failed to resolve stream at '{uri}'")
            if 'error' in resolved:
                raise ResolveError(f"error resolving stream: {resolved['error']}")
            txo = response[uri]

            claim = Claim.from_bytes(binascii.unhexlify(resolved['protobuf']))
            outpoint = f"{resolved['txid']}:{resolved['nout']}"
            resolved_time = self.loop.time() - start_time

            # resume or update an existing stream, if the stream changed: download it and delete the old one after
            updated_stream, to_replace = await self._check_update_or_replace(outpoint, resolved['claim_id'], claim)
            if updated_stream:
                log.info("already have stream for %s", uri)
                if save_file and updated_stream.output_file_exists:
                    save_file = False
                await updated_stream.start(node=self.node, timeout=timeout, save_now=save_file)
                if not updated_stream.output_file_exists and (save_file or file_name or download_directory):
                    await updated_stream.save_file(
                        file_name=file_name, download_directory=download_directory, node=self.node
                    )
                return updated_stream

            if not to_replace and txo.has_price and not txo.purchase_receipt:
                payment = await manager.create_purchase_transaction(
                    wallet.accounts, txo, exchange_rate_manager
                )

            stream = ManagedStream(
                self.loop, self.config, self.blob_manager, claim.stream.source.sd_hash, download_directory,
                file_name, ManagedStream.STATUS_RUNNING, content_fee=payment,
                analytics_manager=self.analytics_manager
            )
            log.info("starting download for %s", uri)

            before_download = self.loop.time()
            await stream.start(self.node, timeout)
            stream.set_claim(resolved, claim)
            if to_replace:  # delete old stream now that the replacement has started downloading
                await self.delete_stream(to_replace)

            if payment is not None:
                await manager.broadcast_or_release(payment)
                payment = None  # to avoid releasing in `finally` later
                log.info("paid fee of %s for %s", dewies_to_lbc(stream.content_fee.outputs[0].amount), uri)
                await self.storage.save_content_fee(stream.stream_hash, stream.content_fee)

            self.streams[stream.sd_hash] = stream
            self.storage.content_claim_callbacks[stream.stream_hash] = lambda: self._update_content_claim(stream)
            await self.storage.save_content_claim(stream.stream_hash, outpoint)
            if save_file:
                await asyncio.wait_for(stream.save_file(node=self.node), timeout - (self.loop.time() - before_download),
                                       loop=self.loop)
            return stream
        except asyncio.TimeoutError:
            error = DownloadDataTimeoutError(stream.sd_hash)
            raise error
        except Exception as err:  # forgive data timeout, don't delete stream
            expected = (DownloadSDTimeoutError, DownloadDataTimeoutError, InsufficientFundsError,
                        KeyFeeAboveMaxAllowedError)
            if isinstance(err, expected):
                log.warning("Failed to download %s: %s", uri, str(err))
            elif isinstance(err, asyncio.CancelledError):
                pass
            else:
                log.exception("Unexpected error downloading stream:")
            error = err
            raise
        finally:
            if payment is not None:
                # payment is set to None after broadcasting, if we're here an exception probably happened
                await manager.ledger.release_tx(payment)
            if self.analytics_manager and (error or (stream and (stream.downloader.time_to_descriptor or
                                                                 stream.downloader.time_to_first_bytes))):
                server = self.wallet_manager.ledger.network.client.server
                self.loop.create_task(
                    self.analytics_manager.send_time_to_first_bytes(
                        resolved_time, self.loop.time() - start_time, None if not stream else stream.download_id,
                        uri, outpoint,
                        None if not stream else len(stream.downloader.blob_downloader.active_connections),
                        None if not stream else len(stream.downloader.blob_downloader.scores),
                        None if not stream else len(stream.downloader.blob_downloader.connection_failures),
                        False if not stream else stream.downloader.added_fixed_peers,
                        self.config.fixed_peer_delay if not stream else stream.downloader.fixed_peers_delay,
                        None if not stream else stream.sd_hash,
                        None if not stream else stream.downloader.time_to_descriptor,
                        None if not (stream and stream.descriptor) else stream.descriptor.blobs[0].blob_hash,
                        None if not (stream and stream.descriptor) else stream.descriptor.blobs[0].length,
                        None if not stream else stream.downloader.time_to_first_bytes,
                        None if not error else error.__class__.__name__,
                        None if not error else str(error),
                        None if not server else f"{server[0]}:{server[1]}"
                    )
                )
Esempio n. 17
0
 async def assertOutputAmount(self, amounts, awaitable):
     self.assertEqual(
         amounts, [dewies_to_lbc(txo.amount) for txo in await awaitable])