Example #1
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 #2
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 #3
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.")