def handle_unlock_wallet(self, details: str): if details is None: return details = details.lower() # The Wallet Unlocker gRPC service disappears from LND's API # after the wallet is unlocked (or created/recovered) if 'unknown service lnrpc.walletunlocker' in details: pass # User needs to create a new wallet elif 'wallet not found' in details: new_wallet_password = get_random_password() keyring_service_name = keyring_user_name = f'lnd_wallet_password' log.info('create_wallet', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) keyring.set_password(service=keyring_service_name, username=keyring_user_name, password=new_wallet_password) seed = self.generate_seed(new_wallet_password) try: self.lnd.client.initialize_wallet( wallet_password=new_wallet_password, seed=seed, seed_password=new_wallet_password) except _Rendezvous: log.error('initialize_wallet error', exc_info=True) raise keyring.set_password(service=f'lnd_mainnet_wallet_password', username=self.lnd.file['bitcoind.rpcuser'], password=new_wallet_password) else: log.warning('unlock_wallet failed', details=details, exc_info=True)
def __init__(self): file_name = 'litecoin.conf' litecoin_data_path = LITECOIN_DATA_PATH[OPERATING_SYSTEM] self.litecoin_configuration_file_path = os.path.join(litecoin_data_path, file_name) log.info( 'litecoin_configuration_file_path', litecoin_configuration_file_path=self.litecoin_configuration_file_path ) self.litecoin = Litecoin( configuration_file_path=self.litecoin_configuration_file_path ) file_name = 'lnd-ltc.conf' lnd_dir_path = LND_DIR_PATH[OPERATING_SYSTEM] self.lnd_configuration_file_path = os.path.join(lnd_dir_path, file_name) log.info( 'lnd_configuration_file_path', lnd_configuration_file_path=self.lnd_configuration_file_path ) self.lnd = Lnd( configuration_file_path=self.lnd_configuration_file_path, litecoin=self.litecoin ) self.lnd_client = LndClient(self.lnd)
def generate_seed(self, new_seed_password: str): try: generate_seed_response = self.client.generate_seed( seed_password=new_seed_password ) except _Rendezvous: log.error('generate_seed error', exc_info=True) raise seed = generate_seed_response.cipher_seed_mnemonic keyring_service_name = f'lnd_seed' keyring_user_name = ''.join(seed[0:2]) log.info( 'generate_seed', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name ) keyring.set_password( service=keyring_service_name, username=keyring_user_name, password='******'.join(seed) ) keyring.set_password( service=f'{keyring_service_name}_seed_password', username=keyring_user_name, password=new_seed_password ) return seed
def handle_unlock_wallet(self, details: str): if details is None: return details = details.lower() # The Wallet Unlocker gRPC service disappears from LND's API # after the wallet is unlocked (or created/recovered) if 'unknown service lnrpc.walletunlocker' in details: pass # User needs to create a new wallet elif 'wallet not found' in details: new_wallet_password = get_random_password() keyring_service_name = keyring_user_name = f'lnd_wallet_password' log.info('create_wallet', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) keyring.set_password(service=keyring_service_name, username=keyring_user_name, password=new_wallet_password) seed = self.generate_seed(new_wallet_password) try: self.node_set.lnd_client.initialize_wallet( wallet_password=new_wallet_password, seed=seed, seed_password=new_wallet_password) except _Rendezvous as e: log.error('initialize_wallet', exc_info=True) # noinspection PyProtectedMember self.error_message.showMessage(e._state.details) return keyring.set_password( service=f'lnd_{self.node_set.litecoin.network}_wallet_password', username=self.node_set.litecoin.file['rpcuser'], password=new_wallet_password) else: log.warning('unlock_wallet failed', details=details, exc_info=True)
def __init__(self, path, **kwargs): super().__init__(**kwargs) self.path = path parent = os.path.abspath(os.path.join(path, pardir)) if not isdir(parent): log.info('Creating directory', path=parent) os.mkdir(parent) if not isfile(self.path): log.info('Creating file', path=self.path) with open(self.path, 'w') as f: f.write('# Auto-Generated Configuration File' + os.linesep + os.linesep) f.write(f'# Node Launcher version {NODE_LAUNCHER_RELEASE}' + os.linesep + os.linesep) f.flush() self.cache = {} self.aliases = { 'rpcport': 'main.rpcport', 'main.rpcport': 'rpcport', 'port': 'main.port', 'main.port': 'port', 'walletdir': 'main.walletdir', 'main.walletdir': 'walletdir' } self.populate_cache() self.file_watcher = QFileSystemWatcher() self.file_watcher.addPath(self.path)
def unlock_wallet(self): password = self.password_prompt( title=f'Unlock {self.node_set.bitcoin.network} LND Wallet', label='Wallet Password') try: self.node_set.lnd_client.unlock(wallet_password=password) except _Rendezvous as e: log.error('unlock_wallet', exc_info=True) # noinspection PyProtectedMember self.error_message.showMessage(e._state.details) return timestamp = str(time.time()) keyring_service_name = f'lnd_{self.node_set.bitcoin.network}_wallet_password' log.info('unlock_wallet', keyring_service_name=keyring_service_name, keyring_user_name=timestamp) keyring.set_password(service=keyring_service_name, username=timestamp, password=password) keyring.set_password(service=keyring_service_name, username=self.node_set.bitcoin.file['rpcuser'], password=password)
def generate_seed(self, new_seed_password: str): try: generate_seed_response = self.node_set.lnd_client.generate_seed( seed_password=new_seed_password) except _Rendezvous as e: log.error('generate_seed', exc_info=True) # noinspection PyProtectedMember self.error_message.showMessage(e._state.details) return seed = generate_seed_response.cipher_seed_mnemonic keyring_service_name = f'lnd_seed' keyring_user_name = ''.join(seed[0:2]) log.info('generate_seed', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) keyring.set_password(service=keyring_service_name, username=keyring_user_name, password='******'.join(seed)) keyring.set_password(service=f'{keyring_service_name}_seed_password', username=keyring_user_name, password=new_seed_password) return seed
def should_prune(input_directory: str, has_bitcoin: bool) -> bool: directory = os.path.realpath(input_directory) try: total, used, free, percent = psutil.disk_usage(os.path.realpath(directory)) except: log.warning( 'should_prune_disk_usage', input_directory=input_directory, directory=directory, exc_info=True ) return False if has_bitcoin: bitcoin_bytes = get_dir_size(directory) free += bitcoin_bytes else: bitcoin_bytes = 0 free_gb = math.floor(free / GIGABYTE) bitcoin_gb = math.ceil(bitcoin_bytes / GIGABYTE) free_gb += bitcoin_gb should_prune = free_gb < AUTOPRUNE_GB log.info( 'should_prune', input_directory=input_directory, has_bitcoin=has_bitcoin, total=total, used=used, free=free, bitcoin_bytes=bitcoin_bytes, percent=percent, free_gb=free_gb, bitcoin_gb=bitcoin_gb, should_prune=should_prune ) return should_prune
def set_button_state(self): old_state = self.state if self.node_set.lnd.running and not self.node_set.lnd.is_unlocked: if self.node_set.lnd.has_wallet: if self.state != 'unlock': self.set_unlock_state() self.auto_unlock_wallet() else: return else: if self.state != 'create': self.set_create_recover_state() else: return elif self.node_set.lnd.running and self.node_set.lnd.is_unlocked: if self.state != 'open': self.set_open_state() else: return elif not self.node_set.lnd.running: self.node_set.lnd.is_unlocked = False if self.state != 'closed': self.set_closed_state() else: return log.info('set_button_state', lnd_is_running=self.node_set.lnd.running, lnd_is_unlocked=self.node_set.lnd.is_unlocked, lnd_has_wallet=self.node_set.lnd.has_wallet, old_state=old_state, new_state=self.state)
def read(self) -> List[Tuple[str, str, str]]: parent = os.path.abspath(os.path.join(self.path, pardir)) if not isdir(parent): log.info('Creating directory', path=parent) os.makedirs(parent) if not isfile(self.path): log.info('Creating file', path=self.path) lines = [ '# Auto-Generated Configuration File' + os.linesep + os.linesep, f'# Node Launcher version {NODE_LAUNCHER_RELEASE}' + os.linesep + os.linesep ] with open(self.path, 'w') as f: f.writelines(lines) with open(self.path, 'r') as f: lines = f.readlines() parsed_lines = [] index = 0 for line in lines: key, value = self.parse_line(line) if key: parsed_lines.append((str(index), key, value)) index += 1 return parsed_lines
def run_command(self, cmd: str): log.info( 'run_command', program=self.program, args=self.args, cmd=cmd ) self.output.append(f'> {cmd}\n') self.input.clear() process = QProcess() process.setProgram(self.program) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_error(process) ) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_output(process) ) connect_args = list(self.args) args = cmd.split(' ') if args[0] == self.program.split('/')[-1]: args.pop(0) process.setArguments(connect_args + args) process.start()
def get_big_drive(self) -> Partition: partitions = self.list_partitions() max_free_space = max([p.gb_free for p in partitions]) for partition in partitions: if partition.gb_free == max_free_space: log.info('get_big_drive', partition=partition) return partition
def lnd(self) -> List[str]: command = [self.software.lnd, f'--configfile="{self.file.path}"'] if self.bitcoin.file['testnet']: command += ['--bitcoin.testnet'] else: command += ['--bitcoin.mainnet'] log.info('lnd', command=command, **self.file.cache) return command
def load(self): file_name = 'torrc' tor_dir_path = TOR_DIR_PATH[OPERATING_SYSTEM] configuration_file_path = os.path.join(tor_dir_path, file_name) log.info('Loading tor configuration file', configuration_file_path=configuration_file_path) self.file = ConfigurationFile(path=configuration_file_path, assign_op=' ')
def run_command(self, cmd: str): log.info('run_command', program=self.program, args=self.args, cmd=cmd) self.output.append(f'> {cmd}\n') self.input.clear() self.process.kill() args = list(self.args) args.append(cmd) self.process.setArguments(args) self.process.start()
def bitcoin_qt(self) -> List[str]: command = [self.software.bitcoin_qt] args = [ f'-conf={self.file.path}', ] if IS_WINDOWS: args = [ f'-conf="{self.file.path}"', ] command += args log.info('bitcoin_qt', command=command, **self.file.cache) return command
def auto_unlock_wallet(self): keyring_service_name = f'lnd_mainnet_wallet_password' keyring_user_name = self.lnd.file['bitcoind.rpcuser'] log.info('auto_unlock_wallet_get_password', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) password = keyring.get_password( service=keyring_service_name, username=keyring_user_name, ) worker = Worker(fn=self.unlock_wallet, lnd=self.lnd, password=password) worker.signals.result.connect(self.handle_unlock_wallet) self.threadpool.start(worker)
def autoconfigure_datadir(self): default_datadir = BITCOIN_DATA_PATH[OPERATING_SYSTEM] big_drive = self.hard_drives.get_big_drive() default_is_big_enough = not self.hard_drives.should_prune( input_directory=default_datadir ) default_is_biggest = self.hard_drives.is_default_partition(big_drive) log.info( 'autoconfigure_datadir', default_is_big_enough=default_is_big_enough, default_is_biggest=default_is_biggest ) if default_is_big_enough or default_is_biggest: self['datadir'] = default_datadir log.info( 'autoconfigure_datadir', datadir=default_datadir ) return if not self.hard_drives.should_prune(big_drive.mountpoint): datadir = os.path.join(big_drive.mountpoint, 'Bitcoin') self['datadir'] = datadir log.info( 'autoconfigure_datadir', datadir=datadir ) if not os.path.exists(self['datadir']): os.mkdir(self['datadir']) else: self['datadir'] = default_datadir log.info( 'autoconfigure_datadir', datadir=default_datadir )
def recover_wallet(self): title = f'Recover {self.node_set.bitcoin.network} LND Wallet' new_wallet_password = self.get_new_password(title=title, password_name='LND Wallet') seed_password = self.password_prompt(title=title, label='Seed Password (Optional)') seed, ok = QInputDialog.getText( self.password_dialog, title, 'Mnemonic Seed (one line with spaces)') if not ok: raise Exception() seed_list = seed.split(' ') keyring_service_name = f'lnd_{self.node_set.bitcoin.network}' keyring_user_name = str(time.time()) log.info('recover_wallet', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) keyring.set_password(service=f'{keyring_service_name}_wallet_password', username=keyring_user_name, password=new_wallet_password) keyring.set_password(service=f'{keyring_service_name}_seed', username=keyring_user_name, password=seed) if seed_password is not None: keyring.set_password( service=f'{keyring_service_name}_seed_password', username=keyring_user_name, password=seed_password) try: self.node_set.lnd_client.initialize_wallet( wallet_password=new_wallet_password, seed=seed_list, seed_password=seed_password, recovery_window=10000) except _Rendezvous as e: log.error('recover_wallet_initialize_wallet', exc_info=True) # noinspection PyProtectedMember self.error_message.showMessage(e._state.details) return keyring.set_password( service=f'lnd_{self.node_set.bitcoin.network}_wallet_password', username=self.node_set.bitcoin.file['rpcuser'], password=new_wallet_password)
def auto_unlock_wallet(self): keyring_service_name = f'lnd_mainnet_wallet_password' keyring_user_name = self.configuration['bitcoind.rpcuser'] log.info('auto_unlock_wallet_get_password', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) password = self.keyring.get_password( service=keyring_service_name, username=keyring_user_name, ) worker = Worker(fn=self.unlock_wallet, configuration=self.configuration, password=password) worker.signals.result.connect(self.handle_unlock_wallet) QThreadPool().start(worker)
def list_partitions(self) -> List[Partition]: partitions = [] try: ps = psutil.disk_partitions() except: log.warning('list_partitions', exc_info=True) return partitions partition_paths = [ p.mountpoint for p in ps if 'removable' not in p.opts ] log.info('partition_paths', partition_paths=partition_paths) for path in partition_paths: free_gb = self.get_gb(path) partitions.append(Partition(path, free_gb), ) return partitions
def auto_unlock_wallet(self): keyring_service_name = f'lnd_{self.node_set.bitcoin.network}_wallet_password' keyring_user_name = self.node_set.bitcoin.file['rpcuser'] log.info('auto_unlock_wallet_get_password', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) password = keyring.get_password( service=keyring_service_name, username=keyring_user_name, ) if password is not None: worker = Worker(fn=self.lnd_poll, lnd=self.node_set.lnd, password=password) worker.signals.result.connect(self.handle_lnd_poll) self.threadpool.start(worker)
def get_gb(path: str) -> int: try: capacity, used, free, percent = psutil.disk_usage(path) except: log.warning('get_gb', path=path, exc_info=True) return 0 free_gb = math.floor(free / GIGABYTE) log.info('get_gb', path=path, capacity=capacity, used=used, free=free, percent=percent, free_gb=free_gb) return free_gb
def __init__(self, path, **kwargs): super().__init__(**kwargs) self.path = path parent = os.path.abspath(os.path.join(path, pardir)) if not isdir(parent): log.info('Creating directory', path=parent) os.mkdir(parent) if not isfile(self.path): log.info('Creating file', path=self.path) with open(self.path, 'w') as f: f.write('# Auto-Generated Configuration File' + os.linesep + os.linesep) f.write(f'# Node Launcher version {NODE_LAUNCHER_RELEASE}' + os.linesep + os.linesep) f.flush() self.cache = {} self.populate_cache()
def read(self) -> List[Tuple[str, str]]: parent = os.path.abspath(os.path.join(self.path, pardir)) if not isdir(parent): log.info('Creating directory', path=parent) os.makedirs(parent) if not isfile(self.path): log.info('Creating file', path=self.path) lines = [ '# Auto-Generated Configuration File' + os.linesep + os.linesep, f'# Node Launcher version {NODE_LAUNCHER_RELEASE}' + os.linesep + os.linesep ] with open(self.path, 'w') as f: f.writelines(lines) with open(self.path, 'r') as f: lines = f.readlines() parsed_lines = [self.parse_line(l) for l in lines] return [l for l in parsed_lines if l[0]]
def __init__(self): file_name = 'bitcoin.conf' bitcoin_data_path = BITCOIN_DATA_PATH[OPERATING_SYSTEM] self.bitcoin_configuration_file_path = os.path.join( bitcoin_data_path, file_name) log.info('bitcoin_configuration_file_path', bitcoin_configuration_file_path=self. bitcoin_configuration_file_path) self.bitcoin = Bitcoin( configuration_file_path=self.bitcoin_configuration_file_path) file_name = 'lnd.conf' lnd_dir_path = LND_DIR_PATH[OPERATING_SYSTEM] self.lnd_configuration_file_path = os.path.join( lnd_dir_path, file_name) log.info('lnd_configuration_file_path', lnd_configuration_file_path=self.lnd_configuration_file_path) self.lnd = Lnd( configuration_file_path=self.lnd_configuration_file_path, bitcoin=self.bitcoin)
def should_prune(input_directory: str) -> bool: directory = os.path.realpath(input_directory) try: total, used, free, percent = psutil.disk_usage(directory) except: log.warning('should_prune_disk_usage', input_directory=input_directory, directory=directory, exc_info=True) return False free_gb = math.floor(free / GIGABYTE) should_prune = free_gb < AUTOPRUNE_GB log.info('should_prune', input_directory=input_directory, total=total, used=used, free=free, percent=percent, free_gb=free_gb, should_prune=should_prune) return should_prune
def run_command(self, command): try: if self.cli is None or self.cli_args is None: self.output_area.append( 'Node starting up, please try again later...') return False self.output_area.append(f'> {command}\n') process = QProcess() process.setProgram(self.cli) process.setCurrentReadChannel(0) # noinspection PyUnresolvedReferences process.readyReadStandardError.connect( lambda: self.handle_cli_error_output(process)) # noinspection PyUnresolvedReferences process.readyReadStandardOutput.connect( lambda: self.handle_cli_output(process)) args = command.split(' ') if args[0] == self.cli.split('/')[-1]: args.pop(0) process.setArguments(self.cli_args + args) process.start() log.info('run_command', program=self.cli, args=self.cli_args, cmd=command) return True except Exception: self.output_area.append( 'Node starting up, please try again later...') return False
def create_wallet(self): new_wallet_password = self.get_new_password( title=f'Create {self.node_set.bitcoin.network} LND Wallet', password_name='LND Wallet') keyring_service_name = f'lnd_{self.node_set.bitcoin.network}_wallet_password' keyring_user_name = str(time.time()) log.info('create_wallet', keyring_service_name=keyring_service_name, keyring_user_name=keyring_user_name) keyring.set_password(service=keyring_service_name, username=keyring_user_name, password=new_wallet_password) new_seed_password = self.get_new_password( title=f'Create {self.node_set.bitcoin.network} LND Wallet', password_name='Mnemonic Seed') seed = self.generate_seed(new_seed_password) self.backup_seed(seed) try: self.node_set.lnd_client.initialize_wallet( wallet_password=new_wallet_password, seed=seed, seed_password=new_seed_password) except _Rendezvous as e: log.error('initialize_wallet', exc_info=True) # noinspection PyProtectedMember self.error_message.showMessage(e._state.details) return keyring.set_password( service=f'lnd_{self.node_set.bitcoin.network}_wallet_password', username=self.node_set.bitcoin.file['rpcuser'], password=new_wallet_password)
def __init__(self, configuration_file_path: str): self.hard_drives = HardDrives() self.software = LitecoinSoftware() self.file = ConfigurationFile(configuration_file_path) self.running = False self.process = None if self.file['datadir'] is None: self.autoconfigure_datadir() if 'litecoin.conf' in os.listdir(self.file['datadir']): actual_conf_file = os.path.join(self.file['datadir'], 'litecoin.conf') log.info( 'datadir_redirect', configuration_file_path=configuration_file_path, actual_conf_file=actual_conf_file ) self.file = ConfigurationFile(actual_conf_file) if self.file['datadir'] is None: self.autoconfigure_datadir() if os.path.exists(os.path.join(self.file['datadir'], 'blocks')): if self.file['prune'] is None: self.set_prune(False) self.wallet_paths = self.get_wallet_paths() if self.file['server'] is None: self.file['server'] = True if self.file['disablewallet'] is None and not self.wallet_paths: self.file['disablewallet'] = True elif self.file['disablewallet'] is None and self.wallet_paths: self.file['disablewallet'] = False if self.file['timeout'] is None: self.file['timeout'] = 6000 if self.file['rpcuser'] is None: self.file['rpcuser'] = '******' if self.file['rpcpassword'] is None: self.file['rpcpassword'] = get_random_password() if self.file['prune'] is None: should_prune = self.hard_drives.should_prune(self.file['datadir'], has_litecoin=True) self.set_prune(should_prune) self.zmq_block_port = get_zmq_port() self.zmq_tx_port = get_zmq_port() self.file['zmqpubrawblock'] = f'tcp://127.0.0.1:{self.zmq_block_port}' self.file['zmqpubrawtx'] = f'tcp://127.0.0.1:{self.zmq_tx_port}' # noinspection PyBroadException try: memory = psutil.virtual_memory() free_mb = round(memory.available / 1000000) free_mb -= int(free_mb * .3) self.file['dbcache'] = free_mb except: log.warning( 'dbcache psutil.virtual_memory', exc_info=True ) self.file['dbcache'] = 1000 self.config_snapshot = self.file.snapshot.copy() self.file.file_watcher.fileChanged.connect(self.config_file_changed) self.process = QProcess() self.process.setProgram(self.software.litecoind) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.process.start()