Example #1
0
    def __init__(self, cli_inputs: CLIInputs):
        self.cli_inputs = cli_inputs
        self.plugin_tools = PluginTools(self, self.cli_inputs)
        self.logger = logging.getLogger(self.COMPONENT_NAME)

        self.src = self.COMPONENT_PATH
        self.datadir = None  # dynamically allocated
        self.id = None  # dynamically allocated
        self.port = None  # dynamically allocated
        self.component_info: Optional[Component] = None
Example #2
0
    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
Example #3
0
    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 __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
Example #5
0
    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
Example #6
0
    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 = Path(electrumsv_node.FILE_PATH).parent
        self.datadir: Optional[Path] = None  # dynamically allocated
        self.id: Optional[str] = None  # dynamically allocated
        self.port: Optional[int] = None  # dynamically allocated
        self.p2p_port: Optional[int] = None  # dynamically allocated
        self.zmq_port: Optional[int] = None  # dynamically allocated
        self.component_info: Optional[Component] = None

        self.network = self.BITCOIN_NETWORK
Example #7
0
class Plugin(AbstractPlugin):
    SERVER_HOST = server_app.SERVER_HOST
    SERVER_PORT = server_app.SERVER_PORT
    RESERVED_PORTS: Set[int] = {SERVER_PORT}
    PING_URL = server_app.PING_URL

    COMPONENT_NAME = get_directory_name(__file__)
    COMPONENT_PATH = Path(os.path.dirname(os.path.abspath(__file__)))
    SCRIPT_PATH = COMPONENT_PATH / "server_app.py"

    def __init__(self, cli_inputs: CLIInputs):
        self.cli_inputs = cli_inputs
        self.plugin_tools = PluginTools(self, self.cli_inputs)
        self.logger = logging.getLogger(self.COMPONENT_NAME)

        self.src = self.COMPONENT_PATH
        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:
        self.logger.debug(
            f"Installing {self.COMPONENT_NAME} is not applicable")

    def start(self) -> None:
        self.id = self.plugin_tools.get_id(self.COMPONENT_NAME)
        logfile = self.plugin_tools.get_logfile_path(self.id)
        env_vars = {"PYTHONUNBUFFERED": "1"}
        command = f"{sys.executable} {self.SCRIPT_PATH}"

        self.plugin_tools.spawn_process(command,
                                        env_vars=env_vars,
                                        id=self.id,
                                        component_name=self.COMPONENT_NAME,
                                        src=self.src,
                                        logfile=logfile)

    def stop(self) -> None:
        self.logger.debug(
            "Attempting to kill the process if it is even running")
        self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME,
                                                        callable=kill_process)

    def reset(self) -> None:
        self.logger.info("resetting the status monitor is not applicable.")
Example #8
0
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")
Example #9
0
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")
Example #10
0
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):
    SERVER_HOST = "127.0.0.1"
    SERVER_PORT = 24242
    RESERVED_PORTS: Set[int] = {SERVER_PORT}

    COMPONENT_NAME = get_directory_name(__file__)
    COMPONENT_PATH = Path(os.path.dirname(os.path.abspath(__file__)))
    ELECTRUMSV_SERVER_MODULE_PATH = Path(
        MODULE_DIR).parent.parent.parent.joinpath("electrumsv-server")

    def __init__(self, cli_inputs: CLIInputs) -> None:
        self.cli_inputs = cli_inputs
        self.plugin_tools = PluginTools(self, self.cli_inputs)
        self.tools = LocalTools(self)
        self.logger = logging.getLogger(self.COMPONENT_NAME)

        self.src = self.COMPONENT_PATH
        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:
        self.logger.debug(
            f"Installing {self.COMPONENT_NAME} is not applicable")

    def start(self) -> None:
        self.datadir, self.id = self.plugin_tools.get_component_datadir(
            self.COMPONENT_NAME)
        logfile = self.plugin_tools.get_logfile_path(self.id)
        env_vars = {"PYTHONUNBUFFERED": "1"}
        os.makedirs(self.ELECTRUMSV_SERVER_MODULE_PATH.joinpath("data"),
                    exist_ok=True)
        os.chdir(self.ELECTRUMSV_SERVER_MODULE_PATH)
        command = f"{sys.executable} -m electrumsv_server --wwwroot-path=wwwroot " \
            f"--data-path={self.datadir} "

        network_choice = self.tools.get_network_choice()
        command += f"--{network_choice}"

        # These mapi attributes are added to cli_inputs as extension cli options
        # (see: extend_start_cli() above)
        if self.cli_inputs.cli_extension_args['mapi_broadcast']:
            command += (
                f" --mapi-broadcast "
                f"--mapi-host={self.cli_inputs.cli_extension_args['mapi_host']} "
                f"--mapi-port={self.cli_inputs.cli_extension_args['mapi_port']}"
            )

        self.plugin_tools.spawn_process(
            command,
            env_vars=env_vars,
            id=self.id,
            component_name=self.COMPONENT_NAME,
            src=self.src,
            logfile=logfile,
            metadata={"datadir": str(self.datadir)})

    def stop(self) -> None:
        self.logger.debug(
            "Attempting to kill the process if it is even running")
        self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME,
                                                        callable=kill_process)

    def reset(self) -> None:
        def reset_server(component_dict: ComponentTypedDict) -> None:
            metadata = component_dict.get("metadata", {})
            assert metadata is not None  # typing bug
            datadir: Path = Path(metadata["datadir"])
            if datadir.exists():
                shutil.rmtree(datadir)
                os.mkdir(datadir)
            else:
                os.makedirs(datadir, exist_ok=True)

        self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME,
                                                        callable=reset_server)
        self.logger.info(
            f"Reset of {self.COMPONENT_NAME} completed successfully.")
Example #12
0
class Plugin(AbstractPlugin):

    BITCOIN_NETWORK = os.getenv("BITCOIN_NETWORK", "regtest")

    DEFAULT_PORT = 18332
    DEFAULT_P2P_PORT = 18444
    DEFAULT_ZMQ_PORT = 28332

    # if ports == None -> set by deterministic port allocation
    NODE_PORT: int = int(os.environ.get("NODE_PORT") or DEFAULT_PORT)
    NODE_P2P_PORT: int = int(
        os.environ.get("NODE_P2P_PORT") or DEFAULT_P2P_PORT)
    NODE_ZMQ_PORT: int = int(
        os.environ.get("NODE_ZMQ_PORT") or DEFAULT_ZMQ_PORT)

    NODE_RPCALLOWIP = os.environ.get("NODE_RPCALLOWIP")  # else 127.0.0.1
    NODE_RPCBIND = os.environ.get("NODE_RPCBIND")

    RESERVED_PORTS: Set[int] = {DEFAULT_PORT, DEFAULT_P2P_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 = Path(electrumsv_node.FILE_PATH).parent
        self.datadir: Optional[Path] = None  # dynamically allocated
        self.id: Optional[str] = None  # dynamically allocated
        self.port: Optional[int] = None  # dynamically allocated
        self.p2p_port: Optional[int] = None  # dynamically allocated
        self.zmq_port: Optional[int] = None  # dynamically allocated
        self.component_info: Optional[Component] = None

        self.network = self.BITCOIN_NETWORK

    def install(self) -> None:
        """The node component has a pip installer at https://pypi.org/project/electrumsv-node/ and
        only official releases from pypi are supported"""
        self.plugin_tools.modify_pythonpath_for_portability(self.src)

        self.tools.fetch_node()
        self.logger.debug(f"Installed {self.COMPONENT_NAME}")

    def start(self) -> None:
        self.plugin_tools.modify_pythonpath_for_portability(self.src)

        # env vars take precedence for port and dbdir
        self.datadir, self.id = self.plugin_tools.allocate_datadir_and_id()
        self.tools.process_cli_args(
        )  # cli args may override network in env vars

        if self.NODE_PORT:
            self.port = self.NODE_PORT
        else:
            self.port = self.plugin_tools.allocate_port()

        if self.NODE_P2P_PORT:
            self.p2p_port = self.NODE_P2P_PORT
        else:
            self.p2p_port = self.plugin_tools.get_component_port(
                self.DEFAULT_P2P_PORT, self.COMPONENT_NAME, self.id)

        if self.NODE_ZMQ_PORT:
            self.zmq_port = self.NODE_ZMQ_PORT
        else:
            self.zmq_port = self.plugin_tools.get_component_port(
                self.DEFAULT_ZMQ_PORT, self.COMPONENT_NAME, self.id)

        extra_params = []
        # TODO: NODE_RPCALLOWIP and NODE_RPCBIND can be removed after the next release of
        #  electrumsv-node as they now default to 0.0.0.0/0 and 0.0.0.0 respectively
        if self.NODE_RPCALLOWIP:
            extra_params.append(f"-rpcallowip={self.NODE_RPCALLOWIP}")
        if self.NODE_RPCBIND:
            extra_params.append(f"-rpcbind={self.NODE_RPCBIND}")

        shell_command = electrumsv_node.shell_command(
            data_path=str(self.datadir),
            rpcport=self.port,
            p2p_port=self.p2p_port,
            zmq_port=self.zmq_port,
            network=self.network,
            print_to_console=True,
            extra_params=extra_params)

        command = " ".join(shell_command)
        logfile = self.plugin_tools.get_logfile_path(self.id)
        self.plugin_tools.spawn_process(
            command,
            env_vars=os.environ.copy(),
            id=self.id,
            component_name=self.COMPONENT_NAME,
            src=self.src,
            logfile=logfile,
            status_endpoint=f"http://*****:*****@127.0.0.1:{self.port}",
            metadata=ComponentMetadata(datadir=str(self.datadir),
                                       rpcport=self.port,
                                       p2p_port=self.p2p_port))
        if electrumsv_node.is_node_running():
            return
        else:
            self.logger.exception("node failed to start")

    def stop(self) -> None:
        """The bitcoin node requires graceful shutdown via the RPC API - a good example of why this
        entrypoint is provided for user customizations (rather than always killing the process)."""
        def stop_node(component_dict: ComponentTypedDict) -> None:
            metadata = component_dict.get("metadata", {})
            assert metadata is not None  # typing bug
            rpcport = metadata.get("rpcport")
            if not rpcport:
                raise Exception("rpcport data not found")
            electrumsv_node.stop(rpcport=rpcport)

        self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME,
                                                        callable=stop_node)
        self.logger.info(
            f"stopped selected {self.COMPONENT_NAME} instance (if running)")

    def reset(self) -> None:
        def reset_node(component_dict: ComponentTypedDict) -> None:
            metadata = component_dict.get("metadata", {})
            assert metadata is not None  # typing bug
            rpcport = metadata.get('rpcport')
            datadir = metadata.get("datadir")
            if not rpcport:
                raise Exception("rpcport data not found")
            electrumsv_node.reset(data_path=datadir, rpcport=rpcport)

        self.plugin_tools.call_for_component_id_or_type(self.COMPONENT_NAME,
                                                        callable=reset_node)
        self.logger.info(
            "Reset of RegTest bitcoin daemon completed successfully.")
Example #13
0
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")