Merge pull request #107 from lbryio/metadata-version

Metadata versioning, publishing things, and USD denominated key fees
This commit is contained in:
Jack Robison 2016-07-29 23:43:11 -04:00 committed by GitHub
commit f5af71f4e6
13 changed files with 871 additions and 218 deletions

View file

@ -2,7 +2,7 @@ import logging
log = logging.getLogger(__name__)
logging.getLogger(__name__).addHandler(logging.NullHandler())
log.setLevel(logging.ERROR)
log.setLevel(logging.INFO)
__version__ = "0.3.11"
version = tuple(__version__.split('.'))

View file

@ -10,6 +10,7 @@ MAX_RESPONSE_INFO_SIZE = 2**16
MAX_BLOB_INFOS_TO_REQUEST = 20
BLOBFILES_DIR = ".blobfiles"
BLOB_SIZE = 2**21
MIN_BLOB_DATA_PAYMENT_RATE = .005 # points/megabyte
MIN_BLOB_INFO_PAYMENT_RATE = .02 # points/1000 infos
MIN_VALUABLE_BLOB_INFO_PAYMENT_RATE = .05 # points/1000 infos
@ -23,6 +24,9 @@ KNOWN_DHT_NODES = [('104.236.42.182', 4000),
POINTTRADER_SERVER = 'http://ec2-54-187-192-68.us-west-2.compute.amazonaws.com:2424'
#POINTTRADER_SERVER = 'http://127.0.0.1:2424'
SEARCH_SERVERS = ["http://lighthouse1.lbry.io:50005",
"http://lighthouse2.lbry.io:50005",
"http://lighthouse3.lbry.io:50005"]
LOG_FILE_NAME = "lbrynet.log"
LOG_POST_URL = "https://lbry.io/log-upload"
@ -42,11 +46,14 @@ DEFAULT_WALLET = "lbryum"
WALLET_TYPES = ["lbryum", "lbrycrd"]
DEFAULT_TIMEOUT = 30
DEFAULT_MAX_SEARCH_RESULTS = 25
DEFAULT_MAX_KEY_FEE = 100.0
DEFAULT_MAX_KEY_FEE = {'USD': {'amount': 25.0, 'address': ''}}
DEFAULT_SEARCH_TIMEOUT = 3.0
DEFAULT_CACHE_TIME = 3600
DEFAULT_UI_BRANCH = "master"
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type']
OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey']
CURRENCIES = {
'BTC': {'type': 'crypto'},
'LBC': {'type': 'crypto'},
'USD': {'type': 'fiat'},
}

View file

@ -22,6 +22,10 @@ class ConnectionClosedBeforeResponseError(Exception):
pass
class KeyFeeAboveMaxAllowed(Exception):
pass
class UnknownNameError(Exception):
def __init__(self, name):
self.name = name
@ -30,6 +34,14 @@ class UnknownNameError(Exception):
return repr(self.name)
class InvalidNameError(Exception):
def __init__(self, name):
self.name = name
def __str__(self):
return repr(self.name)
class UnknownStreamTypeError(Exception):
def __init__(self, stream_type):
self.stream_type = stream_type

View file

@ -0,0 +1,128 @@
import requests
import json
import time
from copy import deepcopy
from googlefinance import getQuotes
from lbrynet.conf import CURRENCIES
from lbrynet.core import utils
import logging
log = logging.getLogger(__name__)
BITTREX_FEE = 0.0025
# Metadata version
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
NAME_ALLOWED_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-'
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources']
OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey']
MV001 = "0.0.1"
MV002 = "0.0.2"
CURRENT_METADATA_VERSION = MV002
METADATA_REVISIONS = {}
METADATA_REVISIONS[MV001] = {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}
METADATA_REVISIONS[MV002] = {'required': ['nsfw', 'ver'], 'optional': ['license_url']}
# Fee version
BASE_FEE_FIELDS = ['amount', 'address']
FV001 = "0.0.1"
CURRENT_FEE_REVISION = FV001
FEE_REVISIONS = {}
FEE_REVISIONS[FV001] = {'required': BASE_FEE_FIELDS, 'optional': []}
def verify_name_characters(name):
for c in name:
assert c in NAME_ALLOWED_CHARSET, "Invalid character"
return True
class LBRYFeeValidator(dict):
def __init__(self, fee_dict):
dict.__init__(self)
assert len(fee_dict) == 1
self.fee_version = None
self.currency_symbol = None
fee_to_load = deepcopy(fee_dict)
for currency in fee_dict:
self._verify_fee(currency, fee_to_load)
self.amount = self._get_amount()
self.address = self[self.currency_symbol]['address']
def _get_amount(self):
amt = self[self.currency_symbol]['amount']
if isinstance(amt, float):
return amt
else:
try:
return float(amt)
except TypeError:
log.error('Failed to convert %s to float', amt)
raise
def _verify_fee(self, currency, f):
# str in case someone made a claim with a wierd fee
assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency)
self.currency_symbol = currency
self.update({currency: {}})
for version in FEE_REVISIONS:
self._load_revision(version, f)
if not f:
self.fee_version = version
break
assert f[self.currency_symbol] == {}, "Unknown fee keys: %s" % json.dumps(f.keys())
def _load_revision(self, version, f):
for k in FEE_REVISIONS[version]['required']:
assert k in f[self.currency_symbol], "Missing required fee field: %s" % k
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
for k in FEE_REVISIONS[version]['optional']:
if k in f[self.currency_symbol]:
self[self.currency_symbol].update({k: f[self.currency_symbol].pop(k)})
class Metadata(dict):
def __init__(self, metadata):
dict.__init__(self)
self.meta_version = None
metadata_to_load = deepcopy(metadata)
self._verify_sources(metadata_to_load)
self._verify_metadata(metadata_to_load)
def _load_revision(self, version, metadata):
for k in METADATA_REVISIONS[version]['required']:
assert k in metadata, "Missing required metadata field: %s" % k
self.update({k: metadata.pop(k)})
for k in METADATA_REVISIONS[version]['optional']:
if k == 'fee':
self._load_fee(metadata)
elif k in metadata:
self.update({k: metadata.pop(k)})
def _load_fee(self, metadata):
if 'fee' in metadata:
self['fee'] = LBRYFeeValidator(metadata.pop('fee'))
def _verify_sources(self, metadata):
assert "sources" in metadata, "No sources given"
for source in metadata['sources']:
assert source in SOURCE_TYPES, "Unknown source type"
def _verify_metadata(self, metadata):
for version in METADATA_REVISIONS:
self._load_revision(version, metadata)
if not metadata:
self.meta_version = version
if utils.version_is_greater_than(self.meta_version, "0.0.1"):
assert self.meta_version == self['ver'], "version mismatch"
break
assert metadata == {}, "Unknown metadata keys: %s" % json.dumps(metadata.keys())

View file

@ -1,16 +1,12 @@
import sys
from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, ILBRYWallet
from lbrynet.core.client.ClientRequest import ClientRequest
from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError
from lbrynet.core.Error import InsufficientFundsError
from lbrynet.core.sqlite_helpers import rerun_if_locked
from lbrynet.conf import BASE_METADATA_FIELDS, SOURCE_TYPES, OPTIONAL_METADATA_FIELDS
from lbryum import SimpleConfig, Network
from lbryum.lbrycrd import COIN, TYPE_ADDRESS
from lbryum.wallet import WalletStorage, Wallet
from lbryum.commands import known_commands, Commands
from lbryum.transaction import Transaction
import datetime
import logging
import json
import subprocess
import socket
import time
import os
import requests
from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
from twisted.internet import threads, reactor, defer, task
@ -19,13 +15,21 @@ from twisted.enterprise import adbapi
from collections import defaultdict, deque
from zope.interface import implements
from decimal import Decimal
import datetime
import logging
import json
import subprocess
import socket
import time
import os
from googlefinance import getQuotes
from lbryum import SimpleConfig, Network
from lbryum.lbrycrd import COIN, TYPE_ADDRESS
from lbryum.wallet import WalletStorage, Wallet
from lbryum.commands import known_commands, Commands
from lbryum.transaction import Transaction
from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, ILBRYWallet
from lbrynet.core.client.ClientRequest import ClientRequest
from lbrynet.core.Error import UnknownNameError, InvalidStreamInfoError, RequestCanceledError
from lbrynet.core.Error import InsufficientFundsError
from lbrynet.core.sqlite_helpers import rerun_if_locked
from lbrynet.conf import SOURCE_TYPES
from lbrynet.core.LBRYMetadata import Metadata
log = logging.getLogger(__name__)
alert = logging.getLogger("lbryalert." + __name__)
@ -97,6 +101,7 @@ class LBRYWallet(object):
def stop(self):
self.stopped = True
# If self.next_manage_call is None, then manage is currently running or else
# start has not been called, so set stopped and do nothing else.
if self.next_manage_call is not None:
@ -315,54 +320,66 @@ class LBRYWallet(object):
return d
def _get_stream_info_from_value(self, result, name):
r_dict = {}
if 'value' in result:
value = result['value']
def _check_result_fields(r):
for k in ['value', 'txid', 'n', 'height', 'amount']:
assert k in r, "getvalueforname response missing field %s" % k
try:
value_dict = json.loads(value)
except (ValueError, TypeError):
return Failure(InvalidStreamInfoError(name))
r_dict['sources'] = value_dict['sources']
for field in BASE_METADATA_FIELDS:
r_dict[field] = value_dict[field]
for field in value_dict:
if field in OPTIONAL_METADATA_FIELDS:
r_dict[field] = value_dict[field]
if 'txid' in result:
d = self._save_name_metadata(name, r_dict['sources']['lbry_sd_hash'], str(result['txid']))
d.addCallback(lambda _: r_dict)
return d
elif 'error' in result:
if 'error' in result:
log.warning("Got an error looking up a name: %s", result['error'])
return Failure(UnknownNameError(name))
else:
log.warning("Got an error looking up a name: %s", json.dumps(result))
_check_result_fields(result)
try:
metadata = Metadata(json.loads(result['value']))
except (ValueError, TypeError):
return Failure(InvalidStreamInfoError(name))
d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash'])
d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version)))
d.addCallback(lambda _: metadata)
return d
def _get_claim_info(self, result, name):
def _check_result_fields(r):
for k in ['value', 'txid', 'n', 'height', 'amount']:
assert k in r, "getvalueforname response missing field %s" % k
def _build_response(m, result):
result['value'] = m
return result
if 'error' in result:
log.warning("Got an error looking up a name: %s", result['error'])
return Failure(UnknownNameError(name))
def claim_name(self, name, bid, sources, metadata, fee=None):
value = {'sources': {}}
for k in SOURCE_TYPES:
if k in sources:
value['sources'][k] = sources[k]
if value['sources'] == {}:
return defer.fail("No source given")
for k in BASE_METADATA_FIELDS:
if k not in metadata:
return defer.fail("Missing required field '%s'" % k)
value[k] = metadata[k]
for k in metadata:
if k not in BASE_METADATA_FIELDS:
value[k] = metadata[k]
if fee is not None:
if "LBC" in fee:
value['fee'] = {'LBC': {'amount': fee['LBC']['amount'], 'address': fee['LBC']['address']}}
_check_result_fields(result)
d = self._send_name_claim(name, json.dumps(value), bid)
try:
metadata = Metadata(json.loads(result['value']))
except (ValueError, TypeError):
return Failure(InvalidStreamInfoError(name))
d = self._save_name_metadata(name, str(result['txid']), metadata['sources']['lbry_sd_hash'])
d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, metadata.meta_version)))
d.addCallback(lambda _: _build_response(metadata, result))
return d
def get_claim_info(self, name):
d = self._get_value_for_name(name)
d.addCallback(lambda r: self._get_claim_info(r, name))
return d
def claim_name(self, name, bid, m):
metadata = Metadata(m)
d = self._send_name_claim(name, json.dumps(metadata), bid)
def _save_metadata(txid):
d = self._save_name_metadata(name, value['sources']['lbry_sd_hash'], txid)
log.info("Saving metadata for claim %s" % txid)
d = self._save_name_metadata(name, txid, metadata['sources']['lbry_sd_hash'])
d.addCallback(lambda _: txid)
return d
@ -407,10 +424,12 @@ class LBRYWallet(object):
d.addCallback(self._get_decoded_tx)
return d
def update_name(self, name, value, amount):
def update_name(self, name, bid, value, old_txid):
d = self._get_value_for_name(name)
d.addCallback(lambda r: (self._update_name(r['txid'], json.dumps(value), amount), r['txid']))
d.addCallback(lambda (new_txid, old_txid): self._update_name_metadata(name, value['sources']['lbry_sd_hash'], old_txid, new_txid))
d.addCallback(lambda r: self.abandon_name(r['txid'] if not old_txid else old_txid))
d.addCallback(lambda r: log.info("Abandon claim tx %s" % str(r)))
d.addCallback(lambda _: self.claim_name(name, bid, value))
return d
def get_name_and_validity_for_sd_hash(self, sd_hash):
@ -520,19 +539,13 @@ class LBRYWallet(object):
" txid text, " +
" sd_hash text)")
def _save_name_metadata(self, name, sd_hash, txid):
d = self.db.runQuery("select * from name_metadata where txid=?", (txid,))
def _save_name_metadata(self, name, txid, sd_hash):
d = self.db.runQuery("select * from name_metadata where name=? and txid=? and sd_hash=?", (name, txid, sd_hash))
d.addCallback(lambda r: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, txid, sd_hash))
if not len(r) else None)
return d
def _update_name_metadata(self, name, sd_hash, old_txid, new_txid):
d = self.db.runQuery("delete * from name_metadata where txid=? and sd_hash=?", (old_txid, sd_hash))
d.addCallback(lambda _: self.db.runQuery("insert into name_metadata values (?, ?, ?)", (name, new_txid, sd_hash)))
d.addCallback(lambda _: new_txid)
return d
def _get_claim_metadata_for_sd_hash(self, sd_hash):
d = self.db.runQuery("select name, txid from name_metadata where sd_hash=?", (sd_hash,))
d.addCallback(lambda r: r[0] if len(r) else None)

View file

@ -1,6 +1,4 @@
import binascii
from datetime import datetime
from decimal import Decimal
import distutils.version
import locale
import logging.handlers
@ -13,18 +11,21 @@ import socket
import string
import subprocess
import sys
from urllib2 import urlopen
from appdirs import user_data_dir
import base58
import requests
import simplejson as json
import pkg_resources
from urllib2 import urlopen
from appdirs import user_data_dir
from datetime import datetime
from decimal import Decimal
from twisted.web import server
from twisted.internet import defer, threads, error, reactor
from twisted.internet.task import LoopingCall
from txjsonrpc import jsonrpclib
from txjsonrpc.web import jsonrpc
from txjsonrpc.web.jsonrpc import Handler
from txjsonrpc.web.jsonrpc import Handler, Proxy
from lbrynet import __version__ as lbrynet_version
from lbryum.version import LBRYUM_VERSION as lbryum_version
@ -32,19 +33,21 @@ from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.core.server.BlobAvailabilityHandler import BlobAvailabilityHandlerFactory
from lbrynet.core.server.BlobRequestHandler import BlobRequestHandlerFactory
from lbrynet.core.server.ServerProtocol import ServerProtocolFactory
from lbrynet.core.Error import UnknownNameError, InsufficientFundsError
from lbrynet.core.Error import UnknownNameError, InsufficientFundsError, InvalidNameError
from lbrynet.lbryfile.StreamDescriptor import LBRYFileStreamType
from lbrynet.lbryfile.client.LBRYFileDownloader import LBRYFileSaverFactory, LBRYFileOpenerFactory
from lbrynet.lbryfile.client.LBRYFileOptions import add_lbry_file_to_sd_identifier
from lbrynet.lbrynet_daemon.LBRYUIManager import LBRYUIManager
from lbrynet.lbrynet_daemon.LBRYDownloader import GetStream
from lbrynet.lbrynet_daemon.LBRYPublisher import Publisher
from lbrynet.lbrynet_daemon.LBRYExchangeRateManager import ExchangeRateManager
from lbrynet.core import utils
from lbrynet.core.LBRYMetadata import verify_name_characters
from lbrynet.core.utils import generate_id
from lbrynet.lbrynet_console.LBRYSettings import LBRYSettings
from lbrynet.conf import MIN_BLOB_DATA_PAYMENT_RATE, DEFAULT_MAX_SEARCH_RESULTS, KNOWN_DHT_NODES, DEFAULT_MAX_KEY_FEE, \
DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, \
BASE_METADATA_FIELDS, OPTIONAL_METADATA_FIELDS, SOURCE_TYPES
DEFAULT_WALLET, DEFAULT_SEARCH_TIMEOUT, DEFAULT_CACHE_TIME, DEFAULT_UI_BRANCH, LOG_POST_URL, LOG_FILE_NAME, SOURCE_TYPES
from lbrynet.conf import SEARCH_SERVERS
from lbrynet.conf import DEFAULT_TIMEOUT, WALLET_TYPES
from lbrynet.core.StreamDescriptor import StreamDescriptorIdentifier, download_sd_blob
from lbrynet.core.Session import LBRYSession
@ -68,7 +71,6 @@ lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
log = logging.getLogger(__name__)
if os.path.isfile(lbrynet_log):
with open(lbrynet_log, 'r') as f:
PREVIOUS_LBRYNET_LOG = len(f.read())
@ -157,8 +159,10 @@ class LBRYDaemon(jsonrpc.JSONRPC):
self.current_db_revision = 1
self.run_server = True
self.session = None
self.exchange_rate_manager = ExchangeRateManager()
self.waiting_on = {}
self.streams = {}
self.pending_claims = {}
self.known_dht_nodes = KNOWN_DHT_NODES
self.first_run_after_update = False
self.uploaded_temp_files = []
@ -242,6 +246,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
self.session_settings['last_version'] = self.default_settings['last_version']
self.first_run_after_update = True
log.info("First run after update")
log.info("lbrynet %s --> %s" % (self.session_settings['last_version']['lbrynet'], self.default_settings['last_version']['lbrynet']))
log.info("lbryum %s --> %s" % (self.session_settings['last_version']['lbryum'], self.default_settings['last_version']['lbryum']))
f = open(self.daemon_conf, "w")
f.write(json.dumps(self.session_settings))
@ -347,6 +353,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
self.internet_connection_checker = LoopingCall(self._check_network_connection)
self.version_checker = LoopingCall(self._check_remote_versions)
self.connection_problem_checker = LoopingCall(self._check_connection_problems)
self.pending_claim_checker = LoopingCall(self._check_pending_claims)
# self.lbrynet_connection_checker = LoopingCall(self._check_lbrynet_connection)
self.sd_identifier = StreamDescriptorIdentifier()
@ -412,6 +419,10 @@ class LBRYDaemon(jsonrpc.JSONRPC):
return server.NOT_DONE_YET
def _cbRender(self, result, request, id, version):
def default_decimal(obj):
if isinstance(obj, Decimal):
return float(obj)
if isinstance(result, Handler):
result = result.result
@ -423,7 +434,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
result = (result,)
# Convert the result (python) to JSON-RPC
try:
s = jsonrpclib.dumps(result, version=version)
s = jsonrpclib.dumps(result, version=version, default=default_decimal)
except:
f = jsonrpclib.Fault(self.FAILURE, "can't serialize output")
s = jsonrpclib.dumps(f, version=version)
@ -480,6 +491,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
self.internet_connection_checker.start(3600)
self.version_checker.start(3600 * 12)
self.connection_problem_checker.start(1)
self.exchange_rate_manager.start()
if host_ui:
self.lbry_ui_manager.update_checker.start(1800, now=False)
@ -603,6 +616,47 @@ class LBRYDaemon(jsonrpc.JSONRPC):
if not self.connected_to_internet:
self.connection_problem = CONNECTION_PROBLEM_CODES[1]
def _add_to_pending_claims(self, name, txid):
log.info("Adding lbry://%s to pending claims, txid %s" % (name, txid))
self.pending_claims[name] = txid
return txid
def _check_pending_claims(self):
# TODO: this was blatantly copied from jsonrpc_start_lbry_file. Be DRY.
def _start_file(f):
d = self.lbry_file_manager.toggle_lbry_file_running(f)
return defer.succeed("Started LBRY file")
def _get_and_start_file(name):
d = defer.succeed(self.pending_claims.pop(name))
d.addCallback(lambda _: self._get_lbry_file("name", name, return_json=False))
d.addCallback(lambda l: _start_file(l) if l.stopped else "LBRY file was already running")
def re_add_to_pending_claims(name):
txid = self.pending_claims.pop(name)
self._add_to_pending_claims(name, txid)
def _process_lbry_file(name, lbry_file):
# lbry_file is an instance of ManagedLBRYFileDownloader or None
# TODO: check for sd_hash in addition to txid
ready_to_start = (
lbry_file and
self.pending_claims[name] == lbry_file.txid
)
if ready_to_start:
_get_and_start_file(name)
else:
re_add_to_pending_claims(name)
for name in self.pending_claims:
log.info("Checking if new claim for lbry://%s is confirmed" % name)
d = self._resolve_name(name, force_refresh=True)
d.addCallback(lambda _: self._get_lbry_file_by_uri(name))
d.addCallbacks(
lambda lbry_file: _process_lbry_file(name, lbry_file),
lambda _: re_add_to_pending_claims(name)
)
def _start_server(self):
if self.peer_port is not None:
@ -721,6 +775,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
self.connection_problem_checker.stop()
if self.lbry_ui_manager.update_checker.running:
self.lbry_ui_manager.update_checker.stop()
if self.pending_claim_checker.running:
self.pending_claim_checker.stop()
self._clean_up_temp_files()
@ -1089,8 +1145,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
return defer.succeed(None)
self.streams[name] = GetStream(self.sd_identifier, self.session, self.session.wallet,
self.lbry_file_manager, max_key_fee=self.max_key_fee,
data_rate=self.data_rate, timeout=timeout,
self.lbry_file_manager, self.exchange_rate_manager,
max_key_fee=self.max_key_fee, data_rate=self.data_rate, timeout=timeout,
download_directory=download_directory, file_name=file_name)
d = self.streams[name].start(stream_info, name)
if wait_for_write:
@ -1120,6 +1176,12 @@ class LBRYDaemon(jsonrpc.JSONRPC):
return defer.succeed(True)
def _resolve_name(self, name, force_refresh=False):
try:
verify_name_characters(name)
except AssertionError:
log.error("Bad name")
return defer.fail(InvalidNameError("Bad name"))
def _cache_stream_info(stream_info):
def _add_txid(txid):
self.name_cache[name]['txid'] = txid
@ -1186,7 +1248,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
def _add_key_fee(data_cost):
d = self._resolve_name(name)
d.addCallback(lambda info: data_cost if 'fee' not in info else data_cost + info['fee']['LBC']['amount'])
d.addCallback(lambda info: self.exchange_rate_manager.to_lbc(info.get('fee', None)))
d.addCallback(lambda fee: data_cost if fee is None else data_cost + fee.amount)
return d
d = self._resolve_name(name)
@ -1196,8 +1259,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
d.addCallback(self.sd_identifier.get_metadata_for_sd_blob)
d.addCallback(lambda metadata: metadata.validator.info_to_show())
d.addCallback(lambda info: int(dict(info)['stream_size']) / 1000000 * self.data_rate)
d.addCallback(_add_key_fee)
d.addErrback(lambda _: _add_key_fee(0.0))
d.addCallbacks(_add_key_fee, lambda _: _add_key_fee(0.0))
reactor.callLater(self.search_timeout, _check_est, d, name)
return d
@ -1305,7 +1367,7 @@ class LBRYDaemon(jsonrpc.JSONRPC):
if f.txid:
d = self._resolve_name(f.uri)
d.addCallback(_add_to_dict)
d.addCallbacks(_add_to_dict, lambda _: _add_to_dict("Pending confirmation"))
else:
d = defer.succeed(message)
return d
@ -1363,6 +1425,10 @@ class LBRYDaemon(jsonrpc.JSONRPC):
return defer.succeed(None)
def _search(self, search):
proxy = Proxy(random.choice(SEARCH_SERVERS))
return proxy.callRemote('search', search)
def _render_response(self, result, code):
return defer.succeed({'result': result, 'code': code})
@ -1482,10 +1548,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
'ui_version': self.ui_version,
'remote_lbrynet': self.git_lbrynet_version,
'remote_lbryum': self.git_lbryum_version,
'lbrynet_update_available': utils.version_is_greater_than(
self.git_lbrynet_version, lbrynet_version),
'lbryum_update_available': utils.version_is_greater_than(
self.git_lbryum_version, lbryum_version)
'lbrynet_update_available': utils.version_is_greater_than(self.git_lbrynet_version, lbrynet_version),
'lbryum_update_available': utils.version_is_greater_than(self.git_lbryum_version, lbryum_version),
}
log.info("Get version info: " + json.dumps(msg))
@ -1667,15 +1731,37 @@ class LBRYDaemon(jsonrpc.JSONRPC):
metadata from name claim
"""
if 'name' in p.keys():
force = p.get('force', False)
if 'name' in p:
name = p['name']
else:
return self._render_response(None, BAD_REQUEST)
d = self._resolve_name(name)
d = self._resolve_name(name, force_refresh=force)
d.addCallbacks(lambda info: self._render_response(info, OK_CODE), lambda _: server.failure)
return d
def jsonrpc_get_claim_info(self, p):
"""
Resolve claim info from a LBRY uri
Args:
'name': name to look up, string, do not include lbry:// prefix
Returns:
txid, amount, value, n, height
"""
def _convert_amount_to_float(r):
r['amount'] = float(r['amount']) / 10**8
return r
name = p['name']
d = self.session.wallet.get_claim_info(name)
d.addCallback(_convert_amount_to_float)
d.addCallback(lambda r: self._render_response(r, OK_CODE))
return d
def jsonrpc_get(self, p):
"""
Download stream from a LBRY uri
@ -1786,9 +1872,24 @@ class LBRYDaemon(jsonrpc.JSONRPC):
d.addCallback(lambda r: self._render_response(r, OK_CODE))
return d
def jsonrpc_get_est_cost(self, p):
"""
Get estimated cost for a lbry uri
Args:
'name': lbry uri
Returns:
estimated cost
"""
name = p['name']
d = self._get_est_cost(name)
d.addCallback(lambda r: self._render_response(r, OK_CODE))
return d
def jsonrpc_search_nametrie(self, p):
"""
Search the nametrie for claims beginning with search (yes, this is a dumb search, it'll be made better)
Search the nametrie for claims
Args:
'search': search query, string
@ -1796,6 +1897,8 @@ class LBRYDaemon(jsonrpc.JSONRPC):
List of search results
"""
# TODO: change this function to "search", and use cached stream size info from the search server
if 'search' in p.keys():
search = p['search']
else:
@ -1805,44 +1908,31 @@ class LBRYDaemon(jsonrpc.JSONRPC):
t = []
for i in n:
if i[0]:
if i[1][0][0] and i[1][1][0] and i[1][2][0]:
i[1][0][1]['value'] = str(i[1][0][1]['value'])
t.append([i[1][0][1], i[1][1][1], i[1][2][1]])
tr = {}
tr.update(i[1][0]['value'])
thumb = tr.get('thumbnail', None)
if thumb is None:
tr['thumbnail'] = "img/Free-speech-flag.svg"
tr['name'] = i[1][0]['name']
tr['cost_est'] = i[1][1]
t.append(tr)
return t
def resolve_claims(claims):
ds = []
for claim in claims:
d1 = defer.succeed(claim)
d2 = self._resolve_name(claim['name'])
d3 = self._get_est_cost(claim['name'])
dl = defer.DeferredList([d1, d2, d3], consumeErrors=True)
ds.append(dl)
return defer.DeferredList(ds)
def get_est_costs(results):
def _save_cost(search_result):
d = self._get_est_cost(search_result['name'])
d.addCallback(lambda p: [search_result, p])
return d
def _disp(results):
log.info('Found ' + str(len(results)) + ' search results')
consolidated_results = []
for r in results:
t = {}
t.update(r[0])
if not 'thumbnail' in r[1].keys():
r[1]['thumbnail'] = "img/Free-speech-flag.svg"
t.update(r[1])
t['cost_est'] = r[2]
consolidated_results.append(t)
# log.info(str(t))
dl = defer.DeferredList([_save_cost(r) for r in results], consumeErrors=True)
return dl
return consolidated_results
log.info('Search: %s' % search)
log.info('Search nametrie: ' + search)
d = self.session.wallet.get_nametrie()
d.addCallback(lambda trie: [claim for claim in trie if claim['name'].startswith(search) and 'txid' in claim])
d = self._search(search)
d.addCallback(lambda claims: claims[:self.max_search_results])
d.addCallback(resolve_claims)
d.addCallback(get_est_costs)
d.addCallback(_clean)
d.addCallback(_disp)
d.addCallback(lambda results: self._render_response(results, OK_CODE))
return d
@ -1889,21 +1979,49 @@ class LBRYDaemon(jsonrpc.JSONRPC):
Returns:
Claim txid
"""
# start(self, name, file_path, bid, metadata, fee=None, sources=None):
name = p['name']
try:
verify_name_characters(name)
except:
log.error("Bad name")
return defer.fail(InvalidNameError("Bad name"))
bid = p['bid']
file_path = p['file_path']
metadata = p['metadata']
def _set_address(address, currency):
log.info("Generated new address for key fee: " + str(address))
metadata['fee'][currency]['address'] = address
return defer.succeed(None)
def _delete_data(lbry_file):
txid = lbry_file.txid
d = self._delete_lbry_file(lbry_file, delete_file=False)
d.addCallback(lambda _: txid)
return d
if not self.pending_claim_checker.running:
self.pending_claim_checker.start(30)
d = self._resolve_name(name, force_refresh=True)
d.addErrback(lambda _: None)
if 'fee' in p:
fee = p['fee']
else:
fee = None
metadata['fee'] = p['fee']
assert len(metadata['fee']) == 1, "Too many fees"
for c in metadata['fee']:
if 'address' not in metadata['fee'][c]:
d.addCallback(lambda _: self.session.wallet.get_new_address())
d.addCallback(lambda addr: _set_address(addr, c))
pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet)
d = pub.start(name, file_path, bid, metadata, fee)
d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE),
lambda err: self._render_response(err.getTraceback(), BAD_REQUEST))
d.addCallback(lambda _: self._get_lbry_file_by_uri(name))
d.addCallbacks(lambda l: None if not l else _delete_data(l), lambda _: None)
d.addCallback(lambda r: pub.start(name, file_path, bid, metadata, r))
d.addCallback(lambda txid: self._add_to_pending_claims(name, txid))
d.addCallback(lambda r: self._render_response(r, OK_CODE))
d.addErrback(lambda err: self._render_response(err.getTraceback(), BAD_REQUEST))
return d
@ -2166,27 +2284,6 @@ class LBRYDaemon(jsonrpc.JSONRPC):
d.addCallback(lambda r: self._render_response(r, OK_CODE))
return d
def jsonrpc_update_name(self, p):
"""
Update name claim
Args:
'name': the uri of the claim to be updated
'metadata': new metadata dict
'amount': bid amount of updated claim
Returns:
txid
"""
name = p['name']
metadata = p['metadata'] if isinstance(p['metadata'], dict) else json.loads(p['metadata'])
amount = p['amount']
d = self.session.wallet.update_name(name, metadata, amount)
d.addCallback(lambda r: self._render_response(r, OK_CODE))
return d
def jsonrpc_log(self, p):
"""
Log message
@ -2282,7 +2379,6 @@ class LBRYDaemon(jsonrpc.JSONRPC):
# No easy way to reveal specific files on Linux, so just open the containing directory
d = threads.deferToThread(subprocess.Popen, ['xdg-open', os.dirname(path)])
d.addCallback(lambda _: self._render_response(True, OK_CODE))
return d

View file

@ -3,14 +3,16 @@ import logging
import os
import sys
from copy import deepcopy
from appdirs import user_data_dir
from datetime import datetime
from twisted.internet import defer
from twisted.internet.task import LoopingCall
from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError
from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError, KeyFeeAboveMaxAllowed
from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.core.StreamDescriptor import download_sd_blob
from lbrynet.core.LBRYMetadata import Metadata, LBRYFeeValidator
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
@ -40,17 +42,17 @@ log = logging.getLogger(__name__)
class GetStream(object):
def __init__(self, sd_identifier, session, wallet, lbry_file_manager, max_key_fee, data_rate=0.5,
timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None):
def __init__(self, sd_identifier, session, wallet, lbry_file_manager, exchange_rate_manager,
max_key_fee, data_rate=0.5, timeout=DEFAULT_TIMEOUT, download_directory=None, file_name=None):
self.wallet = wallet
self.resolved_name = None
self.description = None
self.key_fee = None
self.key_fee_address = None
self.fee = None
self.data_rate = data_rate
self.name = None
self.file_name = file_name
self.session = session
self.exchange_rate_manager = exchange_rate_manager
self.payment_rate_manager = PaymentRateManager(self.session.base_payment_rate_manager)
self.lbry_file_manager = lbry_file_manager
self.sd_identifier = sd_identifier
@ -64,7 +66,7 @@ class GetStream(object):
self.download_directory = download_directory
self.download_path = None
self.downloader = None
self.finished = defer.Deferred()
self.finished = defer.Deferred(None)
self.checker = LoopingCall(self.check_status)
self.code = STREAM_STAGES[0]
@ -83,30 +85,16 @@ class GetStream(object):
self.code = STREAM_STAGES[4]
self.finished.callback(False)
def start(self, stream_info, name):
self.resolved_name = name
self.stream_info = stream_info
if 'sources' in self.stream_info:
self.stream_hash = self.stream_info['sources']['lbry_sd_hash']
else:
raise InvalidStreamInfoError(self.stream_info)
if 'description' in self.stream_info:
self.description = self.stream_info['description']
if 'fee' in self.stream_info:
if 'LBC' in self.stream_info['fee']:
self.key_fee = float(self.stream_info['fee']['LBC']['amount'])
self.key_fee_address = self.stream_info['fee']['LBC']['address']
else:
self.key_fee_address = None
else:
self.key_fee = None
self.key_fee_address = None
if self.key_fee > self.max_key_fee:
log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.key_fee, self.max_key_fee, self.resolved_name))
return defer.fail(None)
else:
pass
def _convert_max_fee(self):
if isinstance(self.max_key_fee, dict):
max_fee = LBRYFeeValidator(self.max_key_fee)
if max_fee.currency_symbol == "LBC":
return max_fee.amount
return self.exchange_rate_manager.to_lbc(self.fee).amount
elif isinstance(self.max_key_fee, float):
return float(self.max_key_fee)
def start(self, stream_info, name):
def _cause_timeout(err):
log.error(err)
log.debug('Forcing a timeout')
@ -131,6 +119,23 @@ class GetStream(object):
download_directory=self.download_directory,
file_name=self.file_name)
self.resolved_name = name
self.stream_info = deepcopy(stream_info)
self.description = self.stream_info['description']
self.stream_hash = self.stream_info['sources']['lbry_sd_hash']
if 'fee' in self.stream_info:
self.fee = LBRYFeeValidator(self.stream_info['fee'])
max_key_fee = self._convert_max_fee()
if self.exchange_rate_manager.to_lbc(self.fee).amount > max_key_fee:
log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.amount,
self.max_key_fee,
self.resolved_name))
return defer.fail(KeyFeeAboveMaxAllowed())
log.info("Key fee %s below limit of %f, downloading lbry://%s" % (json.dumps(self.fee),
max_key_fee,
self.resolved_name))
self.checker.start(1)
self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE))
@ -146,17 +151,20 @@ class GetStream(object):
def _start_download(self, downloader):
def _pay_key_fee():
if self.key_fee is not None and self.key_fee_address is not None:
reserved_points = self.wallet.reserve_points(self.key_fee_address, self.key_fee)
if self.fee is not None:
fee_lbc = self.exchange_rate_manager.to_lbc(self.fee).amount
reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc)
if reserved_points is None:
return defer.fail(InsufficientFundsError())
log.info("Key fee: %f --> %s" % (self.key_fee, self.key_fee_address))
return self.wallet.send_points_to_address(reserved_points, self.key_fee)
return self.wallet.send_points_to_address(reserved_points, fee_lbc)
return defer.succeed(None)
d = _pay_key_fee()
self.downloader = downloader
self.download_path = os.path.join(downloader.download_directory, downloader.file_name)
d.addCallback(lambda _: log.info("Downloading %s --> %s", self.stream_hash, self.downloader.file_name))
d.addCallback(lambda _: self.downloader.start())

View file

@ -0,0 +1,215 @@
import time
import requests
import logging
import json
import googlefinance
from twisted.internet import defer, reactor
from twisted.internet.task import LoopingCall
from lbrynet.core.LBRYMetadata import LBRYFeeValidator
log = logging.getLogger(__name__)
CURRENCY_PAIRS = ["USDBTC", "BTCLBC"]
BITTREX_FEE = 0.0025
COINBASE_FEE = 0.0 #add fee
class ExchangeRate(object):
def __init__(self, market, spot, ts):
assert int(time.time()) - ts < 600
self.currency_pair = (market[0:3], market[3:6])
self.spot = spot
self.ts = ts
def as_dict(self):
return {'spot': self.spot, 'ts': self.ts}
class MarketFeed(object):
def __init__(self, market, name, url, params, fee):
self.market = market
self.name = name
self.url = url
self.params = params
self.fee = fee
self.rate = None
self._updater = LoopingCall(self._update_price)
def _make_request(self):
r = requests.get(self.url, self.params)
return r.text
def _handle_response(self, response):
return NotImplementedError
def _subtract_fee(self, from_amount):
return defer.succeed(from_amount / (1.0 - self.fee))
def _save_price(self, price):
log.info("Saving price update %f for %s" % (price, self.market))
self.rate = ExchangeRate(self.market, price, int(time.time()))
def _update_price(self):
d = defer.succeed(self._make_request())
d.addCallback(self._handle_response)
d.addCallback(self._subtract_fee)
d.addCallback(self._save_price)
def start(self):
if not self._updater.running:
self._updater.start(300)
def stop(self):
if self._updater.running:
self._updater.stop()
class BittrexFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"BTCLBC",
"Bittrex",
"https://bittrex.com/api/v1.1/public/getmarkethistory",
{'market': 'BTC-LBC', 'count': 50},
BITTREX_FEE
)
def _handle_response(self, response):
trades = json.loads(response)['result']
vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades])
return defer.succeed(float(1.0 / vwap))
class GoogleBTCFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"USDBTC",
"Coinbase via Google finance",
None,
None,
COINBASE_FEE
)
def _make_request(self):
return googlefinance.getQuotes('CURRENCY:USDBTC')[0]
def _handle_response(self, response):
return float(response['LastTradePrice'])
def get_default_market_feed(currency_pair):
currencies = None
if isinstance(currency_pair, str):
currencies = (currency_pair[0:3], currency_pair[3:6])
elif isinstance(currency_pair, tuple):
currencies = currency_pair
assert currencies is not None
if currencies == ("USD", "BTC"):
return GoogleBTCFeed()
elif currencies == ("BTC", "LBC"):
return BittrexFeed()
class ExchangeRateManager(object):
def __init__(self):
reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
self.market_feeds = [get_default_market_feed(currency_pair) for currency_pair in CURRENCY_PAIRS]
def start(self):
log.info("Starting exchange rate manager")
for feed in self.market_feeds:
feed.start()
def stop(self):
log.info("Stopping exchange rate manager")
for source in self.market_feeds:
source.stop()
def convert_currency(self, from_currency, to_currency, amount):
log.info("Converting %f %s to %s" % (amount, from_currency, to_currency))
if from_currency == to_currency:
return amount
for market in self.market_feeds:
if market.rate.currency_pair == (from_currency, to_currency):
return amount * market.rate.spot
for market in self.market_feeds:
if market.rate.currency_pair[0] == from_currency:
return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
raise Exception('Unable to convert {} from {} to {}'.format(amount, from_currency, to_currency))
def fee_dict(self):
return {market: market.rate.as_dict() for market in self.market_feeds}
def to_lbc(self, fee):
if fee is None:
return None
if not isinstance(fee, LBRYFeeValidator):
fee_in = LBRYFeeValidator(fee)
else:
fee_in = fee
return LBRYFeeValidator({fee_in.currency_symbol:
{
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
'address': fee_in.address
}
})
class DummyBTCLBCFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"BTCLBC",
"market name",
"derp.com",
None,
0.0
)
class DummyUSDBTCFeed(MarketFeed):
def __init__(self):
MarketFeed.__init__(
self,
"USDBTC",
"market name",
"derp.com",
None,
0.0
)
class DummyExchangeRateManager(object):
def __init__(self, rates):
self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()]
for feed in self.market_feeds:
feed.rate = ExchangeRate(feed.market, rates[feed.market]['spot'], rates[feed.market]['ts'])
def convert_currency(self, from_currency, to_currency, amount):
log.info("Converting %f %s to %s" % (amount, from_currency, to_currency))
for market in self.market_feeds:
if market.rate.currency_pair == (from_currency, to_currency):
return amount * market.rate.spot
for market in self.market_feeds:
if market.rate.currency_pair[0] == from_currency:
return self.convert_currency(market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
def to_lbc(self, fee):
if fee is None:
return None
if not isinstance(fee, LBRYFeeValidator):
fee_in = LBRYFeeValidator(fee)
else:
fee_in = fee
return LBRYFeeValidator({fee_in.currency_symbol:
{
'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
'address': fee_in.address
}
})

View file

@ -10,6 +10,7 @@ from lbrynet.core.Error import InsufficientFundsError
from lbrynet.lbryfilemanager.LBRYFileCreator import create_lbry_file
from lbrynet.lbryfile.StreamDescriptor import publish_sd_blob
from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.core.LBRYMetadata import Metadata, CURRENT_METADATA_VERSION
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloader
from lbrynet.conf import LOG_FILE_NAME
from twisted.internet import threads, defer
@ -39,10 +40,10 @@ class Publisher(object):
self.verified = False
self.lbry_file = None
self.txid = None
self.sources = {}
self.fee = None
self.stream_hash = None
self.metadata = {}
def start(self, name, file_path, bid, metadata, fee=None, sources={}):
def start(self, name, file_path, bid, metadata, old_txid):
def _show_result():
log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid)
@ -51,8 +52,8 @@ class Publisher(object):
self.publish_name = name
self.file_path = file_path
self.bid_amount = bid
self.fee = fee
self.metadata = metadata
self.old_txid = old_txid
d = self._check_file_path(self.file_path)
d.addCallback(lambda _: create_lbry_file(self.session, self.lbry_file_manager,
@ -60,6 +61,7 @@ class Publisher(object):
d.addCallback(self.add_to_lbry_files)
d.addCallback(lambda _: self._create_sd_blob())
d.addCallback(lambda _: self._claim_name())
d.addCallback(lambda _: self.set_status())
d.addCallbacks(lambda _: _show_result(), self._show_publish_error)
return d
@ -72,26 +74,15 @@ class Publisher(object):
return True
return threads.deferToThread(check_file_threaded)
def _get_new_address(self):
d = self.wallet.get_new_address()
def set_address(address):
self.key_fee_address = address
return True
d.addCallback(set_address)
return d
def set_status(self, lbry_file_downloader):
def set_lbry_file(self, lbry_file_downloader):
self.lbry_file = lbry_file_downloader
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED)
d.addCallback(lambda _: lbry_file_downloader.restore())
return d
return defer.succeed(None)
def add_to_lbry_files(self, stream_hash):
self.stream_hash = stream_hash
prm = PaymentRateManager(self.session.base_payment_rate_manager)
d = self.lbry_file_manager.add_lbry_file(stream_hash, prm)
d.addCallback(self.set_status)
d.addCallback(self.set_lbry_file)
return d
def _create_sd_blob(self):
@ -99,19 +90,34 @@ class Publisher(object):
self.lbry_file.stream_hash)
def set_sd_hash(sd_hash):
self.sources['lbry_sd_hash'] = sd_hash
if 'sources' not in self.metadata:
self.metadata['sources'] = {}
self.metadata['sources']['lbry_sd_hash'] = sd_hash
d.addCallback(set_sd_hash)
return d
def set_status(self):
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED)
d.addCallback(lambda _: self.lbry_file.restore())
return d
def _claim_name(self):
self.metadata['content-type'] = mimetypes.guess_type(os.path.join(self.lbry_file.download_directory,
self.lbry_file.file_name))[0]
self.metadata['ver'] = CURRENT_METADATA_VERSION
if self.old_txid:
d = self.wallet.abandon_name(self.old_txid)
d.addCallback(lambda tx: log.info("Abandoned tx %s" % str(tx)))
d.addCallback(lambda _: self.wallet.claim_name(self.publish_name,
self.bid_amount,
Metadata(self.metadata)))
else:
d = self.wallet.claim_name(self.publish_name,
self.bid_amount,
self.sources,
self.metadata,
fee=self.fee)
Metadata(self.metadata))
def set_tx_hash(txid):
self.txid = txid

View file

@ -26,3 +26,4 @@ unqlite==0.2.0
wsgiref==0.1.2
zope.interface==4.1.3
base58==0.2.2
googlefinance==0.7

View file

@ -25,7 +25,7 @@ console_scripts = ['lbrynet-stdin-uploader = lbrynet.lbrynet_console.LBRYStdinUp
requires = ['pycrypto', 'twisted', 'miniupnpc', 'yapsy', 'seccure',
'python-bitcoinrpc==0.1', 'txJSON-RPC', 'requests>=2.4.2', 'unqlite==0.2.0',
'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58']
'leveldb', 'lbryum', 'jsonrpc', 'simplejson', 'appdirs', 'six==1.9.0', 'base58', 'googlefinance']
setup(name='lbrynet',
description='A decentralized media library and marketplace',

View file

@ -0,0 +1,38 @@
import mock
from lbrynet.core import LBRYMetadata
from lbrynet.lbrynet_daemon import LBRYExchangeRateManager
from twisted.trial import unittest
class LBRYFeeFormatTest(unittest.TestCase):
def test_fee_created_with_correct_inputs(self):
fee_dict = {
'USD': {
'amount': 10.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
}
}
fee = LBRYMetadata.LBRYFeeValidator(fee_dict)
self.assertEqual(10.0, fee['USD']['amount'])
class LBRYFeeTest(unittest.TestCase):
def setUp(self):
self.patcher = mock.patch('time.time')
self.time = self.patcher.start()
self.time.return_value = 0
def tearDown(self):
self.time.stop()
def test_fee_converts_to_lbc(self):
fee_dict = {
'USD': {
'amount': 10.0,
'address': "bRcHraa8bYJZL7vkh5sNmGwPDERFUjGPP9"
}
}
rates = {'BTCLBC': {'spot': 3.0, 'ts': 2}, 'USDBTC': {'spot': 2.0, 'ts': 3}}
manager = LBRYExchangeRateManager.DummyExchangeRateManager(rates)
self.assertEqual(60.0, manager.to_lbc(fee_dict).amount)

View file

@ -0,0 +1,129 @@
from lbrynet.core import LBRYMetadata
from twisted.trial import unittest
class MetadataTest(unittest.TestCase):
def test_assertion_if_source_is_missing(self):
metadata = {}
with self.assertRaises(AssertionError):
LBRYMetadata.Metadata(metadata)
def test_metadata_works_without_fee(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
}
m = LBRYMetadata.Metadata(metadata)
self.assertFalse('key' in m)
def test_assertion_if_invalid_source(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'fake': 'source'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
}
with self.assertRaises(AssertionError):
LBRYMetadata.Metadata(metadata)
def test_assertion_if_missing_v001_field(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
}
with self.assertRaises(AssertionError):
LBRYMetadata.Metadata(metadata)
def test_version_is_001_if_all_fields_are_present(self):
metadata = {
'license': 'Oscilloscope Laboratories',
'fee': {'LBC': {'amount': 50.0, 'address': 'bRQJASJrDbFZVAvcpv3NoNWoH74LQd5JNV'}},
'description': 'Four couples meet for Sunday brunch only to discover they are stuck in a house together as the world may be about to end.',
'language': 'en',
'title': "It's a Disaster",
'author': 'Written and directed by Todd Berger',
'sources': {
'lbry_sd_hash': '8d0d6ea64d09f5aa90faf5807d8a761c32a27047861e06f81f41e35623a348a4b0104052161d5f89cf190f9672bc4ead'},
'content-type': 'audio/mpeg',
'thumbnail': 'http://ia.media-imdb.com/images/M/MV5BMTQwNjYzMTQ0Ml5BMl5BanBnXkFtZTcwNDUzODM5Nw@@._V1_SY1000_CR0,0,673,1000_AL_.jpg'
}
m = LBRYMetadata.Metadata(metadata)
self.assertEquals('0.0.1', m.meta_version)
def test_assertion_if_there_is_an_extra_field(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.2',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg',
'MYSTERYFIELD': '?'
}
with self.assertRaises(AssertionError):
LBRYMetadata.Metadata(metadata)
def test_version_is_002_if_all_fields_are_present(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.2',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
m = LBRYMetadata.Metadata(metadata)
self.assertEquals('0.0.2', m.meta_version)
def test_version_claimed_is_001_but_version_is_002(self):
metadata = {
'license': 'NASA',
'fee': {'USD': {'amount': 0.01, 'address': 'baBYSK7CqGSn5KrEmNmmQwAhBSFgo6v47z'}},
'ver': '0.0.1',
'description': 'SDO captures images of the sun in 10 different wavelengths, each of which helps highlight a different temperature of solar material. Different temperatures can, in turn, show specific structures on the sun such as solar flares, which are gigantic explosions of light and x-rays, or coronal loops, which are stream of solar material travelling up and down looping magnetic field lines',
'language': 'en',
'author': 'The SDO Team, Genna Duberstein and Scott Wiessinger',
'title': 'Thermonuclear Art',
'sources': {
'lbry_sd_hash': '8655f713819344980a9a0d67b198344e2c462c90f813e86f0c63789ab0868031f25c54d0bb31af6658e997e2041806eb'},
'nsfw': False,
'content-type': 'video/mp4',
'thumbnail': 'https://svs.gsfc.nasa.gov/vis/a010000/a012000/a012034/Combined.00_08_16_17.Still004.jpg'
}
with self.assertRaises(AssertionError):
LBRYMetadata.Metadata(metadata)