BTC/USD denominated fees, metadata revisions

This commit is contained in:
Jack 2016-07-25 01:40:26 -04:00
parent 6bfb33adb2
commit 2e2d309d38
7 changed files with 112 additions and 114 deletions

View file

@ -51,4 +51,6 @@ SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
CURRENCIES = [
{'BTC': {'type': 'crypto'}},
{'LBC': {'type': 'crypto'}},
{'USD': {'type': 'fiat'}},
]

View file

@ -1,39 +0,0 @@
import requests
import json
from lbrynet.conf import CURRENCIES
class LBRYFee(object):
def __init__(self, currency, amount, address=None):
assert currency in [c.keys()[0] for c in CURRENCIES], "Unsupported currency: %s" % str(currency)
self.address = address
self.currency_symbol = currency
self.currency = [c for c in CURRENCIES if self.currency_symbol in c][0]
if not isinstance(amount, float):
self.amount = float(amount)
else:
self.amount = amount
def convert_to(self, to_currency, rate_dict={}):
if to_currency is self.currency_symbol:
return self.as_dict()
if self.currency[self.currency_symbol]['type'] is 'fiat':
raise NotImplemented
else:
if to_currency not in rate_dict:
params = {'market': '%s-%s' % (self.currency_symbol, to_currency)}
r = requests.get("https://bittrex.com/api/v1.1/public/getticker", params)
last = json.loads(r.text)['result']['Last']
converted = self.amount / float(last)
else:
converted = self.amount / float(rate_dict[to_currency]['last'])
return LBRYFee(to_currency, converted, self.address).as_dict()
def as_dict(self):
return {self.currency_symbol: {'amount': self.amount, 'address': self.address}}
def from_dict(self, fee_dict):
s = fee_dict.keys()[0]
return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address'])

View file

@ -1,13 +1,63 @@
import requests
import json
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type']
from googlefinance import getQuotes
from lbrynet.conf import CURRENCIES
SOURCE_TYPES = ['lbry_sd_hash', 'url', 'btih']
BASE_METADATA_FIELDS = ['title', 'description', 'author', 'language', 'license', 'content-type', 'sources']
OPTIONAL_METADATA_FIELDS = ['thumbnail', 'preview', 'fee', 'contact', 'pubkey']
#v0.0.1 metadata
METADATA_REVISIONS = {'0.0.1': {'required': BASE_METADATA_FIELDS, 'optional': OPTIONAL_METADATA_FIELDS}}
#v0.0.2 metadata additions
METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw'], 'optional': []}
METADATA_REVISIONS['0.0.2'] = {'required': ['nsfw', 'ver'], 'optional': ['licence_url']}
CURRENT_METADATA_VERSION = '0.0.2'
class LBRYFee(object):
def __init__(self, currency, amount, address=None):
assert currency in [c.keys()[0] for c in CURRENCIES], "Unsupported currency: %s" % str(currency)
self.address = address
self.currency_symbol = currency
self.currency = next(c for c in CURRENCIES if self.currency_symbol in c)
if not isinstance(amount, float):
self.amount = float(amount)
else:
self.amount = amount
def __call__(self):
return {self.currency_symbol: {'amount': self.amount, 'address': self.address}}
def convert(self, amount_only=False):
if self.currency_symbol == "LBC":
r = round(float(self.amount), 5)
elif self.currency_symbol == "BTC":
r = round(float(self.BTC_to_LBC(self.amount)), 5)
elif self.currency_symbol == "USD":
r = round(float(self.BTC_to_LBC(self.USD_to_BTC(self.amount))), 5)
if not amount_only:
return {'LBC': {'amount': r, 'address': self.address}}
else:
return r
def USD_to_BTC(self, usd):
r = float(getQuotes('CURRENCY:%sBTC' % self.currency_symbol)[0]['LastTradePrice']) * float(usd)
return r
def BTC_to_LBC(self, btc):
r = requests.get("https://bittrex.com/api/v1.1/public/getticker", {'market': 'BTC-LBC'})
last = json.loads(r.text)['result']['Last']
converted = float(btc) / float(last)
return converted
def fee_from_dict(fee_dict):
s = fee_dict.keys()[0]
return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address'])
class Metadata(dict):
@ -15,6 +65,14 @@ class Metadata(dict):
dict.__init__(self)
self.metaversion = None
m = metadata.copy()
if 'fee' in metadata:
assert fee_from_dict(metadata['fee'])
assert "sources" in metadata, "No sources given"
for source in metadata['sources']:
assert source in SOURCE_TYPES, "Unknown source type"
for version in METADATA_REVISIONS:
for k in METADATA_REVISIONS[version]['required']:
assert k in metadata, "Missing required metadata field: %s" % k
@ -25,4 +83,4 @@ class Metadata(dict):
if not len(m):
self.metaversion = version
break
assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys())
assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys())

View file

@ -4,7 +4,8 @@ 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 lbrynet.conf import SOURCE_TYPES
from lbrynet.core.LBRYMetadata import Metadata
from lbryum import SimpleConfig, Network
from lbryum.lbrycrd import COIN, TYPE_ADDRESS
@ -316,7 +317,6 @@ class LBRYWallet(object):
return d
def _get_stream_info_from_value(self, result, name):
r_dict = {}
if 'value' in result:
value = result['value']
@ -324,16 +324,11 @@ class LBRYWallet(object):
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]
m = Metadata(value_dict)
if 'txid' in result:
d = self._save_name_metadata(name, r_dict['sources']['lbry_sd_hash'], str(result['txid']))
d.addCallback(lambda _: r_dict)
d = self._save_name_metadata(name, m['sources']['lbry_sd_hash'], str(result['txid']))
d.addCallback(lambda _: log.info("lbry://%s complies with %s" % (name, m.metaversion)))
d.addCallback(lambda _: m)
return d
elif 'error' in result:
log.warning("Got an error looking up a name: %s", result['error'])
@ -342,21 +337,14 @@ class LBRYWallet(object):
log.warning("Got an error looking up a name: %s", json.dumps(result))
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")
if fee is not None:
if "LBC" in fee:
value['fee'] = {'LBC': {'amount': fee['LBC']['amount'], 'address': fee['LBC']['address']}}
def claim_name(self, name, bid, m):
d = self._send_name_claim(name, json.dumps(value), bid)
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)
d = self._save_name_metadata(name, metadata['sources']['lbry_sd_hash'], txid)
d.addCallback(lambda _: txid)
return d

View file

@ -54,8 +54,6 @@ from lbrynet.lbryfilemanager.LBRYFileManager import LBRYFileManager
from lbrynet.lbryfile.LBRYFileMetadataManager import DBLBRYFileMetadataManager, TempLBRYFileMetadataManager
# from lbryum import LOG_PATH as lbryum_log
log = logging.getLogger(__name__)
if sys.platform != "darwin":
log_dir = os.path.join(os.path.expanduser("~"), ".lbrynet")
@ -67,13 +65,12 @@ if not os.path.isdir(log_dir):
lbrynet_log = os.path.join(log_dir, LOG_FILE_NAME)
log = logging.getLogger(__name__)
# TODO: configuring a logger on module import drastically reduces the
# amount of control the caller of this code has over logging
#
# Better would be to configure all logging at runtime.
handler = logging.handlers.RotatingFileHandler(lbrynet_log, maxBytes=2097152, backupCount=5)
log = logging.getLogger(__name__)
log.addHandler(handler)
log.setLevel(logging.INFO)
@ -1908,19 +1905,30 @@ class LBRYDaemon(jsonrpc.JSONRPC):
Returns:
Claim txid
"""
# start(self, name, file_path, bid, metadata, fee=None, sources=None):
name = p['name']
bid = p['bid']
file_path = p['file_path']
metadata = Metadata(p['metadata'])
metadata = p['metadata']
def _set_address(address):
metadata['fee']['address'] = address
return defer.succeed(None)
if 'fee' in p:
fee = LBRYFee.from_dict(p['fee'])
metadata['fee'] = p['fee']
if 'address' not in metadata['fee']:
d = self.session.wallet.get_new_address()
d.addCallback(_set_address)
else:
d = defer.succeed(None)
else:
fee = None
d = defer.succeed(None)
pub = Publisher(self.session, self.lbry_file_manager, self.session.wallet)
d = pub.start(name, file_path, bid, metadata, fee)
d.addCallback(lambda _: pub.start(name, file_path, bid, metadata))
d.addCallbacks(lambda msg: self._render_response(msg, OK_CODE),
lambda err: self._render_response(err.getTraceback(), BAD_REQUEST))

View file

@ -11,7 +11,7 @@ from twisted.internet.task import LoopingCall
from lbrynet.core.Error import InvalidStreamInfoError, InsufficientFundsError
from lbrynet.core.PaymentRateManager import PaymentRateManager
from lbrynet.core.StreamDescriptor import download_sd_blob
from lbrynet.core.LBRYFee import LBRYFee
from lbrynet.core.LBRYMetadata import Metadata, fee_from_dict
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
@ -48,8 +48,7 @@ class GetStream(object):
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
@ -59,7 +58,7 @@ class GetStream(object):
self.sd_identifier = sd_identifier
self.stream_hash = None
self.max_key_fee = max_key_fee
self.stream_info = None
self.metadata = None
self.stream_info_manager = None
self.d = defer.Deferred(None)
self.timeout = timeout
@ -87,22 +86,14 @@ class GetStream(object):
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:
self.fee = LBRYFee.from_dict(stream_info['fee'])
else:
self.fee = 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
self.metadata = stream_info
self.stream_hash = self.metadata['sources']['lbry_sd_hash']
if 'fee' in self.metadata:
fee = fee_from_dict(self.metadata['fee'])
if fee.convert(amount_only=True) > 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)
def _cause_timeout():
self.timeout_counter = self.timeout * 2
@ -132,8 +123,8 @@ 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:
reserved_points = self.wallet.reserve_points(self.fee.address, self.fee.convert(amount_only=True))
if reserved_points is None:
return defer.fail(InsufficientFundsError())
log.info("Key fee: %f --> %s" % (self.key_fee, self.key_fee_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
@ -42,10 +43,9 @@ class Publisher(object):
self.verified = False
self.lbry_file = None
self.txid = None
self.sources = {}
self.fee = None
self.metadata = {}
def start(self, name, file_path, bid, metadata, fee=None, sources={}):
def start(self, name, file_path, bid, metadata):
def _show_result():
log.info("Published %s --> lbry://%s txid: %s", self.file_name, self.publish_name, self.txid)
@ -54,7 +54,6 @@ class Publisher(object):
self.publish_name = name
self.file_path = file_path
self.bid_amount = bid
self.fee = fee
self.metadata = metadata
d = self._check_file_path(self.file_path)
@ -75,16 +74,6 @@ 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):
self.lbry_file = lbry_file_downloader
d = self.lbry_file_manager.change_lbry_file_status(self.lbry_file, ManagedLBRYFileDownloader.STATUS_FINISHED)
@ -102,7 +91,9 @@ 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
@ -110,11 +101,10 @@ class Publisher(object):
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
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