class NodeSet(object): lnd_client: LndClient bitcoin: Bitcoin lnd: Lnd tor: Tor 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) self.lnd_client = LndClient(self.lnd) file_name = 'torrc' tor_dir_path = TOR_DIR_PATH[OPERATING_SYSTEM] self.tor_configuration_file_path = os.path.join( tor_dir_path, file_name) log.info('tor_configuration_file_path', tor_configuration_file_path=self.tor_configuration_file_path) self.tor = Tor( configuration_file_path=self.tor_configuration_file_path, lnd=self.lnd) @property def is_testnet(self) -> bool: return self.bitcoin.file['testnet'] @property def is_mainnet(self) -> bool: return not self.bitcoin.file['testnet'] def reset_tls(self): was_running = self.lnd.running if was_running: self.lnd.stop() os.remove(self.lnd_client.tls_cert_path) os.remove(self.lnd_client.tls_key_path) if was_running: self.lnd.launch() self.lnd_client.reset()
class NodeSet(object): lnd_client: LndClient bitcoin: Bitcoin lnd: Lnd network: Network def __init__(self, network: Network): self.network = network self.bitcoin = Bitcoin( network=self.network, configuration_file_path=self.bitcoin_configuration_file_path) self.lnd = Lnd( network=self.network, configuration_file_path=self.lnd_configuration_file_path, bitcoin=self.bitcoin) self.lnd_client = LndClient(self.lnd) @property def is_testnet(self) -> bool: return self.network == TESTNET @property def is_mainnet(self) -> bool: return self.network == MAINNET @property def lnd_configuration_file_path(self) -> str: file_name = 'lnd.conf' if self.is_testnet: file_name = 'lnd-testnet.conf' lnd_dir_path = LND_DIR_PATH[OPERATING_SYSTEM] return os.path.join(lnd_dir_path, file_name) @property def bitcoin_configuration_file_path(self) -> str: file_name = 'bitcoin.conf' if self.is_testnet: file_name = 'bitcoin-testnet.conf' bitcoin_data_path = BITCOIN_DATA_PATH[OPERATING_SYSTEM] return os.path.join(bitcoin_data_path, file_name) def reset_tls(self): was_running = self.lnd.running if was_running: self.lnd.stop() os.remove(self.lnd_client.tls_cert_path) os.remove(self.lnd_client.tls_key_path) if was_running: self.lnd.launch() self.lnd_client.reset()
class NodeSet(object): lnd_client: LndClient litecoin: Litecoin lnd: Lnd 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) @property def is_testnet(self) -> bool: return self.litecoin.file['testnet'] @property def is_mainnet(self) -> bool: return not self.litecoin.file['testnet'] def reset_tls(self): was_running = self.lnd.running if was_running: self.lnd.stop() os.remove(self.lnd_client.tls_cert_path) os.remove(self.lnd_client.tls_key_path) if was_running: self.lnd.launch() self.lnd_client.reset()
class Lnd(object): bitcoin: Bitcoin client: LndClient file: ConfigurationFile software: LndSoftware process: QProcess def __init__(self, configuration_file_path: str, bitcoin: Bitcoin): self.running = False self.is_unlocked = False self.bitcoin = bitcoin self.file = ConfigurationFile(configuration_file_path) self.software = LndSoftware() self.lnddir = LND_DIR_PATH[OPERATING_SYSTEM] # Previous versions of the launcher set lnddir in the config file, # but it is not a valid key so this helps old users upgrading if self.file['lnddir'] is not None: self.file['lnddir'] = None if self.file['debuglevel'] is None: self.file['debuglevel'] = 'info' self.file['bitcoin.active'] = True self.file['bitcoin.node'] = 'bitcoind' self.file['bitcoind.rpchost'] = f'127.0.0.1:{self.bitcoin.rpc_port}' self.file['bitcoind.rpcuser'] = self.bitcoin.file['rpcuser'] self.file['bitcoind.rpcpass'] = self.bitcoin.file['rpcpassword'] self.file['bitcoind.zmqpubrawblock'] = self.bitcoin.file[ 'zmqpubrawblock'] self.file['bitcoind.zmqpubrawtx'] = self.bitcoin.file['zmqpubrawtx'] if self.file['restlisten'] is None: if self.bitcoin.file['testnet']: self.rest_port = get_port(LND_DEFAULT_REST_PORT + 1) else: self.rest_port = get_port(LND_DEFAULT_REST_PORT) self.file['restlisten'] = f'127.0.0.1:{self.rest_port}' else: self.rest_port = self.file['restlisten'].split(':')[-1] if not self.file['rpclisten']: if self.bitcoin.file['testnet']: self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT + 1) else: self.grpc_port = get_port(LND_DEFAULT_GRPC_PORT) self.file['rpclisten'] = f'127.0.0.1:{self.grpc_port}' else: self.grpc_port = int(self.file['rpclisten'].split(':')[-1]) if not self.file['tlsextraip']: self.file['tlsextraip'] = '127.0.0.1' if self.file['color'] is None: self.file['color'] = '#000000' self.macaroon_path = os.path.join( self.lnddir, 'data', 'chain', 'bitcoin', str(self.bitcoin.network) ) self.config_snapshot = self.file.snapshot.copy() self.file.file_watcher.fileChanged.connect(self.config_file_changed) self.bitcoin.file.file_watcher.fileChanged.connect( self.bitcoin_config_file_changed) self.process = QProcess() self.process.setProgram(self.software.lnd) self.process.setCurrentReadChannel(0) self.process.setArguments(self.args) self.client = LndClient(self) @property def args(self): if IS_WINDOWS: arg_list = [ f'--configfile={self.file.path}', ] else: arg_list = [ f'--configfile="{self.file.path}"', ] if self.bitcoin.file['testnet']: arg_list += [ '--bitcoin.testnet' ] else: arg_list += [ '--bitcoin.mainnet' ] return arg_list @property def node_port(self) -> str: if self.file['listen'] is None: if self.bitcoin.file['testnet']: port = get_port(LND_DEFAULT_PEER_PORT + 1) else: port = get_port(LND_DEFAULT_PEER_PORT) self.file['listen'] = f'127.0.0.1:{port}' else: if not isinstance(self.file['listen'], list): port = self.file['listen'].split(':')[-1] else: port = self.file['listen'][0].split(':')[-1] return port def test_tls_cert(self): context = ssl.create_default_context() context.load_verify_locations(cafile=self.tls_cert_path) conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname='127.0.0.1') conn.connect(('127.0.0.1', int(self.rest_port))) cert = conn.getpeercert() return cert @property def admin_macaroon_path(self) -> str: path = os.path.join(self.macaroon_path, 'admin.macaroon') return path @property def wallet_path(self) -> str: wallet_path = os.path.join(self.macaroon_path, 'wallet.db') return wallet_path @property def has_wallet(self) -> bool: return os.path.isfile(self.wallet_path) @property def tls_cert_path(self) -> str: tls_cert_path = os.path.join(self.lnddir, 'tls.cert') return tls_cert_path def lncli_arguments(self) -> List[str]: args = [] if self.grpc_port != LND_DEFAULT_GRPC_PORT: args.append(f'--rpcserver=127.0.0.1:{self.grpc_port}') if self.bitcoin.file['testnet']: args.append(f'--network={self.bitcoin.network}') if self.lnddir != LND_DIR_PATH[OPERATING_SYSTEM]: args.append(f'''--lnddir="{self.lnddir}"''') args.append(f'--macaroonpath="{self.macaroon_path}"') args.append(f'--tlscertpath="{self.tls_cert_path}"') return args @property def lncli(self) -> str: base_command = [ f'"{self.software.lncli}"', ] base_command += self.lncli_arguments() return ' '.join(base_command) @property def rest_url(self) -> str: return f'https://127.0.0.1:{self.rest_port}' @property def grpc_url(self) -> str: return f'127.0.0.1:{self.grpc_port}' def config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) if self.file['restlisten']: self.rest_port = int(self.file['restlisten'].split(':')[-1]) if self.file['rpclisten']: self.grpc_port = int(self.file['rpclisten'].split(':')[-1]) # Some text editors do not modify the file, they delete and replace the file # Check if file is still in file_watcher list of files, if not add back files_watched = self.file.file_watcher.files() if len(files_watched) == 0: self.file.file_watcher.addPath(self.file.path) def bitcoin_config_file_changed(self): # Refresh config file self.file.file_watcher.blockSignals(True) self.file.populate_cache() self.file.file_watcher.blockSignals(False) self.file['bitcoind.rpchost'] = f'127.0.0.1:{self.bitcoin.rpc_port}' self.file['bitcoind.rpcuser'] = self.bitcoin.file['rpcuser'] self.file['bitcoind.rpcpass'] = self.bitcoin.file['rpcpassword'] self.file['bitcoind.zmqpubrawblock'] = self.bitcoin.file[ 'zmqpubrawblock'] self.file['bitcoind.zmqpubrawtx'] = self.bitcoin.file['zmqpubrawtx'] @property def restart_required(self): if self.running: # Did bitcoin details change if self.bitcoin.restart_required: return True and self.running old_config = self.config_snapshot.copy() new_config = self.file.snapshot fields = [ 'restlisten', 'listen', 'rpclisten' ] for field in fields: # First check if field is found in both configs found_in_old_config = field in old_config.keys() found_in_new_config = field in new_config.keys() if found_in_old_config != found_in_new_config: return True # Now check that values are the same if found_in_old_config: if old_config[field] != new_config[field]: return True return False @staticmethod def base64URL_from_base64(s): return s.replace('+', '-').replace('/', '_').rstrip('=') @property def lndconnect_url(self): host = self.grpc_url.split(':')[0] port = self.grpc_url.split(':')[1] return f'lndconnect://{host}:{port}' \ f'?cert={self.tls_cert_path}&macaroon={self.admin_macaroon_path}' @property def lndconnect_qrcode(self): img = qrcode.make(self.lndconnect_url) return img def reset_tls(self): os.remove(self.client.tls_cert_path) os.remove(self.client.tls_key_path) self.process.terminate() self.client.reset()