LBRYFee things

-move exchange rate updates to wallet, do them every half hour
-add convention checker for fees
-pay BTC or USD denominated key fees in converted LBC amount
This commit is contained in:
Jack 2016-07-25 21:45:42 -04:00
parent 75ef652cb3
commit c4a78a149b
3 changed files with 159 additions and 65 deletions

View file

@ -1,63 +1,105 @@
import requests
import json
import time
from googlefinance import getQuotes
from lbrynet.conf import CURRENCIES
import logging
log = logging.getLogger(__name__)
BITTREX_FEE = 0.0025
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', 'ver'], 'optional': ['licence_url']}
CURRENT_METADATA_VERSION = '0.0.2'
#v0.0.1 fee
FEE_REVISIONS = {'0.0.1': {'required': ['amount', 'address'], 'optional': []}}
CURRENT_FEE_REVISION = '0.0.1'
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 __init__(self, fee_dict, rate_dict):
fee = LBRYFeeFormat(fee_dict)
def __call__(self):
return {self.currency_symbol: {'amount': self.amount, 'address': self.address}}
for currency in fee:
self.address = fee[currency]['address']
if not isinstance(fee[currency]['amount'], float):
self.amount = float(fee[currency]['amount'])
else:
self.amount = fee[currency]['amount']
self.currency_symbol = currency
def convert(self, amount_only=False):
assert 'BTCLBC' in rate_dict and 'USDBTC' in rate_dict
for fx in rate_dict:
assert int(time.time()) - int(rate_dict[fx]['ts']) < 3600, "%s quote is out of date" % fx
self._USDBTC = {'spot': rate_dict['USDBTC']['spot'], 'ts': rate_dict['USDBTC']['ts']}
self._BTCLBC = {'spot': rate_dict['BTCLBC']['spot'], 'ts': rate_dict['BTCLBC']['ts']}
def to_lbc(self):
r = None
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)
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)
r = round(float(self._btc_to_lbc(self._usd_to_btc(self.amount))), 5)
assert r is not None
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 to_usd(self):
r = None
if self.currency_symbol == "USD":
r = round(float(self.amount), 5)
elif self.currency_symbol == "BTC":
r = round(float(self._btc_to_usd(self.amount)), 5)
elif self.currency_symbol == "LBC":
r = round(float(self._btc_to_usd(self._lbc_to_btc(self.amount))), 5)
assert r is not None
return r
def _usd_to_btc(self, usd):
return self._USDBTC['spot'] * float(usd)
def _btc_to_usd(self, btc):
return float(btc) / self._USDBTC['spot']
def _btc_to_lbc(self, btc):
return float(btc) / self._BTCLBC['spot'] / (1.0 - BITTREX_FEE)
def _lbc_to_btc(self, lbc):
return self._BTCLBC['spot'] * float(lbc)
def fee_from_dict(fee_dict):
s = fee_dict.keys()[0]
return LBRYFee(s, fee_dict[s]['amount'], fee_dict[s]['address'])
class LBRYFeeFormat(dict):
def __init__(self, fee_dict):
dict.__init__(self)
self.fee_version = None
f = fee_dict.copy()
assert len(fee_dict) == 1
for currency in fee_dict:
assert currency in CURRENCIES, "Unsupported currency: %s" % str(currency)
self.update({currency: {}})
for version in FEE_REVISIONS:
for k in FEE_REVISIONS[version]['required']:
assert k in fee_dict, "Missing required fee field: %s" % k
self[currency].update({k: f.pop(k)})
for k in FEE_REVISIONS[version]['optional']:
if k in fee_dict:
self[currency].update({k: f.pop(k)})
if not len(f):
self.fee_version = version
break
assert f == {}, "Unknown fee keys: %s" % json.dumps(f.keys())
class Metadata(dict):
@ -66,9 +108,6 @@ class Metadata(dict):
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"
@ -78,9 +117,13 @@ class Metadata(dict):
assert k in metadata, "Missing required metadata field: %s" % k
self.update({k: m.pop(k)})
for k in METADATA_REVISIONS[version]['optional']:
if k in metadata:
if k == 'fee':
pass
elif k in metadata:
self.update({k: m.pop(k)})
if not len(m):
if not len(m) or m.keys() == ['fee']:
self.metaversion = version
break
if 'fee' in m:
self.update({'fee': LBRYFeeFormat(m.pop('fee'))})
assert m == {}, "Unknown metadata keys: %s" % json.dumps(m.keys())

View file

@ -1,17 +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 SOURCE_TYPES
from lbrynet.core.LBRYMetadata import Metadata
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
@ -20,15 +15,24 @@ 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__)
log.setLevel(logging.INFO)
alert = logging.getLogger("lbryalert." + __name__)
@ -80,6 +84,50 @@ class LBRYWallet(object):
self._batch_count = 20
self._first_run = self._FIRST_RUN_UNKNOWN
self._USDBTC = None
self._BTCLBC = None
self._exchange_rate_updater = task.LoopingCall(self._update_exchange_rates)
def _usd_to_btc(self):
if self._USDBTC is not None:
if int(time.time()) - int(self._USDBTC['ts']) < 600:
log.info("USDBTC quote is new enough")
return defer.succeed({})
log.info("Getting new USDBTC quote")
x = float(getQuotes('CURRENCY:USDBTC')[0]['LastTradePrice'])
return defer.succeed({'USDBTC': {'spot': x, 'ts': int(time.time())}})
def _btc_to_lbc(self):
if self._BTCLBC is not None:
if int(time.time()) - int(self._BTCLBC['ts']) < 600:
log.info("BTCLBC quote is new enough")
return defer.succeed({})
log.info("Getting new BTCLBC quote")
r = requests.get("https://bittrex.com/api/v1.1/public/getmarkethistory", {'market': 'BTC-LBC', 'count': 50})
trades = json.loads(r.text)['result']
vwap = sum([i['Total'] for i in trades]) / sum([i['Quantity'] for i in trades])
x = (1.0 / float(vwap)) / 0.99975
return defer.succeed({'BTCLBC': {'spot': x, 'ts': int(time.time())}})
def _set_exchange_rates(self, rates):
if 'USDBTC' in rates:
assert int(time.time()) - int(rates['USDBTC']['ts']) < 3600, "new USDBTC quote is too old"
self._USDBTC = {'spot': rates['USDBTC']['spot'], 'ts': rates['USDBTC']['ts']}
log.info("Updated USDBTC rate: %s" % json.dumps(self._USDBTC))
if 'BTCLBC' in rates:
assert int(time.time()) - int(rates['BTCLBC']['ts']) < 3600, "new BTCLBC quote is too old"
self._BTCLBC = {'spot': rates['BTCLBC']['spot'], 'ts': rates['BTCLBC']['ts']}
log.info("Updated BTCLBC rate: %s" % json.dumps(self._BTCLBC))
def _update_exchange_rates(self):
d = self._usd_to_btc()
d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates))
d.addCallback(lambda _: self._btc_to_lbc())
d.addCallbacks(self._set_exchange_rates, lambda _: reactor.callLater(30, self._update_exchange_rates))
def start(self):
def start_manage():
@ -87,6 +135,8 @@ class LBRYWallet(object):
self.manage()
return True
self._exchange_rate_updater.start(1800)
d = self._open_db()
d.addCallback(lambda _: self._start())
d.addCallback(lambda _: start_manage())

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.LBRYMetadata import Metadata, fee_from_dict
from lbrynet.core.LBRYMetadata import Metadata, LBRYFee
from lbrynet.lbryfilemanager.LBRYFileDownloader import ManagedLBRYFileDownloaderFactory
from lbrynet.conf import DEFAULT_TIMEOUT, LOG_FILE_NAME
@ -90,9 +90,9 @@ class GetStream(object):
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))
self.fee = LBRYFee(self.metadata['fee'], {'USDBTC': self.wallet._USDBTC, 'BTCLBC': self.wallet._BTCLBC})
if self.fee.to_lbc() > self.max_key_fee:
log.info("Key fee %f above limit of %f didn't download lbry://%s" % (self.fee.to_lbc(), self.max_key_fee, self.resolved_name))
return defer.fail(None)
def _cause_timeout():
@ -124,11 +124,12 @@ class GetStream(object):
def _start_download(self, downloader):
def _pay_key_fee():
if self.fee is not None:
reserved_points = self.wallet.reserve_points(self.fee.address, self.fee.convert(amount_only=True))
x = self.fee.to_lbc()
reserved_points = self.wallet.reserve_points(self.fee.address, x)
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)
log.info("Key fee: %f --> %s" % (x, self.fee.address))
return self.wallet.send_points_to_address(reserved_points, self.fee.address)
return defer.succeed(None)
d = _pay_key_fee()