Пример #1
0
class LBRYumWallet(Wallet):

    def __init__(self, db_dir, config=None):
        Wallet.__init__(self, db_dir)
        self._config = config
        self.network = None
        self.wallet = None
        self.cmd_runner = None
        self.first_run = False
        self.printed_retrieving_headers = False
        self._start_check = None
        self._catch_up_check = None
        self._caught_up_counter = 0
        self._lag_counter = 0
        self.blocks_behind_alert = 0
        self.catchup_progress = 0
        self.max_behind = 0

    def _start(self):
        network_start_d = defer.Deferred()

        def setup_network():
            self.config = make_config(self._config)
            self.network = Network(self.config)
            alert.info("Loading the wallet...")
            return defer.succeed(self.network.start())


        d = setup_network()

        def check_started():
            if self.network.is_connecting():
                if not self.printed_retrieving_headers and self.network.blockchain.retrieving_headers:
                    alert.info("Running the wallet for the first time...this may take a moment.")
                    self.printed_retrieving_headers = True
                return False
            self._start_check.stop()
            self._start_check = None
            if self.network.is_connected():
                network_start_d.callback(True)
            else:
                network_start_d.errback(ValueError("Failed to connect to network."))

        self._start_check = task.LoopingCall(check_started)

        d.addCallback(lambda _: self._start_check.start(.1))
        d.addCallback(lambda _: network_start_d)
        d.addCallback(lambda _: self._load_wallet())
        d.addCallback(lambda _: self._get_cmd_runner())
        return d

    def _stop(self):
        if self._start_check is not None:
            self._start_check.stop()
            self._start_check = None

        if self._catch_up_check is not None:
            if self._catch_up_check.running:
                self._catch_up_check.stop()
            self._catch_up_check = None

        d = defer.Deferred()

        def check_stopped():
            if self.network:
                if self.network.is_connected():
                    return False
            stop_check.stop()
            self.network = None
            d.callback(True)

        if self.network:
            self.network.stop()

        stop_check = task.LoopingCall(check_stopped)
        stop_check.start(.1)
        return d

    def _load_wallet(self):

        def get_wallet():
            path = self.config.get_wallet_path()
            storage = lbryum.wallet.WalletStorage(path)
            wallet = lbryum.wallet.Wallet(storage)
            if not storage.file_exists:
                self.first_run = True
                seed = wallet.make_seed()
                wallet.add_seed(seed, None)
                wallet.create_master_keys(None)
                wallet.create_main_account()
                wallet.synchronize()
            self.wallet = wallet
            return defer.succeed(True)

        blockchain_caught_d = defer.Deferred()

        def check_caught_up():
            local_height = self.network.get_catchup_progress()
            remote_height = self.network.get_server_height()

            if remote_height != 0 and remote_height - local_height <= 5:
                msg = ""
                if self._caught_up_counter != 0:
                    msg += "All caught up. "
                msg += "Wallet loaded."
                alert.info(msg)
                self._catch_up_check.stop()
                self._catch_up_check = None
                blockchain_caught_d.callback(True)

            elif remote_height != 0:
                past_blocks_behind = self.blocks_behind_alert
                self.blocks_behind_alert = remote_height - local_height
                if self.blocks_behind_alert < past_blocks_behind:
                    self._lag_counter = 0
                    self.is_lagging = False
                else:
                    self._lag_counter += 1
                    if self._lag_counter >= 900:
                        self.is_lagging = True

                if self.blocks_behind_alert > self.max_behind:
                    self.max_behind = self.blocks_behind_alert
                self.catchup_progress = int(100 * (self.blocks_behind_alert / (5 + self.max_behind)))
                if self._caught_up_counter == 0:
                    alert.info('Catching up with the blockchain...showing blocks left...')
                if self._caught_up_counter % 30 == 0:
                    alert.info('%d...', (remote_height - local_height))

                self._caught_up_counter += 1

        def log_error(err):
            log.warning(err.getErrorMessage())
            return defer.fail(err)

        self._catch_up_check = task.LoopingCall(check_caught_up)
        d = get_wallet()
        d.addCallback(self._save_wallet)
        d.addCallback(lambda _: self.wallet.start_threads(self.network))
        d.addCallback(lambda _: self._catch_up_check.start(.1))
        d.addErrback(log_error)
        d.addCallback(lambda _: blockchain_caught_d)
        return d

    def _get_cmd_runner(self):
        self.cmd_runner = Commands(self.config, self.wallet, self.network)

    # run commands as a defer.succeed,
    # lbryum commands should be run this way , unless if the command
    # only makes a lbrum server query, use _run_cmd_as_defer_to_thread()
    def _run_cmd_as_defer_succeed(self, command_name, *args):
        cmd = known_commands[command_name]
        func = getattr(self.cmd_runner, cmd.name)
        return defer.succeed(func(*args))


    # run commands as a deferToThread,  lbryum commands that only make
    # queries to lbryum server should be run this way
    def _run_cmd_as_defer_to_thread(self, command_name, *args):
        cmd = known_commands[command_name]
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, *args)

    def get_balance(self):
        accounts = None
        exclude_claimtrietx = True
        d = self._run_cmd_as_defer_succeed('getbalance', accounts, exclude_claimtrietx)
        d.addCallback(lambda result: Decimal(result['confirmed']) + Decimal(result.get('unconfirmed', 0.0)))
        return d

    def get_new_address(self):
        d = defer.succeed(self.wallet.create_new_address())
        d.addCallback(self._save_wallet)
        return d

    def get_block(self, blockhash):
        return self._run_cmd_as_defer_to_thread('getblock', blockhash)

    def get_most_recent_blocktime(self):
        header = self.network.get_header(self.network.get_local_height())
        return defer.succeed(header['timestamp'])

    def get_best_blockhash(self):
        height = self.network.get_local_height()
        header = self.network.blockchain.read_header(height)
        return defer.succeed(self.network.blockchain.hash_header(header))

    def _get_blockhash(self, height):
        header = self.network.blockchain.read_header(height)
        return defer.succeed(self.network.blockchain.hash_header(header))

    def get_name_claims(self):
        return self._run_cmd_as_defer_succeed('getnameclaims')

    def _check_first_run(self):
        return defer.succeed(self.first_run)

    def _get_claims_for_name(self, name):
        return self._run_cmd_as_defer_to_thread('getclaimsforname', name)

    def _send_name_claim(self, name, val, amount):
        broadcast = False
        log.debug("Name claim %s %s %f", name, val, amount)
        d = self._run_cmd_as_defer_succeed('claim', name, json.dumps(val), amount, broadcast)
        d.addCallback(lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _send_name_claim_update(self, name, claim_id, claim_outpoint, value, amount):
        metadata = json.dumps(value)
        log.debug("Update %s %d %f %s %s '%s'", claim_outpoint['txid'], claim_outpoint['nout'],
                                                     amount, name, claim_id, metadata)
        broadcast = False
        d = self._run_cmd_as_defer_succeed('update', claim_outpoint['txid'], claim_outpoint['nout'],
                                            name, claim_id, metadata, amount, broadcast)
        d.addCallback(lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _abandon_claim(self, claim_outpoint):
        log.debug("Abandon %s %s" % (claim_outpoint['txid'], claim_outpoint['nout']))
        broadcast = False
        d = self._run_cmd_as_defer_succeed('abandon', claim_outpoint['txid'], claim_outpoint['nout'], broadcast)
        d.addCallback(lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _support_claim(self, name, claim_id, amount):
        log.debug("Support %s %s %f" % (name, claim_id, amount))
        broadcast = False
        d = self._run_cmd_as_defer_succeed('support', name, claim_id, amount, broadcast)
        d.addCallback(lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _broadcast_claim_transaction(self, claim_out):
        if 'success' not in claim_out:
            raise Exception('Unexpected claim command output:{}'.format(claim_out))
        if claim_out['success']:
            d = self._broadcast_transaction(claim_out['tx'])
            d.addCallback(lambda _: claim_out)
            return d
        else:
            return defer.succeed(claim_out)

    def _broadcast_transaction(self, raw_tx):
        def _log_tx(r):
            log.debug("Broadcast tx: %s", r)
            return r
        d = self._run_cmd_as_defer_to_thread('broadcast', raw_tx)
        d.addCallback(_log_tx)
        d.addCallback(
            lambda r: r if len(r) == 64 else defer.fail(Exception("Transaction rejected")))
        return d

    def _do_send_many(self, payments_to_send):
        def broadcast_send_many(paytomany_out):
            if 'hex' not in paytomany_out:
                raise Exception('Unepxected paytomany output:{}'.format(paytomany_out))
            return self._broadcast_transaction(paytomany_out['hex'])
        log.debug("Doing send many. payments to send: %s", str(payments_to_send))
        d = self._run_cmd_as_defer_succeed('paytomany', payments_to_send.iteritems())
        d.addCallback(lambda out: broadcast_send_many(out))
        return d

    def _get_value_for_name(self, name):
        block_header = self.network.blockchain.read_header(self.network.get_local_height() - RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS)
        block_hash = self.network.blockchain.hash_header(block_header)
        d = self._run_cmd_as_defer_to_thread('requestvalueforname', name, block_hash)
        d.addCallback(lambda response: Commands._verify_proof(name, block_header['claim_trie_root'], response))
        return d

    def get_claims_from_tx(self, txid):
        return self._run_cmd_as_defer_to_thread('getclaimsfromtx', txid)

    def _get_balance_for_address(self, address):
        return defer.succeed(Decimal(self.wallet.get_addr_received(address))/COIN)

    def get_nametrie(self):
        return self._run_cmd_as_defer_to_thread('getclaimtrie')

    def _get_history(self):
        return self._run_cmd_as_defer_succeed('history')

    def _address_is_mine(self, address):
        return self._run_cmd_as_defer_succeed('ismine', address)

    def get_pub_keys(self, wallet):
        return self._run_cmd_as_defer_succeed('getpubkyes', wallet)

    def _save_wallet(self, val):
        self.wallet.storage.write()
        return defer.succeed(val)
Пример #2
0
class LBRYumWallet(Wallet):
    def __init__(self, storage, config=None):
        Wallet.__init__(self, storage)
        self._config = config
        self.network = None
        self.wallet = None
        self.is_first_run = False
        self.printed_retrieving_headers = False
        self._start_check = None
        self._catch_up_check = None
        self._caught_up_counter = 0
        self._lag_counter = 0
        self.blocks_behind = 0
        self.catchup_progress = 0
        self.max_behind = 0

    def _start(self):
        network_start_d = defer.Deferred()

        def setup_network():
            self.config = make_config(self._config)
            self.network = Network(self.config)
            alert.info("Loading the wallet")
            return defer.succeed(self.network.start())

        d = setup_network()

        def check_started():
            if self.network.is_connecting():
                if not self.printed_retrieving_headers and \
                        self.network.blockchain.retrieving_headers:
                    alert.info(
                        "Running the wallet for the first time. This may take a moment."
                    )
                    self.printed_retrieving_headers = True
                return False
            self._start_check.stop()
            self._start_check = None
            if self.network.is_connected():
                network_start_d.callback(True)
            else:
                network_start_d.errback(
                    ValueError("Failed to connect to network."))

        self._start_check = task.LoopingCall(check_started)

        d.addCallback(lambda _: self._load_wallet())
        d.addCallback(self._save_wallet)
        d.addCallback(lambda _: self._start_check.start(.1))
        d.addCallback(lambda _: network_start_d)
        d.addCallback(lambda _: self._load_blockchain())
        return d

    def _stop(self):
        if self._start_check is not None:
            self._start_check.stop()
            self._start_check = None

        if self._catch_up_check is not None:
            if self._catch_up_check.running:
                self._catch_up_check.stop()
            self._catch_up_check = None

        d = defer.Deferred()

        def check_stopped():
            if self.network:
                if self.network.is_connected():
                    return False
            stop_check.stop()
            self.network = None
            d.callback(True)

        if self.network:
            self.network.stop()

        stop_check = task.LoopingCall(check_stopped)
        stop_check.start(.1)
        return d

    def _load_wallet(self):
        path = self.config.get_wallet_path()
        storage = lbryum.wallet.WalletStorage(path)
        wallet = lbryum.wallet.Wallet(storage)
        if not storage.file_exists:
            self.is_first_run = True
            seed = wallet.make_seed()
            wallet.add_seed(seed, None)
            wallet.create_master_keys(None)
            wallet.create_main_account()
            wallet.synchronize()
        self.wallet = wallet
        self._check_large_wallet()
        return defer.succeed(True)

    def _check_large_wallet(self):
        if len(self.wallet.addresses(include_change=False)) > 1000:
            log.warning(
                "Your wallet is excessively large, please follow instructions here: \
https://github.com/lbryio/lbry/issues/437 to reduce your wallet size")

    def _load_blockchain(self):
        blockchain_caught_d = defer.Deferred()

        def check_caught_up():
            local_height = self.network.get_catchup_progress()
            remote_height = self.network.get_server_height()

            if remote_height == 0:
                return

            height_diff = remote_height - local_height

            if height_diff <= 5:
                self.blocks_behind = 0
                msg = ""
                if self._caught_up_counter != 0:
                    msg += "All caught up. "
                msg += "Wallet loaded."
                alert.info(msg)
                self._catch_up_check.stop()
                self._catch_up_check = None
                blockchain_caught_d.callback(True)
                return

            if height_diff < self.blocks_behind:
                # We're making progress in catching up
                self._lag_counter = 0
                self.is_lagging = False
            else:
                # No progress. Might be lagging
                self._lag_counter += 1
                if self._lag_counter >= 900:
                    self.is_lagging = True

            self.blocks_behind = height_diff

            if self.blocks_behind > self.max_behind:
                self.max_behind = self.blocks_behind
            self.catchup_progress = int(100 * (self.blocks_behind /
                                               (5 + self.max_behind)))
            if self._caught_up_counter == 0:
                alert.info('Catching up with the blockchain')
            if self._caught_up_counter % 30 == 0:
                alert.info('Blocks left: %d', (remote_height - local_height))

            self._caught_up_counter += 1

        def log_error(err):
            log.warning(err.getErrorMessage())
            return defer.fail(err)

        self._catch_up_check = task.LoopingCall(check_caught_up)
        d = defer.succeed(self.wallet.start_threads(self.network))
        d.addCallback(lambda _: self._catch_up_check.start(.1))
        d.addErrback(log_error)
        d.addCallback(lambda _: blockchain_caught_d)
        return d

    def _get_cmd_runner(self):
        return Commands(self.config, self.wallet, self.network)

    # run commands as a defer.succeed,
    # lbryum commands should be run this way , unless if the command
    # only makes a lbrum server query, use _run_cmd_as_defer_to_thread()
    def _run_cmd_as_defer_succeed(self, command_name, *args):
        cmd_runner = self._get_cmd_runner()
        cmd = known_commands[command_name]
        func = getattr(cmd_runner, cmd.name)
        return defer.succeed(func(*args))

    # run commands as a deferToThread,  lbryum commands that only make
    # queries to lbryum server should be run this way
    # TODO: keep track of running threads and cancel them on `stop`
    #       otherwise the application will hang, waiting for threads to complete
    def _run_cmd_as_defer_to_thread(self, command_name, *args):
        cmd_runner = self._get_cmd_runner()
        cmd = known_commands[command_name]
        func = getattr(cmd_runner, cmd.name)
        return threads.deferToThread(func, *args)

    def _update_balance(self):
        accounts = None
        exclude_claimtrietx = True
        d = self._run_cmd_as_defer_succeed('getbalance', accounts,
                                           exclude_claimtrietx)
        d.addCallback(lambda result: Decimal(result['confirmed']) + Decimal(
            result.get('unconfirmed', 0.0)))
        return d

    def get_new_address(self):
        addr = self.wallet.get_unused_address(account=None)
        if addr is None:
            addr = self.wallet.create_new_address()
        d = defer.succeed(addr)
        d.addCallback(self._save_wallet)
        return d

    def get_block(self, blockhash):
        return self._run_cmd_as_defer_to_thread('getblock', blockhash)

    def get_most_recent_blocktime(self):
        header = self.network.get_header(self.network.get_local_height())
        return defer.succeed(header['timestamp'])

    def get_best_blockhash(self):
        height = self.network.get_local_height()
        header = self.network.blockchain.read_header(height)
        return defer.succeed(self.network.blockchain.hash_header(header))

    def _get_blockhash(self, height):
        header = self.network.blockchain.read_header(height)
        return defer.succeed(self.network.blockchain.hash_header(header))

    def get_name_claims(self):
        return self._run_cmd_as_defer_succeed('getnameclaims')

    def _get_claims_for_name(self, name):
        return self._run_cmd_as_defer_to_thread('getclaimsforname', name)

    def _send_name_claim(self, name, val, amount):
        broadcast = False
        log.debug("Name claim %s %s %f", name, val, amount)
        d = self._run_cmd_as_defer_succeed('claim', name, json.dumps(val),
                                           amount, broadcast)
        d.addCallback(
            lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _send_name_claim_update(self, name, claim_id, claim_outpoint, value,
                                amount):
        metadata = json.dumps(value)
        log.debug("Update %s %d %f %s %s '%s'", claim_outpoint['txid'],
                  claim_outpoint['nout'], amount, name, claim_id, metadata)
        broadcast = False
        d = self._run_cmd_as_defer_succeed('update', claim_outpoint['txid'],
                                           claim_outpoint['nout'], name,
                                           claim_id, metadata, amount,
                                           broadcast)
        d.addCallback(
            lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _abandon_claim(self, claim_outpoint):
        log.debug("Abandon %s %s" %
                  (claim_outpoint['txid'], claim_outpoint['nout']))
        broadcast = False
        d = self._run_cmd_as_defer_succeed('abandon', claim_outpoint['txid'],
                                           claim_outpoint['nout'], broadcast)
        d.addCallback(
            lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _support_claim(self, name, claim_id, amount):
        log.debug("Support %s %s %f" % (name, claim_id, amount))
        broadcast = False
        d = self._run_cmd_as_defer_succeed('support', name, claim_id, amount,
                                           broadcast)
        d.addCallback(
            lambda claim_out: self._broadcast_claim_transaction(claim_out))
        return d

    def _broadcast_claim_transaction(self, claim_out):
        if 'success' not in claim_out:
            raise Exception(
                'Unexpected claim command output:{}'.format(claim_out))
        if claim_out['success']:
            d = self._broadcast_transaction(claim_out['tx'])
            d.addCallback(lambda _: claim_out)
            return d
        else:
            return defer.succeed(claim_out)

    def _broadcast_transaction(self, raw_tx):
        def _log_tx(r):
            log.debug("Broadcast tx: %s", r)
            return r

        d = self._run_cmd_as_defer_to_thread('broadcast', raw_tx)
        d.addCallback(_log_tx)
        d.addCallback(lambda r: r if len(r) == 64 else defer.fail(
            Exception("Transaction rejected")))
        return d

    def _do_send_many(self, payments_to_send):
        def broadcast_send_many(paytomany_out):
            if 'hex' not in paytomany_out:
                raise Exception(
                    'Unepxected paytomany output:{}'.format(paytomany_out))
            return self._broadcast_transaction(paytomany_out['hex'])

        log.debug("Doing send many. payments to send: %s",
                  str(payments_to_send))
        d = self._run_cmd_as_defer_succeed('paytomany',
                                           payments_to_send.iteritems())
        d.addCallback(lambda out: broadcast_send_many(out))
        return d

    def _get_value_for_name(self, name):
        block_header = self.network.blockchain.read_header(
            self.network.get_local_height() -
            RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS + 1)
        block_hash = self.network.blockchain.hash_header(block_header)
        d = self._run_cmd_as_defer_to_thread('requestvalueforname', name,
                                             block_hash)
        d.addCallback(lambda response: Commands._verify_proof(
            name, block_header['claim_trie_root'], response))
        return d

    def get_claims_from_tx(self, txid):
        return self._run_cmd_as_defer_to_thread('getclaimsfromtx', txid)

    def _get_balance_for_address(self, address):
        return defer.succeed(
            Decimal(self.wallet.get_addr_received(address)) / COIN)

    def get_nametrie(self):
        return self._run_cmd_as_defer_to_thread('getclaimtrie')

    def _get_history(self):
        return self._run_cmd_as_defer_succeed('history')

    def _address_is_mine(self, address):
        return self._run_cmd_as_defer_succeed('ismine', address)

    def get_pub_keys(self, wallet):
        return self._run_cmd_as_defer_succeed('getpubkyes', wallet)

    def _save_wallet(self, val):
        self.wallet.storage.write()
        return defer.succeed(val)