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)
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)
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)
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