Esempio n. 1
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")
Esempio n. 2
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")
Esempio n. 3
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")
Esempio n. 4
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")