From 6257d9e266212a037a4c38f215ae3c2c7e9e6db6 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 29 May 2025 18:14:40 +0000 Subject: [PATCH] constants.py: add datadir_subdir, cli_flag, config_key methods - use these to generalise recurring "switch-like" ifs - this effectively also adds a `--mainnet` CLI option - closes https://github.com/spesmilo/electrum/issues/9790 --- electrum/commands.py | 26 ++++++-------------------- electrum/constants.py | 30 ++++++++++++++++++++++++++++-- electrum/simple_config.py | 31 +++++++++++++++---------------- run_electrum | 12 ++---------- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index b857db438..78546f7bb 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -2090,21 +2090,10 @@ def add_global_options(parser, suppress=False): group.add_argument( "-P", "--portable", action="store_true", dest="portable", default=False, help=argparse.SUPPRESS if suppress else "Use local 'electrum_data' directory") - group.add_argument( - "--testnet", action="store_true", dest="testnet", default=False, - help=argparse.SUPPRESS if suppress else "Use Testnet") - group.add_argument( - "--testnet4", action="store_true", dest="testnet4", default=False, - help=argparse.SUPPRESS if suppress else "Use Testnet4") - group.add_argument( - "--regtest", action="store_true", dest="regtest", default=False, - help=argparse.SUPPRESS if suppress else "Use Regtest") - group.add_argument( - "--simnet", action="store_true", dest="simnet", default=False, - help=argparse.SUPPRESS if suppress else "Use Simnet") - group.add_argument( - "--signet", action="store_true", dest="signet", default=False, - help=argparse.SUPPRESS if suppress else "Use Signet") + for chain in constants.NETS_LIST: + group.add_argument( + f"--{chain.cli_flag()}", action="store_true", dest=chain.config_key(), default=False, + help=argparse.SUPPRESS if suppress else f"Use {chain.NET_NAME} chain") group.add_argument( "-o", "--offline", action="store_true", dest=SimpleConfig.NETWORK_OFFLINE.key(), default=None, help=argparse.SUPPRESS if suppress else "Run offline") @@ -2134,11 +2123,8 @@ def get_simple_parser(): parser = PassThroughOptionParser() parser.add_option("-D", "--dir", dest="electrum_path", help="electrum directory") parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") - parser.add_option("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") - parser.add_option("--testnet4", action="store_true", dest="testnet4", default=False, help="Use Testnet4") - parser.add_option("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest") - parser.add_option("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet") - parser.add_option("--signet", action="store_true", dest="signet", default=False, help="Use Signet") + for chain in constants.NETS_LIST: + parser.add_option(f"--{chain.cli_flag()}", action="store_true", dest=chain.config_key(), default=False, help=f"Use {chain.NET_NAME} chain") return parser diff --git a/electrum/constants.py b/electrum/constants.py index 11c0f8baf..f3da2303d 100644 --- a/electrum/constants.py +++ b/electrum/constants.py @@ -25,7 +25,7 @@ import os import json -from typing import Sequence, Tuple, Mapping, Type, List +from typing import Sequence, Tuple, Mapping, Type, List, Optional from .lntransport import LNPeerAddr from .util import inv_dict, all_subclasses, classproperty @@ -118,6 +118,23 @@ class AbstractNet: cls._cached_checkpoints = read_json(os.path.join('chains', cls.NET_NAME, 'checkpoints.json'), default_file) return cls._cached_checkpoints + @classmethod + def datadir_subdir(cls) -> Optional[str]: + """The name of the folder in the filesystem. + None means top-level, used by mainnet. + """ + return cls.NET_NAME + + @classmethod + def cli_flag(cls) -> str: + """as used in e.g. `$ run_electrum --testnet4`""" + return cls.NET_NAME + + @classmethod + def config_key(cls) -> str: + """as used for SimpleConfig.get()""" + return cls.NET_NAME + class BitcoinMainnet(AbstractNet): @@ -156,6 +173,10 @@ class BitcoinMainnet(AbstractNet): 'lseed.darosior.ninja', ] + @classmethod + def datadir_subdir(cls): + return None + class BitcoinTestnet(AbstractNet): @@ -229,7 +250,12 @@ class BitcoinSignet(BitcoinTestnet): LN_DNS_SEEDS = [] -NETS_LIST = tuple(all_subclasses(AbstractNet)) +NETS_LIST = tuple(all_subclasses(AbstractNet)) # type: Sequence[Type[AbstractNet]] + +assert len(NETS_LIST) == len(set([chain.NET_NAME for chain in NETS_LIST])), "NET_NAME must be unique for each concrete AbstractNet" +assert len(NETS_LIST) == len(set([chain.datadir_subdir() for chain in NETS_LIST])), "datadir must be unique for each concrete AbstractNet" +assert len(NETS_LIST) == len(set([chain.cli_flag() for chain in NETS_LIST])), "cli_flag must be unique for each concrete AbstractNet" +assert len(NETS_LIST) == len(set([chain.config_key() for chain in NETS_LIST])), "config_key must be unique for each concrete AbstractNet" # don't import net directly, import the module instead (so that net is singleton) net = BitcoinMainnet # type: Type[AbstractNet] diff --git a/electrum/simple_config.py b/electrum/simple_config.py index 9975597c1..a20eb350b 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -2,11 +2,12 @@ import json import threading import os import stat -from typing import Union, Optional, Dict, Sequence, Any, Set, Callable, AbstractSet +from typing import Union, Optional, Dict, Sequence, Any, Set, Callable, AbstractSet, Type from functools import cached_property from copy import deepcopy +from . import constants from . import util from . import invoices from .util import base_units, base_unit_name_to_decimal_point, decimal_point_to_base_unit_name, UnknownBaseUnit, DECIMAL_POINT_DEFAULT @@ -232,25 +233,23 @@ class SimpleConfig(Logger): make_dir(path, allow_symlink=False) return path + def get_selected_chain(self) -> Type[constants.AbstractNet]: + selected_chains = [ + chain for chain in constants.NETS_LIST + if self.get(chain.config_key())] + if selected_chains: + # note: if multiple are selected, we just pick one deterministically random + return selected_chains[0] + return constants.BitcoinMainnet + def electrum_path(self): path = self.electrum_path_root() - if self.get('testnet'): - path = os.path.join(path, 'testnet') - make_dir(path, allow_symlink=False) - elif self.get('testnet4'): - path = os.path.join(path, 'testnet4') - make_dir(path, allow_symlink=False) - elif self.get('regtest'): - path = os.path.join(path, 'regtest') - make_dir(path, allow_symlink=False) - elif self.get('simnet'): - path = os.path.join(path, 'simnet') - make_dir(path, allow_symlink=False) - elif self.get('signet'): - path = os.path.join(path, 'signet') + chain = self.get_selected_chain() + if subdir := chain.datadir_subdir(): + path = os.path.join(path, subdir) make_dir(path, allow_symlink=False) - self.logger.info(f"electrum directory {path}") + self.logger.info(f"electrum directory {path} (chain={chain.NET_NAME})") return path def rename_config_keys(self, config, keypairs, deprecation_warning=False): diff --git a/run_electrum b/run_electrum index a13391f10..dcf2dc0c5 100755 --- a/run_electrum +++ b/run_electrum @@ -420,16 +420,8 @@ def main(): _logger.info(f"get_default_language: failed. got exc={e!r}") set_language(lang) - if config.get('testnet'): - constants.BitcoinTestnet.set_as_network() - elif config.get('testnet4'): - constants.BitcoinTestnet4.set_as_network() - elif config.get('regtest'): - constants.BitcoinRegtest.set_as_network() - elif config.get('simnet'): - constants.BitcoinSimnet.set_as_network() - elif config.get('signet'): - constants.BitcoinSignet.set_as_network() + chain = config.get_selected_chain() + chain.set_as_network() # check if we received a valid payment identifier uri = config_options.get('url')