Beispiel #1
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

    def _is_first_run(self):
        return (not self.printed_retrieving_headers and
                self.network.blockchain.retrieving_headers)

    def _start(self):
        network_start_d = defer.Deferred()
        self.config = make_config(self._config)

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

        def check_started():
            if self.network.is_connecting():
                if self._is_first_run():
                    log.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 = setup_network()
        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 on_update_callback(event, *args):
            # This callback is called by lbryum when something chain
            # related has happened
            local_height = self.network.get_local_height()
            remote_height = self.network.get_server_height()
            updated_blocks_behind = self.network.get_blocks_behind()
            log.info(
                'Local Height: %s, remote height: %s, behind: %s',
                local_height, remote_height, updated_blocks_behind)

            self.blocks_behind = updated_blocks_behind
            if local_height != remote_height:
                return

            assert self.blocks_behind == 0
            self.network.unregister_callback(on_update_callback)
            log.info("Wallet Loaded")
            reactor.callFromThread(blockchain_caught_d.callback, True)

        self.network.register_callback(on_update_callback, ['updated'])

        d = defer.succeed(self.wallet.start_threads(self.network))
        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):
        height = self.network.get_local_height()
        if height < 0:
            return defer.succeed(None)
        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()
        if height < 0:
            return defer.succeed(None)
        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):
        height_to_check = self.network.get_local_height() - RECOMMENDED_CLAIMTRIE_HASH_CONFIRMS + 1
        if height_to_check < 0:
            msg = "Height to check is less than 0, blockchain headers are likely not initialized"
            raise Exception(msg)
        block_header = self.network.blockchain.read_header(height_to_check)
        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)

    # returns a list of public keys associated with address
    # (could be multiple public keys if a multisig address)
    def get_pub_keys(self, address):
        return self._run_cmd_as_defer_succeed('getpubkeys', address)

    def _save_wallet(self, val):
        self.wallet.storage.write()
        return defer.succeed(val)
Beispiel #2
0
 def setup_network():
     self.network = Network(self.config)
     log.info("Loading the wallet")
     return defer.succeed(self.network.start())
Beispiel #3
0
 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())
Beispiel #4
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)
Beispiel #5
0
 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())
Beispiel #6
0
class LBRYumWallet(Wallet):
    def __init__(self, storage, config=None):
        Wallet.__init__(self, storage)
        self._config = config
        self.config = make_config(self._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

    def _is_first_run(self):
        return (not self.printed_retrieving_headers
                and self.network.blockchain.retrieving_headers)

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

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

        def check_started():
            if self.network.is_connecting():
                if self._is_first_run():
                    log.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 = setup_network()
        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())
        d.addCallback(lambda _: log.info("Subscribing to addresses"))
        d.addCallback(
            lambda _: self.wallet.wait_until_synchronized(lambda _: None))
        d.addCallback(lambda _: log.info("Synchronized wallet"))

        storage = lbryum.wallet.WalletStorage(self.config.get_wallet_path())
        if storage.get('use_encryption') is True:
            d.addCallback(lambda _: self.wallet.wait_until_authenticated(
                self.decrypt_wallet()))
            d.addCallback(lambda _: log.info("Decrypted wallet"))

        return d

    def decrypt_wallet(self):
        from lbryum.util import InvalidPassword
        import getpass

        password = getpass.getpass("Password for encrypted wallet: ")

        try:
            seed = self.wallet.check_password(password)
            self.wallet.set_is_decrypted(True)
        except InvalidPassword:
            log.error("Error: This password does not decode this wallet.")
            os._exit(1)

    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.wallet:
            self.wallet.stop_threads()
            log.info("Stopped wallet")
        if self.network:
            self.network.stop()
            log.info("Stopped connection to lbryum server")

        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 on_update_callback(event, *args):
            # This callback is called by lbryum when something chain
            # related has happened
            local_height = self.network.get_local_height()
            remote_height = self.network.get_server_height()
            updated_blocks_behind = self.network.get_blocks_behind()
            log.info('Local Height: %s, remote height: %s, behind: %s',
                     local_height, remote_height, updated_blocks_behind)

            self.blocks_behind = updated_blocks_behind
            if local_height != remote_height:
                return

            assert self.blocks_behind == 0
            self.network.unregister_callback(on_update_callback)
            log.info("Wallet Loaded")
            reactor.callFromThread(blockchain_caught_d.callback, True)

        self.network.register_callback(on_update_callback, ['updated'])

        d = defer.succeed(self.wallet.start_threads(self.network))
        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, **kwargs):
        cmd_runner = self._get_cmd_runner()
        cmd = known_commands[command_name]
        func = getattr(cmd_runner, cmd.name)
        return defer.succeed(func(*args, **kwargs))

    # 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, **kwargs):
        cmd_runner = self._get_cmd_runner()
        cmd = known_commands[command_name]
        func = getattr(cmd_runner, cmd.name)
        return threads.deferToThread(func, *args, **kwargs)

    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

    # Always create and return a brand new address
    @defer.inlineCallbacks
    def get_new_address(self):
        addr = self.wallet.create_new_address(account=None)
        yield self._save_wallet()
        defer.returnValue(addr)

    # Get the balance of a given address.

    def get_address_balance(self, address, include_balance=False):
        c, u, x = self.wallet.get_addr_balance(address)
        if include_balance is False:
            return Decimal(float(c) / COIN)
        else:
            return Decimal((float(c) + float(u) + float(x)) / COIN)

    # Return an address with no balance in it, if
    # there is none, create a brand new address
    @defer.inlineCallbacks
    def get_unused_address(self):
        addr = self.wallet.get_unused_address(account=None)
        if addr is None:
            addr = self.wallet.create_new_address()
        yield self._save_wallet()
        defer.returnValue(addr)

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

    def get_most_recent_blocktime(self):
        height = self.network.get_local_height()
        if height < 0:
            return defer.succeed(None)
        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()
        if height < 0:
            return defer.succeed(None)
        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_transaction(self, txid):
        return self._run_cmd_as_defer_to_thread("gettransaction", txid)

    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)

    @defer.inlineCallbacks
    def _send_name_claim(self, name, value, amount, certificate_id=None):
        log.info("Send claim: %s for %s: %s ", name, amount, value)
        claim_out = yield self._run_cmd_as_defer_succeed(
            'claim', name, value, amount, certificate_id=certificate_id)
        defer.returnValue(claim_out)

    @defer.inlineCallbacks
    def _abandon_claim(self, claim_id):
        log.debug("Abandon %s" % claim_id)
        tx_out = yield self._run_cmd_as_defer_succeed('abandon', claim_id)
        defer.returnValue(tx_out)

    @defer.inlineCallbacks
    def _support_claim(self, name, claim_id, amount):
        log.debug("Support %s %s %f" % (name, claim_id, amount))
        broadcast = False
        tx = yield self._run_cmd_as_defer_succeed('support', name, claim_id,
                                                  amount, broadcast)
        claim_out = yield self._broadcast_claim_transaction(tx)
        defer.returnValue(claim_out)

    @defer.inlineCallbacks
    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']:
            yield self._broadcast_transaction(claim_out['tx'])
        defer.returnValue(claim_out)

    @defer.inlineCallbacks
    def _broadcast_transaction(self, raw_tx):
        txid = yield self._run_cmd_as_defer_to_thread('broadcast', raw_tx)
        log.info("Broadcast tx: %s", txid)
        if len(txid) != 64:
            raise Exception("Transaction rejected. Raw tx: {}".format(raw_tx))
        defer.returnValue(txid)

    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):
        if not name:
            raise Exception("No name given")
        return self._run_cmd_as_defer_to_thread('getvalueforname', name)

    def _get_value_for_uri(self, uri):
        if not uri:
            raise Exception("No uri given")
        return self._run_cmd_as_defer_to_thread('getvalueforuri', uri)

    def _claim_certificate(self, name, amount):
        return self._run_cmd_as_defer_to_thread('claimcertificate', name,
                                                amount)

    def _get_certificate_claims(self):
        return self._run_cmd_as_defer_succeed('getcertificateclaims')

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

    def _get_claim_by_outpoint(self, txid, nout):
        return self._run_cmd_as_defer_to_thread('getclaimbyoutpoint', txid,
                                                nout)

    def _get_claim_by_claimid(self, claim_id):
        return self._run_cmd_as_defer_to_thread('getclaimbyid', claim_id)

    def _get_balance_for_address(self, address):
        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)

    # returns a list of public keys associated with address
    # (could be multiple public keys if a multisig address)
    def get_pub_keys(self, address):
        return self._run_cmd_as_defer_succeed('getpubkeys', address)

    def list_addresses(self):
        return self._run_cmd_as_defer_succeed('listaddresses')

    def _save_wallet(self, val=None):
        self.wallet.storage.write()
        return defer.succeed(val)
Beispiel #7
0
class LBRYumWallet(LBRYWallet):

    def __init__(self, db_dir):
        LBRYWallet.__init__(self, db_dir)
        self.config = None
        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 = SimpleConfig()
            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:
            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 = WalletStorage(path)
            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

        blockchain_caught_d = defer.Deferred()

        def check_caught_up():
            local_height = self.network.get_local_height()
            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


        self._catch_up_check = task.LoopingCall(check_caught_up)

        d = threads.deferToThread(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.addCallback(lambda _: blockchain_caught_d)
        return d

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

    def get_balance(self):
        cmd = known_commands['getbalance']
        func = getattr(self.cmd_runner, cmd.name)
        d = threads.deferToThread(func)
        d.addCallback(lambda result: result['unmatured'] if 'unmatured' in result else result['confirmed'])
        d.addCallback(Decimal)
        return d

    def get_new_address(self):
        d = threads.deferToThread(self.wallet.create_new_address)
        d.addCallback(self._save_wallet)
        return d

    def get_block(self, blockhash):
        cmd = known_commands['getblock']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, 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()
        d = threads.deferToThread(self.network.blockchain.read_header, height)
        d.addCallback(lambda header: self.network.blockchain.hash_header(header))
        return d

    def get_name_claims(self):
        cmd = known_commands['getnameclaims']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func)

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

    def _get_raw_tx(self, txid):
        cmd = known_commands['gettransaction']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, txid)

    def _send_name_claim(self, name, val, amount):
        def send_claim(address):
            cmd = known_commands['claimname']
            func = getattr(self.cmd_runner, cmd.name)
            return threads.deferToThread(func, address, amount, name, val)
        d = self.get_new_address()
        d.addCallback(send_claim)
        d.addCallback(self._broadcast_transaction)
        return d

    def _get_decoded_tx(self, raw_tx):
        tx = Transaction(raw_tx)
        decoded_tx = {}
        decoded_tx['vout'] = []
        for output in tx.outputs():
            out = {}
            out['value'] = Decimal(output[2]) / Decimal(COIN)
            decoded_tx['vout'].append(out)
        return decoded_tx

    def _send_abandon(self, txid, address, amount):
        log.info("Abandon " + str(txid) + " " + str(address) + " " + str(amount))
        cmd = known_commands['abandonclaim']
        func = getattr(self.cmd_runner, cmd.name)
        d = threads.deferToThread(func, txid, address, amount)
        d.addCallback(self._broadcast_transaction)
        return d

    def _broadcast_transaction(self, raw_tx):
        log.info("Broadcast: " + str(raw_tx))
        cmd = known_commands['broadcast']
        func = getattr(self.cmd_runner, cmd.name)
        d = threads.deferToThread(func, raw_tx)
        d.addCallback(self._save_wallet)
        return d

    def _do_send_many(self, payments_to_send):
        log.warning("Doing send many. payments to send: %s", str(payments_to_send))
        outputs = [(TYPE_ADDRESS, address, int(amount*COIN)) for address, amount in payments_to_send.iteritems()]
        d = threads.deferToThread(self.wallet.mktx, outputs, None, self.config)
        d.addCallback(lambda tx: threads.deferToThread(self.wallet.sendtx, tx))
        d.addCallback(self._save_wallet)
        return d

    def _get_value_for_name(self, name):
        cmd = known_commands['getvalueforname']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, name)

    def get_claims_from_tx(self, txid):
        cmd = known_commands['getclaimsfromtx']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, txid)

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

    def get_nametrie(self):
        cmd = known_commands['getclaimtrie']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func)

    def get_history(self):
        cmd = known_commands['history']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func)

    def get_tx_json(self, txid):
        def _decode(raw_tx):
            tx = Transaction(raw_tx).deserialize()
            decoded_tx = {}
            for txkey in tx.keys():
                if isinstance(tx[txkey], list):
                    decoded_tx[txkey] = []
                    for i in tx[txkey]:
                        tmp = {}
                        for k in i.keys():
                            if isinstance(i[k], Decimal):
                                tmp[k] = float(i[k] / 1e8)
                            else:
                                tmp[k] = i[k]
                        decoded_tx[txkey].append(tmp)
                else:
                    decoded_tx[txkey] = tx[txkey]
            return decoded_tx

        d = self._get_raw_tx(txid)
        d.addCallback(_decode)
        return d

    def get_pub_keys(self, wallet):
        cmd = known_commands['getpubkeys']
        func = getattr(self.cmd_runner, cmd.name)
        return threads.deferToThread(func, wallet)

    def _save_wallet(self, val):
        d = threads.deferToThread(self.wallet.storage.write)
        d.addCallback(lambda _: val)
        return d