{"action":"created","comment":{"url":"https://api.github.com/repos/lbryio/lbry/pulls/comments/211657260","pull_request_review_id":148125572,"id":211657260,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMTY1NzI2MA==","diff_hunk":"@@ -1,1325 +0,0 @@\n-from collections import defaultdict, deque\n-import datetime\n-import logging\n-from decimal import Decimal\n-\n-from zope.interface import implements\n-from twisted.internet import threads, reactor, defer, task\n-from twisted.python.failure import Failure\n-from twisted.internet.error import ConnectionAborted\n-\n-from lbryum import wallet as lbryum_wallet\n-from lbryum.network import Network\n-from lbryum.simple_config import SimpleConfig\n-from lbryum.constants import COIN\n-from lbryum.commands import Commands\n-from lbryum.errors import InvalidPassword\n-\n-from lbryschema.uri import parse_lbry_uri\n-from lbryschema.claim import ClaimDict\n-from lbryschema.error import DecodeError\n-from lbryschema.decode import smart_decode\n-\n-from lbrynet.interfaces import IRequestCreator, IQueryHandlerFactory, IQueryHandler, IWallet\n-from lbrynet.core.utils import DeferredDict\n-from lbrynet.core.client.ClientRequest import ClientRequest\n-from lbrynet.core.Error import InsufficientFundsError, UnknownNameError\n-from lbrynet.core.Error import UnknownClaimID, UnknownURI, NegativeFundsError, UnknownOutpoint\n-from lbrynet.core.Error import DownloadCanceledError, RequestCanceledError\n-\n-log = logging.getLogger(__name__)\n-\n-\n-class ReservedPoints(object):\n-    def __init__(self, identifier, amount):\n-        self.identifier = identifier\n-        self.amount = amount\n-\n-\n-class ClaimOutpoint(dict):\n-    def __init__(self, txid, nout):\n-        if len(txid) != 64:\n-            raise TypeError('{} is not a txid'.format(txid))\n-        self['txid'] = txid\n-        self['nout'] = nout\n-\n-    def __repr__(self):\n-        return \"{}:{}\".format(self['txid'], self['nout'])\n-\n-    def __eq__(self, compare):\n-        if isinstance(compare, dict):\n-            # TODO: lbryum returns nout's in dicts as \"nOut\" , need to fix this\n-            if 'nOut' in compare:\n-                return (self['txid'], self['nout']) == (compare['txid'], compare['nOut'])\n-            elif 'nout' in compare:\n-                return (self['txid'], self['nout']) == (compare['txid'], compare['nout'])\n-        elif isinstance(compare, (str, unicode)):\n-            return compare == self.__repr__()\n-        else:\n-            raise TypeError('cannot compare {}'.format(type(compare)))\n-\n-    def __ne__(self, compare):\n-        return not self.__eq__(compare)\n-\n-\n-class Wallet(object):\n-    \"\"\"This class implements the Wallet interface for the LBRYcrd payment system\"\"\"\n-    implements(IWallet)\n-\n-    def __init__(self, storage):\n-        self.storage = storage\n-        self.next_manage_call = None\n-        self.wallet_balance = Decimal(0.0)\n-        self.total_reserved_points = Decimal(0.0)\n-        self.peer_addresses = {}  # {Peer: string}\n-        self.queued_payments = defaultdict(Decimal)  # {address(string): amount(Decimal)}\n-        self.expected_balances = defaultdict(Decimal)  # {address(string): amount(Decimal)}\n-        self.current_address_given_to_peer = {}  # {Peer: address(string)}\n-        # (Peer, address(string), amount(Decimal), time(datetime), count(int),\n-        # incremental_amount(float))\n-        self.expected_balance_at_time = deque()\n-        self.max_expected_payment_time = datetime.timedelta(minutes=3)\n-        self.stopped = True\n-\n-        self.manage_running = False\n-        self._manage_count = 0\n-        self._balance_refresh_time = 3\n-        self._batch_count = 20\n-        self._pending_claim_checker = task.LoopingCall(self.fetch_and_save_heights_for_pending_claims)\n-\n-    @defer.inlineCallbacks\n-    def start(self):\n-        log.info(\"Starting wallet.\")\n-        yield self._start()\n-        self.stopped = False\n-        self.manage()\n-        self._pending_claim_checker.start(30)\n-        defer.returnValue(True)\n-\n-    @staticmethod\n-    def log_stop_error(err):\n-        log.error(\"An error occurred stopping the wallet: %s\", err.getTraceback())\n-\n-    def stop(self):\n-        log.info(\"Stopping wallet.\")\n-        self.stopped = True\n-\n-        if self._pending_claim_checker.running:\n-            self._pending_claim_checker.stop()\n-        # If self.next_manage_call is None, then manage is currently running or else\n-        # start has not been called, so set stopped and do nothing else.\n-        if self.next_manage_call is not None:\n-            self.next_manage_call.cancel()\n-            self.next_manage_call = None\n-\n-        d = self.manage(do_full=True)\n-        d.addErrback(self.log_stop_error)\n-        d.addCallback(lambda _: self._stop())\n-        d.addErrback(self.log_stop_error)\n-        return d\n-\n-    def manage(self, do_full=False):\n-        self.next_manage_call = None\n-        have_set_manage_running = [False]\n-        self._manage_count += 1\n-        if self._manage_count % self._batch_count == 0:\n-            self._manage_count = 0\n-            do_full = True\n-\n-        def check_if_manage_running():\n-\n-            d = defer.Deferred()\n-\n-            def fire_if_not_running():\n-                if self.manage_running is False:\n-                    self.manage_running = True\n-                    have_set_manage_running[0] = True\n-                    d.callback(True)\n-                elif do_full is False:\n-                    d.callback(False)\n-                else:\n-                    task.deferLater(reactor, 1, fire_if_not_running)\n-\n-            fire_if_not_running()\n-            return d\n-\n-        d = check_if_manage_running()\n-\n-        def do_manage():\n-            if do_full:\n-                d = self._check_expected_balances()\n-                d.addCallback(lambda _: self._send_payments())\n-            else:\n-                d = defer.succeed(True)\n-\n-            def log_error(err):\n-                if isinstance(err, AttributeError):\n-                    log.warning(\"Failed to get an updated balance\")\n-                    log.warning(\"Last balance update: %s\", str(self.wallet_balance))\n-\n-            d.addCallbacks(lambda _: self.update_balance(), log_error)\n-            return d\n-\n-        d.addCallback(lambda should_run: do_manage() if should_run else None)\n-\n-        def set_next_manage_call():\n-            if not self.stopped:\n-                self.next_manage_call = reactor.callLater(self._balance_refresh_time, self.manage)\n-\n-        d.addCallback(lambda _: set_next_manage_call())\n-\n-        def log_error(err):\n-            log.error(\"Something went wrong during manage. Error message: %s\",\n-                      err.getErrorMessage())\n-            return err\n-\n-        d.addErrback(log_error)\n-\n-        def set_manage_not_running(arg):\n-            if have_set_manage_running[0] is True:\n-                self.manage_running = False\n-            return arg\n-\n-        d.addBoth(set_manage_not_running)\n-        return d\n-\n-    @defer.inlineCallbacks\n-    def update_balance(self):\n-        \"\"\" obtain balance from lbryum wallet and set self.wallet_balance\n-        \"\"\"\n-        balance = yield self._update_balance()\n-        if self.wallet_balance != balance:\n-            log.debug(\"Got a new balance: %s\", balance)\n-        self.wallet_balance = balance\n-\n-    def get_info_exchanger(self):\n-        return LBRYcrdAddressRequester(self)\n-\n-    def get_wallet_info_query_handler_factory(self):\n-        return LBRYcrdAddressQueryHandlerFactory(self)\n-\n-    def reserve_points(self, identifier, amount):\n-        \"\"\"Ensure a certain amount of points are available to be sent as\n-        payment, before the service is rendered\n-\n-        @param identifier: The peer to which the payment will ultimately be sent\n-\n-        @param amount: The amount of points to reserve\n-\n-        @return: A ReservedPoints object which is given to send_points\n-            once the service has been rendered\n-        \"\"\"\n-        rounded_amount = Decimal(str(round(amount, 8)))\n-        if rounded_amount < 0:\n-            raise NegativeFundsError(rounded_amount)\n-        if self.get_balance() >= rounded_amount:\n-            self.total_reserved_points += rounded_amount\n-            return ReservedPoints(identifier, rounded_amount)\n-        return None\n-\n-    def cancel_point_reservation(self, reserved_points):\n-        \"\"\"\n-        Return all of the points that were reserved previously for some ReservedPoints object\n-\n-        @param reserved_points: ReservedPoints previously returned by reserve_points\n-\n-        @return: None\n-        \"\"\"\n-        self.total_reserved_points -= reserved_points.amount\n-\n-    def send_points(self, reserved_points, amount):\n-        \"\"\"\n-        Schedule a payment to be sent to a peer\n-\n-        @param reserved_points: ReservedPoints object previously returned by reserve_points\n-\n-        @param amount: amount of points to actually send, must be less than or equal to the\n-            amount reserved in reserved_points\n-\n-        @return: Deferred which fires when the payment has been scheduled\n-        \"\"\"\n-        rounded_amount = Decimal(str(round(amount, 8)))\n-        peer = reserved_points.identifier\n-        assert rounded_amount <= reserved_points.amount\n-        assert peer in self.peer_addresses\n-        self.queued_payments[self.peer_addresses[peer]] += rounded_amount\n-        # make any unused points available\n-        self.total_reserved_points -= (reserved_points.amount - rounded_amount)\n-        log.debug(\"ordering that %s points be sent to %s\", str(rounded_amount),\n-                  str(self.peer_addresses[peer]))\n-        peer.update_stats('points_sent', amount)\n-        return defer.succeed(True)\n-\n-    def send_points_to_address(self, reserved_points, amount):\n-        \"\"\"\n-        Schedule a payment to be sent to an address\n-\n-        @param reserved_points: ReservedPoints object previously returned by reserve_points\n-\n-        @param amount: amount of points to actually send. must be less than or equal to the\n-            amount reserved in reserved_points\n-\n-        @return: Deferred which fires when the payment has been scheduled\n-        \"\"\"\n-        rounded_amount = Decimal(str(round(amount, 8)))\n-        address = reserved_points.identifier\n-        assert rounded_amount <= reserved_points.amount\n-        self.queued_payments[address] += rounded_amount\n-        self.total_reserved_points -= (reserved_points.amount - rounded_amount)\n-        log.debug(\"Ordering that %s points be sent to %s\", str(rounded_amount),\n-                  str(address))\n-        return defer.succeed(True)\n-\n-    def add_expected_payment(self, peer, amount):\n-        \"\"\"Increase the number of points expected to be paid by a peer\"\"\"\n-        rounded_amount = Decimal(str(round(amount, 8)))\n-        assert peer in self.current_address_given_to_peer\n-        address = self.current_address_given_to_peer[peer]\n-        log.debug(\"expecting a payment at address %s in the amount of %s\",\n-                  str(address), str(rounded_amount))\n-        self.expected_balances[address] += rounded_amount\n-        expected_balance = self.expected_balances[address]\n-        expected_time = datetime.datetime.now() + self.max_expected_payment_time\n-        self.expected_balance_at_time.append(\n-            (peer, address, expected_balance, expected_time, 0, amount))\n-        peer.update_stats('expected_points', amount)\n-\n-    def update_peer_address(self, peer, address):\n-        self.peer_addresses[peer] = address\n-\n-    def get_unused_address_for_peer(self, peer):\n-        def set_address_for_peer(address):\n-            self.current_address_given_to_peer[peer] = address\n-            return address\n-\n-        d = self.get_least_used_address()\n-        d.addCallback(set_address_for_peer)\n-        return d\n-\n-    def _send_payments(self):\n-        payments_to_send = {}\n-        for address, points in self.queued_payments.items():\n-            if points > 0:\n-                log.debug(\"Should be sending %s points to %s\", str(points), str(address))\n-                payments_to_send[address] = points\n-                self.total_reserved_points -= points\n-            else:\n-                log.info(\"Skipping dust\")\n-\n-            del self.queued_payments[address]\n-\n-        if payments_to_send:\n-            log.debug(\"Creating a transaction with outputs %s\", str(payments_to_send))\n-            d = self._do_send_many(payments_to_send)\n-            d.addCallback(lambda txid: log.debug(\"Sent transaction %s\", txid))\n-            return d\n-\n-        log.debug(\"There were no payments to send\")\n-        return defer.succeed(True)\n-\n-    ######\n-\n-    @defer.inlineCallbacks\n-    def fetch_and_save_heights_for_pending_claims(self):\n-        pending_outpoints = yield self.storage.get_pending_claim_outpoints()\n-        if pending_outpoints:\n-            tx_heights = yield DeferredDict({txid: self.get_height_for_txid(txid) for txid in pending_outpoints},\n-                                            consumeErrors=True)\n-            outpoint_heights = {}\n-            for txid, outputs in pending_outpoints.iteritems():\n-                if txid in tx_heights:\n-                    for nout in outputs:\n-                        outpoint_heights[\"%s:%i\" % (txid, nout)] = tx_heights[txid]\n-            yield self.storage.save_claim_tx_heights(outpoint_heights)\n-\n-    @defer.inlineCallbacks\n-    def get_claim_by_claim_id(self, claim_id, check_expire=True):\n-        claim = yield self._get_claim_by_claimid(claim_id)\n-        try:\n-            result = self._handle_claim_result(claim)\n-        except (UnknownNameError, UnknownClaimID, UnknownURI) as err:\n-            result = {'error': err.message}\n-        defer.returnValue(result)\n-\n-    @defer.inlineCallbacks\n-    def get_my_claim(self, name):\n-        my_claims = yield self.get_name_claims()\n-        my_claim = False\n-        for claim in my_claims:\n-            if claim['name'] == name:\n-                claim['value'] = ClaimDict.load_dict(claim['value'])\n-                my_claim = claim\n-                break\n-        defer.returnValue(my_claim)\n-\n-    def _decode_claim_result(self, claim):\n-        if 'has_signature' in claim and claim['has_signature']:\n-            if not claim['signature_is_valid']:\n-                log.warning(\"lbry://%s#%s has an invalid signature\",\n-                            claim['name'], claim['claim_id'])\n-        try:\n-            decoded = smart_decode(claim['value'])\n-            claim_dict = decoded.claim_dict\n-            claim['value'] = claim_dict\n-            claim['hex'] = decoded.serialized.encode('hex')\n-        except DecodeError:\n-            claim['hex'] = claim['value']\n-            claim['value'] = None\n-            claim['error'] = \"Failed to decode value\"\n-        return claim\n-\n-    def _handle_claim_result(self, results):\n-        if not results:\n-            #TODO: cannot determine what name we searched for here\n-            # we should fix lbryum commands that return None\n-            raise UnknownNameError(\"\")\n-\n-        if 'error' in results:\n-            if results['error'] in ['name is not claimed', 'claim not found']:\n-                if 'claim_id' in results:\n-                    raise UnknownClaimID(results['claim_id'])\n-                elif 'name' in results:\n-                    raise UnknownNameError(results['name'])\n-                elif 'uri' in results:\n-                    raise UnknownURI(results['uri'])\n-                elif 'outpoint' in results:\n-                    raise UnknownOutpoint(results['outpoint'])\n-            raise Exception(results['error'])\n-\n-        # case where return value is {'certificate':{'txid', 'value',...},...}\n-        if 'certificate' in results:\n-            results['certificate'] = self._decode_claim_result(results['certificate'])\n-\n-        # case where return value is {'claim':{'txid','value',...},...}\n-        if 'claim' in results:\n-            results['claim'] = self._decode_claim_result(results['claim'])\n-\n-        # case where return value is {'txid','value',...}\n-        # returned by queries that are not name resolve related\n-        # (getclaimbyoutpoint, getclaimbyid, getclaimsfromtx)\n-        elif 'value' in results:\n-            results = self._decode_claim_result(results)\n-\n-        # case where there is no 'certificate', 'value', or 'claim' key\n-        elif 'certificate' not in results:\n-            msg = 'result in unexpected format:{}'.format(results)\n-            assert False, msg\n-\n-        return results\n-\n-    @defer.inlineCallbacks\n-    def save_claim(self, claim_info):\n-        claims = []\n-        if 'value' in claim_info:\n-            if claim_info['value']:\n-                claims.append(claim_info)\n-        else:\n-            if 'certificate' in claim_info and claim_info['certificate']['value']:\n-                claims.append(claim_info['certificate'])\n-            if 'claim' in claim_info and claim_info['claim']['value']:\n-                claims.append(claim_info['claim'])\n-        yield self.storage.save_claims(claims)\n-\n-    @defer.inlineCallbacks\n-    def save_claims(self, claim_infos):\n-        to_save = []\n-        for info in claim_infos:\n-            if 'value' in info:\n-                if info['value']:\n-                    to_save.append(info)\n-            else:\n-                if 'certificate' in info and info['certificate']['value']:\n-                    to_save.append(info['certificate'])\n-                if 'claim' in info and info['claim']['value']:\n-                    to_save.append(info['claim'])\n-        yield self.storage.save_claims(to_save)\n-\n-    @defer.inlineCallbacks\n-    def resolve(self, *uris, **kwargs):\n-        page = kwargs.get('page', 0)\n-        page_size = kwargs.get('page_size', 10)\n-\n-        result = {}\n-        batch_results = yield self._get_values_for_uris(page, page_size, *uris)\n-        to_save = []\n-        for uri, resolve_results in batch_results.iteritems():\n-            try:\n-                result[uri] = self._handle_claim_result(resolve_results)\n-                to_save.append(result[uri])\n-            except (UnknownNameError, UnknownClaimID, UnknownURI) as err:\n-                result[uri] = {'error': err.message}\n-        yield self.save_claims(to_save)\n-        defer.returnValue(result)\n-\n-    @defer.inlineCallbacks\n-    def get_claims_by_ids(self, *claim_ids):\n-        claims = yield self._get_claims_by_claimids(*claim_ids)\n-        for claim in claims.itervalues():\n-            yield self.save_claim(claim)\n-        defer.returnValue(claims)\n-\n-    @defer.inlineCallbacks\n-    def get_claim_by_outpoint(self, txid, nout, check_expire=True):\n-        claim = yield self._get_claim_by_outpoint(txid, nout)\n-        try:\n-            result = self._handle_claim_result(claim)\n-            yield self.save_claim(result)\n-        except UnknownOutpoint as err:\n-            result = {'error': err.message}\n-        defer.returnValue(result)\n-\n-    @defer.inlineCallbacks\n-    def get_claim_by_name(self, name):\n-        get_name_result = yield self._get_value_for_name(name)\n-        result = self._handle_claim_result(get_name_result)\n-        yield self.save_claim(result)\n-        defer.returnValue(result)\n-\n-    @defer.inlineCallbacks\n-    def get_claims_for_name(self, name):\n-        result = yield self._get_claims_for_name(name)\n-        claims = result['claims']\n-        claims_for_return = []\n-        for claim in claims:\n-            try:\n-                decoded = smart_decode(claim['value'])\n-                claim['value'] = decoded.claim_dict\n-                claim['hex'] = decoded.serialized.encode('hex')\n-                yield self.save_claim(claim)\n-                claims_for_return.append(claim)\n-            except DecodeError:\n-                claim['hex'] = claim['value']\n-                claim['value'] = None\n-                claim['error'] = \"Failed to decode\"\n-                log.warning(\"Failed to decode claim value for lbry://%s#%s\", claim['name'],\n-                            claim['claim_id'])\n-                claims_for_return.append(claim)\n-\n-        result['claims'] = claims_for_return\n-        defer.returnValue(result)\n-\n-    def _process_claim_out(self, claim_out):\n-        claim_out.pop('success')\n-        claim_out['fee'] = float(claim_out['fee'])\n-        return claim_out\n-\n-    @defer.inlineCallbacks\n-    def claim_new_channel(self, channel_name, amount):\n-        parsed_channel_name = parse_lbry_uri(channel_name)\n-        if not parsed_channel_name.is_channel:\n-            raise Exception(\"Invalid channel name\")\n-        elif (parsed_channel_name.path or parsed_channel_name.claim_id or\n-              parsed_channel_name.bid_position or parsed_channel_name.claim_sequence):\n-            raise Exception(\"New channel claim should have no fields other than name\")\n-        log.info(\"Preparing to make certificate claim for %s\", channel_name)\n-        channel_claim = yield self._claim_certificate(parsed_channel_name.name, amount)\n-        if not channel_claim['success']:\n-            msg = 'Claiming of channel {} failed: {}'.format(channel_name, channel_claim['reason'])\n-            log.error(msg)\n-            raise Exception(msg)\n-        yield self.save_claim(self._get_temp_claim_info(channel_claim, channel_name, amount))\n-        defer.returnValue(channel_claim)\n-\n-    @defer.inlineCallbacks\n-    def channel_list(self):\n-        certificates = yield self.get_certificates_for_signing()\n-        results = []\n-        for claim in certificates:\n-            formatted = self._handle_claim_result(claim)\n-            results.append(formatted)\n-        defer.returnValue(results)\n-\n-    def _get_temp_claim_info(self, claim_result, name, bid):\n-        # save the claim information with a height and sequence of 0, this will be reset upon next resolve\n-        return {\n-            \"claim_id\": claim_result['claim_id'],\n-            \"name\": name,\n-            \"amount\": bid,\n-            \"address\": claim_result['claim_address'],\n-            \"txid\": claim_result['txid'],\n-            \"nout\": claim_result['nout'],\n-            \"value\": claim_result['value'],\n-            \"height\": -1,\n-            \"claim_sequence\": -1,\n-        }\n-\n-    @defer.inlineCallbacks\n-    def claim_name(self, name, bid, metadata, certificate_id=None, claim_address=None,\n-                   change_address=None):\n-        \"\"\"\n-        Claim a name, or update if name already claimed by user\n-\n-        @param name: str, name to claim\n-        @param bid: float, bid amount\n-        @param metadata: ClaimDict compliant dict\n-        @param certificate_id: str (optional), claim id of channel certificate\n-        @param claim_address: str (optional), address to send claim to\n-        @param change_address: str (optional), address to send change\n-\n-        @return: Deferred which returns a dict containing below items\n-            txid - txid of the resulting transaction\n-            nout - nout of the resulting claim\n-            fee - transaction fee paid to make claim\n-            claim_id -  claim id of the claim\n-        \"\"\"\n-\n-        decoded = ClaimDict.load_dict(metadata)\n-        serialized = decoded.serialized\n-\n-        if self.get_balance() <= bid:\n-            amt = yield self.get_max_usable_balance_for_claim(name)\n-            if bid > amt:\n-                raise InsufficientFundsError()\n-\n-        claim = yield self._send_name_claim(name, serialized.encode('hex'),\n-                                            bid, certificate_id, claim_address, change_address)\n-\n-        if not claim['success']:\n-            msg = 'Claiming of name {} failed: {}'.format(name, claim['reason'])\n-            log.error(msg)\n-            raise Exception(msg)\n-        claim = self._process_claim_out(claim)\n-        yield self.storage.save_claims([self._get_temp_claim_info(claim, name, bid)])\n-        defer.returnValue(claim)\n-\n-    @defer.inlineCallbacks\n-    def abandon_claim(self, claim_id, txid, nout):\n-        claim_out = yield self._abandon_claim(claim_id, txid, nout)\n-\n-        if not claim_out['success']:\n-            msg = 'Abandon of {}/{}:{} failed: {}'.format(\n-                            claim_id, txid, nout, claim_out['reason'])\n-            raise Exception(msg)\n-\n-        claim_out = self._process_claim_out(claim_out)\n-        defer.returnValue(claim_out)\n-\n-    def support_claim(self, name, claim_id, amount):\n-        def _parse_support_claim_out(claim_out):\n-            if not claim_out['success']:\n-                msg = 'Support of {}:{} failed: {}'.format(name, claim_id, claim_out['reason'])\n-                raise Exception(msg)\n-            claim_out = self._process_claim_out(claim_out)\n-            return defer.succeed(claim_out)\n-\n-        if self.get_balance() < amount:\n-            raise InsufficientFundsError()\n-\n-        d = self._support_claim(name, claim_id, amount)\n-        d.addCallback(lambda claim_out: _parse_support_claim_out(claim_out))\n-        return d\n-\n-    @defer.inlineCallbacks\n-    def tip_claim(self, claim_id, amount):\n-        claim_out = yield self._tip_claim(claim_id, amount)\n-        if claim_out:\n-            result = self._process_claim_out(claim_out)\n-            defer.returnValue(result)\n-        else:\n-            raise Exception(\"failed to send tip of %f to claim id %s\" % (amount, claim_id))\n-\n-    def get_block_info(self, height):\n-        d = self._get_blockhash(height)\n-        return d\n-\n-    def get_history(self):\n-        d = self._get_history()\n-        return d\n-\n-    def address_is_mine(self, address):\n-        d = self._address_is_mine(address)\n-        return d\n-\n-    def get_transaction(self, txid):\n-        d = self._get_transaction(txid)\n-        return d\n-\n-    def wait_for_tx_in_wallet(self, txid):\n-        return self._wait_for_tx_in_wallet(txid)\n-\n-    def get_balance(self):\n-        return self.wallet_balance - self.total_reserved_points - sum(self.queued_payments.values())\n-\n-    def _check_expected_balances(self):\n-        now = datetime.datetime.now()\n-        balances_to_check = []\n-        try:\n-            while self.expected_balance_at_time[0][3] < now:\n-                balances_to_check.append(self.expected_balance_at_time.popleft())\n-        except IndexError:\n-            pass\n-        ds = []\n-        for balance_to_check in balances_to_check:\n-            log.debug(\"Checking balance of address %s\", str(balance_to_check[1]))\n-            d = self._get_balance_for_address(balance_to_check[1])\n-            d.addCallback(lambda bal: bal >= balance_to_check[2])\n-            ds.append(d)\n-        dl = defer.DeferredList(ds)\n-\n-        def handle_checks(results):\n-            for balance, (success, result) in zip(balances_to_check, results):\n-                peer = balance[0]\n-                if success is True:\n-                    if result is False:\n-                        if balance[4] <= 1:  # first or second strike, give them another chance\n-                            new_expected_balance = (\n-                                balance[0],\n-                                balance[1],\n-                                balance[2],\n-                                datetime.datetime.now() + self.max_expected_payment_time,\n-                                balance[4] + 1,\n-                                balance[5]\n-                            )\n-                            self.expected_balance_at_time.append(new_expected_balance)\n-                            peer.update_score(-5.0)\n-                        else:\n-                            peer.update_score(-50.0)\n-                    else:\n-                        if balance[4] == 0:\n-                            peer.update_score(balance[5])\n-                        peer.update_stats('points_received', balance[5])\n-                else:\n-                    log.warning(\"Something went wrong checking a balance. Peer: %s, account: %s,\"\n-                                \"expected balance: %s, expected time: %s, count: %s, error: %s\",\n-                                str(balance[0]), str(balance[1]), str(balance[2]), str(balance[3]),\n-                                str(balance[4]), str(result.getErrorMessage()))\n-\n-        dl.addCallback(handle_checks)\n-        return dl\n-\n-    # ======== Must be overridden ======== #\n-\n-    def _get_blockhash(self, height):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_transaction(self, txid):\n-        return defer.fail(NotImplementedError())\n-\n-    def _wait_for_tx_in_wallet(self, txid):\n-        return defer.fail(NotImplementedError())\n-\n-    def _update_balance(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_new_address(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_address_balance(self, address):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_block(self, blockhash):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_most_recent_blocktime(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_best_blockhash(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_name_claims(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_claims_for_name(self, name):\n-        return defer.fail(NotImplementedError())\n-\n-    def _claim_certificate(self, name, amount):\n-        return defer.fail(NotImplementedError())\n-\n-    def _send_name_claim(self, name, val, amount, certificate_id=None, claim_address=None,\n-                         change_address=None):\n-        return defer.fail(NotImplementedError())\n-\n-    def _abandon_claim(self, claim_id, txid, nout):\n-        return defer.fail(NotImplementedError())\n-\n-    def _support_claim(self, name, claim_id, amount):\n-        return defer.fail(NotImplementedError())\n-\n-    def _tip_claim(self, claim_id, amount):\n-        return defer.fail(NotImplementedError())\n-\n-    def _do_send_many(self, payments_to_send):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_value_for_name(self, name):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_claims_from_tx(self, txid):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_balance_for_address(self, address):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_history(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def _address_is_mine(self, address):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_value_for_uri(self, uri):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_certificate_claims(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_claim_by_outpoint(self, txid, nout):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_claim_by_claimid(self, claim_id):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_claims_by_claimids(self, *claim_ids):\n-        return defer.fail(NotImplementedError())\n-\n-    def _get_values_for_uris(self, page, page_size, *uris):\n-        return defer.fail(NotImplementedError())\n-\n-    def claim_renew_all_before_expiration(self, height):\n-        return defer.fail(NotImplementedError())\n-\n-    def claim_renew(self, txid, nout):\n-        return defer.fail(NotImplementedError())\n-\n-    def send_claim_to_address(self, claim_id, destination, amount):\n-        return defer.fail(NotImplementedError())\n-\n-    def import_certificate_info(self, serialized_certificate_info):\n-        return defer.fail(NotImplementedError())\n-\n-    def export_certificate_info(self, certificate_claim_id):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_certificates_for_signing(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_unused_address(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_least_used_address(self, account=None, for_change=False, max_count=100):\n-        return defer.fail(NotImplementedError())\n-\n-    def decrypt_wallet(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def encrypt_wallet(self, new_password, update_keyring=False):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_max_usable_balance_for_claim(self, claim_name):\n-        return defer.fail(NotImplementedError())\n-\n-    def get_height_for_txid(self, txid):\n-        return defer.fail(NotImplementedError())\n-\n-    def _start(self):\n-        return defer.fail(NotImplementedError())\n-\n-    def _stop(self):\n-        pass\n-\n-\n-class LBRYumWallet(Wallet):\n-    def __init__(self, storage, config=None):\n-        Wallet.__init__(self, storage)\n-        self._config = config\n-        self.config = make_config(self._config)\n-        self.network = None\n-        self.wallet = None\n-        self._cmd_runner = None\n-        self.wallet_unlocked_d = defer.Deferred()\n-        self.is_first_run = False\n-        self.printed_retrieving_headers = False\n-        self._start_check = None\n-        self._catch_up_check = None\n-        self._caught_up_counter = 0\n-        self._lag_counter = 0\n-        self.blocks_behind = 0\n-        self.catchup_progress = 0\n-        self.is_wallet_unlocked = None\n-\n-    def _is_first_run(self):\n-        return (not self.printed_retrieving_headers and\n-                self.network.blockchain.retrieving_headers)\n-\n-    def get_cmd_runner(self):\n-        if self._cmd_runner is None:\n-            self._cmd_runner = Commands(self.config, self.wallet, self.network)\n-\n-        return self._cmd_runner\n-\n-    def check_locked(self):\n-        \"\"\"\n-        Checks if the wallet is encrypted(locked) or not\n-\n-        :return: (boolean) indicating whether the wallet is locked or not\n-        \"\"\"\n-        if not self._cmd_runner:\n-            raise Exception(\"Command runner hasn't been initialized yet\")\n-        elif self._cmd_runner.locked:\n-            log.info(\"Waiting for wallet password\")\n-            self.wallet_unlocked_d.addCallback(self.unlock)\n-        return self.is_wallet_unlocked\n-\n-    def unlock(self, password):\n-        if self._cmd_runner and self._cmd_runner.locked:\n-            try:\n-                self._cmd_runner.unlock_wallet(password)\n-                self.is_wallet_unlocked = True\n-                log.info(\"Unlocked the wallet!\")\n-            except InvalidPassword:\n-                log.warning(\"Incorrect password, try again\")\n-                self.wallet_unlocked_d = defer.Deferred()\n-                self.wallet_unlocked_d.addCallback(self.unlock)\n-                return defer.succeed(False)\n-        return defer.succeed(True)\n-\n-    def _start(self):\n-        network_start_d = defer.Deferred()\n-\n-        def setup_network():\n-            self.network = Network(self.config)\n-            log.info(\"Loading the wallet\")\n-            return defer.succeed(self.network.start())\n-\n-        def check_started():\n-            if self.network.is_connecting():\n-                if self._is_first_run():\n-                    log.info(\"Running the wallet for the first time. This may take a moment.\")\n-                    self.printed_retrieving_headers = True\n-                return False\n-            self._start_check.stop()\n-            self._start_check = None\n-            if self.network.is_connected():\n-                network_start_d.callback(True)\n-            else:\n-                network_start_d.errback(ValueError(\"Failed to connect to network.\"))\n-\n-        self._start_check = task.LoopingCall(check_started)\n-\n-        d = setup_network()\n-        d.addCallback(lambda _: self._load_wallet())\n-        d.addCallback(lambda _: self._start_check.start(.1))\n-        d.addCallback(lambda _: network_start_d)\n-        d.addCallback(lambda _: self._load_blockchain())\n-        d.addCallback(lambda _: log.info(\"Subscribing to addresses\"))\n-        d.addCallback(lambda _: self.wallet.wait_until_synchronized(lambda _: None))\n-        d.addCallback(lambda _: log.info(\"Synchronized wallet\"))\n-        d.addCallback(lambda _: self.get_cmd_runner())\n-        d.addCallbacks(lambda _: log.info(\"Set up lbryum command runner\"))\n-        return d\n-\n-    def _stop(self):\n-        if self._start_check is not None:\n-            self._start_check.stop()\n-            self._start_check = None\n-\n-        if self._catch_up_check is not None:\n-            if self._catch_up_check.running:\n-                self._catch_up_check.stop()\n-            self._catch_up_check = None\n-\n-        d = defer.Deferred()\n-\n-        def check_stopped():\n-            if self.network:\n-                if self.network.is_connected():\n-                    return False\n-            stop_check.stop()\n-            self.network = None\n-            d.callback(True)\n-\n-        if self.wallet:\n-            self.wallet.stop_threads()\n-            log.info(\"Stopped wallet\")\n-        if self.network:\n-            self.network.stop()\n-            log.info(\"Stopped connection to lbryum server\")\n-\n-        stop_check = task.LoopingCall(check_stopped)\n-        stop_check.start(.1)\n-        return d\n-\n-    def _load_wallet(self):\n-        path = self.config.get_wallet_path()\n-        storage = lbryum_wallet.WalletStorage(path)\n-        wallet = lbryum_wallet.Wallet(storage)\n-        if not storage.file_exists:\n-            self.is_first_run = True\n-            seed = wallet.make_seed()\n-            wallet.add_seed(seed, None)\n-            wallet.create_master_keys(None)\n-            wallet.create_main_account()\n-            wallet.synchronize()\n-        self.wallet = wallet\n-        self.is_wallet_unlocked = not self.wallet.use_encryption\n-        self._check_large_wallet()\n-        return defer.succeed(True)\n-\n-    def _check_large_wallet(self):\n-        addr_count = len(self.wallet.addresses(include_change=False))\n-        if addr_count > 1000:\n-            log.warning(\"Your wallet is excessively large (%i addresses), \"","path":"lbrynet/core/Wallet.py","position":960,"original_position":960,"commit_id":"5099e19f424f34640131550009d1a86a4c776c74","original_commit_id":"5099e19f424f34640131550009d1a86a4c776c74","user":{"login":"eukreign","id":69960,"node_id":"MDQ6VXNlcjY5OTYw","avatar_url":"https://avatars1.githubusercontent.com/u/69960?v=4","gravatar_id":"","url":"https://api.github.com/users/eukreign","html_url":"https://github.com/eukreign","followers_url":"https://api.github.com/users/eukreign/followers","following_url":"https://api.github.com/users/eukreign/following{/other_user}","gists_url":"https://api.github.com/users/eukreign/gists{/gist_id}","starred_url":"https://api.github.com/users/eukreign/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eukreign/subscriptions","organizations_url":"https://api.github.com/users/eukreign/orgs","repos_url":"https://api.github.com/users/eukreign/repos","events_url":"https://api.github.com/users/eukreign/events{/privacy}","received_events_url":"https://api.github.com/users/eukreign/received_events","type":"User","site_admin":false},"body":"You are commenting on a file that was deleted.","created_at":"2018-08-21T15:44:42Z","updated_at":"2018-08-21T15:44:42Z","html_url":"https://github.com/lbryio/lbry/pull/1155#discussion_r211657260","pull_request_url":"https://api.github.com/repos/lbryio/lbry/pulls/1155","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/comments/211657260"},"html":{"href":"https://github.com/lbryio/lbry/pull/1155#discussion_r211657260"},"pull_request":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/1155"}},"in_reply_to_id":211598018},"pull_request":{"url":"https://api.github.com/repos/lbryio/lbry/pulls/1155","id":177332688,"node_id":"MDExOlB1bGxSZXF1ZXN0MTc3MzMyNjg4","html_url":"https://github.com/lbryio/lbry/pull/1155","diff_url":"https://github.com/lbryio/lbry/pull/1155.diff","patch_url":"https://github.com/lbryio/lbry/pull/1155.patch","issue_url":"https://api.github.com/repos/lbryio/lbry/issues/1155","number":1155,"state":"open","locked":false,"title":"Twisted Based Wallet Implementation","user":{"login":"eukreign","id":69960,"node_id":"MDQ6VXNlcjY5OTYw","avatar_url":"https://avatars1.githubusercontent.com/u/69960?v=4","gravatar_id":"","url":"https://api.github.com/users/eukreign","html_url":"https://github.com/eukreign","followers_url":"https://api.github.com/users/eukreign/followers","following_url":"https://api.github.com/users/eukreign/following{/other_user}","gists_url":"https://api.github.com/users/eukreign/gists{/gist_id}","starred_url":"https://api.github.com/users/eukreign/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eukreign/subscriptions","organizations_url":"https://api.github.com/users/eukreign/orgs","repos_url":"https://api.github.com/users/eukreign/repos","events_url":"https://api.github.com/users/eukreign/events{/privacy}","received_events_url":"https://api.github.com/users/eukreign/received_events","type":"User","site_admin":false},"body":"### For Reviewers\r\n\r\nThis PR is split across three repositories,\r\n1) a brand new project called [torba](https://github.com/lbryio/torba) which is a generic implementation of bitcoin based wallets (integration tests run against bitcoinsegwit and eventually bitcoincash and others);\r\n2) the custom LBRY extensions ontop of torba are part of this PR and found in [lbrynet/wallet](https://github.com/lbryio/lbry/tree/lbryum-refactor/lbrynet/wallet);\r\n3) [orchstr8](https://github.com/lbryio/orchstr8) integration testing framework that orchestrates starting the blockchain binary (`-regtest`), electrumx (lbryumx) and the torba (lbrynet.wallet) and facilitating their interaction in a controlled environment.\r\n\r\nThis is a very major change to the daemon and should be evaluated carefully for security, performance and reliability before making it into a release. Furthermore, this PR also ports lbrynet to Python 3 and is no longer compatible with Python 2. **(see explanation at end)\r\n\r\nHere are the main points of interest that should be reviewed:\r\n\r\n### Coin (UTXO) Selection Algorithm\r\n\r\nTorba implements the same coin selection algorithm as latest C++ Bitcoin. Please review the algorithm and tests for completeness and correctness:\r\n\r\nTorba (impl): https://github.com/lbryio/torba/blob/master/torba/coinselection.py\r\nBitcoin (impl): https://github.com/bitcoin/bitcoin/blob/master/src/wallet/coinselection.cpp\r\n\r\nTorba (tests): https://github.com/lbryio/torba/blob/master/tests/unit/test_coinselection.py\r\nBitcoin (tests): https://github.com/bitcoin/bitcoin/blob/master/src/wallet/test/coinselector_tests.cpp\r\n\r\nWhitepaper: https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf\r\n\r\n### SQLite Storage\r\n\r\nOld wallet stored things either in JSON in the wallet file or in memory in Python dictoinaries and lists when running. This has been completely replaced by an SQLite storage solution. Please review the base torba DDL and then the LBRY extension adding extra claim related attributes to the `txo` table:\r\n\r\nTorba: https://github.com/lbryio/torba/blob/master/torba/basedatabase.py\r\nLBRY: https://github.com/lbryio/lbry/blob/lbryum-refactor/lbrynet/wallet/database.py\r\n\r\nGetting the DDL right from the beginning is a good idea, because updating code is cheap but updating DDL is time consuming for us (writing migrations) and the users (waiting for migrations to finish running).\r\n\r\n### Initial Sync & Subsequent Starts\r\n\r\nFaster wallet operation is the main reason for this PR; so it's important that the header and TX synchronization code paths fast and correct. Please review the following areas:\r\n\r\nhttps://github.com/lbryio/torba/blob/d4107600a5882fd9d8ccd13a0d3694c96d2d7c05/torba/baseledger.py#L226-L249\r\n`update_headers` is called on startup (or if you were offline for too long and have more than one header to catchup) while `process_header` is called on regular sequential updates from electrumx (but switched to `update_headers` if it determines we're behind more than one).\r\n\r\nhttps://github.com/lbryio/torba/blob/d4107600a5882fd9d8ccd13a0d3694c96d2d7c05/torba/baseledger.py#L251-L331\r\n`update_account` is called on startup to catchup with blockchain or recreate wallet from scratch, this is done by generating addresses, sync'ing them, then generating more addresses, sync'ing them, etc, this repeats until all addresses have been generated (stopping when address gap is reached).\r\nafter the initial startup most of the normal process is between `process_status` which receives events from electrumx and calls `update_history` which then proceeds to process the history for an address.\r\none complication to keep in mind with `update_history` is that electrumx will send duplicate updates for the same TX if it involves two of your addresses (common case when you pay and have change, or when you pay yourself, etc), it's important to avoid race conditions in those cases (done with `self._transaction_processing_locks`).\r\n\r\n### Simple Payment Verification (SPV)\r\n\r\nTorba is called an SPV wallet because it verifies that TXs are in the blockchain without having a copy of the blockchain. This verification is predicated on having correct headers (or at least the headers and the TX should come from different parties that were unlikely to have conspired with each other).\r\n\r\nSPV works by organizing TXs in a block into merkle trees, then headers contain the root hash of this merkle tree and you can separately, for each TX, request all of the hashes between your TX and the root hash. Then you just traverse up the tree from your TX hashing every two complementary nodes until you reach the root hash, if the TX is valid the root hash in the header should mach the root hash you calculated. Done.\r\n\r\nHere is the implementation, please verify for correctness:\r\nhttps://github.com/lbryio/torba/blob/d4107600a5882fd9d8ccd13a0d3694c96d2d7c05/torba/baseledger.py#L189-L198\r\n\r\nThis feature is currently covered by integration tests and does not have dedicated unit tests.\r\n\r\n### Bitcoin Script\r\n\r\nTorba implements a general purpose extensible bitcoin script parser. LBRY extends this to add our 3 custom op codes.\r\n\r\nTorba (impl): https://github.com/lbryio/torba/blob/master/torba/basescript.py\r\nLBRY (impl): https://github.com/lbryio/lbry/blob/lbryum-refactor/lbrynet/wallet/script.py\r\n\r\nTorba (tests): https://github.com/lbryio/torba/blob/master/tests/unit/test_script.py\r\nLBRY (tests): https://github.com/lbryio/lbry/blob/lbryum-refactor/tests/unit/wallet/test_script.py\r\n\r\n### Creating Transactions\r\n\r\nCan't forget this one, the whole point we're all here :-D\r\n\r\nThere is a special `create` constructor on `Transaction` class that will accept any inputs/outputs and then try to balance the two sides of the equation until it is correct (adding necessary inputs/outputs and adding change for excessive inputs). On `lbrynet` side there are convenient wrappers around it to make it even easier to create LBRY specific claim transactions.\r\n\r\nTorba: https://github.com/lbryio/torba/blob/d4107600a5882fd9d8ccd13a0d3694c96d2d7c05/torba/basetransaction.py#L380-L428\r\nLBRY: https://github.com/lbryio/lbry/blob/lbryum-refactor/lbrynet/wallet/transaction.py\r\n\r\n### All The Things\r\n\r\nAbove is a list of some of the critical aspects that should get some review before we go to releases. There is a lot more code than outlined above, if you feel motivated please review all of it and provide feedback. I would greatly appreciate it if people find issues that we can fix now before the code goes live! Thanks!\r\n\r\nEssentially this means all of `torba`: https://github.com/lbryio/torba\r\nEverything in `lbrynet.wallet` directory: https://github.com/lbryio/lbry/tree/lbryum-refactor/lbrynet/wallet\r\n\r\n### Try Me\r\n\r\nYou can downoad automated builds of the new daemon here:\r\nhttp://build.lbry.io/daemon/\r\n\r\n\r\n** Python 3 port was done because 1) https://pythonclock.org  2) torba's testing framework [orchstr8](https://github.com/lbryio/orchstr8) loads electrumx ([lbryumx](https://github.com/lbryio/lbryumx)) and the wallet into a single Python process using the same asyncio reactor which makes debugging very nice (when you hit breakpoint, both server and client freeze), electrumx is Python 3 only, therefore torba had to straddle Python2/Python3 support to work in both `orchstr8` and in `lbrynet`, keeping this compatibility was a real time sink 3) Python 3 is significantly faster (thus new daemon should have perceptible speed increase) and stricter / failing faster (therefore we should hit bugs earlier in testing).\r\n\r\n---\r\n\r\n### Progress\r\n\r\nThis is a pure twisted wallet implementation for lbry providing increased stability and performance; completely replaces `lbryum`. Still a work in progress, **do not merge**.\r\n\r\nDepends on this pull request in lbryum-server: https://github.com/lbryio/lbryum-server/pull/64\r\n\r\nEach leaf task below is 5pt (about a day of work).\r\n\r\nCore Functionality I1\r\n\r\n- [X] groking existing thread/plain socket based architecture, considering how to refactor it\r\n  - [X] lbry portion\r\n  - [X] lbryum portion\r\n- [x] header sync\r\n  - [x] basic implementation / initial pass\r\n  - [x] match current implementation + integration and/or unit tests (whichever ones don't get done as part of TDD)\r\n- [x] subscribe to get address notifications\r\n- [X] creating transactions\r\n  - [x] refactor Transaction class\r\n  - [x] round trip-able script parser / generator\r\n    - [x] input scripts (pub key, script hash / multi-sig)\r\n    - [x] output scripts (pub key, scripts hash) x (claim name, claim support, claim update)\r\n  - [x] refactor Transaction class II\r\n    - [x] use new script parser / generator\r\n    - [x] coin chooser\r\n    - [x] signing (single-sig and multi-sig)\r\n- [x] move any code still being used from `lbryum` into `lbrynet/wallet`\r\n\r\nCore Functionality I2\r\n- [x] expose APIs to create/update/support name claims\r\n- [x] use SQLiteStorage for storing transaction history\r\n- [x] base wallet as separate module\r\n- [x] studying https://cardanodocs.com/technical/formal-specification-for-a-cardano-wallet/\r\n\r\nCore Functionality I3\r\n- [x] fix orchstr8 to run with lbryumx\r\n- [x] release generic parts of wallet as https://github.com/lbryio/torba\r\n- [x] add regtest support to lbry-app (filed issue and provided branch with problematic code, https://github.com/lbryio/lbry-app/issues/1526)\r\n- [x] works with lbry-app (ALPHA release, pending lbrycrd and lbry-app regtest support)\r\n\r\nSeparate Issues Created After I3\r\n- [x] store almost everything in SQLite (https://github.com/lbryio/lbry/issues/1242)\r\n- [x] SPV (https://github.com/lbryio/lbry/issues/1221)\r\n- [x] save and use channel certificates (https://github.com/lbryio/lbry/issues/1223)\r\n- [x] locking of addresses being used in transaction until the tx is written to block chain (https://github.com/lbryio/lbry/issues/1222)\r\n- [ ] migrating wallets (https://github.com/lbryio/lbry/issues/1224)\r\n- [x] verify lbryumx responses (https://github.com/lbryio/lbry/issues/1272)\r\n- [ ] plain API calls (such as resolve) (https://github.com/lbryio/lbry/issues/1220)\r\n- [ ] performance test cases (https://github.com/lbryio/lbry/issues/1219)\r\n- [ ] regression tests:\r\n  - [ ] Allow update of claims even if they don't exist in file_list (https://github.com/lbryio/lbry/issues/1165)\r\n  - [ ] UTXOs remain stuck unconfirmed (https://github.com/lbryio/lbry/issues/1192)","created_at":"2018-03-26T03:05:42Z","updated_at":"2018-08-21T15:44:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5b95b2d18bfb418af5ee449dd30991d7d4210dad","assignee":{"login":"roylee17","id":97372,"node_id":"MDQ6VXNlcjk3Mzcy","avatar_url":"https://avatars1.githubusercontent.com/u/97372?v=4","gravatar_id":"","url":"https://api.github.com/users/roylee17","html_url":"https://github.com/roylee17","followers_url":"https://api.github.com/users/roylee17/followers","following_url":"https://api.github.com/users/roylee17/following{/other_user}","gists_url":"https://api.github.com/users/roylee17/gists{/gist_id}","starred_url":"https://api.github.com/users/roylee17/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/roylee17/subscriptions","organizations_url":"https://api.github.com/users/roylee17/orgs","repos_url":"https://api.github.com/users/roylee17/repos","events_url":"https://api.github.com/users/roylee17/events{/privacy}","received_events_url":"https://api.github.com/users/roylee17/received_events","type":"User","site_admin":false},"assignees":[{"login":"roylee17","id":97372,"node_id":"MDQ6VXNlcjk3Mzcy","avatar_url":"https://avatars1.githubusercontent.com/u/97372?v=4","gravatar_id":"","url":"https://api.github.com/users/roylee17","html_url":"https://github.com/roylee17","followers_url":"https://api.github.com/users/roylee17/followers","following_url":"https://api.github.com/users/roylee17/following{/other_user}","gists_url":"https://api.github.com/users/roylee17/gists{/gist_id}","starred_url":"https://api.github.com/users/roylee17/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/roylee17/subscriptions","organizations_url":"https://api.github.com/users/roylee17/orgs","repos_url":"https://api.github.com/users/roylee17/repos","events_url":"https://api.github.com/users/roylee17/events{/privacy}","received_events_url":"https://api.github.com/users/roylee17/received_events","type":"User","site_admin":false},{"login":"akinwale","id":4160319,"node_id":"MDQ6VXNlcjQxNjAzMTk=","avatar_url":"https://avatars0.githubusercontent.com/u/4160319?v=4","gravatar_id":"","url":"https://api.github.com/users/akinwale","html_url":"https://github.com/akinwale","followers_url":"https://api.github.com/users/akinwale/followers","following_url":"https://api.github.com/users/akinwale/following{/other_user}","gists_url":"https://api.github.com/users/akinwale/gists{/gist_id}","starred_url":"https://api.github.com/users/akinwale/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akinwale/subscriptions","organizations_url":"https://api.github.com/users/akinwale/orgs","repos_url":"https://api.github.com/users/akinwale/repos","events_url":"https://api.github.com/users/akinwale/events{/privacy}","received_events_url":"https://api.github.com/users/akinwale/received_events","type":"User","site_admin":false},{"login":"kaykurokawa","id":7075051,"node_id":"MDQ6VXNlcjcwNzUwNTE=","avatar_url":"https://avatars2.githubusercontent.com/u/7075051?v=4","gravatar_id":"","url":"https://api.github.com/users/kaykurokawa","html_url":"https://github.com/kaykurokawa","followers_url":"https://api.github.com/users/kaykurokawa/followers","following_url":"https://api.github.com/users/kaykurokawa/following{/other_user}","gists_url":"https://api.github.com/users/kaykurokawa/gists{/gist_id}","starred_url":"https://api.github.com/users/kaykurokawa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaykurokawa/subscriptions","organizations_url":"https://api.github.com/users/kaykurokawa/orgs","repos_url":"https://api.github.com/users/kaykurokawa/repos","events_url":"https://api.github.com/users/kaykurokawa/events{/privacy}","received_events_url":"https://api.github.com/users/kaykurokawa/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"roylee17","id":97372,"node_id":"MDQ6VXNlcjk3Mzcy","avatar_url":"https://avatars1.githubusercontent.com/u/97372?v=4","gravatar_id":"","url":"https://api.github.com/users/roylee17","html_url":"https://github.com/roylee17","followers_url":"https://api.github.com/users/roylee17/followers","following_url":"https://api.github.com/users/roylee17/following{/other_user}","gists_url":"https://api.github.com/users/roylee17/gists{/gist_id}","starred_url":"https://api.github.com/users/roylee17/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/roylee17/subscriptions","organizations_url":"https://api.github.com/users/roylee17/orgs","repos_url":"https://api.github.com/users/roylee17/repos","events_url":"https://api.github.com/users/roylee17/events{/privacy}","received_events_url":"https://api.github.com/users/roylee17/received_events","type":"User","site_admin":false},{"login":"akinwale","id":4160319,"node_id":"MDQ6VXNlcjQxNjAzMTk=","avatar_url":"https://avatars0.githubusercontent.com/u/4160319?v=4","gravatar_id":"","url":"https://api.github.com/users/akinwale","html_url":"https://github.com/akinwale","followers_url":"https://api.github.com/users/akinwale/followers","following_url":"https://api.github.com/users/akinwale/following{/other_user}","gists_url":"https://api.github.com/users/akinwale/gists{/gist_id}","starred_url":"https://api.github.com/users/akinwale/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akinwale/subscriptions","organizations_url":"https://api.github.com/users/akinwale/orgs","repos_url":"https://api.github.com/users/akinwale/repos","events_url":"https://api.github.com/users/akinwale/events{/privacy}","received_events_url":"https://api.github.com/users/akinwale/received_events","type":"User","site_admin":false},{"login":"kaykurokawa","id":7075051,"node_id":"MDQ6VXNlcjcwNzUwNTE=","avatar_url":"https://avatars2.githubusercontent.com/u/7075051?v=4","gravatar_id":"","url":"https://api.github.com/users/kaykurokawa","html_url":"https://github.com/kaykurokawa","followers_url":"https://api.github.com/users/kaykurokawa/followers","following_url":"https://api.github.com/users/kaykurokawa/following{/other_user}","gists_url":"https://api.github.com/users/kaykurokawa/gists{/gist_id}","starred_url":"https://api.github.com/users/kaykurokawa/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaykurokawa/subscriptions","organizations_url":"https://api.github.com/users/kaykurokawa/orgs","repos_url":"https://api.github.com/users/kaykurokawa/repos","events_url":"https://api.github.com/users/kaykurokawa/events{/privacy}","received_events_url":"https://api.github.com/users/kaykurokawa/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[{"id":649017603,"node_id":"MDU6TGFiZWw2NDkwMTc2MDM=","url":"https://api.github.com/repos/lbryio/lbry/labels/Epic","name":"Epic","color":"d1d0cf","default":false},{"id":799785446,"node_id":"MDU6TGFiZWw3OTk3ODU0NDY=","url":"https://api.github.com/repos/lbryio/lbry/labels/area:%20network","name":"area: network","color":"fcd8d1","default":false},{"id":799785267,"node_id":"MDU6TGFiZWw3OTk3ODUyNjc=","url":"https://api.github.com/repos/lbryio/lbry/labels/area:%20wallet","name":"area: wallet","color":"d4edd0","default":false},{"id":637716390,"node_id":"MDU6TGFiZWw2Mzc3MTYzOTA=","url":"https://api.github.com/repos/lbryio/lbry/labels/core%20team","name":"core team","color":"fbca04","default":false},{"id":799744340,"node_id":"MDU6TGFiZWw3OTk3NDQzNDA=","url":"https://api.github.com/repos/lbryio/lbry/labels/level:%204","name":"level: 4","color":"4673B9","default":false},{"id":250584511,"node_id":"MDU6TGFiZWwyNTA1ODQ1MTE=","url":"https://api.github.com/repos/lbryio/lbry/labels/type:%20improvement","name":"type: improvement","color":"0094ff","default":false},{"id":789885709,"node_id":"MDU6TGFiZWw3ODk4ODU3MDk=","url":"https://api.github.com/repos/lbryio/lbry/labels/type:%20refactor","name":"type: refactor","color":"6638ff","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/lbryio/lbry/pulls/1155/commits","review_comments_url":"https://api.github.com/repos/lbryio/lbry/pulls/1155/comments","review_comment_url":"https://api.github.com/repos/lbryio/lbry/pulls/comments{/number}","comments_url":"https://api.github.com/repos/lbryio/lbry/issues/1155/comments","statuses_url":"https://api.github.com/repos/lbryio/lbry/statuses/5099e19f424f34640131550009d1a86a4c776c74","head":{"label":"lbryio:lbryum-refactor","ref":"lbryum-refactor","sha":"5099e19f424f34640131550009d1a86a4c776c74","user":{"login":"lbryio","id":13889222,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODg5MjIy","avatar_url":"https://avatars3.githubusercontent.com/u/13889222?v=4","gravatar_id":"","url":"https://api.github.com/users/lbryio","html_url":"https://github.com/lbryio","followers_url":"https://api.github.com/users/lbryio/followers","following_url":"https://api.github.com/users/lbryio/following{/other_user}","gists_url":"https://api.github.com/users/lbryio/gists{/gist_id}","starred_url":"https://api.github.com/users/lbryio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbryio/subscriptions","organizations_url":"https://api.github.com/users/lbryio/orgs","repos_url":"https://api.github.com/users/lbryio/repos","events_url":"https://api.github.com/users/lbryio/events{/privacy}","received_events_url":"https://api.github.com/users/lbryio/received_events","type":"Organization","site_admin":false},"repo":{"id":41103286,"node_id":"MDEwOlJlcG9zaXRvcnk0MTEwMzI4Ng==","name":"lbry","full_name":"lbryio/lbry","owner":{"login":"lbryio","id":13889222,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODg5MjIy","avatar_url":"https://avatars3.githubusercontent.com/u/13889222?v=4","gravatar_id":"","url":"https://api.github.com/users/lbryio","html_url":"https://github.com/lbryio","followers_url":"https://api.github.com/users/lbryio/followers","following_url":"https://api.github.com/users/lbryio/following{/other_user}","gists_url":"https://api.github.com/users/lbryio/gists{/gist_id}","starred_url":"https://api.github.com/users/lbryio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbryio/subscriptions","organizations_url":"https://api.github.com/users/lbryio/orgs","repos_url":"https://api.github.com/users/lbryio/repos","events_url":"https://api.github.com/users/lbryio/events{/privacy}","received_events_url":"https://api.github.com/users/lbryio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lbryio/lbry","description":"A fully decentralized network for distributing data","fork":false,"url":"https://api.github.com/repos/lbryio/lbry","forks_url":"https://api.github.com/repos/lbryio/lbry/forks","keys_url":"https://api.github.com/repos/lbryio/lbry/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lbryio/lbry/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lbryio/lbry/teams","hooks_url":"https://api.github.com/repos/lbryio/lbry/hooks","issue_events_url":"https://api.github.com/repos/lbryio/lbry/issues/events{/number}","events_url":"https://api.github.com/repos/lbryio/lbry/events","assignees_url":"https://api.github.com/repos/lbryio/lbry/assignees{/user}","branches_url":"https://api.github.com/repos/lbryio/lbry/branches{/branch}","tags_url":"https://api.github.com/repos/lbryio/lbry/tags","blobs_url":"https://api.github.com/repos/lbryio/lbry/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lbryio/lbry/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lbryio/lbry/git/refs{/sha}","trees_url":"https://api.github.com/repos/lbryio/lbry/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lbryio/lbry/statuses/{sha}","languages_url":"https://api.github.com/repos/lbryio/lbry/languages","stargazers_url":"https://api.github.com/repos/lbryio/lbry/stargazers","contributors_url":"https://api.github.com/repos/lbryio/lbry/contributors","subscribers_url":"https://api.github.com/repos/lbryio/lbry/subscribers","subscription_url":"https://api.github.com/repos/lbryio/lbry/subscription","commits_url":"https://api.github.com/repos/lbryio/lbry/commits{/sha}","git_commits_url":"https://api.github.com/repos/lbryio/lbry/git/commits{/sha}","comments_url":"https://api.github.com/repos/lbryio/lbry/comments{/number}","issue_comment_url":"https://api.github.com/repos/lbryio/lbry/issues/comments{/number}","contents_url":"https://api.github.com/repos/lbryio/lbry/contents/{+path}","compare_url":"https://api.github.com/repos/lbryio/lbry/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lbryio/lbry/merges","archive_url":"https://api.github.com/repos/lbryio/lbry/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lbryio/lbry/downloads","issues_url":"https://api.github.com/repos/lbryio/lbry/issues{/number}","pulls_url":"https://api.github.com/repos/lbryio/lbry/pulls{/number}","milestones_url":"https://api.github.com/repos/lbryio/lbry/milestones{/number}","notifications_url":"https://api.github.com/repos/lbryio/lbry/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lbryio/lbry/labels{/name}","releases_url":"https://api.github.com/repos/lbryio/lbry/releases{/id}","deployments_url":"https://api.github.com/repos/lbryio/lbry/deployments","created_at":"2015-08-20T15:24:10Z","updated_at":"2018-08-21T07:45:16Z","pushed_at":"2018-08-20T20:55:00Z","git_url":"git://github.com/lbryio/lbry.git","ssh_url":"git@github.com:lbryio/lbry.git","clone_url":"https://github.com/lbryio/lbry.git","svn_url":"https://github.com/lbryio/lbry","homepage":"https://lbry.io","size":9401,"stargazers_count":4016,"watchers_count":4016,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":241,"mirror_url":null,"archived":false,"open_issues_count":141,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":241,"open_issues":141,"watchers":4016,"default_branch":"master"}},"base":{"label":"lbryio:master","ref":"master","sha":"d4a7dbf80576b3d354198496d966f36ca1d0b78a","user":{"login":"lbryio","id":13889222,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODg5MjIy","avatar_url":"https://avatars3.githubusercontent.com/u/13889222?v=4","gravatar_id":"","url":"https://api.github.com/users/lbryio","html_url":"https://github.com/lbryio","followers_url":"https://api.github.com/users/lbryio/followers","following_url":"https://api.github.com/users/lbryio/following{/other_user}","gists_url":"https://api.github.com/users/lbryio/gists{/gist_id}","starred_url":"https://api.github.com/users/lbryio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbryio/subscriptions","organizations_url":"https://api.github.com/users/lbryio/orgs","repos_url":"https://api.github.com/users/lbryio/repos","events_url":"https://api.github.com/users/lbryio/events{/privacy}","received_events_url":"https://api.github.com/users/lbryio/received_events","type":"Organization","site_admin":false},"repo":{"id":41103286,"node_id":"MDEwOlJlcG9zaXRvcnk0MTEwMzI4Ng==","name":"lbry","full_name":"lbryio/lbry","owner":{"login":"lbryio","id":13889222,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEzODg5MjIy","avatar_url":"https://avatars3.githubusercontent.com/u/13889222?v=4","gravatar_id":"","url":"https://api.github.com/users/lbryio","html_url":"https://github.com/lbryio","followers_url":"https://api.github.com/users/lbryio/followers","following_url":"https://api.github.com/users/lbryio/following{/other_user}","gists_url":"https://api.github.com/users/lbryio/gists{/gist_id}","starred_url":"https://api.github.com/users/lbryio/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lbryio/subscriptions","organizations_url":"https://api.github.com/users/lbryio/orgs","repos_url":"https://api.github.com/users/lbryio/repos","events_url":"https://api.github.com/users/lbryio/events{/privacy}","received_events_url":"https://api.github.com/users/lbryio/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/lbryio/lbry","description":"A fully decentralized network for distributing data","fork":false,"url":"https://api.github.com/repos/lbryio/lbry","forks_url":"https://api.github.com/repos/lbryio/lbry/forks","keys_url":"https://api.github.com/repos/lbryio/lbry/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lbryio/lbry/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lbryio/lbry/teams","hooks_url":"https://api.github.com/repos/lbryio/lbry/hooks","issue_events_url":"https://api.github.com/repos/lbryio/lbry/issues/events{/number}","events_url":"https://api.github.com/repos/lbryio/lbry/events","assignees_url":"https://api.github.com/repos/lbryio/lbry/assignees{/user}","branches_url":"https://api.github.com/repos/lbryio/lbry/branches{/branch}","tags_url":"https://api.github.com/repos/lbryio/lbry/tags","blobs_url":"https://api.github.com/repos/lbryio/lbry/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lbryio/lbry/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lbryio/lbry/git/refs{/sha}","trees_url":"https://api.github.com/repos/lbryio/lbry/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lbryio/lbry/statuses/{sha}","languages_url":"https://api.github.com/repos/lbryio/lbry/languages","stargazers_url":"https://api.github.com/repos/lbryio/lbry/stargazers","contributors_url":"https://api.github.com/repos/lbryio/lbry/contributors","subscribers_url":"https://api.github.com/repos/lbryio/lbry/subscribers","subscription_url":"https://api.github.com/repos/lbryio/lbry/subscription","commits_url":"https://api.github.com/repos/lbryio/lbry/commits{/sha}","git_commits_url":"https://api.github.com/repos/lbryio/lbry/git/commits{/sha}","comments_url":"https://api.github.com/repos/lbryio/lbry/comments{/number}","issue_comment_url":"https://api.github.com/repos/lbryio/lbry/issues/comments{/number}","contents_url":"https://api.github.com/repos/lbryio/lbry/contents/{+path}","compare_url":"https://api.github.com/repos/lbryio/lbry/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lbryio/lbry/merges","archive_url":"https://api.github.com/repos/lbryio/lbry/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lbryio/lbry/downloads","issues_url":"https://api.github.com/repos/lbryio/lbry/issues{/number}","pulls_url":"https://api.github.com/repos/lbryio/lbry/pulls{/number}","milestones_url":"https://api.github.com/repos/lbryio/lbry/milestones{/number}","notifications_url":"https://api.github.com/repos/lbryio/lbry/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lbryio/lbry/labels{/name}","releases_url":"https://api.github.com/repos/lbryio/lbry/releases{/id}","deployments_url":"https://api.github.com/repos/lbryio/lbry/deployments","created_at":"2015-08-20T15:24:10Z","updated_at":"2018-08-21T07:45:16Z","pushed_at":"2018-08-20T20:55:00Z","git_url":"git://github.com/lbryio/lbry.git","ssh_url":"git@github.com:lbryio/lbry.git","clone_url":"https://github.com/lbryio/lbry.git","svn_url":"https://github.com/lbryio/lbry","homepage":"https://lbry.io","size":9401,"stargazers_count":4016,"watchers_count":4016,"language":"Python","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":241,"mirror_url":null,"archived":false,"open_issues_count":141,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":241,"open_issues":141,"watchers":4016,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/1155"},"html":{"href":"https://github.com/lbryio/lbry/pull/1155"},"issue":{"href":"https://api.github.com/repos/lbryio/lbry/issues/1155"},"comments":{"href":"https://api.github.com/repos/lbryio/lbry/issues/1155/comments"},"review_comments":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/1155/comments"},"review_comment":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/lbryio/lbry/pulls/1155/commits"},"statuses":{"href":"https://api.github.com/repos/lbryio/lbry/statuses/5099e19f424f34640131550009d1a86a4c776c74"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"eukreign"}}