lbry-sdk/lbrynet/daemon/Daemon.py

3266 lines
131 KiB
Python
Raw Normal View History

import logging.handlers
2016-06-03 09:45:46 +02:00
import mimetypes
2016-02-29 19:25:47 +01:00
import os
import requests
2017-01-02 20:52:24 +01:00
import urllib
2018-07-21 20:12:29 +02:00
import json
2017-03-16 20:35:54 +01:00
import textwrap
from binascii import hexlify, unhexlify
2017-10-10 21:04:48 +02:00
from copy import deepcopy
from decimal import Decimal, InvalidOperation
from twisted.web import server
from twisted.internet import defer, reactor
from twisted.internet.task import LoopingCall
from twisted.python.failure import Failure
2016-02-29 19:25:47 +01:00
2018-07-05 04:16:02 +02:00
from torba.constants import COIN
2017-04-11 04:47:54 +02:00
from lbryschema.claim import ClaimDict
from lbryschema.uri import parse_lbry_uri
from lbryschema.error import URIParseError, DecodeError
2017-08-15 17:56:26 +02:00
from lbryschema.validator import validate_claim_id
from lbryschema.address import decode_address
from lbryschema.decode import smart_decode
2017-04-11 04:47:54 +02:00
# TODO: importing this when internet is disabled raises a socket.gaierror
2017-04-26 20:18:41 +02:00
from lbrynet.core.system_info import get_lbrynet_version
2018-01-22 22:04:08 +01:00
from lbrynet import conf
from lbrynet.reflector import reupload
from lbrynet.daemon.Components import WALLET_COMPONENT, DATABASE_COMPONENT, DHT_COMPONENT, BLOB_COMPONENT
from lbrynet.daemon.Components import STREAM_IDENTIFIER_COMPONENT, FILE_MANAGER_COMPONENT, RATE_LIMITER_COMPONENT
from lbrynet.daemon.Components import EXCHANGE_RATE_MANAGER_COMPONENT, PAYMENT_RATE_COMPONENT, UPNP_COMPONENT
from lbrynet.daemon.ComponentManager import RequiredCondition
from lbrynet.daemon.Downloader import GetStream
from lbrynet.daemon.Publisher import Publisher
from lbrynet.daemon.auth.server import AuthJSONRPCServer
2017-07-13 20:49:25 +02:00
from lbrynet.core import utils, system_info
from lbrynet.core.StreamDescriptor import download_sd_blob
from lbrynet.core.Error import InsufficientFundsError, UnknownNameError
from lbrynet.core.Error import DownloadDataTimeout, DownloadSDTimeout
from lbrynet.core.Error import NullFundsError, NegativeFundsError
2018-03-29 16:46:29 +02:00
from lbrynet.dht.error import TimeoutError
from lbrynet.core.Peer import Peer
from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader
from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader
from lbrynet.wallet.account import Account as LBRYAccount
2016-06-07 10:19:51 +02:00
log = logging.getLogger(__name__)
requires = AuthJSONRPCServer.requires
2016-06-28 20:28:59 +02:00
INITIALIZING_CODE = 'initializing'
# TODO: make this consistent with the stages in Downloader.py
DOWNLOAD_METADATA_CODE = 'downloading_metadata'
DOWNLOAD_TIMEOUT_CODE = 'timeout'
DOWNLOAD_RUNNING_CODE = 'running'
DOWNLOAD_STOPPED_CODE = 'stopped'
STREAM_STAGES = [
2017-01-04 23:10:36 +01:00
(INITIALIZING_CODE, 'Initializing'),
2017-01-03 20:13:01 +01:00
(DOWNLOAD_METADATA_CODE, 'Downloading metadata'),
(DOWNLOAD_RUNNING_CODE, 'Started %s, got %s/%s blobs, stream status: %s'),
(DOWNLOAD_STOPPED_CODE, 'Paused stream'),
(DOWNLOAD_TIMEOUT_CODE, 'Stream timed out')
]
CONNECTION_STATUS_CONNECTED = 'connected'
CONNECTION_STATUS_NETWORK = 'network_connection'
CONNECTION_MESSAGES = {
CONNECTION_STATUS_CONNECTED: 'No connection problems detected',
CONNECTION_STATUS_NETWORK: "Your internet connection appears to have been interrupted",
}
2016-12-19 19:27:45 +01:00
SHORT_ID_LEN = 20
MAX_UPDATE_FEE_ESTIMATE = 0.3
2016-12-19 19:27:45 +01:00
2018-05-21 06:24:18 +02:00
DIRECTION_ASCENDING = 'asc'
DIRECTION_DESCENDING = 'desc'
DIRECTIONS = DIRECTION_ASCENDING, DIRECTION_DESCENDING
2016-10-19 01:09:35 +02:00
class IterableContainer:
def __iter__(self):
for attr in dir(self):
if not attr.startswith("_"):
yield getattr(self, attr)
def __contains__(self, item):
for attr in self:
if item == attr:
return True
return False
class Checker:
2016-10-19 01:09:35 +02:00
"""The looping calls the daemon runs"""
2018-08-02 23:33:56 +02:00
INTERNET_CONNECTION = 'internet_connection_checker', 300
# CONNECTION_STATUS = 'connection_status_checker'
2016-10-19 01:09:35 +02:00
class _FileID(IterableContainer):
2016-10-19 01:09:35 +02:00
"""The different ways a file can be identified"""
SD_HASH = 'sd_hash'
FILE_NAME = 'file_name'
2017-02-20 01:22:21 +01:00
STREAM_HASH = 'stream_hash'
ROWID = "rowid"
CLAIM_ID = "claim_id"
OUTPOINT = "outpoint"
TXID = "txid"
NOUT = "nout"
CHANNEL_CLAIM_ID = "channel_claim_id"
CLAIM_NAME = "claim_name"
CHANNEL_NAME = "channel_name"
FileID = _FileID()
2016-10-19 01:09:35 +02:00
# TODO add login credentials in a conf file
# TODO alert if your copy of a lbry file is out of date with the name record
2016-12-19 19:27:45 +01:00
class NoValidSearch(Exception):
pass
class CheckInternetConnection:
def __init__(self, daemon):
self.daemon = daemon
def __call__(self):
self.daemon.connected_to_internet = utils.check_connection()
class AlwaysSend:
2016-10-11 19:50:44 +02:00
def __init__(self, value_generator, *args, **kwargs):
self.value_generator = value_generator
self.args = args
self.kwargs = kwargs
def __call__(self):
d = defer.maybeDeferred(self.value_generator, *self.args, **self.kwargs)
d.addCallback(lambda v: (True, v))
return d
def sort_claim_results(claims):
claims.sort(key=lambda d: (d['height'], d['name'], d['claim_id'], d['txid'], d['nout']))
return claims
def is_first_run():
if os.path.isfile(conf.settings.get_db_revision_filename()):
return False
if os.path.isfile(os.path.join(conf.settings['data_dir'], 'lbrynet.sqlite')):
return False
if os.path.isfile(os.path.join(conf.settings['lbryum_wallet_dir'], 'blockchain_headers')):
return False
return True
DHT_HAS_CONTACTS = "dht_has_contacts"
WALLET_IS_UNLOCKED = "wallet_is_unlocked"
class DHTHasContacts(RequiredCondition):
name = DHT_HAS_CONTACTS
component = DHT_COMPONENT
message = "your node is not connected to the dht"
@staticmethod
def evaluate(component):
return len(component.contacts) > 0
class WalletIsLocked(RequiredCondition):
name = WALLET_IS_UNLOCKED
component = WALLET_COMPONENT
message = "your wallet is locked"
@staticmethod
def evaluate(component):
2018-07-12 05:18:59 +02:00
d = component.check_locked()
d.addCallback(lambda r: not r)
return d
class Daemon(AuthJSONRPCServer):
"""
2016-03-24 03:27:48 +01:00
LBRYnet daemon, a jsonrpc interface to lbry functions
"""
component_attributes = {
DATABASE_COMPONENT: "storage",
DHT_COMPONENT: "dht_node",
WALLET_COMPONENT: "wallet",
STREAM_IDENTIFIER_COMPONENT: "sd_identifier",
FILE_MANAGER_COMPONENT: "file_manager",
EXCHANGE_RATE_MANAGER_COMPONENT: "exchange_rate_manager",
PAYMENT_RATE_COMPONENT: "payment_rate_manager",
RATE_LIMITER_COMPONENT: "rate_limiter",
BLOB_COMPONENT: "blob_manager",
UPNP_COMPONENT: "upnp"
}
def __init__(self, analytics_manager=None, component_manager=None):
to_skip = list(conf.settings['components_to_skip'])
if 'reflector' not in to_skip and not conf.settings['run_reflector_server']:
to_skip.append('reflector')
looping_calls = {
Checker.INTERNET_CONNECTION[0]: (LoopingCall(CheckInternetConnection(self)),
Checker.INTERNET_CONNECTION[1])
}
AuthJSONRPCServer.__init__(self, analytics_manager=analytics_manager, component_manager=component_manager,
use_authentication=conf.settings['use_auth_http'], to_skip=to_skip,
looping_calls=looping_calls)
self.is_first_run = is_first_run()
2018-07-20 21:35:09 +02:00
# TODO: move this to a component
self.connected_to_internet = True
2017-01-03 20:13:01 +01:00
self.connection_status_code = None
# components
2018-07-20 21:35:09 +02:00
# TODO: delete these, get the components where needed
self.storage = None
self.dht_node = None
#self.wallet = None
self.sd_identifier = None
self.file_manager = None
self.exchange_rate_manager = None
self.payment_rate_manager = None
self.rate_limiter = None
self.blob_manager = None
self.upnp = None
2018-07-20 21:35:09 +02:00
# TODO: delete this
self.streams = {}
2016-05-14 23:36:30 +02:00
2018-07-05 04:16:02 +02:00
@property
def ledger(self):
2018-07-06 07:17:20 +02:00
return self.wallet.default_account.ledger
2018-07-05 04:16:02 +02:00
@property
def wallet(self):
return self.session.wallet
@defer.inlineCallbacks
def setup(self):
2018-07-20 21:35:09 +02:00
log.info("Starting lbrynet-daemon")
log.info("Platform: %s", json.dumps(system_info.get_platform()))
yield super(Daemon, self).setup()
2018-07-20 21:35:09 +02:00
log.info("Started lbrynet-daemon")
2017-09-28 19:51:20 +02:00
def _stop_streams(self):
"""stop pending GetStream downloads"""
2018-07-12 05:18:59 +02:00
for sd_hash, stream in self.streams.items():
2017-09-28 19:51:20 +02:00
stream.cancel(reason="daemon shutdown")
def _shutdown(self):
2017-09-28 19:51:20 +02:00
self._stop_streams()
return super(Daemon, self)._shutdown()
def _download_blob(self, blob_hash, rate_manager=None, timeout=None):
"""
Download a blob
:param blob_hash (str): blob hash
:param rate_manager (PaymentRateManager), optional: the payment rate manager to use,
defaults to session.payment_rate_manager
:param timeout (int): blob timeout
:return: BlobFile
"""
2017-04-10 19:26:47 +02:00
if not blob_hash:
raise Exception("Nothing to download")
2016-08-02 08:54:04 +02:00
rate_manager = rate_manager or self.payment_rate_manager
timeout = timeout or 30
downloader = StandaloneBlobDownloader(
blob_hash, self.blob_manager, self.dht_node.peer_finder, self.rate_limiter,
rate_manager, self.wallet, timeout
)
return downloader.download()
2016-08-02 05:15:32 +02:00
@defer.inlineCallbacks
2017-09-27 23:02:36 +02:00
def _get_stream_analytics_report(self, claim_dict):
sd_hash = claim_dict.source_hash
try:
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(sd_hash)
2017-09-28 19:54:01 +02:00
except Exception:
2017-09-27 23:02:36 +02:00
stream_hash = None
report = {
"sd_hash": sd_hash,
"stream_hash": stream_hash,
}
blobs = {}
2017-09-28 19:54:01 +02:00
try:
sd_host = yield self.blob_manager.get_host_downloaded_from(sd_hash)
2017-09-28 19:54:01 +02:00
except Exception:
sd_host = None
2017-09-27 23:02:36 +02:00
report["sd_blob"] = sd_host
if stream_hash:
blob_infos = yield self.storage.get_blobs_for_stream(stream_hash)
2017-09-27 23:02:36 +02:00
report["known_blobs"] = len(blob_infos)
else:
blob_infos = []
report["known_blobs"] = 0
2017-10-02 17:21:51 +02:00
# for blob_hash, blob_num, iv, length in blob_infos:
# try:
# host = yield self.session.blob_manager.get_host_downloaded_from(blob_hash)
# except Exception:
# host = None
# if host:
# blobs[blob_num] = host
# report["blobs"] = json.dumps(blobs)
2017-09-27 23:02:36 +02:00
defer.returnValue(report)
@defer.inlineCallbacks
def _download_name(self, name, claim_dict, sd_hash, txid, nout, timeout=None, file_name=None):
"""
Add a lbry file to the file manager, start the download, and return the new lbry file.
If it already exists in the file manager, return the existing lbry file
"""
2017-09-28 19:53:12 +02:00
@defer.inlineCallbacks
def _download_finished(download_id, name, claim_dict):
report = yield self._get_stream_analytics_report(claim_dict)
self.analytics_manager.send_download_finished(download_id, name, report, claim_dict)
2017-09-28 19:53:12 +02:00
@defer.inlineCallbacks
def _download_failed(error, download_id, name, claim_dict):
report = yield self._get_stream_analytics_report(claim_dict)
self.analytics_manager.send_download_errored(error, download_id, name, claim_dict,
report)
if sd_hash in self.streams:
downloader = self.streams[sd_hash]
result = yield downloader.finished_deferred
defer.returnValue(result)
else:
download_id = utils.random_string()
2017-06-02 20:00:13 +02:00
self.analytics_manager.send_download_started(download_id, name, claim_dict)
2018-08-03 15:36:03 +02:00
self.streams[sd_hash] = GetStream(
self.sd_identifier, self.wallet, self.exchange_rate_manager, self.blob_manager,
self.dht_node.peer_finder, self.rate_limiter, self.payment_rate_manager, self.storage,
conf.settings['max_key_fee'], conf.settings['disable_max_key_fee'], conf.settings['data_rate'],
timeout
)
try:
2018-02-12 20:13:30 +01:00
lbry_file, finished_deferred = yield self.streams[sd_hash].start(
claim_dict, name, txid, nout, file_name
)
finished_deferred.addCallbacks(
lambda _: _download_finished(download_id, name, claim_dict),
lambda e: _download_failed(e, download_id, name, claim_dict)
)
result = yield self._get_lbry_file_dict(lbry_file, full_status=True)
2017-09-28 19:53:12 +02:00
except Exception as err:
2017-09-28 21:46:02 +02:00
yield _download_failed(err, download_id, name, claim_dict)
2017-09-28 19:53:12 +02:00
if isinstance(err, (DownloadDataTimeout, DownloadSDTimeout)):
log.warning('Failed to get %s (%s)', name, err)
else:
log.error('Failed to get %s (%s)', name, err)
if self.streams[sd_hash].downloader and self.streams[sd_hash].code != 'running':
yield self.streams[sd_hash].downloader.stop(err)
result = {'error': err.message}
2017-09-28 19:54:01 +02:00
finally:
del self.streams[sd_hash]
defer.returnValue(result)
@defer.inlineCallbacks
2018-07-12 05:18:59 +02:00
def _publish_stream(self, name, bid, claim_dict, file_path=None, certificate=None,
2017-06-12 19:32:01 +02:00
claim_address=None, change_address=None):
2018-08-03 15:36:03 +02:00
publisher = Publisher(
2018-07-12 05:18:59 +02:00
self.blob_manager, self.payment_rate_manager, self.storage, self.file_manager, self.wallet, certificate
2018-08-03 15:36:03 +02:00
)
2017-05-29 22:38:08 +02:00
parse_lbry_uri(name)
if not file_path:
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(
claim_dict['stream']['source']['source'])
tx = yield publisher.publish_stream(name, bid, claim_dict, stream_hash, claim_address)
else:
tx = yield publisher.create_and_publish_stream(name, bid, claim_dict, file_path, claim_address)
if conf.settings['reflect_uploads']:
d = reupload.reflect_file(publisher.lbry_file)
d.addCallbacks(lambda _: log.info("Reflected new publication to lbry://%s", name),
log.exception)
2017-04-27 02:02:00 +02:00
self.analytics_manager.send_claim_action('publish')
2018-07-12 19:23:18 +02:00
nout = 0
script = tx.outputs[nout].script
log.info("Success! Published to lbry://%s txid: %s nout: %d", name, tx.id, nout)
2018-07-12 19:23:18 +02:00
defer.returnValue({
"success": True,
"txid": tx.id,
2018-07-12 19:23:18 +02:00
"nout": nout,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": hexlify(tx.get_claim_id(0)[::-1]),
2018-07-12 19:23:18 +02:00
"value": hexlify(script.values['claim']),
"claim_address": self.ledger.hash160_to_address(script.values['pubkey_hash'])
})
2017-06-09 19:47:13 +02:00
@defer.inlineCallbacks
2018-07-10 06:30:13 +02:00
def _resolve(self, *uris, **kwargs):
"""Resolves a URI. Can check the cache first before going out to the blockchain and stores the result.
Args:
name: the lbry://<name> to resolve
force_refresh: if True, always go out to the blockchain to resolve.
"""
2017-06-09 19:47:13 +02:00
2018-07-10 06:30:13 +02:00
page = kwargs.get('page', 0)
page_size = kwargs.get('page_size', 10)
check_cache = kwargs.get('check_cache', False) # TODO: put caching back (was force_refresh parameter)
results = yield self.wallet.resolve(*uris, page=page, page_size=page_size)
self.save_claims((value for value in results.values() if 'error' not in value))
yield defer.returnValue(results)
@defer.inlineCallbacks
def save_claims(self, claim_infos):
to_save = []
for info in claim_infos:
if 'value' in info:
if info['value']:
to_save.append(info)
else:
if 'certificate' in info and info['certificate']['value']:
to_save.append(info['certificate'])
if 'claim' in info and info['claim']['value']:
to_save.append(info['claim'])
yield self.session.storage.save_claims(to_save)
2016-11-30 22:23:48 +01:00
def _get_or_download_sd_blob(self, blob, sd_hash):
if blob:
return self.blob_manager.get_blob(blob[0])
2018-07-20 21:35:09 +02:00
return download_sd_blob(
sd_hash, self.blob_manager, self.dht_node.peer_finder, self.rate_limiter, self.payment_rate_manager,
self.wallet, timeout=conf.settings['search_timeout'], download_mirrors=conf.settings['download_mirrors']
2018-07-20 21:35:09 +02:00
)
2016-11-30 22:23:48 +01:00
def get_or_download_sd_blob(self, sd_hash):
2016-12-07 16:10:46 +01:00
"""Return previously downloaded sd blob if already in the blob
manager, otherwise download and return it
2016-11-30 22:23:48 +01:00
"""
d = self.blob_manager.completed_blobs([sd_hash])
2016-11-30 22:23:48 +01:00
d.addCallback(self._get_or_download_sd_blob, sd_hash)
return d
def get_size_from_sd_blob(self, sd_blob):
"""
Get total stream size in bytes from a sd blob
"""
d = self.sd_identifier.get_metadata_for_sd_blob(sd_blob)
d.addCallback(lambda metadata: metadata.validator.info_to_show())
d.addCallback(lambda info: int(dict(info)['stream_size']))
return d
2016-12-02 20:39:01 +01:00
def _get_est_cost_from_stream_size(self, size):
2016-11-30 22:23:48 +01:00
"""
Calculate estimated LBC cost for a stream given its size in bytes
"""
if self.payment_rate_manager.generous:
2016-11-30 22:23:48 +01:00
return 0.0
2017-01-17 04:23:20 +01:00
return size / (10 ** 6) * conf.settings['data_rate']
2016-11-30 22:23:48 +01:00
2017-04-12 20:46:27 +02:00
@defer.inlineCallbacks
2017-04-11 04:47:54 +02:00
def get_est_cost_using_known_size(self, uri, size):
2016-12-02 20:39:01 +01:00
"""
Calculate estimated LBC cost for a stream given its size in bytes
"""
cost = self._get_est_cost_from_stream_size(size)
2018-07-15 16:54:59 +02:00
resolved = yield self._resolve(uri)
2017-06-09 19:47:13 +02:00
if uri in resolved and 'claim' in resolved[uri]:
claim = ClaimDict.load_dict(resolved[uri]['claim']['value'])
2017-04-12 20:46:27 +02:00
final_fee = self._add_key_fee_to_est_data_cost(claim.source_fee, cost)
result = yield self._render_response(final_fee)
defer.returnValue(result)
else:
defer.returnValue(None)
2016-12-02 20:39:01 +01:00
2016-11-30 22:23:48 +01:00
def get_est_cost_from_sd_hash(self, sd_hash):
"""
Get estimated cost from a sd hash
"""
d = self.get_or_download_sd_blob(sd_hash)
d.addCallback(self.get_size_from_sd_blob)
2016-12-02 20:39:01 +01:00
d.addCallback(self._get_est_cost_from_stream_size)
return d
def _get_est_cost_from_metadata(self, metadata, name):
2017-04-12 20:46:27 +02:00
d = self.get_est_cost_from_sd_hash(metadata.source_hash)
def _handle_err(err):
if isinstance(err, Failure):
2016-12-07 16:10:46 +01:00
log.warning(
"Timeout getting blob for cost est for lbry://%s, using only key fee", name)
return 0.0
raise err
d.addErrback(_handle_err)
2017-04-12 20:46:27 +02:00
d.addCallback(lambda data_cost: self._add_key_fee_to_est_data_cost(metadata.source_fee,
data_cost))
return d
2017-04-12 20:46:27 +02:00
def _add_key_fee_to_est_data_cost(self, fee, data_cost):
2017-05-30 23:07:23 +02:00
fee_amount = 0.0 if not fee else self.exchange_rate_manager.convert_currency(fee.currency,
"LBC",
fee.amount)
2016-12-02 20:39:01 +01:00
return data_cost + fee_amount
@defer.inlineCallbacks
2017-04-11 04:47:54 +02:00
def get_est_cost_from_uri(self, uri):
2016-11-30 22:23:48 +01:00
"""
Resolve a name and return the estimated stream cost
"""
2017-06-09 19:47:13 +02:00
2018-07-10 06:30:13 +02:00
resolved = (yield self._resolve(uri))[uri]
2017-06-09 19:47:13 +02:00
if resolved:
claim_response = resolved[uri]
else:
2017-04-11 04:47:54 +02:00
claim_response = None
result = None
2017-04-11 04:47:54 +02:00
if claim_response and 'claim' in claim_response:
if 'value' in claim_response['claim'] and claim_response['claim']['value'] is not None:
claim_value = ClaimDict.load_dict(claim_response['claim']['value'])
cost = yield self._get_est_cost_from_metadata(claim_value, uri)
result = round(cost, 5)
else:
log.warning("Failed to estimate cost for %s", uri)
defer.returnValue(result)
2017-04-11 04:47:54 +02:00
def get_est_cost(self, uri, size=None):
2016-12-07 16:10:46 +01:00
"""Get a cost estimate for a lbry stream, if size is not provided the
sd blob will be downloaded to determine the stream size
2016-11-30 22:23:48 +01:00
"""
2016-12-02 20:39:01 +01:00
2016-11-30 22:23:48 +01:00
if size is not None:
2017-04-11 04:47:54 +02:00
return self.get_est_cost_using_known_size(uri, size)
return self.get_est_cost_from_uri(uri)
@defer.inlineCallbacks
def _get_lbry_file_dict(self, lbry_file, full_status=False):
key = hexlify(lbry_file.key) if lbry_file.key else None
full_path = os.path.join(lbry_file.download_directory, lbry_file.file_name)
mime_type = mimetypes.guess_type(full_path)[0]
if os.path.isfile(full_path):
with open(full_path) as written_file:
written_file.seek(0, os.SEEK_END)
written_bytes = written_file.tell()
else:
2017-09-12 22:00:32 +02:00
written_bytes = 0
size = num_completed = num_known = status = None
if full_status:
size = yield lbry_file.get_total_bytes()
file_status = yield lbry_file.status()
num_completed = file_status.num_completed
num_known = file_status.num_known
status = file_status.running_status
2017-04-09 22:10:07 +02:00
result = {
'completed': lbry_file.completed,
'file_name': lbry_file.file_name,
'download_directory': lbry_file.download_directory,
'points_paid': lbry_file.points_paid,
'stopped': lbry_file.stopped,
'stream_hash': lbry_file.stream_hash,
'stream_name': lbry_file.stream_name,
'suggested_file_name': lbry_file.suggested_file_name,
'sd_hash': lbry_file.sd_hash,
'download_path': full_path,
'mime_type': mime_type,
'key': key,
'total_bytes': size,
'written_bytes': written_bytes,
'blobs_completed': num_completed,
'blobs_in_stream': num_known,
'status': status,
'claim_id': lbry_file.claim_id,
'txid': lbry_file.txid,
'nout': lbry_file.nout,
'outpoint': lbry_file.outpoint,
'metadata': lbry_file.metadata,
'channel_claim_id': lbry_file.channel_claim_id,
'channel_name': lbry_file.channel_name,
'claim_name': lbry_file.claim_name
2017-04-09 22:10:07 +02:00
}
defer.returnValue(result)
2017-02-20 01:22:21 +01:00
@defer.inlineCallbacks
2017-03-08 17:53:22 +01:00
def _get_lbry_file(self, search_by, val, return_json=False, full_status=False):
lbry_file = None
if search_by in FileID:
for l_f in self.file_manager.lbry_files:
if l_f.__dict__.get(search_by) == val:
lbry_file = l_f
break
else:
raise NoValidSearch('{} is not a valid search operation'.format(search_by))
if return_json and lbry_file:
lbry_file = yield self._get_lbry_file_dict(lbry_file, full_status=full_status)
2017-02-20 01:22:21 +01:00
defer.returnValue(lbry_file)
@defer.inlineCallbacks
def _get_lbry_files(self, return_json=False, full_status=True, **kwargs):
lbry_files = list(self.file_manager.lbry_files)
if kwargs:
for search_type, value in iter_lbry_file_search_values(kwargs):
lbry_files = [l_f for l_f in lbry_files if l_f.__dict__[search_type] == value]
2017-03-08 20:04:40 +01:00
if return_json:
file_dicts = []
for lbry_file in lbry_files:
lbry_file_dict = yield self._get_lbry_file_dict(lbry_file, full_status=full_status)
file_dicts.append(lbry_file_dict)
lbry_files = file_dicts
2017-06-02 20:00:13 +02:00
log.debug("Collected %i lbry files", len(lbry_files))
defer.returnValue(lbry_files)
2018-05-02 01:30:33 +02:00
def _sort_lbry_files(self, lbry_files, sort_by):
for field, direction in sort_by:
2018-05-21 06:24:18 +02:00
is_reverse = direction == DIRECTION_DESCENDING
key_getter = create_key_getter(field) if field else None
lbry_files = sorted(lbry_files, key=key_getter, reverse=is_reverse)
return lbry_files
def _parse_lbry_files_sort(self, sort):
"""
Given a sort string like 'file_name, desc' or 'points_paid',
2018-05-02 01:30:33 +02:00
parse the string into a tuple of (field, direction).
Direction defaults to ascending.
"""
2018-05-21 06:24:18 +02:00
pieces = [p.strip() for p in sort.split(',')]
field = pieces.pop(0)
direction = DIRECTION_ASCENDING
if pieces and pieces[0] in DIRECTIONS:
direction = pieces[0]
return field, direction
def _get_single_peer_downloader(self):
downloader = SinglePeerDownloader()
downloader.setup(self.wallet)
return downloader
@defer.inlineCallbacks
def _blob_availability(self, blob_hash, search_timeout, blob_timeout, downloader=None):
if not downloader:
downloader = self._get_single_peer_downloader()
result = {}
search_timeout = search_timeout or conf.settings['peer_search_timeout']
blob_timeout = blob_timeout or conf.settings['sd_download_timeout']
is_available = False
reachable_peers = []
unreachable_peers = []
try:
peers = yield self.jsonrpc_peer_list(blob_hash, search_timeout)
peer_infos = [{"peer": Peer(x[0], x[1]),
"blob_hash": blob_hash,
"timeout": blob_timeout} for x in peers if x[2]]
dl = []
dl_peers = []
dl_results = []
for peer_info in peer_infos:
d = downloader.download_temp_blob_from_peer(**peer_info)
dl.append(d)
dl_peers.append("%s:%i" % (peer_info['peer'].host, peer_info['peer'].port))
for dl_peer, (success, download_result) in zip(dl_peers,
(yield defer.DeferredList(dl))):
if success:
if download_result:
reachable_peers.append(dl_peer)
else:
unreachable_peers.append(dl_peer)
dl_results.append(download_result)
is_available = any(dl_results)
except Exception as err:
result['error'] = "Failed to get peers for blob: %s" % err
response = {
'is_available': is_available,
'reachable_peers': reachable_peers,
'unreachable_peers': unreachable_peers,
}
defer.returnValue(response)
2017-01-03 20:13:01 +01:00
############################################################################
# #
# JSON-RPC API methods start here #
# #
############################################################################
@defer.inlineCallbacks
2018-08-02 23:33:56 +02:00
def jsonrpc_status(self):
2016-03-24 03:27:48 +01:00
"""
2017-05-28 22:01:53 +02:00
Get daemon status
2017-05-28 22:01:53 +02:00
Usage:
2018-08-02 23:33:56 +02:00
status
2017-06-12 22:19:26 +02:00
2018-08-03 19:31:51 +02:00
Options:
None
2017-06-12 22:19:26 +02:00
Returns:
(dict) lbrynet-daemon status
{
'installation_id': (str) installation id - base58,
'is_running': (bool),
'is_first_run': bool,
'skipped_components': (list) [names of skipped components (str)],
'startup_status': { Does not include components which have been skipped
'database': (bool),
'wallet': (bool),
'session': (bool),
'dht': (bool),
'hash_announcer': (bool),
'stream_identifier': (bool),
'file_manager': (bool),
2018-08-02 23:33:56 +02:00
'blob_manager': (bool),
'blockchain_headers': (bool),
'peer_protocol_server': (bool),
'reflector': (bool),
'upnp': (bool),
'exchange_rate_manager': (bool),
2017-06-12 22:19:26 +02:00
},
'connection_status': {
'code': (str) connection status code,
'message': (str) connection status message
2017-06-12 22:19:26 +02:00
},
2018-08-02 23:33:56 +02:00
'blockchain_headers': {
'downloading_headers': (bool),
'download_progress': (float) 0-100.0
},
'wallet': {
'blocks': (int) local blockchain height,
'blocks_behind': (int) remote_height - local_height,
'best_blockhash': (str) block hash of most recent block,
2018-08-13 04:04:48 +02:00
'is_encrypted': (bool),
'is_locked': (bool),
2017-06-12 22:19:26 +02:00
},
'dht': {
2018-07-20 23:22:10 +02:00
'node_id': (str) lbry dht node id - hex encoded,
'peers_in_routing_table': (int) the number of peers in the routing table,
},
2018-08-02 23:33:56 +02:00
'blob_manager': {
'finished_blobs': (int) number of finished blobs in the blob manager,
},
'hash_announcer': {
'announce_queue_size': (int) number of blobs currently queued to be announced
},
'file_manager': {
'managed_files': (int) count of files in the file manager,
}
2017-06-12 22:19:26 +02:00
}
2017-01-03 20:13:01 +01:00
"""
2017-05-28 22:01:53 +02:00
2018-08-02 23:33:56 +02:00
connection_code = CONNECTION_STATUS_CONNECTED if self.connected_to_internet else CONNECTION_STATUS_NETWORK
2017-01-03 20:13:01 +01:00
response = {
'installation_id': conf.settings.installation_id,
'is_running': all(self.component_manager.get_components_status().values()),
'is_first_run': self.is_first_run,
'skipped_components': self.component_manager.skip_components,
2018-07-24 18:34:58 +02:00
'startup_status': self.component_manager.get_components_status(),
2017-01-03 20:13:01 +01:00
'connection_status': {
'code': connection_code,
'message': CONNECTION_MESSAGES[connection_code],
2017-01-03 20:13:01 +01:00
},
}
for component in self.component_manager.components:
status = yield defer.maybeDeferred(component.get_status)
if status:
response[component.component_name] = status
defer.returnValue(response)
def jsonrpc_version(self):
"""
Get lbry version information
2017-05-28 22:01:53 +02:00
Usage:
version
Options:
None
Returns:
2017-03-14 00:14:11 +01:00
(dict) Dictionary of lbry version information
{
2017-03-15 21:31:58 +01:00
'build': (str) build type (e.g. "dev", "rc", "release"),
'ip': (str) remote ip, if available,
2017-03-14 00:14:11 +01:00
'lbrynet_version': (str) lbrynet_version,
'lbryum_version': (str) lbryum_version,
'lbryschema_version': (str) lbryschema_version,
2017-03-15 21:31:58 +01:00
'os_release': (str) os release string
'os_system': (str) os name
'platform': (str) platform string
'processor': (str) processor type,
'python_version': (str) python version,
2017-03-14 00:14:11 +01:00
}
"""
2018-07-20 21:35:09 +02:00
platform_info = system_info.get_platform()
log.info("Get version info: " + json.dumps(platform_info))
return self._render_response(platform_info)
# @AuthJSONRPCServer.deprecated() # deprecated actually disables the call
def jsonrpc_report_bug(self, message=None):
2017-01-02 20:52:24 +01:00
"""
Report a bug to slack
2017-05-28 22:01:53 +02:00
Usage:
report_bug (<message> | --message=<message>)
Options:
--message=<message> : (str) Description of the bug
2017-01-02 20:52:24 +01:00
Returns:
2017-03-14 00:14:11 +01:00
(bool) true if successful
2017-01-02 20:52:24 +01:00
"""
2018-07-20 21:35:09 +02:00
platform_name = system_info.get_platform()['platform']
report_bug_to_slack(
message,
conf.settings.installation_id,
platform_name,
2017-04-26 20:18:41 +02:00
get_lbrynet_version()
)
return self._render_response(True)
2017-01-02 20:52:24 +01:00
2017-01-03 20:13:01 +01:00
def jsonrpc_settings_get(self):
"""
Get daemon settings
2017-05-28 22:01:53 +02:00
Usage:
settings_get
Options:
None
Returns:
2017-03-14 00:14:11 +01:00
(dict) Dictionary of daemon settings
See ADJUSTABLE_SETTINGS in lbrynet/conf.py for full list of settings
"""
return self._render_response(conf.settings.get_adjustable_settings_dict())
def jsonrpc_settings_set(self, **kwargs):
"""
Set daemon settings
Usage:
settings_set [--download_directory=<download_directory>]
[--data_rate=<data_rate>]
[--download_timeout=<download_timeout>]
[--peer_port=<peer_port>]
[--max_key_fee=<max_key_fee>]
[--disable_max_key_fee=<disable_max_key_fee>]
[--use_upnp=<use_upnp>]
[--run_reflector_server=<run_reflector_server>]
[--cache_time=<cache_time>]
[--reflect_uploads=<reflect_uploads>]
[--share_usage_data=<share_usage_data>]
[--peer_search_timeout=<peer_search_timeout>]
[--sd_download_timeout=<sd_download_timeout>]
[--auto_renew_claim_height_delta=<auto_renew_claim_height_delta>]
Options:
--download_directory=<download_directory> : (str) path of download directory
--data_rate=<data_rate> : (float) 0.0001
--download_timeout=<download_timeout> : (int) 180
--peer_port=<peer_port> : (int) 3333
--max_key_fee=<max_key_fee> : (dict) maximum key fee for downloads,
in the format:
{
'currency': <currency_symbol>,
'amount': <amount>
}.
In the CLI, it must be an escaped JSON string
Supported currency symbols: LBC, USD, BTC
--disable_max_key_fee=<disable_max_key_fee> : (bool) False
--use_upnp=<use_upnp> : (bool) True
--run_reflector_server=<run_reflector_server> : (bool) False
--cache_time=<cache_time> : (int) 150
--reflect_uploads=<reflect_uploads> : (bool) True
--share_usage_data=<share_usage_data> : (bool) True
--peer_search_timeout=<peer_search_timeout> : (int) 3
--sd_download_timeout=<sd_download_timeout> : (int) 3
--auto_renew_claim_height_delta=<auto_renew_claim_height_delta> : (int) 0
claims set to expire within this many blocks will be
automatically renewed after startup (if set to 0, renews
will not be made automatically)
Returns:
(dict) Updated dictionary of daemon settings
"""
2018-07-24 18:32:00 +02:00
# TODO: improve upon the current logic, it could be made better
new_settings = kwargs
setting_types = {
'download_directory': str,
'data_rate': float,
'download_timeout': int,
'peer_port': int,
'max_key_fee': dict,
'use_upnp': bool,
'run_reflector_server': bool,
'cache_time': int,
'reflect_uploads': bool,
'share_usage_data': bool,
'disable_max_key_fee': bool,
'peer_search_timeout': int,
'sd_download_timeout': int,
'auto_renew_claim_height_delta': int
}
for key, setting_type in setting_types.items():
2018-07-24 18:32:00 +02:00
if key in new_settings:
if isinstance(new_settings[key], setting_type):
conf.settings.update({key: new_settings[key]},
data_types=(conf.TYPE_RUNTIME, conf.TYPE_PERSISTED))
2018-07-22 01:08:28 +02:00
elif setting_type is dict and isinstance(new_settings[key], str):
2018-07-24 18:32:00 +02:00
decoded = json.loads(str(new_settings[key]))
conf.settings.update({key: decoded},
data_types=(conf.TYPE_RUNTIME, conf.TYPE_PERSISTED))
else:
converted = setting_type(new_settings[key])
conf.settings.update({key: converted},
data_types=(conf.TYPE_RUNTIME, conf.TYPE_PERSISTED))
conf.settings.save_conf_file_settings()
return self._render_response(conf.settings.get_adjustable_settings_dict())
def jsonrpc_help(self, command=None):
2017-01-03 20:13:01 +01:00
"""
2017-01-11 21:31:08 +01:00
Return a useful message for an API command
2017-05-28 22:01:53 +02:00
Usage:
help [<command> | --command=<command>]
Options:
--command=<command> : (str) command to retrieve documentation for
Returns:
(str) Help message
"""
if command is None:
2017-03-09 13:58:36 +01:00
return self._render_response({
2017-03-08 23:10:03 +01:00
'about': 'This is the LBRY JSON-RPC API',
'command_help': 'Pass a `command` parameter to this method to see ' +
'help for that command (e.g. `help command=resolve_name`)',
'command_list': 'Get a full list of commands using the `commands` method',
'more_info': 'Visit https://lbry.io/api for more info',
2017-03-09 13:58:36 +01:00
})
2017-01-03 20:13:01 +01:00
fn = self.callable_methods.get(command)
if fn is None:
2017-03-08 23:14:31 +01:00
raise Exception(
"No help available for '{}'. It is not a valid command.".format(command)
)
2017-03-08 23:10:03 +01:00
2017-03-09 13:58:36 +01:00
return self._render_response({
'help': textwrap.dedent(fn.__doc__ or '')
2017-03-09 13:58:36 +01:00
})
2017-01-03 20:13:01 +01:00
def jsonrpc_commands(self):
"""
Return a list of available commands
2017-05-28 22:01:53 +02:00
Usage:
commands
Options:
None
2017-01-03 20:13:01 +01:00
Returns:
2017-03-14 00:14:11 +01:00
(list) list of available commands
2017-01-03 20:13:01 +01:00
"""
2017-05-28 22:01:53 +02:00
return self._render_response(sorted([command for command in self.callable_methods.keys()]))
@requires(WALLET_COMPONENT)
2018-07-15 07:20:44 +02:00
@defer.inlineCallbacks
2017-04-23 19:33:06 +02:00
def jsonrpc_wallet_balance(self, address=None, include_unconfirmed=False):
2017-01-03 20:13:01 +01:00
"""
Return the balance of the wallet
2016-03-24 03:27:48 +01:00
2017-05-28 22:01:53 +02:00
Usage:
wallet_balance [<address> | --address=<address>] [--include_unconfirmed]
2017-05-28 22:01:53 +02:00
Options:
--address=<address> : (str) If provided only the balance for this
address will be given
--include_unconfirmed : (bool) Include unconfirmed
2017-04-23 19:33:06 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(float) amount of lbry credits in wallet
"""
assert address is None, "Limiting by address needs to be re-implemented in new wallet."
dewies = yield self.wallet.default_account.get_balance(
0 if include_unconfirmed else 6
)
defer.returnValue(round(dewies / COIN, 3))
2017-04-23 19:33:06 +02:00
@requires(WALLET_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_wallet_unlock(self, password):
"""
Unlock an encrypted wallet
Usage:
wallet_unlock (<password> | --password=<password>)
Options:
--password=<password> : (str) password for unlocking wallet
Returns:
(bool) true if wallet is unlocked, otherwise false
"""
# the check_locked() in the if statement is needed because that is what sets
# the wallet_unlocked_d deferred ¯\_(ツ)_/¯
if not self.wallet.check_locked():
d = self.wallet.wallet_unlocked_d
2017-12-18 19:17:54 +01:00
d.callback(password)
result = yield d
else:
2017-12-18 19:17:54 +01:00
result = True
response = yield self._render_response(result)
defer.returnValue(response)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_wallet_decrypt(self):
"""
Decrypt an encrypted wallet, this will remove the wallet password
Usage:
wallet_decrypt
Options:
None
Returns:
(bool) true if wallet is decrypted, otherwise false
"""
result = self.wallet.decrypt_wallet()
response = yield self._render_response(result)
defer.returnValue(response)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_wallet_encrypt(self, new_password):
"""
Encrypt a wallet with a password, if the wallet is already encrypted this will update
the password
Usage:
wallet_encrypt (<new_password> | --new_password=<new_password>)
Options:
--new_password=<new_password> : (str) password string to be used for encrypting wallet
Returns:
(bool) true if wallet is decrypted, otherwise false
"""
self.wallet.encrypt_wallet(new_password)
response = yield self._render_response(self.wallet.wallet.use_encryption)
defer.returnValue(response)
@defer.inlineCallbacks
def jsonrpc_stop(self):
"""
Stop lbrynet-daemon
2016-03-24 03:27:48 +01:00
2017-05-28 22:01:53 +02:00
Usage:
stop
2017-05-28 22:01:53 +02:00
Options:
None
Returns:
2017-03-14 00:14:11 +01:00
(string) Shutdown message
"""
log.info("Shutting down lbrynet daemon")
response = yield self._render_response("Shutting down")
reactor.callLater(0.1, reactor.fireSystemEvent, "shutdown")
defer.returnValue(response)
@requires(FILE_MANAGER_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_file_list(self, sort=None, **kwargs):
2017-01-03 20:13:01 +01:00
"""
List files limited by optional filters
2017-05-28 22:01:53 +02:00
Usage:
file_list [--sd_hash=<sd_hash>] [--file_name=<file_name>] [--stream_hash=<stream_hash>]
[--rowid=<rowid>] [--claim_id=<claim_id>] [--outpoint=<outpoint>] [--txid=<txid>] [--nout=<nout>]
[--channel_claim_id=<channel_claim_id>] [--channel_name=<channel_name>]
[--claim_name=<claim_name>] [--full_status] [--sort=<sort_method>...]
2017-05-28 22:01:53 +02:00
Options:
--sd_hash=<sd_hash> : (str) get file with matching sd hash
--file_name=<file_name> : (str) get file with matching file name in the
downloads folder
--stream_hash=<stream_hash> : (str) get file with matching stream hash
--rowid=<rowid> : (int) get file with matching row id
--claim_id=<claim_id> : (str) get file with matching claim id
--outpoint=<outpoint> : (str) get file with matching claim outpoint
--txid=<txid> : (str) get file with matching claim txid
--nout=<nout> : (int) get file with matching claim nout
--channel_claim_id=<channel_claim_id> : (str) get file with matching channel claim id
--channel_name=<channel_name> : (str) get file with matching channel name
--claim_name=<claim_name> : (str) get file with matching claim name
--full_status : (bool) full status, populate the
'message' and 'size' fields
--sort=<sort_method> : (str) sort by any property, like 'file_name'
or 'metadata.author'; to specify direction
append ',asc' or ',desc'
Returns:
2017-03-14 00:14:11 +01:00
(list) List of files
[
{
2017-03-14 00:14:11 +01:00
'completed': (bool) true if download is completed,
'file_name': (str) name of file,
'download_directory': (str) download directory,
'points_paid': (float) credit paid to download file,
'stopped': (bool) true if download is stopped,
'stream_hash': (str) stream hash of file,
'stream_name': (str) stream name ,
'suggested_file_name': (str) suggested file name,
'sd_hash': (str) sd hash of file,
'download_path': (str) download path of file,
'mime_type': (str) mime type of file,
'key': (str) key attached to file,
'total_bytes': (int) file size in bytes, None if full_status is false,
'written_bytes': (int) written size in bytes,
'blobs_completed': (int) num_completed, None if full_status is false,
'blobs_in_stream': (int) None if full_status is false,
'status': (str) downloader status, None if full_status is false,
'claim_id': (str) None if full_status is false or if claim is not found,
'outpoint': (str) None if full_status is false or if claim is not found,
'txid': (str) None if full_status is false or if claim is not found,
'nout': (int) None if full_status is false or if claim is not found,
'metadata': (dict) None if full_status is false or if claim is not found,
'channel_claim_id': (str) None if full_status is false or if claim is not found or signed,
'channel_name': (str) None if full_status is false or if claim is not found or signed,
'claim_name': (str) None if full_status is false or if claim is not found
2017-03-14 00:14:11 +01:00
},
]
"""
result = yield self._get_lbry_files(return_json=True, **kwargs)
if sort:
sort_by = [self._parse_lbry_files_sort(s) for s in sort]
result = self._sort_lbry_files(result, sort_by)
response = yield self._render_response(result)
defer.returnValue(response)
@requires(WALLET_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_resolve_name(self, name, force=False):
"""
Resolve stream info from a LBRY name
2017-05-28 22:01:53 +02:00
Usage:
resolve_name (<name> | --name=<name>) [--force]
2017-05-28 22:01:53 +02:00
Options:
--name=<name> : (str) the name to resolve
--force : (bool) force refresh and do not check cache
2017-05-28 22:01:53 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(dict) Metadata dictionary from name claim, None if the name is not
resolvable
"""
try:
2018-07-10 06:30:13 +02:00
name = parse_lbry_uri(name).name
metadata = yield self._resolve(name, check_cache=not force)
if name in metadata:
metadata = metadata[name]
except UnknownNameError:
log.info('Name %s is not known', name)
defer.returnValue(None)
else:
defer.returnValue(metadata)
@requires(WALLET_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_claim_show(self, txid=None, nout=None, claim_id=None):
2016-07-28 22:12:20 +02:00
"""
2017-06-23 20:47:28 +02:00
Resolve claim info from txid/nout or with claim ID
2016-07-28 22:12:20 +02:00
2017-05-28 22:01:53 +02:00
Usage:
claim_show [<txid> | --txid=<txid>] [<nout> | --nout=<nout>]
[<claim_id> | --claim_id=<claim_id>]
2017-05-28 22:01:53 +02:00
Options:
--txid=<txid> : (str) look for claim with this txid, nout must
also be specified
--nout=<nout> : (int) look for claim with this nout, txid must
also be specified
--claim_id=<claim_id> : (str) look for claim with this claim id
2017-05-28 22:01:53 +02:00
2017-03-14 00:14:11 +01:00
Returns:
2017-06-23 20:47:28 +02:00
(dict) Dictionary containing claim info as below,
2017-03-11 22:04:10 +01:00
2017-03-14 00:14:11 +01:00
{
'txid': (str) txid of claim
'nout': (int) nout of claim
'amount': (float) amount of claim
'value': (str) value of claim
'height' : (int) height of claim takeover
'claim_id': (str) claim ID of claim
'supports': (list) list of supports associated with claim
}
2017-06-23 20:47:28 +02:00
if claim cannot be resolved, dictionary as below will be returned
{
'error': (str) reason for error
}
2016-07-28 22:12:20 +02:00
"""
if claim_id is not None and txid is None and nout is None:
claim_results = yield self.wallet.get_claim_by_claim_id(claim_id)
elif txid is not None and nout is not None and claim_id is None:
claim_results = yield self.wallet.get_claim_by_outpoint(txid, int(nout))
else:
raise Exception("Must specify either txid/nout, or claim_id")
2017-07-04 03:20:57 +02:00
response = yield self._render_response(claim_results)
defer.returnValue(response)
@requires(WALLET_COMPONENT)
@defer.inlineCallbacks
2017-06-09 19:47:13 +02:00
def jsonrpc_resolve(self, force=False, uri=None, uris=[]):
2017-04-07 02:45:05 +02:00
"""
2017-06-09 19:47:13 +02:00
Resolve given LBRY URIs
2017-04-07 02:45:05 +02:00
2017-05-28 22:01:53 +02:00
Usage:
resolve [--force] (<uri> | --uri=<uri>) [<uris>...]
2017-05-28 22:01:53 +02:00
Options:
--force : (bool) force refresh and ignore cache
--uri=<uri> : (str) uri to resolve
--uris=<uris> : (list) uris to resolve
2017-05-28 22:01:53 +02:00
2017-04-07 02:45:05 +02:00
Returns:
2017-06-09 19:47:13 +02:00
Dictionary of results, keyed by uri
'<uri>': {
If a resolution error occurs:
'error': Error message
If the uri resolves to a channel or a claim in a channel:
'certificate': {
2017-04-12 23:42:55 +02:00
'address': (str) claim address,
'amount': (float) claim amount,
'effective_amount': (float) claim amount including supports,
'claim_id': (str) claim id,
'claim_sequence': (int) claim sequence number,
'decoded_claim': (bool) whether or not the claim value was decoded,
'height': (int) claim height,
'depth': (int) claim depth,
'has_signature': (bool) included if decoded_claim
'name': (str) claim name,
'permanent_url': (str) permanent url of the certificate claim,
'supports: (list) list of supports [{'txid': (str) txid,
'nout': (int) nout,
'amount': (float) amount}],
2017-04-12 23:42:55 +02:00
'txid': (str) claim txid,
'nout': (str) claim nout,
'signature_is_valid': (bool), included if has_signature,
'value': ClaimDict if decoded, otherwise hex string
2017-04-12 20:04:11 +02:00
}
2017-06-09 19:47:13 +02:00
2017-08-17 20:32:28 +02:00
If the uri resolves to a channel:
'claims_in_channel': (int) number of claims in the channel,
2017-06-09 19:47:13 +02:00
If the uri resolves to a claim:
'claim': {
'address': (str) claim address,
'amount': (float) claim amount,
'effective_amount': (float) claim amount including supports,
'claim_id': (str) claim id,
'claim_sequence': (int) claim sequence number,
'decoded_claim': (bool) whether or not the claim value was decoded,
'height': (int) claim height,
'depth': (int) claim depth,
'has_signature': (bool) included if decoded_claim
'name': (str) claim name,
'permanent_url': (str) permanent url of the claim,
2017-06-09 19:47:13 +02:00
'channel_name': (str) channel name if claim is in a channel
'supports: (list) list of supports [{'txid': (str) txid,
'nout': (int) nout,
'amount': (float) amount}]
2017-06-09 19:47:13 +02:00
'txid': (str) claim txid,
'nout': (str) claim nout,
'signature_is_valid': (bool), included if has_signature,
'value': ClaimDict if decoded, otherwise hex string
}
2017-04-07 02:45:05 +02:00
}
"""
2017-06-09 19:47:13 +02:00
uris = tuple(uris)
if uri is not None:
uris += (uri,)
results = {}
valid_uris = tuple()
for u in uris:
try:
parse_lbry_uri(u)
valid_uris += (u,)
2017-06-09 19:47:13 +02:00
except URIParseError:
results[u] = {"error": "%s is not a valid uri" % u}
2018-07-10 06:30:13 +02:00
resolved = yield self._resolve(*valid_uris, check_cache=not force)
2017-06-09 19:47:13 +02:00
for resolved_uri in resolved:
results[resolved_uri] = resolved[resolved_uri]
response = yield self._render_response(results)
defer.returnValue(response)
2017-04-07 02:45:05 +02:00
@requires(STREAM_IDENTIFIER_COMPONENT, WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT,
DHT_COMPONENT, RATE_LIMITER_COMPONENT, PAYMENT_RATE_COMPONENT, DATABASE_COMPONENT,
conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_get(self, uri, file_name=None, timeout=None):
2017-01-03 20:13:01 +01:00
"""
Download stream from a LBRY name.
2017-05-28 22:01:53 +02:00
Usage:
get <uri> [<file_name> | --file_name=<file_name>] [<timeout> | --timeout=<timeout>]
2017-05-28 22:01:53 +02:00
Options:
--uri=<uri> : (str) uri of the content to download
--file_name=<file_name> : (str) specified name for the downloaded file
--timeout=<timeout> : (int) download timeout in number of seconds
2017-05-28 22:01:53 +02:00
Returns:
(dict) Dictionary containing information about the stream
2017-03-06 23:24:13 +01:00
{
2018-02-12 20:13:30 +01:00
'completed': (bool) true if download is completed,
'file_name': (str) name of file,
'download_directory': (str) download directory,
'points_paid': (float) credit paid to download file,
'stopped': (bool) true if download is stopped,
'stream_hash': (str) stream hash of file,
'stream_name': (str) stream name ,
'suggested_file_name': (str) suggested file name,
'sd_hash': (str) sd hash of file,
'download_path': (str) download path of file,
'mime_type': (str) mime type of file,
'key': (str) key attached to file,
'total_bytes': (int) file size in bytes, None if full_status is false,
'written_bytes': (int) written size in bytes,
'blobs_completed': (int) num_completed, None if full_status is false,
'blobs_in_stream': (int) None if full_status is false,
'status': (str) downloader status, None if full_status is false,
'claim_id': (str) claim id,
'outpoint': (str) claim outpoint string,
'txid': (str) claim txid,
'nout': (int) claim nout,
'metadata': (dict) claim metadata,
'channel_claim_id': (str) None if claim is not signed
'channel_name': (str) None if claim is not signed
'claim_name': (str) claim name
2017-03-06 23:24:13 +01:00
}
"""
2017-02-20 01:22:21 +01:00
2018-07-20 21:35:09 +02:00
timeout = timeout if timeout is not None else conf.settings['download_timeout']
parsed_uri = parse_lbry_uri(uri)
if parsed_uri.is_channel and not parsed_uri.path:
raise Exception("cannot download a channel claim, specify a /path")
2018-07-10 06:30:13 +02:00
resolved_result = yield self._resolve(uri)
2017-06-09 19:47:13 +02:00
if resolved_result and uri in resolved_result:
resolved = resolved_result[uri]
else:
resolved = None
2017-06-09 19:47:13 +02:00
if not resolved or 'value' not in resolved:
if 'claim' not in resolved:
2018-02-28 20:59:12 +01:00
raise Exception(
"Failed to resolve stream at lbry://{}".format(uri.replace("lbry://", ""))
)
else:
resolved = resolved['claim']
txid, nout, name = resolved['txid'], resolved['nout'], resolved['name']
2017-06-02 20:00:13 +02:00
claim_dict = ClaimDict.load_dict(resolved['value'])
sd_hash = claim_dict.source_hash
if sd_hash in self.streams:
log.info("Already waiting on lbry://%s to start downloading", name)
yield self.streams[sd_hash].data_downloading_deferred
lbry_file = yield self._get_lbry_file(FileID.SD_HASH, sd_hash, return_json=False)
2017-03-06 23:24:13 +01:00
if lbry_file:
2017-03-06 23:24:13 +01:00
if not os.path.isfile(os.path.join(lbry_file.download_directory, lbry_file.file_name)):
log.info("Already have lbry file but missing file in %s, rebuilding it",
lbry_file.download_directory)
yield lbry_file.start()
else:
log.info('Already have a file for %s', name)
result = yield self._get_lbry_file_dict(lbry_file, full_status=True)
else:
result = yield self._download_name(name, claim_dict, sd_hash, txid, nout,
timeout=timeout, file_name=file_name)
2017-03-06 23:24:13 +01:00
response = yield self._render_response(result)
defer.returnValue(response)
2015-12-08 06:37:49 +01:00
@requires(FILE_MANAGER_COMPONENT)
2017-01-03 20:13:01 +01:00
@defer.inlineCallbacks
2017-03-24 18:37:31 +01:00
def jsonrpc_file_set_status(self, status, **kwargs):
2017-01-03 20:13:01 +01:00
"""
2017-03-24 18:37:31 +01:00
Start or stop downloading a file
2017-05-28 22:01:53 +02:00
Usage:
file_set_status (<status> | --status=<status>) [--sd_hash=<sd_hash>]
[--file_name=<file_name>] [--stream_hash=<stream_hash>] [--rowid=<rowid>]
2017-05-28 22:01:53 +02:00
Options:
--status=<status> : (str) one of "start" or "stop"
--sd_hash=<sd_hash> : (str) set status of file with matching sd hash
--file_name=<file_name> : (str) set status of file with matching file name in the
downloads folder
--stream_hash=<stream_hash> : (str) set status of file with matching stream hash
--rowid=<rowid> : (int) set status of file with matching row id
2017-05-28 22:01:53 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(str) Confirmation message
"""
2017-01-03 20:13:01 +01:00
if status not in ['start', 'stop']:
raise Exception('Status must be "start" or "stop".')
search_type, value = get_lbry_file_search_value(kwargs)
2017-01-03 20:13:01 +01:00
lbry_file = yield self._get_lbry_file(search_type, value, return_json=False)
if not lbry_file:
raise Exception('Unable to find a file for {}:{}'.format(search_type, value))
2017-01-03 20:13:01 +01:00
if status == 'start' and lbry_file.stopped or status == 'stop' and not lbry_file.stopped:
yield self.file_manager.toggle_lbry_file_running(lbry_file)
2017-03-24 18:37:31 +01:00
msg = "Started downloading file" if status == 'start' else "Stopped downloading file"
2017-01-03 20:13:01 +01:00
else:
2017-01-10 02:05:27 +01:00
msg = (
2017-03-24 18:37:31 +01:00
"File was already being downloaded" if status == 'start'
2017-04-26 20:15:38 +02:00
else "File was already stopped"
2017-01-10 02:05:27 +01:00
)
response = yield self._render_response(msg)
defer.returnValue(response)
2016-01-17 05:06:24 +01:00
@requires(FILE_MANAGER_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_file_delete(self, delete_from_download_dir=False, delete_all=False, **kwargs):
2016-03-24 03:27:48 +01:00
"""
2017-05-28 22:01:53 +02:00
Delete a LBRY file
Usage:
file_delete [--delete_from_download_dir] [--delete_all] [--sd_hash=<sd_hash>] [--file_name=<file_name>]
[--stream_hash=<stream_hash>] [--rowid=<rowid>] [--claim_id=<claim_id>] [--txid=<txid>]
[--nout=<nout>] [--claim_name=<claim_name>] [--channel_claim_id=<channel_claim_id>]
[--channel_name=<channel_name>]
2017-05-28 22:01:53 +02:00
Options:
--delete_from_download_dir : (bool) delete file from download directory,
instead of just deleting blobs
--delete_all : (bool) if there are multiple matching files,
allow the deletion of multiple files.
Otherwise do not delete anything.
--sd_hash=<sd_hash> : (str) delete by file sd hash
--file_name<file_name> : (str) delete by file name in downloads folder
--stream_hash=<stream_hash> : (str) delete by file stream hash
--rowid=<rowid> : (int) delete by file row id
--claim_id=<claim_id> : (str) delete by file claim id
--txid=<txid> : (str) delete by file claim txid
--nout=<nout> : (int) delete by file claim nout
--claim_name=<claim_name> : (str) delete by file claim name
--channel_claim_id=<channel_claim_id> : (str) delete by file channel claim id
--channel_name=<channel_name> : (str) delete by file channel claim name
2016-01-21 04:00:28 +01:00
Returns:
2017-03-14 00:14:11 +01:00
(bool) true if deletion was successful
2016-03-24 03:27:48 +01:00
"""
2016-01-21 04:00:28 +01:00
2017-03-08 20:19:54 +01:00
lbry_files = yield self._get_lbry_files(return_json=False, **kwargs)
2017-04-07 02:45:05 +02:00
if len(lbry_files) > 1:
2017-04-07 02:45:05 +02:00
if not delete_all:
log.warning("There are %i files to delete, use narrower filters to select one",
len(lbry_files))
response = yield self._render_response(False)
defer.returnValue(response)
2017-04-07 02:45:05 +02:00
else:
log.warning("Deleting %i files",
len(lbry_files))
if not lbry_files:
2017-03-08 20:19:54 +01:00
log.warning("There is no file to delete")
result = False
else:
2017-04-07 02:45:05 +02:00
for lbry_file in lbry_files:
file_name, stream_hash = lbry_file.file_name, lbry_file.stream_hash
if lbry_file.sd_hash in self.streams:
del self.streams[lbry_file.sd_hash]
yield self.file_manager.delete_lbry_file(lbry_file,
delete_file=delete_from_download_dir)
log.info("Deleted file: %s", file_name)
result = True
response = yield self._render_response(result)
defer.returnValue(response)
@requires(STREAM_IDENTIFIER_COMPONENT, WALLET_COMPONENT, EXCHANGE_RATE_MANAGER_COMPONENT, BLOB_COMPONENT,
DHT_COMPONENT, RATE_LIMITER_COMPONENT, PAYMENT_RATE_COMPONENT, DATABASE_COMPONENT,
conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
2017-04-11 04:47:54 +02:00
def jsonrpc_stream_cost_estimate(self, uri, size=None):
2017-01-03 20:13:01 +01:00
"""
Get estimated cost for a lbry stream
2017-05-28 22:01:53 +02:00
Usage:
stream_cost_estimate (<uri> | --uri=<uri>) [<size> | --size=<size>]
2017-05-28 22:01:53 +02:00
Options:
--uri=<uri> : (str) uri to use
--size=<size> : (float) stream size in bytes. if provided an sd blob won't be
2017-05-28 22:01:53 +02:00
downloaded.
2017-01-03 20:13:01 +01:00
Returns:
(float) Estimated cost in lbry credits, returns None if uri is not
resolvable
2017-01-03 20:13:01 +01:00
"""
2017-04-11 04:47:54 +02:00
cost = yield self.get_est_cost(uri, size)
defer.returnValue(cost)
2017-01-03 20:13:01 +01:00
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-04-07 02:45:05 +02:00
@defer.inlineCallbacks
def jsonrpc_channel_new(self, channel_name, amount):
"""
2017-05-28 22:01:53 +02:00
Generate a publisher key and create a new '@' prefixed certificate claim
2017-04-07 02:45:05 +02:00
2017-05-28 22:01:53 +02:00
Usage:
channel_new (<channel_name> | --channel_name=<channel_name>)
(<amount> | --amount=<amount>)
2017-04-07 02:45:05 +02:00
Options:
--channel_name=<channel_name> : (str) name of the channel prefixed with '@'
--amount=<amount> : (float) bid amount on the channel
2017-04-07 02:45:05 +02:00
Returns:
(dict) Dictionary containing result of the claim
{
'tx' : (str) hex encoded transaction
'txid' : (str) txid of resulting claim
'nout' : (int) nout of the resulting claim
'fee' : (float) fee paid for the claim transaction
'claim_id' : (str) claim ID of the resulting claim
}
"""
2018-07-16 01:43:16 +02:00
amount = int(amount * COIN)
2018-07-05 04:16:02 +02:00
tx = yield self.wallet.claim_new_channel(channel_name, amount)
2018-07-12 18:18:58 +02:00
self.wallet.save()
2018-07-05 04:16:02 +02:00
script = tx.outputs[0].script
result = {
"success": True,
2018-07-15 21:50:32 +02:00
"txid": tx.id,
2018-07-05 04:16:02 +02:00
"nout": 0,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": hexlify(tx.get_claim_id(0)[::-1]),
2018-07-05 04:16:02 +02:00
"value": hexlify(script.values['claim']),
"claim_address": self.ledger.hash160_to_address(script.values['pubkey_hash'])
}
2017-04-27 02:02:00 +02:00
self.analytics_manager.send_new_channel()
2017-04-10 17:01:28 +02:00
log.info("Claimed a new channel! Result: %s", result)
2018-07-05 04:16:02 +02:00
defer.returnValue(result)
2017-04-07 02:45:05 +02:00
@requires(WALLET_COMPONENT)
2017-04-07 02:45:05 +02:00
@defer.inlineCallbacks
def jsonrpc_channel_list(self):
2017-04-07 02:45:05 +02:00
"""
Get certificate claim infos for channels that can be published to
2017-04-07 02:45:05 +02:00
2017-05-28 22:01:53 +02:00
Usage:
channel_list
2017-05-28 22:01:53 +02:00
Options:
None
2017-04-07 02:45:05 +02:00
Returns:
(list) ClaimDict, includes 'is_mine' field to indicate if the certificate claim
is in the wallet.
2017-04-07 02:45:05 +02:00
"""
result = yield self.wallet.channel_list()
2017-04-07 02:45:05 +02:00
response = yield self._render_response(result)
defer.returnValue(response)
@requires(WALLET_COMPONENT)
@AuthJSONRPCServer.deprecated("channel_list")
def jsonrpc_channel_list_mine(self):
"""
Get certificate claim infos for channels that can be published to (deprecated)
Usage:
channel_list_mine
Options:
None
Returns:
(list) ClaimDict
"""
return self.jsonrpc_channel_list()
@requires(WALLET_COMPONENT)
2017-11-22 19:46:34 +01:00
@defer.inlineCallbacks
def jsonrpc_channel_export(self, claim_id):
"""
Export serialized channel signing information for a given certificate claim id
Usage:
channel_export (<claim_id> | --claim_id=<claim_id>)
Options:
--claim_id=<claim_id> : (str) Claim ID to export information about
2017-11-22 19:46:34 +01:00
Returns:
(str) Serialized certificate information
"""
result = yield self.wallet.export_certificate_info(claim_id)
2017-11-22 19:46:34 +01:00
defer.returnValue(result)
@requires(WALLET_COMPONENT)
2017-11-22 19:46:34 +01:00
@defer.inlineCallbacks
def jsonrpc_channel_import(self, serialized_certificate_info):
"""
Import serialized channel signing information (to allow signing new claims to the channel)
Usage:
2018-03-26 19:16:55 +02:00
channel_import (<serialized_certificate_info> | --serialized_certificate_info=<serialized_certificate_info>)
2017-11-22 19:46:34 +01:00
Options:
--serialized_certificate_info=<serialized_certificate_info> : (str) certificate info
2017-11-22 19:46:34 +01:00
Returns:
(dict) Result dictionary
"""
result = yield self.wallet.import_certificate_info(serialized_certificate_info)
2017-11-22 19:46:34 +01:00
defer.returnValue(result)
@requires(WALLET_COMPONENT, FILE_MANAGER_COMPONENT, BLOB_COMPONENT, PAYMENT_RATE_COMPONENT, DATABASE_COMPONENT,
conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_publish(self, name, bid, metadata=None, file_path=None, fee=None, title=None,
description=None, author=None, language=None, license=None,
2017-04-07 02:45:05 +02:00
license_url=None, thumbnail=None, preview=None, nsfw=None, sources=None,
channel_name=None, channel_id=None,
2017-06-12 19:32:01 +02:00
claim_address=None, change_address=None):
2016-03-24 03:27:48 +01:00
"""
2017-03-28 17:47:01 +02:00
Make a new name claim and publish associated data to lbrynet,
update over existing claim if user already has a claim for name.
2016-01-21 04:00:28 +01:00
Fields required in the final Metadata are:
'title'
'description'
'author'
'language'
2017-05-28 22:01:53 +02:00
'license'
'nsfw'
Metadata can be set by either using the metadata argument or by setting individual arguments
fee, title, description, author, language, license, license_url, thumbnail, preview, nsfw,
or sources. Individual arguments will overwrite the fields specified in metadata argument.
2017-05-28 22:01:53 +02:00
Usage:
publish (<name> | --name=<name>) (<bid> | --bid=<bid>) [--metadata=<metadata>]
[--file_path=<file_path>] [--fee=<fee>] [--title=<title>]
[--description=<description>] [--author=<author>] [--language=<language>]
[--license=<license>] [--license_url=<license_url>] [--thumbnail=<thumbnail>]
[--preview=<preview>] [--nsfw=<nsfw>] [--sources=<sources>]
[--channel_name=<channel_name>] [--channel_id=<channel_id>]
2017-06-12 19:32:01 +02:00
[--claim_address=<claim_address>] [--change_address=<change_address>]
2017-05-28 22:01:53 +02:00
Options:
--name=<name> : (str) name of the content
--bid=<bid> : (decimal) amount to back the claim
--metadata=<metadata> : (dict) ClaimDict to associate with the claim.
--file_path=<file_path> : (str) path to file to be associated with name. If provided,
2017-05-28 22:01:53 +02:00
a lbry stream of this file will be used in 'sources'.
If no path is given but a sources dict is provided,
it will be used. If neither are provided, an
error is raised.
--fee=<fee> : (dict) Dictionary representing key fee to download content:
2017-05-30 23:07:23 +02:00
{
'currency': currency_symbol,
'amount': decimal,
2017-05-30 23:07:23 +02:00
'address': str, optional
}
2017-05-28 22:01:53 +02:00
supported currencies: LBC, USD, BTC
If an address is not provided a new one will be
automatically generated. Default fee is zero.
--title=<title> : (str) title of the publication
--description=<description> : (str) description of the publication
--author=<author> : (str) author of the publication
--language=<language> : (str) language of the publication
--license=<license> : (str) publication license
--license_url=<license_url> : (str) publication license url
--thumbnail=<thumbnail> : (str) thumbnail url
--preview=<preview> : (str) preview url
--nsfw=<nsfw> : (bool) whether the content is nsfw
--sources=<sources> : (str) {'lbry_sd_hash': sd_hash} specifies sd hash of file
--channel_name=<channel_name> : (str) name of the publisher channel name in the wallet
--channel_id=<channel_id> : (str) claim id of the publisher channel, does not check
2017-05-28 22:01:53 +02:00
for channel claim being in the wallet. This allows
publishing to a channel where only the certificate
private key is in the wallet.
--claim_address=<claim_address> : (str) address where the claim is sent to, if not specified
new address wil automatically be created
2017-05-20 17:59:55 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(dict) Dictionary containing result of the claim
{
'tx' : (str) hex encoded transaction
'txid' : (str) txid of resulting claim
'nout' : (int) nout of the resulting claim
'fee' : (decimal) fee paid for the claim transaction
2017-03-14 00:14:11 +01:00
'claim_id' : (str) claim ID of the resulting claim
}
2016-03-24 03:27:48 +01:00
"""
try:
parse_lbry_uri(name)
except (TypeError, URIParseError):
raise Exception("Invalid name given to publish")
try:
bid = Decimal(str(bid))
except InvalidOperation:
raise TypeError("Bid does not represent a valid decimal.")
if bid <= 0.0:
raise ValueError("Bid value must be greater than 0.0")
2018-07-12 05:18:59 +02:00
bid = int(bid * COIN)
available = yield self.wallet.default_account.get_balance()
if bid >= available:
# TODO: add check for existing claim balance
#balance = yield self.wallet.get_max_usable_balance_for_claim(name)
#max_bid_amount = balance - MAX_UPDATE_FEE_ESTIMATE
#if balance <= MAX_UPDATE_FEE_ESTIMATE:
raise InsufficientFundsError(
"Insufficient funds, please deposit additional LBC. Minimum additional LBC needed {}"
.format(round((bid - available)/COIN + 0.01, 2))
)
# .format(MAX_UPDATE_FEE_ESTIMATE - balance))
#elif bid > max_bid_amount:
# raise InsufficientFundsError(
# "Please lower the bid value, the maximum amount you can specify for this claim is {}."
# .format(max_bid_amount))
metadata = metadata or {}
if fee is not None:
metadata['fee'] = fee
if title is not None:
metadata['title'] = title
if description is not None:
metadata['description'] = description
if author is not None:
metadata['author'] = author
if language is not None:
metadata['language'] = language
if license is not None:
metadata['license'] = license
if license_url is not None:
2017-05-30 16:48:21 +02:00
metadata['licenseUrl'] = license_url
if thumbnail is not None:
metadata['thumbnail'] = thumbnail
if preview is not None:
metadata['preview'] = preview
if nsfw is not None:
metadata['nsfw'] = bool(nsfw)
2017-04-03 21:58:20 +02:00
metadata['version'] = '_0_1_0'
# check for original deprecated format {'currency':{'address','amount'}}
# add address, version to fee if unspecified
if 'fee' in metadata:
if len(metadata['fee'].keys()) == 1 and isinstance(metadata['fee'].values()[0], dict):
raise Exception('Old format for fee no longer supported. '
'Fee must be specified as {"currency":,"address":,"amount":}')
if 'amount' in metadata['fee'] and 'currency' in metadata['fee']:
if not metadata['fee']['amount']:
log.warning("Stripping empty fee from published metadata")
del metadata['fee']
elif 'address' not in metadata['fee']:
2018-07-12 05:18:59 +02:00
address = yield self.wallet.default_account.receiving.get_or_create_usable_address()
metadata['fee']['address'] = address
2017-06-02 17:50:36 +02:00
if 'fee' in metadata and 'version' not in metadata['fee']:
metadata['fee']['version'] = '_0_0_1'
2017-04-03 21:58:20 +02:00
claim_dict = {
2017-04-07 02:45:05 +02:00
'version': '_0_0_1',
'claimType': 'streamType',
'stream': {
'metadata': metadata,
'version': '_0_0_1'
}
}
2017-04-03 21:58:20 +02:00
# this will be used to verify the format with lbryschema
claim_copy = deepcopy(claim_dict)
2017-04-03 21:58:20 +02:00
if sources is not None:
claim_dict['stream']['source'] = sources
claim_copy['stream']['source'] = sources
elif file_path is not None:
if not os.path.isfile(file_path):
raise Exception("invalid file path to publish")
# since the file hasn't yet been made into a stream, we don't have
# a valid Source for the claim when validating the format, we'll use a fake one
claim_copy['stream']['source'] = {
'version': '_0_0_1',
'sourceType': 'lbry_sd_hash',
'source': '0' * 96,
'contentType': ''
}
else:
# there is no existing source to use, and a file was not provided to make a new one
raise Exception("no source provided to publish")
try:
ClaimDict.load_dict(claim_copy)
# the metadata to use in the claim can be serialized by lbryschema
except DecodeError as err:
# there was a problem with a metadata field, raise an error here rather than
# waiting to find out when we go to publish the claim (after having made the stream)
raise Exception("invalid publish metadata: %s" % err.message)
2017-04-03 21:58:20 +02:00
log.info("Publish: %s", {
'name': name,
'file_path': file_path,
'bid': bid,
2017-06-12 19:32:01 +02:00
'claim_address': claim_address,
'change_address': change_address,
'claim_dict': claim_dict,
2017-12-29 20:09:21 +01:00
'channel_id': channel_id,
'channel_name': channel_name
})
2018-07-12 18:14:47 +02:00
certificate = None
if channel_name:
certificates = yield self.wallet.get_certificates(channel_name)
for cert in certificates:
if cert.claim_id == channel_id:
certificate = cert
break
if certificate is None:
2017-04-07 02:45:05 +02:00
raise Exception("Cannot publish using channel %s" % channel_name)
2018-07-12 05:18:59 +02:00
result = yield self._publish_stream(name, bid, claim_dict, file_path, certificate,
2017-06-12 19:32:01 +02:00
claim_address, change_address)
response = yield self._render_response(result)
defer.returnValue(response)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-02-13 20:17:53 +01:00
@defer.inlineCallbacks
2017-08-08 18:37:28 +02:00
def jsonrpc_claim_abandon(self, claim_id=None, txid=None, nout=None):
2016-03-24 03:27:48 +01:00
"""
Abandon a name and reclaim credits from the claim
2016-12-09 04:05:31 +01:00
2017-05-28 22:01:53 +02:00
Usage:
2017-08-08 18:37:28 +02:00
claim_abandon [<claim_id> | --claim_id=<claim_id>]
[<txid> | --txid=<txid>] [<nout> | --nout=<nout>]
2017-05-28 22:01:53 +02:00
Options:
--claim_id=<claim_id> : (str) claim_id of the claim to abandon
--txid=<txid> : (str) txid of the claim to abandon
--nout=<nout> : (int) nout of the claim to abandon
Returns:
2017-03-14 00:14:11 +01:00
(dict) Dictionary containing result of the claim
{
txid : (str) txid of resulting transaction
fee : (float) fee paid for the transaction
}
2016-03-24 03:27:48 +01:00
"""
2017-08-08 18:37:28 +02:00
if claim_id is None and txid is None and nout is None:
raise Exception('Must specify claim_id, or txid and nout')
if txid is None and nout is not None:
raise Exception('Must specify txid')
if nout is None and txid is not None:
raise Exception('Must specify nout')
tx = yield self.wallet.abandon_claim(claim_id, txid, nout)
result = {
"success": True,
"txid": tx.id,
"nout": 0,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": claim_id
}
self.analytics_manager.send_claim_action('abandon')
defer.returnValue(result)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-04-27 02:02:00 +02:00
@defer.inlineCallbacks
def jsonrpc_claim_new_support(self, name, claim_id, amount):
2016-08-08 08:32:56 +02:00
"""
Support a name claim
2017-05-28 22:01:53 +02:00
Usage:
claim_new_support (<name> | --name=<name>) (<claim_id> | --claim_id=<claim_id>)
(<amount> | --amount=<amount>)
2017-05-28 22:01:53 +02:00
Options:
--name=<name> : (str) name of the claim to support
--claim_id=<claim_id> : (str) claim_id of the claim to support
--amount=<amount> : (float) amount of support
Returns:
2017-03-14 00:14:11 +01:00
(dict) Dictionary containing result of the claim
{
txid : (str) txid of resulting support claim
nout : (int) nout of the resulting support claim
fee : (float) fee paid for the transaction
}
2016-08-08 08:32:56 +02:00
"""
result = yield self.wallet.support_claim(name, claim_id, amount)
2017-04-27 02:02:00 +02:00
self.analytics_manager.send_claim_action('new_support')
defer.returnValue(result)
2016-08-08 08:32:56 +02:00
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-11-28 17:00:17 +01:00
@defer.inlineCallbacks
def jsonrpc_claim_renew(self, outpoint=None, height=None):
"""
Renew claim(s) or support(s)
Usage:
claim_renew (<outpoint> | --outpoint=<outpoint>) | (<height> | --height=<height>)
Options:
--outpoint=<outpoint> : (str) outpoint of the claim to renew
--height=<height> : (str) update claims expiring before or at this block height
Returns:
(dict) Dictionary where key is the the original claim's outpoint and
value is the result of the renewal
2017-11-28 17:00:17 +01:00
{
outpoint:{
'tx' : (str) hex encoded transaction
'txid' : (str) txid of resulting claim
'nout' : (int) nout of the resulting claim
'fee' : (float) fee paid for the claim transaction
'claim_id' : (str) claim ID of the resulting claim
},
2017-11-28 17:00:17 +01:00
}
"""
if outpoint is None and height is None:
raise Exception("must provide an outpoint or a height")
elif outpoint is not None:
if len(outpoint.split(":")) == 2:
txid, nout = outpoint.split(":")
nout = int(nout)
else:
raise Exception("invalid outpoint")
result = yield self.wallet.claim_renew(txid, nout)
result = {outpoint: result}
2017-11-28 17:00:17 +01:00
else:
height = int(height)
result = yield self.wallet.claim_renew_all_before_expiration(height)
2017-11-28 17:00:17 +01:00
defer.returnValue(result)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-06-12 16:30:18 +02:00
@defer.inlineCallbacks
def jsonrpc_claim_send_to_address(self, claim_id, address, amount=None):
"""
Send a name claim to an address
Usage:
claim_send_to_address (<claim_id> | --claim_id=<claim_id>)
(<address> | --address=<address>)
[<amount> | --amount=<amount>]
Options:
--claim_id=<claim_id> : (str) claim_id to send
--address=<address> : (str) address to send the claim to
--amount<amount> : (int) Amount of credits to claim name for, defaults to the current amount
on the claim
Returns:
(dict) Dictionary containing result of the claim
{
'tx' : (str) hex encoded transaction
'txid' : (str) txid of resulting claim
'nout' : (int) nout of the resulting claim
'fee' : (float) fee paid for the claim transaction
'claim_id' : (str) claim ID of the resulting claim
}
2017-06-12 16:30:18 +02:00
"""
result = yield self.wallet.send_claim_to_address(claim_id, address, amount)
2017-06-12 16:30:18 +02:00
response = yield self._render_response(result)
defer.returnValue(response)
2017-01-03 20:13:01 +01:00
# TODO: claim_list_mine should be merged into claim_list, but idk how to authenticate it -Grin
@requires(WALLET_COMPONENT)
2017-01-03 20:13:01 +01:00
def jsonrpc_claim_list_mine(self):
"""
List my name claims
2016-03-24 03:27:48 +01:00
2017-05-28 22:01:53 +02:00
Usage:
claim_list_mine
Options:
None
Returns:
2017-03-14 00:14:11 +01:00
(list) List of name claims owned by user
2017-03-11 22:04:10 +01:00
[
{
2017-03-14 00:14:11 +01:00
'address': (str) address that owns the claim
'amount': (float) amount assigned to the claim
'blocks_to_expiration': (int) number of blocks until it expires
'category': (str) "claim", "update" , or "support"
'claim_id': (str) claim ID of the claim
'confirmations': (int) number of blocks of confirmations for the claim
'expiration_height': (int) the block height which the claim will expire
'expired': (bool) true if expired, false otherwise
'height': (int) height of the block containing the claim
'is_spent': (bool) true if claim is abandoned, false otherwise
'name': (str) name of the claim
'permanent_url': (str) permanent url of the claim,
'txid': (str) txid of the claim
2017-03-14 00:14:11 +01:00
'nout': (int) nout of the claim
'value': (str) value of the claim
2017-03-11 22:04:10 +01:00
},
]
2016-03-24 03:27:48 +01:00
"""
d = self.wallet.get_name_claims()
d.addCallback(lambda claims: self._render_response(claims))
return d
@requires(WALLET_COMPONENT)
2017-04-07 02:45:05 +02:00
@defer.inlineCallbacks
def jsonrpc_claim_list(self, name):
2016-08-08 07:43:10 +02:00
"""
2017-06-09 19:47:13 +02:00
List current claims and information about them for a given name
2016-08-08 07:43:10 +02:00
2017-05-28 22:01:53 +02:00
Usage:
claim_list (<name> | --name=<name>)
Options:
--name=<name> : (str) name of the claim to list info about
Returns:
2017-03-14 00:14:11 +01:00
(dict) State of claims assigned for the name
2017-03-11 22:04:10 +01:00
{
2017-03-14 00:14:11 +01:00
'claims': (list) list of claims for the name
2017-03-11 22:04:10 +01:00
[
{
2017-03-14 00:14:11 +01:00
'amount': (float) amount assigned to the claim
'effective_amount': (float) total amount assigned to the claim,
including supports
'claim_id': (str) claim ID of the claim
'height': (int) height of block containing the claim
'txid': (str) txid of the claim
'nout': (int) nout of the claim
'permanent_url': (str) permanent url of the claim,
2017-03-14 00:14:11 +01:00
'supports': (list) a list of supports attached to the claim
'value': (str) the value of the claim
2017-03-11 22:04:10 +01:00
},
]
2017-03-14 00:14:11 +01:00
'supports_without_claims': (list) supports without any claims attached to them
'last_takeover_height': (int) the height of last takeover for the name
2017-03-11 22:04:10 +01:00
}
2016-08-08 07:43:10 +02:00
"""
claims = yield self.wallet.get_claims_for_name(name) # type: dict
sort_claim_results(claims['claims'])
2017-04-07 02:45:05 +02:00
defer.returnValue(claims)
2016-08-08 07:43:10 +02:00
@requires(WALLET_COMPONENT)
2017-06-09 19:47:32 +02:00
@defer.inlineCallbacks
def jsonrpc_claim_list_by_channel(self, page=0, page_size=10, uri=None, uris=[]):
"""
Get paginated claims in a channel specified by a channel uri
Usage:
claim_list_by_channel (<uri> | --uri=<uri>) [<uris>...] [--page=<page>]
[--page_size=<page_size>]
Options:
--uri=<uri> : (str) uri of the channel
--uris=<uris> : (list) uris of the channel
--page=<page> : (int) which page of results to return where page 1 is the first
page, defaults to no pages
--page_size=<page_size> : (int) number of results in a page, default of 10
2017-06-09 19:47:32 +02:00
Returns:
{
resolved channel uri: {
If there was an error:
'error': (str) error message
'claims_in_channel': the total number of results for the channel,
2017-06-09 19:47:32 +02:00
If a page of results was requested:
'returned_page': page number returned,
'claims_in_channel': [
{
'absolute_channel_position': (int) claim index number in sorted list of
claims which assert to be part of the
channel
'address': (str) claim address,
'amount': (float) claim amount,
'effective_amount': (float) claim amount including supports,
'claim_id': (str) claim id,
'claim_sequence': (int) claim sequence number,
'decoded_claim': (bool) whether or not the claim value was decoded,
'height': (int) claim height,
'depth': (int) claim depth,
'has_signature': (bool) included if decoded_claim
'name': (str) claim name,
'supports: (list) list of supports [{'txid': (str) txid,
'nout': (int) nout,
'amount': (float) amount}],
2017-06-09 19:47:32 +02:00
'txid': (str) claim txid,
'nout': (str) claim nout,
'signature_is_valid': (bool), included if has_signature,
'value': ClaimDict if decoded, otherwise hex string
}
],
}
}
"""
uris = tuple(uris)
if uri is not None:
uris += (uri,)
2017-06-09 19:47:32 +02:00
results = {}
valid_uris = tuple()
for chan_uri in uris:
try:
parsed = parse_lbry_uri(chan_uri)
if not parsed.is_channel:
results[chan_uri] = {"error": "%s is not a channel uri" % parsed.name}
elif parsed.path:
results[chan_uri] = {"error": "%s is a claim in a channel" % parsed.path}
else:
valid_uris += (chan_uri,)
2017-06-09 19:47:32 +02:00
except URIParseError:
results[chan_uri] = {"error": "%s is not a valid uri" % chan_uri}
2018-07-10 06:30:13 +02:00
resolved = yield self._resolve(*valid_uris, page=page, page_size=page_size)
2017-06-09 19:47:32 +02:00
for u in resolved:
if 'error' in resolved[u]:
results[u] = resolved[u]
else:
results[u] = {
'claims_in_channel': resolved[u]['claims_in_channel']
}
2017-06-09 19:47:32 +02:00
if page:
results[u]['returned_page'] = page
results[u]['claims_in_channel'] = resolved[u].get('claims_in_channel', [])
response = yield self._render_response(results)
defer.returnValue(response)
@requires(WALLET_COMPONENT)
def jsonrpc_transaction_list(self):
2017-01-03 20:13:01 +01:00
"""
2017-03-14 00:14:11 +01:00
List transactions belonging to wallet
2017-05-28 22:01:53 +02:00
Usage:
transaction_list
2017-05-28 22:01:53 +02:00
Options:
None
Returns:
2018-01-18 19:41:17 +01:00
(list) List of transactions
{
"claim_info": (list) claim info if in txn [{
"address": (str) address of claim,
"balance_delta": (float) bid amount,
"amount": (float) claim amount,
"claim_id": (str) claim id,
"claim_name": (str) claim name,
"nout": (int) nout
}],
"abandon_info": (list) abandon info if in txn [{
"address": (str) address of abandoned claim,
"balance_delta": (float) returned amount,
"amount": (float) claim amount,
"claim_id": (str) claim id,
"claim_name": (str) claim name,
"nout": (int) nout
}],
"confirmations": (int) number of confirmations for the txn,
"date": (str) date and time of txn,
"fee": (float) txn fee,
"support_info": (list) support info if in txn [{
"address": (str) address of support,
"balance_delta": (float) support amount,
"amount": (float) support amount,
"claim_id": (str) claim id,
"claim_name": (str) claim name,
"is_tip": (bool),
"nout": (int) nout
}],
"timestamp": (int) timestamp,
"txid": (str) txn id,
"update_info": (list) update info if in txn [{
"address": (str) address of claim,
2018-01-18 19:41:17 +01:00
"balance_delta": (float) credited/debited
"amount": (float) absolute amount,
"claim_id": (str) claim id,
"claim_name": (str) claim name,
"nout": (int) nout
}],
"value": (float) value of txn
}
"""
d = self.wallet.get_history()
d.addCallback(lambda r: self._render_response(r))
return d
@requires(WALLET_COMPONENT)
def jsonrpc_transaction_show(self, txid):
2017-01-03 20:13:01 +01:00
"""
Get a decoded transaction from a txid
2017-05-28 22:01:53 +02:00
Usage:
transaction_show (<txid> | --txid=<txid>)
Options:
--txid=<txid> : (str) txid of the transaction
2017-01-03 20:13:01 +01:00
Returns:
2017-03-14 00:14:11 +01:00
(dict) JSON formatted transaction
2017-01-03 20:13:01 +01:00
"""
d = self.wallet.get_transaction(txid)
d.addCallback(lambda r: self._render_response(r))
2017-01-03 20:13:01 +01:00
return d
@requires(WALLET_COMPONENT)
def jsonrpc_wallet_is_address_mine(self, address):
2016-09-02 07:27:30 +02:00
"""
Checks if an address is associated with the current wallet.
2017-05-28 22:01:53 +02:00
Usage:
wallet_is_address_mine (<address> | --address=<address>)
Options:
--address=<address> : (str) address to check
2016-09-02 07:27:30 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(bool) true, if address is associated with current wallet
2016-09-02 07:27:30 +02:00
"""
d = self.wallet.address_is_mine(address)
d.addCallback(lambda is_mine: self._render_response(is_mine))
2016-09-02 07:27:30 +02:00
return d
@requires(WALLET_COMPONENT)
2017-03-17 20:07:18 +01:00
def jsonrpc_wallet_public_key(self, address):
"""
Get public key from wallet address
2017-05-28 22:01:53 +02:00
Usage:
wallet_public_key (<address> | --address=<address>)
Options:
--address=<address> : (str) address for which to get the public key
Returns:
2017-03-17 20:07:18 +01:00
(list) list of public keys associated with address.
Could contain more than one public key if multisig.
"""
d = self.wallet.get_pub_keys(address)
d.addCallback(lambda r: self._render_response(r))
2017-03-17 20:07:18 +01:00
return d
@requires(WALLET_COMPONENT)
2017-03-19 15:51:39 +01:00
@defer.inlineCallbacks
def jsonrpc_wallet_list(self):
"""
List wallet addresses
2017-05-28 22:01:53 +02:00
Usage:
wallet_list
Options:
None
2017-03-19 15:51:39 +01:00
Returns:
List of wallet addresses
"""
addresses = yield self.wallet.list_addresses()
2017-03-19 15:51:39 +01:00
response = yield self._render_response(addresses)
defer.returnValue(response)
@requires(WALLET_COMPONENT)
2017-01-03 20:13:01 +01:00
def jsonrpc_wallet_new_address(self):
2016-03-24 03:27:48 +01:00
"""
Generate a new wallet address
2017-05-28 22:01:53 +02:00
Usage:
wallet_new_address
Options:
None
Returns:
2017-03-14 00:14:11 +01:00
(str) New wallet address in base58
2016-03-24 03:27:48 +01:00
"""
2016-02-25 23:17:07 +01:00
def _disp(address):
log.info("Got new wallet address: " + address)
return defer.succeed(address)
2016-02-25 23:17:07 +01:00
d = self.wallet.get_new_address()
2016-02-25 23:17:07 +01:00
d.addCallback(_disp)
d.addCallback(lambda address: self._render_response(address))
2016-02-25 23:17:07 +01:00
return d
@requires(WALLET_COMPONENT)
def jsonrpc_wallet_unused_address(self):
"""
Return an address containing no balance, will create
a new address if there is none.
2017-05-28 22:01:53 +02:00
Usage:
wallet_unused_address
Options:
None
Returns:
(str) Unused wallet address in base58
"""
def _disp(address):
2018-07-12 18:49:46 +02:00
address = str(address)
log.info("Got unused wallet address: " + address)
return defer.succeed(address)
d = self.wallet.get_unused_address()
d.addCallback(_disp)
d.addCallback(lambda address: self._render_response(address))
return d
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-08-15 17:11:38 +02:00
@AuthJSONRPCServer.deprecated("wallet_send")
2017-04-27 02:02:00 +02:00
@defer.inlineCallbacks
def jsonrpc_send_amount_to_address(self, amount, address):
"""
2017-05-28 22:01:53 +02:00
Queue a payment of credits to an address
Usage:
send_amount_to_address (<amount> | --amount=<amount>) (<address> | --address=<address>)
Options:
--amount=<amount> : (float) amount to send
--address=<address> : (str) address to send credits to
2017-03-14 00:14:11 +01:00
Returns:
(bool) true if payment successfully scheduled
"""
if amount < 0:
raise NegativeFundsError()
elif not amount:
raise NullFundsError()
reserved_points = self.wallet.reserve_points(address, amount)
if reserved_points is None:
2017-04-27 02:02:00 +02:00
raise InsufficientFundsError()
yield self.wallet.send_points_to_address(reserved_points, amount)
2017-04-27 02:02:00 +02:00
self.analytics_manager.send_credits_sent()
defer.returnValue(True)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-08-15 17:11:38 +02:00
@defer.inlineCallbacks
def jsonrpc_wallet_send(self, amount, address=None, claim_id=None):
2017-08-15 17:11:38 +02:00
"""
Send credits. If given an address, send credits to it. If given a claim id, send a tip
to the owner of a claim specified by uri. A tip is a claim support where the recipient
of the support is the claim address for the claim being supported.
Usage:
wallet_send (<amount> | --amount=<amount>)
((<address> | --address=<address>) | (<claim_id> | --claim_id=<claim_id>))
2017-08-15 17:11:38 +02:00
Options:
2018-08-13 22:18:31 +02:00
--amount=<amount> : (Decimal) amount of credit to send
--address=<address> : (str) address to send credits to
2018-08-13 22:18:31 +02:00
--claim_id=<claim_id> : (str) claim_id of the claim to send to tip to
Returns:
2017-08-15 17:11:38 +02:00
If sending to an address:
(bool) true if payment successfully scheduled
If sending a claim tip:
(dict) Dictionary containing the result of the support
{
txid : (str) txid of resulting support claim
nout : (int) nout of the resulting support claim
fee : (float) fee paid for the transaction
}
"""
if address and claim_id:
raise Exception("Given both an address and a claim id")
elif not address and not claim_id:
raise Exception("Not given an address or a claim id")
try:
amount = Decimal(str(amount))
except InvalidOperation:
raise TypeError("Amount does not represent a valid decimal.")
if amount < 0:
raise NegativeFundsError()
elif not amount:
raise NullFundsError()
2017-08-15 17:11:38 +02:00
if address:
# raises an error if the address is invalid
decode_address(address)
2017-08-15 17:11:38 +02:00
result = yield self.jsonrpc_send_amount_to_address(amount, address)
else:
2017-08-15 17:56:26 +02:00
validate_claim_id(claim_id)
result = yield self.wallet.tip_claim(claim_id, amount)
2017-08-15 17:11:38 +02:00
self.analytics_manager.send_claim_action('new_support')
defer.returnValue(result)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
2017-10-18 02:25:17 +02:00
@defer.inlineCallbacks
def jsonrpc_wallet_prefill_addresses(self, num_addresses, amount, no_broadcast=False):
"""
Create new addresses, each containing `amount` credits
Usage:
wallet_prefill_addresses [--no_broadcast]
(<num_addresses> | --num_addresses=<num_addresses>)
(<amount> | --amount=<amount>)
Options:
--no_broadcast : (bool) whether to broadcast or not
--num_addresses=<num_addresses> : (int) num of addresses to create
--amount=<amount> : (float) initial amount in each address
2017-10-18 02:25:17 +02:00
Returns:
2017-10-25 18:36:54 +02:00
(dict) the resulting transaction
2017-10-18 02:25:17 +02:00
"""
if amount < 0:
raise NegativeFundsError()
elif not amount:
raise NullFundsError()
broadcast = not no_broadcast
tx = yield self.wallet.create_addresses_with_balance(
2017-10-18 02:25:17 +02:00
num_addresses, amount, broadcast=broadcast)
tx['broadcast'] = broadcast
defer.returnValue(tx)
@requires(WALLET_COMPONENT)
2017-11-01 22:17:38 +01:00
@defer.inlineCallbacks
2017-11-02 12:14:26 +01:00
def jsonrpc_utxo_list(self):
2017-11-01 22:17:38 +01:00
"""
List unspent transaction outputs
Usage:
2017-11-02 12:14:26 +01:00
utxo_list
2017-11-01 22:17:38 +01:00
Options:
None
2017-11-01 22:17:38 +01:00
Returns:
2017-11-02 12:14:26 +01:00
(list) List of unspent transaction outputs (UTXOs)
2017-11-01 22:17:38 +01:00
[
{
"address": (str) the output address
"amount": (float) unspent amount
"height": (int) block height
"is_claim": (bool) is the tx a claim
"is_coinbase": (bool) is the tx a coinbase tx
"is_support": (bool) is the tx a support
"is_update": (bool) is the tx an update
"nout": (int) nout of the output
"txid": (str) txid of the output
},
...
]
"""
unspent = yield self.wallet.list_unspent()
2017-11-01 22:17:38 +01:00
for i, utxo in enumerate(unspent):
utxo['txid'] = utxo.pop('prevout_hash')
utxo['nout'] = utxo.pop('prevout_n')
utxo['amount'] = utxo.pop('value')
utxo['is_coinbase'] = utxo.pop('coinbase')
unspent[i] = utxo
defer.returnValue(unspent)
@requires(WALLET_COMPONENT)
def jsonrpc_block_show(self, blockhash=None, height=None):
"""
2017-03-14 00:14:11 +01:00
Get contents of a block
2017-05-28 22:01:53 +02:00
Usage:
block_show (<blockhash> | --blockhash=<blockhash>) | (<height> | --height=<height>)
Options:
--blockhash=<blockhash> : (str) hash of the block to look up
--height=<height> : (int) height of the block to look up
2017-05-28 22:01:53 +02:00
2017-03-14 00:14:11 +01:00
Returns:
(dict) Requested block
"""
if blockhash is not None:
d = self.wallet.get_block(blockhash)
elif height is not None:
d = self.wallet.get_block_info(height)
d.addCallback(lambda b: self.wallet.get_block(b))
else:
# TODO: return a useful error message
return server.failure
d.addCallback(lambda r: self._render_response(r))
return d
@requires(WALLET_COMPONENT, DHT_COMPONENT, BLOB_COMPONENT, RATE_LIMITER_COMPONENT, PAYMENT_RATE_COMPONENT,
conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_blob_get(self, blob_hash, timeout=None, encoding=None, payment_rate_manager=None):
"""
Download and return a blob
2017-05-28 22:01:53 +02:00
Usage:
blob_get (<blob_hash> | --blob_hash=<blob_hash>) [--timeout=<timeout>]
[--encoding=<encoding>] [--payment_rate_manager=<payment_rate_manager>]
Options:
--blob_hash=<blob_hash> : (str) blob hash of the blob to get
--timeout=<timeout> : (int) timeout in number of seconds
--encoding=<encoding> : (str) by default no attempt at decoding
is made, can be set to one of the
2017-05-28 22:01:53 +02:00
following decoders:
'json'
--payment_rate_manager=<payment_rate_manager> : (str) if not given the default payment rate
2017-05-28 22:01:53 +02:00
manager will be used.
supported alternative rate managers:
'only-free'
Returns:
2017-03-14 00:14:11 +01:00
(str) Success/Fail message or (dict) decoded data
"""
decoders = {
'json': json.loads
}
timeout = timeout or 30
2018-08-03 15:36:03 +02:00
blob = yield self._download_blob(blob_hash, rate_manager=self.payment_rate_manager, timeout=timeout)
if encoding and encoding in decoders:
blob_file = blob.open_for_reading()
result = decoders[encoding](blob_file.read())
blob_file.close()
else:
result = "Downloaded blob %s" % blob_hash
response = yield self._render_response(result)
defer.returnValue(response)
@requires(BLOB_COMPONENT, DATABASE_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_blob_delete(self, blob_hash):
"""
Delete a blob
2017-05-28 22:01:53 +02:00
Usage:
blob_delete (<blob_hash> | --blob_hash=<blob_hash)
2017-06-09 18:14:03 +02:00
Options:
--blob_hash=<blob_hash> : (str) blob hash of the blob to delete
Returns:
2017-03-14 00:14:11 +01:00
(str) Success/fail message
"""
if blob_hash not in self.blob_manager.blobs:
response = yield self._render_response("Don't have that blob")
defer.returnValue(response)
try:
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(blob_hash)
yield self.storage.delete_stream(stream_hash)
except Exception as err:
pass
yield self.blob_manager.delete_blobs([blob_hash])
response = yield self._render_response("Deleted %s" % blob_hash)
defer.returnValue(response)
@requires(DHT_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_peer_list(self, blob_hash, timeout=None):
2016-08-03 09:16:06 +02:00
"""
Get peers for blob hash
2017-05-28 22:01:53 +02:00
Usage:
2017-06-01 18:16:19 +02:00
peer_list (<blob_hash> | --blob_hash=<blob_hash>) [<timeout> | --timeout=<timeout>]
2017-05-28 22:01:53 +02:00
Options:
--blob_hash=<blob_hash> : (str) find available peers for this blob hash
--timeout=<timeout> : (int) peer search timeout in seconds
2017-05-28 22:01:53 +02:00
2016-08-03 09:16:06 +02:00
Returns:
(list) List of contact dictionaries {'host': <peer ip>, 'port': <peer port>, 'node_id': <peer node id>}
2016-08-03 09:16:06 +02:00
"""
if not utils.is_valid_blobhash(blob_hash):
raise Exception("invalid blob hash")
2018-07-05 04:16:02 +02:00
finished_deferred = self.dht_node.iterativeFindValue(unhexlify(blob_hash))
2018-05-24 00:26:33 +02:00
def trap_timeout(err):
err.trap(defer.TimeoutError)
return []
finished_deferred.addTimeout(timeout or conf.settings['peer_search_timeout'], self.dht_node.clock)
2018-05-24 00:26:33 +02:00
finished_deferred.addErrback(trap_timeout)
peers = yield finished_deferred
results = [
{
2018-05-24 00:26:33 +02:00
"node_id": node_id.encode('hex'),
"host": host,
2018-05-24 00:26:33 +02:00
"port": port
}
2018-05-24 00:26:33 +02:00
for node_id, host, port in peers
]
defer.returnValue(results)
2016-08-03 09:16:06 +02:00
@requires(DATABASE_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_blob_announce(self, blob_hash=None, stream_hash=None, sd_hash=None):
"""
Announce blobs to the DHT
Usage:
2018-03-26 19:16:55 +02:00
blob_announce [<blob_hash> | --blob_hash=<blob_hash>]
[<stream_hash> | --stream_hash=<stream_hash>] | [<sd_hash> | --sd_hash=<sd_hash>]
Options:
--blob_hash=<blob_hash> : (str) announce a blob, specified by blob_hash
--stream_hash=<stream_hash> : (str) announce all blobs associated with
stream_hash
--sd_hash=<sd_hash> : (str) announce all blobs associated with
sd_hash and the sd_hash itself
Returns:
(bool) true if successful
"""
2018-03-01 22:42:52 +01:00
blob_hashes = []
if blob_hash:
blob_hashes.append(blob_hash)
elif stream_hash or sd_hash:
if sd_hash and stream_hash:
raise Exception("either the sd hash or the stream hash should be provided, not both")
if sd_hash:
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(sd_hash)
blobs = yield self.storage.get_blobs_for_stream(stream_hash, only_completed=True)
blob_hashes.extend(blob.blob_hash for blob in blobs if blob.blob_hash is not None)
else:
raise Exception('single argument must be specified')
yield self.storage.should_single_announce_blobs(blob_hashes, immediate=True)
response = yield self._render_response(True)
defer.returnValue(response)
@requires(FILE_MANAGER_COMPONENT)
2017-03-16 20:35:54 +01:00
@defer.inlineCallbacks
2017-08-04 20:48:35 +02:00
def jsonrpc_file_reflect(self, **kwargs):
2016-08-18 03:33:41 +02:00
"""
2017-08-04 20:48:35 +02:00
Reflect all the blobs in a file matching the filter criteria
2016-08-18 03:33:41 +02:00
2017-05-28 22:01:53 +02:00
Usage:
2017-08-04 20:48:35 +02:00
file_reflect [--sd_hash=<sd_hash>] [--file_name=<file_name>]
2017-11-28 18:12:12 +01:00
[--stream_hash=<stream_hash>] [--rowid=<rowid>]
2017-08-04 20:48:35 +02:00
[--reflector=<reflector>]
Options:
--sd_hash=<sd_hash> : (str) get file with matching sd hash
--file_name=<file_name> : (str) get file with matching file name in the
2017-08-04 20:48:35 +02:00
downloads folder
--stream_hash=<stream_hash> : (str) get file with matching stream hash
--rowid=<rowid> : (int) get file with matching row id
--reflector=<reflector> : (str) reflector server, ip address or url
2017-08-04 20:48:35 +02:00
by default choose a server from the config
2017-05-28 22:01:53 +02:00
2016-08-18 03:33:41 +02:00
Returns:
2017-08-04 20:48:35 +02:00
(list) list of blobs reflected
2016-08-18 03:33:41 +02:00
"""
2017-08-04 20:48:35 +02:00
reflector_server = kwargs.get('reflector', None)
lbry_files = yield self._get_lbry_files(**kwargs)
if len(lbry_files) > 1:
raise Exception('Too many (%i) files found, need one' % len(lbry_files))
elif not lbry_files:
raise Exception('No file found')
lbry_file = lbry_files[0]
results = yield reupload.reflect_file(lbry_file, reflector_server=reflector_server)
2017-08-04 20:48:35 +02:00
defer.returnValue(results)
2016-08-18 03:33:41 +02:00
@requires(BLOB_COMPONENT, WALLET_COMPONENT)
@defer.inlineCallbacks
def jsonrpc_blob_list(self, uri=None, stream_hash=None, sd_hash=None, needed=None,
finished=None, page_size=None, page=None):
2016-08-19 08:41:23 +02:00
"""
2017-03-14 00:14:11 +01:00
Returns blob hashes. If not given filters, returns all blobs known by the blob manager
2016-08-26 06:32:33 +02:00
2017-06-22 00:16:41 +02:00
Usage:
blob_list [--needed] [--finished] [<uri> | --uri=<uri>]
[<stream_hash> | --stream_hash=<stream_hash>]
[<sd_hash> | --sd_hash=<sd_hash>]
[<page_size> | --page_size=<page_size>]
2017-06-22 00:16:41 +02:00
[<page> | --page=<page>]
Options:
--needed : (bool) only return needed blobs
--finished : (bool) only return finished blobs
--uri=<uri> : (str) filter blobs by stream in a uri
--stream_hash=<stream_hash> : (str) filter blobs by stream hash
--sd_hash=<sd_hash> : (str) filter blobs by sd hash
--page_size=<page_size> : (int) results page size
--page=<page> : (int) page of results to return
2017-06-22 00:16:41 +02:00
2016-08-26 06:32:33 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(list) List of blob hashes
2016-08-19 08:41:23 +02:00
"""
if uri or stream_hash or sd_hash:
if uri:
2018-07-10 06:30:13 +02:00
metadata = (yield self._resolve(uri))[uri]
sd_hash = utils.get_sd_hash(metadata)
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(sd_hash)
elif stream_hash:
sd_hash = yield self.storage.get_sd_blob_hash_for_stream(stream_hash)
elif sd_hash:
stream_hash = yield self.storage.get_stream_hash_for_sd_hash(sd_hash)
sd_hash = yield self.storage.get_sd_blob_hash_for_stream(stream_hash)
if stream_hash:
crypt_blobs = yield self.storage.get_blobs_for_stream(stream_hash)
blobs = yield defer.gatherResults([
self.blob_manager.get_blob(crypt_blob.blob_hash, crypt_blob.length)
for crypt_blob in crypt_blobs if crypt_blob.blob_hash is not None
])
else:
blobs = []
# get_blobs_for_stream does not include the sd blob, so we'll add it manually
if sd_hash in self.blob_manager.blobs:
blobs = [self.blob_manager.blobs[sd_hash]] + blobs
else:
2018-07-12 05:18:59 +02:00
blobs = self.blob_manager.blobs.values()
if needed:
blobs = [blob for blob in blobs if not blob.get_is_verified()]
if finished:
blobs = [blob for blob in blobs if blob.get_is_verified()]
blob_hashes = [blob.blob_hash for blob in blobs if blob.blob_hash]
page_size = page_size or len(blob_hashes)
page = page or 0
start_index = page * page_size
stop_index = start_index + page_size
blob_hashes_for_return = blob_hashes[start_index:stop_index]
response = yield self._render_response(blob_hashes_for_return)
defer.returnValue(response)
2016-08-27 01:58:53 +02:00
@requires(BLOB_COMPONENT)
2018-03-22 21:54:29 +01:00
def jsonrpc_blob_reflect(self, blob_hashes, reflector_server=None):
"""
Reflects specified blobs
Usage:
blob_reflect (<blob_hashes>...) [--reflector_server=<reflector_server>]
Options:
2018-04-12 19:27:06 +02:00
--reflector_server=<reflector_server> : (str) reflector address
2018-03-22 21:54:29 +01:00
Returns:
(list) reflected blob hashes
"""
d = reupload.reflect_blob_hashes(blob_hashes, self.blob_manager, reflector_server)
2018-03-22 21:54:29 +01:00
d.addCallback(lambda r: self._render_response(r))
return d
@requires(BLOB_COMPONENT)
2017-01-03 20:13:01 +01:00
def jsonrpc_blob_reflect_all(self):
2016-08-27 01:58:53 +02:00
"""
Reflects all saved blobs
2017-05-28 22:01:53 +02:00
Usage:
blob_reflect_all
Options:
None
2016-08-27 01:58:53 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(bool) true if successful
2016-08-27 01:58:53 +02:00
"""
d = self.blob_manager.get_all_verified_blobs()
d.addCallback(reupload.reflect_blob_hashes, self.blob_manager)
d.addCallback(lambda r: self._render_response(r))
2016-08-19 08:41:23 +02:00
return d
@requires(DHT_COMPONENT)
2018-03-29 16:46:29 +02:00
@defer.inlineCallbacks
def jsonrpc_peer_ping(self, node_id, address=None, port=None):
2018-03-29 16:46:29 +02:00
"""
Send a kademlia ping to the specified peer. If address and port are provided the peer is directly pinged,
if not provided the peer is located first.
2018-03-29 16:46:29 +02:00
Usage:
peer_ping (<node_id> | --node_id=<node_id>) [<address> | --address=<address>] [<port> | --port=<port>]
2018-03-29 16:46:29 +02:00
Options:
--address=<address> : (str) ip address of the peer
--port=<port> : (int) udp port of the peer
2018-03-29 16:46:29 +02:00
Returns:
(str) pong, or {'error': <error message>} if an error is encountered
"""
contact = None
if node_id and address and port:
contact = self.dht_node.contact_manager.get_contact(node_id.decode('hex'), address, int(port))
if not contact:
contact = self.dht_node.contact_manager.make_contact(
node_id.decode('hex'), address, int(port), self.dht_node._protocol
)
if not contact:
try:
contact = yield self.dht_node.findContact(node_id.decode('hex'))
except TimeoutError:
result = {'error': 'timeout finding peer'}
defer.returnValue(result)
2018-03-29 16:46:29 +02:00
if not contact:
defer.returnValue({'error': 'peer not found'})
try:
result = yield contact.ping()
except TimeoutError:
result = {'error': 'ping timeout'}
defer.returnValue(result)
@requires(DHT_COMPONENT)
2017-10-10 21:04:48 +02:00
def jsonrpc_routing_table_get(self):
"""
Get DHT routing information
Usage:
routing_table_get
Options:
None
2017-10-10 21:04:48 +02:00
Returns:
(dict) dictionary containing routing and contact information
{
"buckets": {
<bucket index>: [
{
"address": (str) peer address,
2018-05-24 00:26:43 +02:00
"port": (int) peer udp port
2017-10-10 21:04:48 +02:00
"node_id": (str) peer node id,
"blobs": (list) blob hashes announced by peer
}
2017-10-11 21:14:29 +02:00
]
},
2017-10-10 21:04:48 +02:00
"contacts": (list) contact node ids,
2017-10-11 21:14:29 +02:00
"blob_hashes": (list) all of the blob hashes stored by peers in the list of buckets,
2017-10-10 21:04:48 +02:00
"node_id": (str) the local dht node id
2017-10-11 21:14:29 +02:00
}
2017-10-10 21:04:48 +02:00
"""
result = {}
data_store = self.dht_node._dataStore._dict
2017-10-10 21:04:48 +02:00
datastore_len = len(data_store)
hosts = {}
if datastore_len:
for k, v in data_store.items():
2018-06-06 23:18:48 +02:00
for contact, value, lastPublished, originallyPublished, originalPublisherID in v:
2018-07-22 01:08:28 +02:00
blobs = blobs.get(contact, [])
2017-10-10 21:04:48 +02:00
blobs.append(k.encode('hex'))
hosts[contact] = blobs
contact_set = []
blob_hashes = []
result['buckets'] = {}
for i in range(len(self.dht_node._routingTable._buckets)):
for contact in self.dht_node._routingTable._buckets[i]._contacts:
2017-10-10 21:04:48 +02:00
contacts = result['buckets'].get(i, [])
if contact in hosts:
blobs = hosts[contact]
del hosts[contact]
else:
blobs = []
host = {
"address": contact.address,
2018-05-24 00:26:43 +02:00
"port": contact.port,
2017-10-10 21:04:48 +02:00
"node_id": contact.id.encode("hex"),
"blobs": blobs,
}
for blob_hash in blobs:
if blob_hash not in blob_hashes:
blob_hashes.append(blob_hash)
contacts.append(host)
result['buckets'][i] = contacts
if contact.id.encode('hex') not in contact_set:
contact_set.append(contact.id.encode("hex"))
result['contacts'] = contact_set
result['blob_hashes'] = blob_hashes
result['node_id'] = self.dht_node.node_id.encode('hex')
2017-10-10 21:04:48 +02:00
return self._render_response(result)
2018-07-24 18:30:47 +02:00
# the single peer downloader needs wallet access
@requires(DHT_COMPONENT, WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
def jsonrpc_blob_availability(self, blob_hash, search_timeout=None, blob_timeout=None):
"""
Get blob availability
Usage:
blob_availability (<blob_hash>) [<search_timeout> | --search_timeout=<search_timeout>]
[<blob_timeout> | --blob_timeout=<blob_timeout>]
Options:
--blob_hash=<blob_hash> : (str) check availability for this blob hash
--search_timeout=<search_timeout> : (int) how long to search for peers for the blob
in the dht
--blob_timeout=<blob_timeout> : (int) how long to try downloading from a peer
Returns:
(dict) {
"is_available": <bool, true if blob is available from a peer from peer list>
"reachable_peers": ["<ip>:<port>"],
"unreachable_peers": ["<ip>:<port>"]
}
"""
return self._blob_availability(blob_hash, search_timeout, blob_timeout)
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@AuthJSONRPCServer.deprecated("stream_availability")
2017-04-10 19:26:47 +02:00
def jsonrpc_get_availability(self, uri, sd_timeout=None, peer_timeout=None):
2016-09-30 19:28:01 +02:00
"""
2017-04-10 19:26:47 +02:00
Get stream availability for lbry uri
2016-09-30 19:28:01 +02:00
2017-05-28 22:01:53 +02:00
Usage:
2017-06-01 18:16:19 +02:00
get_availability (<uri> | --uri=<uri>) [<sd_timeout> | --sd_timeout=<sd_timeout>]
2017-05-28 22:01:53 +02:00
[<peer_timeout> | --peer_timeout=<peer_timeout>]
Options:
--uri=<uri> : (str) check availability for this uri
--sd_timeout=<sd_timeout> : (int) sd blob download timeout
--peer_timeout=<peer_timeout> : (int) how long to look for peers
2016-09-30 19:28:01 +02:00
Returns:
2017-03-14 00:14:11 +01:00
(float) Peers per blob / total blobs
2016-09-30 19:28:01 +02:00
"""
return self.jsonrpc_stream_availability(uri, peer_timeout, sd_timeout)
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks
def jsonrpc_stream_availability(self, uri, search_timeout=None, blob_timeout=None):
"""
Get stream availability for lbry uri
2016-09-30 19:28:01 +02:00
Usage:
stream_availability (<uri> | --uri=<uri>)
[<search_timeout> | --search_timeout=<search_timeout>]
[<blob_timeout> | --blob_timeout=<blob_timeout>]
2016-09-30 19:28:01 +02:00
Options:
--uri=<uri> : (str) check availability for this uri
--search_timeout=<search_timeout> : (int) how long to search for peers for the blob
in the dht
--blob_timeout=<blob_timeout> : (int) how long to try downloading from a peer
2017-04-10 19:26:47 +02:00
Returns:
(dict) {
'is_available': <bool>,
'did_decode': <bool>,
'did_resolve': <bool>,
'is_stream': <bool>,
'num_blobs_in_stream': <int>,
'sd_hash': <str>,
'sd_blob_availability': <dict> see `blob_availability`,
'head_blob_hash': <str>,
'head_blob_availability': <dict> see `blob_availability`,
'use_upnp': <bool>,
'upnp_redirect_is_set': <bool>,
'error': <None> | <str> error message
}
"""
search_timeout = search_timeout or conf.settings['peer_search_timeout']
blob_timeout = blob_timeout or conf.settings['sd_download_timeout']
response = {
'is_available': False,
'did_decode': False,
'did_resolve': False,
'is_stream': False,
'num_blobs_in_stream': None,
'sd_hash': None,
'sd_blob_availability': {},
'head_blob_hash': None,
'head_blob_availability': {},
'use_upnp': conf.settings['use_upnp'],
'upnp_redirect_is_set': len(self.upnp.upnp_redirects),
'error': None
}
2017-04-10 19:26:47 +02:00
try:
2018-07-10 06:30:13 +02:00
resolved_result = (yield self._resolve(uri))[uri]
response['did_resolve'] = True
except UnknownNameError:
response['error'] = "Failed to resolve name"
defer.returnValue(response)
except URIParseError:
response['error'] = "Invalid URI"
defer.returnValue(response)
2017-04-10 19:26:47 +02:00
try:
claim_obj = smart_decode(resolved_result[uri]['claim']['hex'])
response['did_decode'] = True
except DecodeError:
response['error'] = "Failed to decode claim value"
defer.returnValue(response)
response['is_stream'] = claim_obj.is_stream
if not claim_obj.is_stream:
response['error'] = "Claim for \"%s\" does not contain a stream" % uri
defer.returnValue(response)
sd_hash = claim_obj.source_hash
response['sd_hash'] = sd_hash
head_blob_hash = None
downloader = self._get_single_peer_downloader()
have_sd_blob = sd_hash in self.blob_manager.blobs
try:
sd_blob = yield self.jsonrpc_blob_get(sd_hash, timeout=blob_timeout,
encoding="json")
if not have_sd_blob:
yield self.jsonrpc_blob_delete(sd_hash)
if sd_blob and 'blobs' in sd_blob:
response['num_blobs_in_stream'] = len(sd_blob['blobs']) - 1
head_blob_hash = sd_blob['blobs'][0]['blob_hash']
head_blob_availability = yield self._blob_availability(head_blob_hash,
search_timeout,
blob_timeout,
downloader)
response['head_blob_availability'] = head_blob_availability
except Exception as err:
response['error'] = err
response['head_blob_hash'] = head_blob_hash
response['sd_blob_availability'] = yield self._blob_availability(sd_hash,
search_timeout,
blob_timeout,
downloader)
response['is_available'] = response['sd_blob_availability'].get('is_available') and \
response['head_blob_availability'].get('is_available')
defer.returnValue(response)
2016-09-30 19:28:01 +02:00
@defer.inlineCallbacks
2017-06-05 22:17:19 +02:00
def jsonrpc_cli_test_command(self, pos_arg, pos_args=[], pos_arg2=None, pos_arg3=None,
a_arg=False, b_arg=False):
"""
This command is only for testing the CLI argument parsing
Usage:
cli_test_command [--a_arg] [--b_arg] (<pos_arg> | --pos_arg=<pos_arg>)
[<pos_args>...] [--pos_arg2=<pos_arg2>]
[--pos_arg3=<pos_arg3>]
Options:
--a_arg : (bool) a arg
--b_arg : (bool) b arg
--pos_arg=<pos_arg> : (int) pos arg
--pos_args=<pos_args> : (int) pos args
--pos_arg2=<pos_arg2> : (int) pos arg 2
--pos_arg3=<pos_arg3> : (int) pos arg 3
Returns:
pos args
"""
2017-06-05 22:17:19 +02:00
out = (pos_arg, pos_args, pos_arg2, pos_arg3, a_arg, b_arg)
response = yield self._render_response(out)
defer.returnValue(response)
2018-07-26 05:29:13 +02:00
@requires("wallet")
def jsonrpc_account_balance(self, account_name=None, confirmations=6,
include_reserved=False, include_claims=False):
"""
Return the balance of an individual account or all of the accounts.
Usage:
account_balance [<account_name>] [--confirmations=<confirmations>]
2018-07-27 20:04:37 +02:00
[--include_reserved] [--include_claims]
Options:
--account=<account_name> : (str) If provided only the balance for this
account will be given
--confirmations=<confirmations> : (int) required confirmations (default: 6)
2018-07-27 20:04:37 +02:00
--include_reserved : (bool) include reserved UTXOs (default: false)
--include_claims : (bool) include claims, requires than a
LBC account is specified (default: false)
Returns:
(map) balance of account(s)
"""
if account_name:
for account in self.wallet.accounts:
if account.name == account_name:
if include_claims and not isinstance(account, LBRYAccount):
raise Exception(
"'--include-claims' requires specifying an LBC ledger account. "
"Found '{}', but it's an {} ledger account."
.format(account_name, account.ledger.symbol)
)
args = {
'confirmations': confirmations,
'include_reserved': include_reserved
}
if include_claims:
args['include_claims'] = True
return account.get_balance(**args)
raise Exception("Couldn't find an account named: '{}'.".format(account_name))
else:
if include_claims:
raise Exception("'--include-claims' requires specifying an LBC account.")
return self.wallet.get_balances(confirmations)
2018-07-26 05:29:13 +02:00
@requires("wallet")
def jsonrpc_account_max_gap(self, account_name):
"""
Finds ranges of consecutive addresses that are unused and returns the length
of the longest such range: for change and receiving address chains. This is
useful to figure out ideal values to set for 'receiving_gap' and 'change_gap'
account settings.
Usage:
account_max_gap <account_name>
Options:
--account=<account_name> : (str) account for which to get max gaps
Returns:
(map) maximum gap for change and receiving addresses
"""
for account in self.wallet.accounts:
if account.name == account_name:
return account.get_max_gap()
raise Exception("Couldn't find an account named: '{}'.".format(account_name))
2017-01-02 20:52:24 +01:00
def loggly_time_string(dt):
formatted_dt = dt.strftime("%Y-%m-%dT%H:%M:%S")
2017-01-03 20:13:01 +01:00
milliseconds = str(round(dt.microsecond * (10.0 ** -5), 3))
2017-01-02 22:09:28 +01:00
return urllib.quote_plus(formatted_dt + milliseconds + "Z")
2017-01-02 20:52:24 +01:00
def get_loggly_query_string(installation_id):
2017-01-02 22:09:28 +01:00
base_loggly_search_url = "https://lbry.loggly.com/search#"
2017-01-02 20:52:24 +01:00
now = utils.now()
yesterday = now - utils.timedelta(days=1)
2017-01-02 22:09:28 +01:00
params = {
'terms': 'json.installation_id:{}*'.format(installation_id[:SHORT_ID_LEN]),
2017-01-02 22:09:28 +01:00
'from': loggly_time_string(yesterday),
'to': loggly_time_string(now)
}
data = urllib.urlencode(params)
return base_loggly_search_url + data
2017-01-02 20:52:24 +01:00
def report_bug_to_slack(message, installation_id, platform_name, app_version):
webhook = utils.deobfuscate(conf.settings['SLACK_WEBHOOK'])
2017-01-02 20:52:24 +01:00
payload_template = "os: %s\n version: %s\n<%s|loggly>\n%s"
payload_params = (
platform_name,
app_version,
get_loggly_query_string(installation_id),
2017-01-02 20:52:24 +01:00
message
)
payload = {
"text": payload_template % payload_params
}
requests.post(webhook, json.dumps(payload))
def get_lbry_file_search_value(search_fields):
for searchtype in FileID:
value = search_fields.get(searchtype, None)
if value is not None:
return searchtype, value
raise NoValidSearch('{} is missing a valid search type'.format(search_fields))
def iter_lbry_file_search_values(search_fields):
for searchtype in FileID:
value = search_fields.get(searchtype, None)
if value is not None:
yield searchtype, value
2018-05-21 06:24:18 +02:00
def create_key_getter(field):
search_path = field.split('.')
def key_getter(value):
for key in search_path:
try:
value = value[key]
except KeyError as e:
errmsg = "Failed to get '{}', key {} was not found."
raise Exception(errmsg.format(field, str(e)))
2018-05-21 06:24:18 +02:00
return value
return key_getter