update wallet for new lbryum functionality

This commit is contained in:
Jack Robison 2017-04-06 20:42:07 -04:00
parent bcf7a28fc8
commit ccc94a0db9

View file

@ -10,20 +10,18 @@ from zope.interface import implements
from decimal import Decimal from decimal import Decimal
from lbryum import SimpleConfig, Network from lbryum import SimpleConfig, Network
from lbryum.lbrycrd import COIN, RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS from lbryum.lbrycrd import COIN
import lbryum.wallet import lbryum.wallet
from lbryum.commands import known_commands, Commands from lbryum.commands import known_commands, Commands
from lbryschema.uri import parse_lbry_uri
from lbryschema.claim import ClaimDict from lbryschema.claim import ClaimDict
from lbryschema.decode import smart_decode
from lbryschema.error import DecodeError from lbryschema.error import DecodeError
from lbrynet.core import utils
from lbrynet.core.sqlite_helpers import rerun_if_locked from lbrynet.core.sqlite_helpers import rerun_if_locked
from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, IWallet from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, IWallet
from lbrynet.core.client.ClientRequest import ClientRequest from lbrynet.core.client.ClientRequest import ClientRequest
from lbrynet.core.Error import (UnknownNameError, InvalidStreamInfoError, RequestCanceledError, from lbrynet.core.Error import RequestCanceledError, InsufficientFundsError
InsufficientFundsError)
from lbrynet.db_migrator.migrate1to2 import UNSET_NOUT from lbrynet.db_migrator.migrate1to2 import UNSET_NOUT
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -450,65 +448,10 @@ class Wallet(object):
log.debug("There were no payments to send") log.debug("There were no payments to send")
return defer.succeed(True) return defer.succeed(True)
def get_stream_info_for_name(self, name): ######
d = self._get_value_for_name(name)
d.addCallback(self._get_stream_info_from_value, name)
return d
def get_txid_for_name(self, name): def get_claim(self, claim_id):
d = self._get_value_for_name(name) return self._get_claim_by_claimid(claim_id)
d.addCallback(lambda r: None if 'txid' not in r else r['txid'])
return d
def get_stream_info_from_claim_outpoint(self, name, txid, nout):
claim_outpoint = ClaimOutpoint(txid, nout)
d = self.get_claims_from_tx(claim_outpoint['txid'])
def get_claim_for_name(claims):
for claim in claims:
if claim_outpoint == claim:
claim['txid'] = txid
return claim
return Failure(UnknownNameError(name))
d.addCallback(get_claim_for_name)
d.addCallback(self._get_stream_info_from_value, name)
return d
def _get_stream_info_from_value(self, result, name):
def _check_result_fields(r):
for k in ['value', 'txid', 'nout', 'height', 'amount']:
assert k in r, "getvalueforname response missing field %s" % k
def _log_success(claim_id):
log.debug("lbry://%s complies with %s, claimid: %s",
name, claim_dict.claim_dict['version'], claim_id)
return defer.succeed(None)
if 'error' in result:
log.warning("Got an error looking up lbry://%s: %s", name, result['error'])
return Failure(UnknownNameError(name))
_check_result_fields(result)
try:
claim_dict = smart_decode(result['value'].decode('hex'))
except (TypeError, ValueError, DecodeError):
return Failure(InvalidStreamInfoError(name, result['value']))
#TODO: what if keys don't exist here,
# probablly need get_sd_hash() function fro ClaimDict
sd_hash = utils.get_sd_hash(claim_dict.claim_dict)
claim_outpoint = ClaimOutpoint(result['txid'], result['nout'])
d = self._save_name_metadata(name, claim_outpoint, sd_hash)
d.addCallback(lambda _: self.get_claimid(name, result['txid'], result['nout']))
d.addCallback(lambda cid: _log_success(cid))
d.addCallback(lambda _: claim_dict.claim_dict)
return d
def get_claim(self, name, claim_id):
d = self.get_claims_for_name(name)
d.addCallback(
lambda claims: next(
claim for claim in claims['claims'] if claim['claim_id'] == claim_id))
return d
def get_claimid(self, name, txid, nout): def get_claimid(self, name, txid, nout):
def _get_id_for_return(claim_id): def _get_id_for_return(claim_id):
@ -530,96 +473,135 @@ class Wallet(object):
d.addCallback(_get_id_for_return) d.addCallback(_get_id_for_return)
return d return d
@defer.inlineCallbacks
def get_my_claim(self, name): def get_my_claim(self, name):
def _get_claim_for_return(claim): my_claims = yield self.get_name_claims()
if not claim: my_claim = False
return False for claim in my_claims:
claim['value'] = smart_decode(claim['value'].decode('hex')).claim_dict if claim['name'] == name:
return claim claim['value'] = ClaimDict.load_dict(claim['value'])
my_claim = claim
def _get_my_unspent_claim(claims): break
for claim in claims: defer.returnValue(my_claim)
is_unspent = (
claim['name'] == name and
not claim['is_spent'] and
not claim.get('supported_claimid', False)
)
if is_unspent:
return claim
return False
d = self.get_name_claims()
d.addCallback(_get_my_unspent_claim)
d.addCallback(_get_claim_for_return)
return d
def get_claim_info(self, name, txid=None, nout=None): def get_claim_info(self, name, txid=None, nout=None):
if txid is None or nout is None: if txid is None or nout is None:
d = self._get_value_for_name(name) return self.get_claim_by_name(name)
d.addCallback(lambda r: self._get_claim_info(name, ClaimOutpoint(r['txid'], r['nout'])))
else: else:
d = self._get_claim_info(name, ClaimOutpoint(txid, nout)) return self.get_claim_by_outpoint(ClaimOutpoint(txid, nout))
# TODO: this catches every exception, fix this
d.addErrback(lambda _: False)
return d
def _format_claim_for_return(self, name, claim, claim_dict, meta_version): @defer.inlineCallbacks
result = {} def _handle_claim_result(self, results):
result['claim_id'] = claim['claim_id'] if 'error' in results:
result['amount'] = claim['effective_amount'] raise Exception(results['error'])
result['height'] = claim['height'] elif 'claim' in results:
result['name'] = name claim = results['claim']
result['txid'] = claim['txid'] if 'has_signature' in claim and claim['has_signature']:
result['nout'] = claim['nout'] if not claim['signature_is_valid']:
result['value'] = claim_dict raise Exception("Claim has invalid signature")
result['supports'] = [
{'txid': support['txid'], 'nout': support['nout']} for support in claim['supports']]
result['meta_version'] = meta_version
return result
def _get_claim_info(self, name, claim_outpoint):
def _build_response(claim):
try: try:
claim_dict = smart_decode(claim['value'].decode('hex')).claim_dict decoded = ClaimDict.load_dict(claim['value'])
meta_ver = claim_dict['stream']['metadata']['version'] claim_dict = decoded.claim_dict
sd_hash = utils.get_sd_hash(claim_dict) outpoint = ClaimOutpoint(claim['txid'], claim['nout'])
d = self._save_name_metadata(name, claim_outpoint, sd_hash) name = claim['name']
claim['value'] = claim_dict
yield self._save_name_metadata(name, outpoint, decoded.source_hash)
yield self._update_claimid(claim['claim_id'], name, outpoint)
except (TypeError, ValueError, KeyError, DecodeError): except (TypeError, ValueError, KeyError, DecodeError):
claim_dict = claim['value'] claim = claim['value']
meta_ver = "Non-compliant" log.warning(results)
d = defer.succeed(None) results = claim
elif 'value' in results:
if 'has_signature' in results and results['has_signature']:
if not results['signature_is_valid']:
raise Exception("Claim has invalid signature")
try:
decoded = ClaimDict.load_dict(results['value'])
claim_dict = decoded.claim_dict
outpoint = ClaimOutpoint(results['txid'], results['nout'])
name = results['name']
yield self._save_name_metadata(name, outpoint, decoded.source_hash)
yield self._update_claimid(results['claim_id'], name, outpoint)
except (TypeError, ValueError, KeyError, DecodeError):
claim_dict = results['value']
log.warning(results)
results['value'] = claim_dict
log.info("get claim info lbry://%s#%s", results['name'], results['claim_id'])
defer.returnValue(results)
d.addCallback(lambda _: self._format_claim_for_return(name, @defer.inlineCallbacks
claim, def resolve_uri(self, uri):
claim_dict=claim_dict, resolve_results = yield self._get_value_for_uri(uri)
meta_version=meta_ver)) if 'claim' in resolve_results:
log.info( formatted = yield self._handle_claim_result(resolve_results)
"get claim info lbry://%s metadata: %s, claimid: %s", resolve_results['claim'] = formatted
name, meta_ver, claim['claim_id']) result = resolve_results
return d elif 'claims_in_channel' in resolve_results:
claims_for_return = []
for claim in resolve_results['claims_in_channel']:
formatted = yield self._handle_claim_result(claim)
claims_for_return.append(formatted)
resolve_results['claims_in_channel'] = claims_for_return
result = resolve_results
else:
result = None
defer.returnValue(result)
d = self.get_claimid(name, claim_outpoint['txid'], claim_outpoint['nout']) @defer.inlineCallbacks
d.addCallback(lambda claim_id: self.get_claim(name, claim_id)) def get_claim_by_outpoint(self, claim_outpoint):
d.addCallback(_build_response) claim = yield self._get_claim_by_outpoint(claim_outpoint['txid'], claim_outpoint['nout'])
return d result = yield self._handle_claim_result(claim)
defer.returnValue(result)
@defer.inlineCallbacks
def get_claim_by_name(self, name):
get_name_result = yield self._get_value_for_name(name)
result = yield self._handle_claim_result(get_name_result)
defer.returnValue(result)
@defer.inlineCallbacks
def get_claims_for_name(self, name): def get_claims_for_name(self, name):
d = self._get_claims_for_name(name) result = yield self._get_claims_for_name(name)
return d claims = result['claims']
claims_for_return = []
for claim in claims:
claim['value'] = ClaimDict.load_dict(claim['value']).claim_dict
claims_for_return.append(claim)
result['claims'] = claims_for_return
defer.returnValue(result)
def _process_claim_out(self, claim_out): def _process_claim_out(self, claim_out):
claim_out.pop('success') claim_out.pop('success')
claim_out['fee'] = float(claim_out['fee']) claim_out['fee'] = float(claim_out['fee'])
return claim_out return claim_out
def claim_new_channel(self, channel_name, amount):
parsed_channel_name = parse_lbry_uri(channel_name)
if not parsed_channel_name.is_channel:
raise Exception("Invalid channel name")
elif (parsed_channel_name.path or parsed_channel_name.claim_id or
parsed_channel_name.bid_position or parsed_channel_name.claim_sequence):
raise Exception("New channel claim should have no fields other than name")
return self._claim_certificate(parsed_channel_name.name, amount)
@defer.inlineCallbacks @defer.inlineCallbacks
def claim_name(self, name, bid, metadata): def channel_list(self):
certificates = yield self._get_certificate_claims()
results = []
for claim in certificates:
formatted = yield self._handle_claim_result(claim)
results.append(formatted)
defer.returnValue(results)
@defer.inlineCallbacks
def claim_name(self, name, bid, metadata, certificate_id=None):
""" """
Claim a name, or update if name already claimed by user Claim a name, or update if name already claimed by user
@param name: str, name to claim @param name: str, name to claim
@param bid: float, bid amount @param bid: float, bid amount
@param metadata: Metadata compliant dict @param metadata: ClaimDict compliant dict
@param certificate_id: str (optional), claim id of channel certificate
@return: Deferred which returns a dict containing below items @return: Deferred which returns a dict containing below items
txid - txid of the resulting transaction txid - txid of the resulting transaction
@ -627,22 +609,14 @@ class Wallet(object):
fee - transaction fee paid to make claim fee - transaction fee paid to make claim
claim_id - claim id of the claim claim_id - claim id of the claim
""" """
claim_dict = ClaimDict.load_dict(metadata)
my_claim = yield self.get_my_claim(name)
if my_claim: decoded = ClaimDict.load_dict(metadata)
log.info("Updating claim") serialized = decoded.serialized
if self.get_balance() < Decimal(bid) - Decimal(my_claim['amount']):
raise InsufficientFundsError() if self.get_balance() < Decimal(bid):
old_claim_outpoint = ClaimOutpoint(my_claim['txid'], my_claim['nout']) raise InsufficientFundsError()
claim = yield self._send_name_claim_update(name, my_claim['claim_id'],
old_claim_outpoint, claim_dict.serialized, bid) claim = yield self._send_name_claim(name, serialized.encode('hex'), bid, certificate_id)
claim['claim_id'] = my_claim['claim_id']
else:
log.info("Making a new claim")
if self.get_balance() < bid:
raise InsufficientFundsError()
claim = yield self._send_name_claim(name, claim_dict.serialized, bid)
if not claim['success']: if not claim['success']:
msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) msg = 'Claim to name {} failed: {}'.format(name, claim['reason'])
@ -651,24 +625,20 @@ class Wallet(object):
claim = self._process_claim_out(claim) claim = self._process_claim_out(claim)
claim_outpoint = ClaimOutpoint(claim['txid'], claim['nout']) claim_outpoint = ClaimOutpoint(claim['txid'], claim['nout'])
log.info("Saving metadata for claim %s %d", claim['txid'], claim['nout']) log.info("Saving metadata for claim %s %d", claim['txid'], claim['nout'])
yield self._save_name_metadata(name, claim_outpoint, yield self._update_claimid(claim['claim_id'], name, claim_outpoint)
utils.get_sd_hash(claim_dict.claim_dict)) yield self._save_name_metadata(name, claim_outpoint, decoded.source_hash)
defer.returnValue(claim) defer.returnValue(claim)
@defer.inlineCallbacks @defer.inlineCallbacks
def abandon_claim(self, txid, nout): def abandon_claim(self, claim_id):
def _parse_abandon_claim_out(claim_out): claim_out = yield self._abandon_claim(claim_id)
if not claim_out['success']:
msg = 'Abandon of {}:{} failed: {}'.format(txid, nout, claim_out['reason'])
raise Exception(msg)
claim_out = self._process_claim_out(claim_out)
log.info("Abandoned claim tx %s (n: %i) --> %s", txid, nout, claim_out)
return defer.succeed(claim_out)
claim_outpoint = ClaimOutpoint(txid, nout) if not claim_out['success']:
claim_out = yield self._abandon_claim(claim_outpoint) msg = 'Abandon of {} failed: {}'.format(claim_id, claim_out['reason'])
result = yield _parse_abandon_claim_out(claim_out) raise Exception(msg)
defer.returnValue(result)
claim_out = self._process_claim_out(claim_out)
defer.returnValue(claim_out)
def support_claim(self, name, claim_id, amount): def support_claim(self, name, claim_id, amount):
def _parse_support_claim_out(claim_out): def _parse_support_claim_out(claim_out):
@ -778,13 +748,13 @@ class Wallet(object):
def _get_claims_for_name(self, name): def _get_claims_for_name(self, name):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
def _send_name_claim(self, name, val, amount): def _claim_certificate(self, name, amount):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
def _abandon_claim(self, claim_outpoint): def _send_name_claim(self, name, val, amount, certificate_id=None):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
def _send_name_claim_update(self, name, claim_id, claim_outpoint, value, amount): def _abandon_claim(self, claim_id):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
def _support_claim(self, name, claim_id, amount): def _support_claim(self, name, claim_id, amount):
@ -808,6 +778,18 @@ class Wallet(object):
def _address_is_mine(self, address): def _address_is_mine(self, address):
return defer.fail(NotImplementedError()) return defer.fail(NotImplementedError())
def _get_value_for_uri(self, uri):
return defer.fail(NotImplementedError())
def _get_certificate_claims(self):
return defer.fail(NotImplementedError())
def _get_claim_by_outpoint(self, txid, nout):
return defer.fail(NotImplementedError())
def _get_claim_by_claimid(self, claim_id):
return defer.fail(NotImplementedError())
def _start(self): def _start(self):
pass pass
@ -947,21 +929,21 @@ class LBRYumWallet(Wallet):
# run commands as a defer.succeed, # run commands as a defer.succeed,
# lbryum commands should be run this way , unless if the command # lbryum commands should be run this way , unless if the command
# only makes a lbrum server query, use _run_cmd_as_defer_to_thread() # only makes a lbrum server query, use _run_cmd_as_defer_to_thread()
def _run_cmd_as_defer_succeed(self, command_name, *args): def _run_cmd_as_defer_succeed(self, command_name, *args, **kwargs):
cmd_runner = self._get_cmd_runner() cmd_runner = self._get_cmd_runner()
cmd = known_commands[command_name] cmd = known_commands[command_name]
func = getattr(cmd_runner, cmd.name) func = getattr(cmd_runner, cmd.name)
return defer.succeed(func(*args)) return defer.succeed(func(*args, **kwargs))
# run commands as a deferToThread, lbryum commands that only make # run commands as a deferToThread, lbryum commands that only make
# queries to lbryum server should be run this way # queries to lbryum server should be run this way
# TODO: keep track of running threads and cancel them on `stop` # TODO: keep track of running threads and cancel them on `stop`
# otherwise the application will hang, waiting for threads to complete # otherwise the application will hang, waiting for threads to complete
def _run_cmd_as_defer_to_thread(self, command_name, *args): def _run_cmd_as_defer_to_thread(self, command_name, *args, **kwargs):
cmd_runner = self._get_cmd_runner() cmd_runner = self._get_cmd_runner()
cmd = known_commands[command_name] cmd = known_commands[command_name]
func = getattr(cmd_runner, cmd.name) func = getattr(cmd_runner, cmd.name)
return threads.deferToThread(func, *args) return threads.deferToThread(func, *args, **kwargs)
def _update_balance(self): def _update_balance(self):
accounts = None accounts = None
@ -1019,35 +1001,17 @@ class LBRYumWallet(Wallet):
return self._run_cmd_as_defer_to_thread('getclaimsforname', name) return self._run_cmd_as_defer_to_thread('getclaimsforname', name)
@defer.inlineCallbacks @defer.inlineCallbacks
def _send_name_claim(self, name, value, amount): def _send_name_claim(self, name, value, amount, certificate_id=None):
broadcast = False log.info("Send claim: %s for %s: %s ", name, amount, value)
log.debug("Name claim %s %f", name, amount) claim_out = yield self._run_cmd_as_defer_succeed('claim', name, value, amount,
tx = yield self._run_cmd_as_defer_succeed('claim', name, value, amount, broadcast) certificate_id=certificate_id)
claim_out = yield self._broadcast_claim_transaction(tx)
defer.returnValue(claim_out) defer.returnValue(claim_out)
@defer.inlineCallbacks @defer.inlineCallbacks
def _send_name_claim_update(self, name, claim_id, claim_outpoint, value, amount): def _abandon_claim(self, claim_id):
log.debug("Update %s %d %f %s %s", claim_outpoint['txid'], claim_outpoint['nout'], log.debug("Abandon %s" % claim_id)
amount, name, claim_id) tx_out = yield self._run_cmd_as_defer_succeed('abandon', claim_id)
broadcast = False defer.returnValue(tx_out)
tx = yield self._run_cmd_as_defer_succeed(
'update', claim_outpoint['txid'], claim_outpoint['nout'],
name, claim_id, value, amount, broadcast
)
claim_out = yield self._broadcast_claim_transaction(tx)
defer.returnValue(claim_out)
@defer.inlineCallbacks
def _abandon_claim(self, claim_outpoint):
log.debug("Abandon %s %s" % (claim_outpoint['txid'], claim_outpoint['nout']))
broadcast = False
abandon_tx = yield self._run_cmd_as_defer_succeed(
'abandon', claim_outpoint['txid'], claim_outpoint['nout'], broadcast
)
claim_out = yield self._broadcast_claim_transaction(abandon_tx)
defer.returnValue(claim_out)
@defer.inlineCallbacks @defer.inlineCallbacks
def _support_claim(self, name, claim_id, amount): def _support_claim(self, name, claim_id, amount):
@ -1085,20 +1049,30 @@ class LBRYumWallet(Wallet):
return d return d
def _get_value_for_name(self, name): def _get_value_for_name(self, name):
height_to_check = self.network.get_local_height() - RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS + 1 if not name:
if height_to_check < 0: raise Exception("No name given")
msg = "Height to check is less than 0, blockchain headers are likely not initialized" return self._run_cmd_as_defer_to_thread('getvalueforname', name)
raise Exception(msg)
block_header = self.network.blockchain.read_header(height_to_check) def _get_value_for_uri(self, uri):
block_hash = self.network.blockchain.hash_header(block_header) if not uri:
d = self._run_cmd_as_defer_to_thread('requestvalueforname', name, block_hash) raise Exception("No uri given")
d.addCallback(lambda response: Commands._verify_proof(name, block_header['claim_trie_root'], return self._run_cmd_as_defer_to_thread('getvalueforuri', uri)
response))
return d def _claim_certificate(self, name, amount):
return self._run_cmd_as_defer_to_thread('claimcertificate', name, amount)
def _get_certificate_claims(self):
return self._run_cmd_as_defer_succeed('getcertificateclaims')
def get_claims_from_tx(self, txid): def get_claims_from_tx(self, txid):
return self._run_cmd_as_defer_to_thread('getclaimsfromtx', txid) return self._run_cmd_as_defer_to_thread('getclaimsfromtx', txid)
def _get_claim_by_outpoint(self, txid, nout):
return self._run_cmd_as_defer_to_thread('getclaimbyoutpoint', txid, nout)
def get_claim_by_claimid(self, claim_id):
return self._run_cmd_as_defer_to_thread('getclaimbyid', claim_id)
def _get_balance_for_address(self, address): def _get_balance_for_address(self, address):
return defer.succeed(Decimal(self.wallet.get_addr_received(address)) / COIN) return defer.succeed(Decimal(self.wallet.get_addr_received(address)) / COIN)