diff --git a/chia/daemon/server.py b/chia/daemon/server.py index 22594c13cff3..ce6fe3f474ac 100644 --- a/chia/daemon/server.py +++ b/chia/daemon/server.py @@ -1542,7 +1542,7 @@ async def async_run_daemon(root_path: Path, wait_for_unlock: bool = False) -> in chia_init(root_path, should_check_keys=(not wait_for_unlock)) config = load_config(root_path, "config.yaml") setproctitle("chia_daemon") - initialize_service_logging("daemon", config) + initialize_service_logging("daemon", config, root_path=root_path) crt_path = root_path / config["daemon_ssl"]["private_crt"] key_path = root_path / config["daemon_ssl"]["private_key"] ca_crt_path = root_path / config["private_ssl_ca"]["crt"] @@ -1589,11 +1589,13 @@ def run_daemon(root_path: Path, wait_for_unlock: bool = False) -> int: def main() -> int: - from chia.util.default_root import DEFAULT_ROOT_PATH + from chia.util.default_root import resolve_root_path from chia.util.keychain import Keychain + root_path = resolve_root_path(override=None) + wait_for_unlock = "--wait-for-unlock" in sys.argv[1:] and Keychain.is_keyring_locked() - return run_daemon(DEFAULT_ROOT_PATH, wait_for_unlock) + return run_daemon(root_path, wait_for_unlock) if __name__ == "__main__": diff --git a/chia/seeder/dns_server.py b/chia/seeder/dns_server.py index 941cc106ea9c..1a68a6edcdd4 100644 --- a/chia/seeder/dns_server.py +++ b/chia/seeder/dns_server.py @@ -22,7 +22,7 @@ from chia.server.signal_handlers import SignalHandlers from chia.util.chia_logging import initialize_service_logging from chia.util.config import load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.path import path_from_root SERVICE_NAME = "seeder" @@ -577,12 +577,13 @@ def create_dns_server_service(config: dict[str, Any], root_path: Path) -> DNSSer def main() -> None: # pragma: no cover freeze_support() - root_path = DEFAULT_ROOT_PATH + root_path = resolve_root_path(override=None) + # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) dns_server = create_dns_server_service(config, root_path) asyncio.run(run_dns_server(dns_server)) diff --git a/chia/seeder/start_crawler.py b/chia/seeder/start_crawler.py index a5fa0d7426dd..158d1b4d86f6 100644 --- a/chia/seeder/start_crawler.py +++ b/chia/seeder/start_crawler.py @@ -18,7 +18,7 @@ from chia.types.aliases import CrawlerService from chia.util.chia_logging import initialize_service_logging from chia.util.config import load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path # See: https://bugs.python.org/issue29288 "".encode("idna") @@ -65,15 +65,15 @@ def create_full_node_crawler_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config overrides = service_config["network_overrides"]["constants"][service_config["selected_network"]] updated_constants = replace_str_to_bytes(DEFAULT_CONSTANTS, **overrides) - initialize_service_logging(service_name=SERVICE_NAME, config=config) - service = create_full_node_crawler_service(DEFAULT_ROOT_PATH, config, updated_constants) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) + service = create_full_node_crawler_service(root_path, config, updated_constants) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -83,7 +83,9 @@ async def async_main() -> int: def main() -> int: freeze_support() - return async_run(async_main()) + root_path = resolve_root_path(override=None) + + return async_run(async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_data_layer.py b/chia/server/start_data_layer.py index fc15b4efea95..4ea67b66107c 100644 --- a/chia/server/start_data_layer.py +++ b/chia/server/start_data_layer.py @@ -20,7 +20,7 @@ from chia.types.aliases import DataLayerService, WalletService from chia.util.chia_logging import initialize_logging from chia.util.config import load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.ints import uint16 from chia.util.task_timing import maybe_manage_task_instrumentation @@ -91,26 +91,26 @@ def create_data_layer_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml", fill_missing_services=True) - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME, fill_missing_services=True) + config = load_config(root_path, "config.yaml", fill_missing_services=True) + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME, fill_missing_services=True) config[SERVICE_NAME] = service_config initialize_logging( service_name=SERVICE_NAME, logging_config=service_config["logging"], - root_path=DEFAULT_ROOT_PATH, + root_path=root_path, ) create_all_ssl( - root_path=DEFAULT_ROOT_PATH, + root_path=root_path, private_node_names=["data_layer"], public_node_names=["data_layer"], overwrite=False, ) plugins_config = config["data_layer"].get("plugins", {}) - service_dir = DEFAULT_ROOT_PATH / SERVICE_NAME + service_dir = root_path / SERVICE_NAME old_uploaders = config["data_layer"].get("uploaders", []) new_uploaders = plugins_config.get("uploaders", []) @@ -130,7 +130,7 @@ async def async_main() -> int: *conf_file_uploaders, ] - service = create_data_layer_service(DEFAULT_ROOT_PATH, config, downloaders, uploaders) + service = create_data_layer_service(root_path, config, downloaders, uploaders) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -139,10 +139,12 @@ async def async_main() -> int: def main() -> int: + root_path = resolve_root_path(override=None) + with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_farmer.py b/chia/server/start_farmer.py index 0acedf562d05..056ef3e1d6c8 100644 --- a/chia/server/start_farmer.py +++ b/chia/server/start_farmer.py @@ -17,7 +17,7 @@ from chia.types.aliases import FarmerService from chia.util.chia_logging import initialize_service_logging from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.keychain import Keychain from chia.util.task_timing import maybe_manage_task_instrumentation @@ -68,16 +68,16 @@ def create_farmer_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config - config_pool = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", "pool") + config_pool = load_config_cli(root_path, "config.yaml", "pool") config["pool"] = config_pool - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) - service = create_farmer_service(DEFAULT_ROOT_PATH, config, config_pool, DEFAULT_CONSTANTS) + service = create_farmer_service(root_path, config, config_pool, DEFAULT_CONSTANTS) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -86,10 +86,12 @@ async def async_main() -> int: def main() -> int: + root_path = resolve_root_path(override=None) + with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_full_node.py b/chia/server/start_full_node.py index f5e9f796901f..c628f2a8b094 100644 --- a/chia/server/start_full_node.py +++ b/chia/server/start_full_node.py @@ -18,7 +18,7 @@ from chia.types.aliases import FullNodeService from chia.util.chia_logging import initialize_service_logging from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.ints import uint16 from chia.util.task_timing import maybe_manage_task_instrumentation @@ -72,17 +72,17 @@ async def create_full_node_service( ) -async def async_main(service_config: dict[str, Any]) -> int: +async def async_main(service_config: dict[str, Any], root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") + config = load_config(root_path, "config.yaml") config[SERVICE_NAME] = service_config network_id = service_config["selected_network"] overrides = service_config["network_overrides"]["constants"][network_id] update_testnet_overrides(network_id, overrides) updated_constants = replace_str_to_bytes(DEFAULT_CONSTANTS, **overrides) - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) - service = await create_full_node_service(DEFAULT_ROOT_PATH, config, updated_constants) + service = await create_full_node_service(root_path, config, updated_constants) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -92,11 +92,12 @@ async def async_main(service_config: dict[str, Any]) -> int: def main() -> int: freeze_support() + root_path = resolve_root_path(override=None) with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) target_peer_count = service_config.get("target_peer_count", 40) - service_config.get( "target_outbound_peer_count", 8 ) @@ -104,7 +105,7 @@ def main() -> int: target_peer_count = None if not service_config.get("use_chia_loop_policy", True): target_peer_count = None - return async_run(coro=async_main(service_config), connection_limit=target_peer_count) + return async_run(coro=async_main(service_config, root_path=root_path), connection_limit=target_peer_count) if __name__ == "__main__": diff --git a/chia/server/start_harvester.py b/chia/server/start_harvester.py index a91d29c369ed..19559f5985b7 100644 --- a/chia/server/start_harvester.py +++ b/chia/server/start_harvester.py @@ -18,7 +18,7 @@ from chia.types.peer_info import UnresolvedPeerInfo from chia.util.chia_logging import initialize_service_logging from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.task_timing import maybe_manage_task_instrumentation # See: https://bugs.python.org/issue29288 @@ -64,15 +64,15 @@ def create_harvester_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) farmer_peers = get_unresolved_peer_infos(service_config, NodeType.FARMER) - service = create_harvester_service(DEFAULT_ROOT_PATH, config, DEFAULT_CONSTANTS, farmer_peers) + service = create_harvester_service(root_path, config, DEFAULT_CONSTANTS, farmer_peers) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -81,10 +81,12 @@ async def async_main() -> int: def main() -> int: + root_path = resolve_root_path(override=None) + with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_introducer.py b/chia/server/start_introducer.py index d661fcc61987..e8208875e70f 100644 --- a/chia/server/start_introducer.py +++ b/chia/server/start_introducer.py @@ -14,7 +14,7 @@ from chia.types.aliases import IntroducerService from chia.util.chia_logging import initialize_service_logging from chia.util.config import load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.task_timing import maybe_manage_task_instrumentation # See: https://bugs.python.org/issue29288 @@ -53,14 +53,14 @@ def create_introducer_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) - service = create_introducer_service(DEFAULT_ROOT_PATH, config) + service = create_introducer_service(root_path, config) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -69,10 +69,12 @@ async def async_main() -> int: def main() -> int: + root_path = resolve_root_path(override=None) + with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_timelord.py b/chia/server/start_timelord.py index 93b789dc0e03..357b1c2a4fca 100644 --- a/chia/server/start_timelord.py +++ b/chia/server/start_timelord.py @@ -17,7 +17,7 @@ from chia.types.aliases import TimelordService from chia.util.chia_logging import initialize_service_logging from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.task_timing import maybe_manage_task_instrumentation # See: https://bugs.python.org/issue29288 @@ -61,14 +61,14 @@ def create_timelord_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) - service = create_timelord_service(DEFAULT_ROOT_PATH, config, DEFAULT_CONSTANTS) + service = create_timelord_service(root_path, config, DEFAULT_CONSTANTS) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -77,10 +77,12 @@ async def async_main() -> int: def main() -> int: + root_path = resolve_root_path(override=None) + with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/server/start_wallet.py b/chia/server/start_wallet.py index e46dcf5a273d..885b8c1b04e3 100644 --- a/chia/server/start_wallet.py +++ b/chia/server/start_wallet.py @@ -16,7 +16,7 @@ from chia.types.aliases import WalletService from chia.util.chia_logging import initialize_service_logging from chia.util.config import get_unresolved_peer_infos, load_config, load_config_cli -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.keychain import Keychain from chia.util.task_timing import maybe_manage_task_instrumentation from chia.wallet.wallet_node import WalletNode @@ -72,10 +72,10 @@ def create_wallet_service( ) -async def async_main() -> int: +async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(DEFAULT_ROOT_PATH, "config.yaml") - service_config = load_config_cli(DEFAULT_ROOT_PATH, "config.yaml", SERVICE_NAME) + config = load_config(root_path, "config.yaml") + service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config # This is simulator @@ -89,9 +89,9 @@ async def async_main() -> int: service_config["selected_network"] = "testnet0" else: constants = DEFAULT_CONSTANTS - initialize_service_logging(service_name=SERVICE_NAME, config=config) + initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path) - service = create_wallet_service(DEFAULT_ROOT_PATH, config, constants) + service = create_wallet_service(root_path, config, constants) async with SignalHandlers.manage() as signal_handlers: await service.setup_process_global_state(signal_handlers=signal_handlers) await service.run() @@ -101,11 +101,12 @@ async def async_main() -> int: def main() -> int: freeze_support() + root_path = resolve_root_path(override=None) with maybe_manage_task_instrumentation( enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None ): - return async_run(coro=async_main()) + return async_run(coro=async_main(root_path=root_path)) if __name__ == "__main__": diff --git a/chia/simulator/start_simulator.py b/chia/simulator/start_simulator.py index 0cd4c418eed7..f50d323fc74d 100644 --- a/chia/simulator/start_simulator.py +++ b/chia/simulator/start_simulator.py @@ -19,7 +19,7 @@ from chia.util.bech32m import decode_puzzle_hash from chia.util.chia_logging import initialize_logging from chia.util.config import load_config, load_config_cli, override_config -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.ints import uint16 SimulatorFullNodeService = Service[FullNode, FullNodeSimulator, SimulatorFullNodeRpcApi] @@ -77,8 +77,12 @@ class StartedSimulator: async def async_main( test_mode: bool = False, automated_testing: bool = False, - root_path: Path = DEFAULT_ROOT_PATH, + root_path: Optional[Path] = None, ) -> StartedSimulator: + root_path = resolve_root_path(override=root_path) + # helping mypy out for now + assert root_path is not None + # Same as full node, but the root_path is defined above config = load_config(root_path, "config.yaml") service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) @@ -130,7 +134,9 @@ async def async_main( def main() -> int: freeze_support() - return async_run(async_main()).exit_code + root_path = resolve_root_path(override=None) + + return async_run(async_main(root_path=root_path)).exit_code if __name__ == "__main__": diff --git a/chia/timelord/timelord_launcher.py b/chia/timelord/timelord_launcher.py index 93cf657eab72..c99c133e21c6 100644 --- a/chia/timelord/timelord_launcher.py +++ b/chia/timelord/timelord_launcher.py @@ -16,7 +16,7 @@ from chia.server.signal_handlers import SignalHandlers from chia.util.chia_logging import initialize_logging from chia.util.config import load_config -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import resolve_root_path from chia.util.network import resolve from chia.util.setproctitle import setproctitle @@ -176,7 +176,8 @@ def main(): if os.name == "nt": log.info("Timelord launcher not supported on Windows.") return 1 - root_path = DEFAULT_ROOT_PATH + root_path = resolve_root_path(override=None) + setproctitle("chia_timelord_launcher") net_config = load_config(root_path, "config.yaml") config = net_config["timelord_launcher"] diff --git a/chia/util/chia_logging.py b/chia/util/chia_logging.py index 8dfaceccc1d0..0870a988ea20 100644 --- a/chia/util/chia_logging.py +++ b/chia/util/chia_logging.py @@ -11,7 +11,6 @@ from chia import __version__ from chia.util.chia_version import chia_short_version -from chia.util.default_root import DEFAULT_ROOT_PATH from chia.util.path import path_from_root default_log_level = "WARNING" @@ -128,8 +127,7 @@ def set_log_level(log_level: str, service_name: str) -> list[str]: return error_strings -def initialize_service_logging(service_name: str, config: dict[str, Any]) -> None: - logging_root_path = DEFAULT_ROOT_PATH +def initialize_service_logging(service_name: str, config: dict[str, Any], root_path: Path) -> None: if service_name == "daemon": # TODO: Maybe introduce a separate `daemon` section in the config instead of having `daemon_port`, `logging` # and the daemon related stuff as top level entries. @@ -141,6 +139,6 @@ def initialize_service_logging(service_name: str, config: dict[str, Any]) -> Non initialize_logging( service_name=service_name, logging_config=logging_config, - root_path=logging_root_path, + root_path=root_path, beta_root_path=beta_config_path, ) diff --git a/chia/util/default_root.py b/chia/util/default_root.py index 0ef024c7f695..46930b509fd6 100644 --- a/chia/util/default_root.py +++ b/chia/util/default_root.py @@ -4,9 +4,24 @@ import os from pathlib import Path +from typing import Optional DEFAULT_ROOT_PATH = Path(os.path.expanduser(os.getenv("CHIA_ROOT", "~/.chia/mainnet"))).resolve() DEFAULT_KEYS_ROOT_PATH = Path(os.path.expanduser(os.getenv("CHIA_KEYS_ROOT", "~/.chia_keys"))).resolve() SIMULATOR_ROOT_PATH = Path(os.path.expanduser(os.getenv("CHIA_SIMULATOR_ROOT", "~/.chia/simulator"))).resolve() + + +def resolve_root_path(*, override: Optional[Path]) -> Path: + candidates = [ + override, + os.environ.get("CHIA_ROOT"), + "~/.chia/mainnet", + ] + + for candidate in candidates: + if candidate is not None: + return Path(candidate).expanduser().resolve() + + raise RuntimeError("unreachable: last candidate is hardcoded to be found")