class Plugin(AbstractPlugin): DEFAULT_PORT = 49241 RESERVED_PORTS: Set[int] = {DEFAULT_PORT} COMPONENT_NAME = get_directory_name(__file__) DEFAULT_REMOTE_REPO = "https://github.com/electrumsv/simple-indexer" SIMPLE_INDEX_RESET = os.environ.get("SIMPLE_INDEX_RESET", '1') def __init__(self, cli_inputs: CLIInputs) -> None: self.cli_inputs = cli_inputs self.config = Config() self.plugin_tools = PluginTools(self, self.cli_inputs) self.tools = LocalTools(self) self.logger = logging.getLogger(self.COMPONENT_NAME) self.src = self.plugin_tools.get_source_dir(dirname="simple-indexer") self.datadir = None # dynamically allocated self.id = None # dynamically allocated self.port = None # dynamically allocated self.component_info: Optional[Component] = None def install(self) -> None: """required state: source_dir - which are derivable from 'repo' and 'branch' flags""" self.plugin_tools.modify_pythonpath_for_portability(self.src) repo = self.cli_inputs.repo if self.cli_inputs.repo == "": repo = self.DEFAULT_REMOTE_REPO if is_remote_repo(repo): self.tools.fetch_simple_indexer(repo, self.cli_inputs.branch) self.tools.packages_simple_indexer(repo, self.cli_inputs.branch) def start(self) -> None: """plugin datadir, id, port are allocated dynamically""" self.logger.debug(f"Starting RegTest simple_indexer daemon...") assert self.src is not None # typing bug if not self.src.exists(): self.logger.error(f"source code directory does not exist - try 'electrumsv-sdk install " f"{self.COMPONENT_NAME}' to install the plugin first") sys.exit(1) self.plugin_tools.modify_pythonpath_for_portability(self.src) self.datadir, self.id = self.plugin_tools.allocate_datadir_and_id() self.port = self.plugin_tools.allocate_port() command = f"{sys.executable} {self.src.joinpath('server.py')}" env_vars = { "PYTHONUNBUFFERED": "1", "SIMPLE_INDEX_RESET": self.SIMPLE_INDEX_RESET, } logfile = self.plugin_tools.get_logfile_path(self.id) self.plugin_tools.spawn_process(command, env_vars=env_vars, id=self.id, component_name=self.COMPONENT_NAME, src=self.src, logfile=logfile, status_endpoint=f"http://127.0.0.1:{self.port}", metadata={"datadir": str(self.datadir)}) def stop(self) -> None: """some components require graceful shutdown via a REST API or RPC API but most can use the generic 'app_state.kill_component()' function to track down the pid and kill the process.""" self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME, callable=kill_process) self.logger.info(f"stopped selected {self.COMPONENT_NAME} instance (if running)") def reset(self) -> None: self.logger.debug("Resetting state of RegTest simple_indexer server...") assert self.src is not None db_path = self.src.joinpath("simple_indexer.db") node_headers_path = self.src.joinpath("node_headers.mmap") local_headers_path = self.src.joinpath("local_headers.mmap") if db_path.exists(): os.remove(db_path) if node_headers_path.exists(): os.remove(node_headers_path) if local_headers_path.exists(): os.remove(local_headers_path) self.logger.info("Reset of RegTest simple_indexer completed successfully")
class Plugin(AbstractPlugin): DEFAULT_PORT = 5050 RESERVED_PORTS: Set[int] = {DEFAULT_PORT} COMPONENT_NAME = get_directory_name(__file__) NODE_HOST = os.environ.get("NODE_HOST") or "127.0.0.1" NODE_RPC_PORT = int(os.environ.get("NODE_RPC_PORT") or 18332) NODE_RPC_USERNAME = os.environ.get("NODE_RPC_USERNAME") or "rpcuser" NODE_RPC_PASSWORD = os.environ.get("NODE_RPC_PASSWORD") or "rpcpassword" NODE_ZMQ_PORT = int(os.environ.get("NODE_ZMQ_PORT") or 28332) MERCHANT_API_HOST = os.environ.get("MERCHANT_API_HOST") or "127.0.0.1" MERCHANT_API_PORT = int( os.environ.get("MERCHANT_API_PORT") or DEFAULT_PORT) def __init__(self, cli_inputs: CLIInputs): self.cli_inputs = cli_inputs self.config = Config() self.plugin_tools = PluginTools(self, self.cli_inputs) self.logger = logging.getLogger(self.COMPONENT_NAME) self.src = self.plugin_tools.get_source_dir(dirname="merchant_api") self.datadir: Optional[Path] = None # dynamically allocated self.id: Optional[str] = None # dynamically allocated self.port: Optional[int] = None # dynamically allocated self.component_info: Optional[Component] = None download_and_init_postgres() # only if necessary def install(self) -> None: assert self.src is not None # typing bug in mypy download_and_install(self.src) if SDK_SKIP_POSTGRES_INIT != 1: if SDK_PORTABLE_MODE == 1: # stop_postgres() # reset_postgres() start_postgres() prepare_fresh_postgres() drop_db_on_install() check_postgres_db() self.logger.debug(f"Installed {self.COMPONENT_NAME}") def start(self) -> None: assert self.src is not None # typing bug if SDK_PORTABLE_MODE == 1: download_and_install(self.src) start_postgres() self.logger.debug(f"Starting Merchant API") prepare_fresh_postgres() check_postgres_db() if not self.src.exists(): self.logger.error( f"source code directory does not exist - try 'electrumsv-sdk install " f"{self.COMPONENT_NAME}' to install the plugin first") sys.exit(1) self.id = self.plugin_tools.get_id(self.COMPONENT_NAME) self.port = self.MERCHANT_API_PORT # The primary reason we need this to be the current directory is so that the `settings.conf` # file is directly accessible to the MAPI executable (it should look there first). os.chdir(self.src) # EXE RUN MODE load_env_vars() try: chmod_exe(self.src) command = get_run_path(self.src) except FileNotFoundError: self.logger.error( f"Could not find version: {MERCHANT_API_VERSION} of the " f"merchant_api. Have you tried re-running 'electrumsv-sdk install merchant_api' to " f"pull the latest version?") return logfile = self.plugin_tools.get_logfile_path(self.id) status_endpoint = "http://127.0.0.1:5050/mapi/feeQuote" self.add_node_thread = AddNodeThread(mapi_url="http://127.0.0.1:5050", max_wait_time=10) self.add_node_thread.start() self.plugin_tools.spawn_process(str(command), env_vars=os.environ.copy(), id=self.id, component_name=self.COMPONENT_NAME, src=self.src, logfile=logfile, status_endpoint=status_endpoint) # will keep trying to add node until mAPI REST API available (up to a limited wait time) self.logger.info("Adding node to mAPI instance (if not already added)") self.add_node_thread.join() def stop(self) -> None: self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME, callable=kill_process) if SDK_PORTABLE_MODE == 1: assert self.config.DATADIR is not None postgres_install_path = self.config.DATADIR / "postgres" # Set this environment variable before importing postgres script os.environ['SDK_POSTGRES_INSTALL_DIR'] = str(postgres_install_path) from .. import _postgres if asyncio.run(_postgres.check_running()): stop_postgres() self.logger.info( f"stopped selected {self.COMPONENT_NAME} instance (if running)") def reset(self) -> None: self.logger.info("resetting Merchant API is not applicable")
class Plugin(AbstractPlugin): # ---------- Environment Variables ---------- # BITCOIN_NETWORK = os.getenv("BITCOIN_NETWORK", "regtest") # For documentation purposes only (these env vars will be detected by electrumsv too) ELECTRUMSV_ACCOUNT_XPRV = os.getenv("ELECTRUMSV_ACCOUNT_XPRV") BITCOIN_NODE_HOST = os.environ.get("BITCOIN_NODE_HOST") or "127.0.0.1" BITCOIN_NODE_PORT = os.environ.get("BITCOIN_NODE_PORT") or 18332 BITCOIN_NODE_RPCUSER = os.environ.get("BITCOIN_NODE_RPCUSER") or "rpcuser" BITCOIN_NODE_RPCPASSWORD = os.environ.get( "BITCOIN_NODE_RPCPASSWORD") or "rpcpassword" RESTAPI_HOST = os.environ.get("RESTAPI_HOST") DEFAULT_PORT = 9999 RESERVED_PORTS: Set[int] = {DEFAULT_PORT} COMPONENT_NAME = get_directory_name(__file__) DEFAULT_REMOTE_REPO = "https://github.com/electrumsv/electrumsv.git" DEFAULT_BRANCH = "develop" def __init__(self, cli_inputs: CLIInputs): self.cli_inputs = cli_inputs self.config = Config() self.plugin_tools = PluginTools(self, self.cli_inputs) self.tools = LocalTools(self) self.logger = logging.getLogger(self.COMPONENT_NAME) self.src = self.plugin_tools.get_source_dir(dirname="electrumsv") self.datadir = None # dynamically allocated self.id = None # dynamically allocated self.port = None # dynamically allocated self.component_info: Optional[Component] = None self.network = self.BITCOIN_NETWORK def install(self) -> None: """required state: source_dir - which are derivable from 'repo' and 'branch' flags""" self.plugin_tools.modify_pythonpath_for_portability(self.src) repo = self.cli_inputs.repo if self.cli_inputs.repo == "": repo = self.DEFAULT_REMOTE_REPO branch = self.cli_inputs.branch if self.cli_inputs.branch == "": branch = self.DEFAULT_BRANCH if is_remote_repo(repo): self.tools.fetch_electrumsv(repo, branch) self.tools.packages_electrumsv(repo, branch) self.logger.debug(f"Installed {self.COMPONENT_NAME}") def start(self) -> None: """plugin datadir, id, port are allocated dynamically""" self.plugin_tools.modify_pythonpath_for_portability(self.src) self.logger.debug(f"Starting RegTest electrumsv daemon...") assert self.src is not None if not self.src.exists(): self.logger.error( f"source code directory does not exist - try 'electrumsv-sdk install " f"{self.COMPONENT_NAME}' to install the plugin first") sys.exit(1) self.tools.process_cli_args() self.datadir, self.id = self.plugin_tools.allocate_datadir_and_id() self.port = self.plugin_tools.allocate_port() logfile = self.plugin_tools.get_logfile_path(self.id) metadata = ComponentMetadata(config_path=str( self.datadir.joinpath("regtest/cli_inputs")), datadir=str(self.datadir)) status_endpoint = f"http://127.0.0.1:{self.port}" os.makedirs(self.datadir.joinpath("regtest/wallets"), exist_ok=True) if self.tools.is_offline_cli_mode(): # 'reset' recurses into here... command, env_vars = self.tools.generate_command() self.plugin_tools.spawn_process(command, env_vars=env_vars, id=self.id, component_name=self.COMPONENT_NAME, src=self.src, logfile=logfile, status_endpoint=status_endpoint, metadata=metadata) if self.tools.wallet_db_exists(): if self.cli_inputs.cli_extension_args['deterministic_seed']: if self.tools.wallet_db_exists(): raise ValueError( f"Cannot set a deterministic seed. This wallet: '{self.id}' " f"already exists. Please try 'electrumsv-sdk reset --deterministic-seed " f"--id={self.id}' or create a new wallet with a new --id." ) # If daemon or gui mode continue... elif not self.tools.wallet_db_exists(): if self.cli_inputs.cli_extension_args['deterministic_seed']: set_deterministic_electrumsv_seed( self.cli_inputs.selected_component, self.cli_inputs.component_id) # reset wallet self.tools.delete_wallet(datadir=self.datadir) self.tools.create_wallet(datadir=self.datadir, wallet_name='worker1.sqlite') if not self.tools.wallet_db_exists(): self.logger.exception("wallet db creation failed unexpectedly") command, env_vars = self.tools.generate_command() self.plugin_tools.spawn_process(command, env_vars=env_vars, id=self.id, component_name=self.COMPONENT_NAME, src=self.src, logfile=logfile, status_endpoint=status_endpoint, metadata=metadata) def stop(self) -> None: """some components require graceful shutdown via a REST API or RPC API but most can use the generic 'plugin_tools.kill_component()' function.""" is_new_terminal = not (self.cli_inputs.inline_flag or self.cli_inputs.background_flag) self.plugin_tools.call_for_component_id_or_type( self.COMPONENT_NAME, callable=partial(kill_process, graceful_wait_period=5.0, is_new_terminal=is_new_terminal)) self.logger.info( f"stopped selected {self.COMPONENT_NAME} instance (if running)") def reset(self) -> None: """ reset_electrumsv will be called many times for different component ids if applicable. - the reset entrypoint is only relevant for RegTest """ self.plugin_tools.modify_pythonpath_for_portability(self.src) def reset_electrumsv(component_dict: ComponentTypedDict) -> None: self.logger.debug( "Resetting state of RegTest electrumsv server...") # reset is sometimes used with no args and so the --deterministic-seed extension # doesn't take effect if hasattr(self.cli_inputs, 'deterministic_seed'): if self.cli_inputs.cli_extension_args['deterministic_seed']: set_deterministic_electrumsv_seed( self.cli_inputs.selected_component, self.id) metadata = component_dict.get('metadata', {}) assert metadata is not None # typing bug self.datadir = Path(metadata["datadir"]) self.id = component_dict.get('id') self.tools.delete_wallet(datadir=self.datadir) self.tools.create_wallet(datadir=self.datadir, wallet_name='worker1.sqlite') self.plugin_tools.call_for_component_id_or_type( self.COMPONENT_NAME, callable=reset_electrumsv) self.logger.info( "Reset of RegTest electrumsv wallet completed successfully")
class Plugin(AbstractPlugin): # As per woc-explorer/cli_inputs.js # TODO This is obsolete. ElectrumX is no longer supported in the SDK. Either we need to get # rid of the WOC components or replace them with an explorer component which we would # embed in the TestUI project. ELECTRUMX_HOST = os.environ.get("ELECTRUMX_HOST") or "127.0.0.1" ELECTRUMX_PORT = os.environ.get("ELECTRUMX_PORT") or 51001 # As per woc-explorer/app.js RPC_HOST = os.environ.get("RPC_HOST") or "127.0.0.1" RPC_PORT = int(os.environ.get("RPC_PORT") or 18332) RPC_USERNAME = os.environ.get("RPC_USERNAME") or "rpcuser" RPC_PASSWORD = os.environ.get("RPC_PASSWORD") or "rpcpassword" DEFAULT_PORT = 3002 RESERVED_PORTS: Set[int] = {DEFAULT_PORT} COMPONENT_NAME = get_directory_name(__file__) def __init__(self, cli_inputs: CLIInputs): self.cli_inputs = cli_inputs self.config = Config() self.plugin_tools = PluginTools(self, self.cli_inputs) self.tools = LocalTools(self) self.logger = logging.getLogger(self.COMPONENT_NAME) self.src = self.plugin_tools.get_source_dir("woc-explorer") self.datadir = None # N/A self.id = self.plugin_tools.get_id(self.COMPONENT_NAME) self.port = None # N/A self.component_info: Optional[Component] = None def install(self) -> None: if not self.cli_inputs.repo == "": # default self.logger.error( "ignoring --repo flag for whatsonchain - not applicable.") self.tools.fetch_whatsonchain( url="https://github.com/AustEcon/woc-explorer.git", branch='') self.tools.packages_whatsonchain() self.logger.debug(f"Installed {self.COMPONENT_NAME}") def start(self) -> None: self.logger.debug(f"Starting whatsonchain explorer...") assert self.src is not None # typing bug if not self.src.exists(): self.logger.error( f"source code directory does not exist - try 'electrumsv-sdk install " f"{self.COMPONENT_NAME}' to install the plugin first") sys.exit(1) if not self.tools.check_node_for_woc(self.RPC_HOST, self.RPC_PORT, self.RPC_USERNAME, self.RPC_PASSWORD): sys.exit(1) os.chdir(self.src) # npm without .cmd extension doesn't work with Popen shell=False if sys.platform == "win32": command = f"npm.cmd start" elif sys.platform in {"linux", "darwin"}: command = f"npm start" env_vars = { "PYTHONUNBUFFERED": "1", "ELECTRUMX_HOST": self.ELECTRUMX_HOST, "ELECTRUMX_PORT": str(self.ELECTRUMX_PORT), "RPC_HOST": self.RPC_HOST, "RPC_PORT": str(self.RPC_PORT), "RPC_USERNAME": self.RPC_USERNAME, "RPC_PASSWORD": self.RPC_PASSWORD, } self.id = self.plugin_tools.get_id(self.COMPONENT_NAME) logfile = self.plugin_tools.get_logfile_path(self.id) status_endpoint = "http://127.0.0.1:3002" self.plugin_tools.spawn_process(command, env_vars=env_vars, id=self.id, component_name=self.COMPONENT_NAME, src=self.src, logfile=logfile, status_endpoint=status_endpoint) def stop(self) -> None: """some components require graceful shutdown via a REST API or RPC API but most can use the generic 'app_state.kill_component()' function to track down the pid and kill the process.""" self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME, callable=kill_process) self.logger.info( f"stopped selected {self.COMPONENT_NAME} instance (if running)") def reset(self) -> None: self.logger.info("resetting the whatsonchain is not applicable")