diff --git a/chia/_tests/conftest.py b/chia/_tests/conftest.py index 8ce767056c49..9305887a267b 100644 --- a/chia/_tests/conftest.py +++ b/chia/_tests/conftest.py @@ -92,7 +92,7 @@ from chia_rs.sized_ints import uint128 from chia._tests.environments.wallet import WalletEnvironment, WalletState, WalletTestFramework -from chia._tests.util.setup_nodes import setup_farmer_multi_harvester +from chia._tests.util.setup_nodes import setup_farmer_solver_multi_harvester from chia.full_node.full_node_rpc_client import FullNodeRpcClient from chia.simulator.block_tools import BlockTools, create_block_tools_async, test_constants from chia.simulator.keyring import TempKeyring @@ -866,7 +866,7 @@ async def farmer_one_harvester_simulator_wallet( ] ]: async with setup_simulators_and_wallets_service(1, 1, blockchain_constants) as (nodes, wallets, bt): - async with setup_farmer_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True) as ( + async with setup_farmer_solver_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True) as ( harvester_services, farmer_service, _, @@ -879,7 +879,9 @@ async def farmer_one_harvester_simulator_wallet( @pytest.fixture(scope="function") async def farmer_one_harvester(tmp_path: Path, get_b_tools: BlockTools) -> AsyncIterator[FarmerOneHarvester]: - async with setup_farmer_multi_harvester(get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True) as _: + async with setup_farmer_solver_multi_harvester( + get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True + ) as _: yield _ @@ -890,13 +892,11 @@ async def farmer_one_harvester(tmp_path: Path, get_b_tools: BlockTools) -> Async async def farmer_one_harvester_solver( tmp_path: Path, get_b_tools: BlockTools ) -> AsyncIterator[FarmerOneHarvesterSolver]: - async with setup_farmer_multi_harvester(get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True) as ( - harvester_services, - farmer_service, - bt, - ): - farmer_peer = UnresolvedPeerInfo(bt.config["self_hostname"], farmer_service._server.get_port()) - async with setup_solver(tmp_path / "solver", bt, bt.constants, farmer_peer=farmer_peer) as solver_service: + async with setup_solver(tmp_path / "solver", get_b_tools, get_b_tools.constants) as solver_service: + solver_peer = UnresolvedPeerInfo(get_b_tools.config["self_hostname"], solver_service._server.get_port()) + async with setup_farmer_solver_multi_harvester( + get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=True, solver_peer=solver_peer + ) as (harvester_services, farmer_service, bt): yield harvester_services, farmer_service, solver_service, bt @@ -904,7 +904,9 @@ async def farmer_one_harvester_solver( async def farmer_one_harvester_not_started( tmp_path: Path, get_b_tools: BlockTools ) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]: - async with setup_farmer_multi_harvester(get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=False) as _: + async with setup_farmer_solver_multi_harvester( + get_b_tools, 1, tmp_path, get_b_tools.constants, start_services=False + ) as _: yield _ @@ -912,7 +914,9 @@ async def farmer_one_harvester_not_started( async def farmer_two_harvester_not_started( tmp_path: Path, get_b_tools: BlockTools ) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]: - async with setup_farmer_multi_harvester(get_b_tools, 2, tmp_path, get_b_tools.constants, start_services=False) as _: + async with setup_farmer_solver_multi_harvester( + get_b_tools, 2, tmp_path, get_b_tools.constants, start_services=False + ) as _: yield _ @@ -920,7 +924,9 @@ async def farmer_two_harvester_not_started( async def farmer_three_harvester_not_started( tmp_path: Path, get_b_tools: BlockTools ) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]: - async with setup_farmer_multi_harvester(get_b_tools, 3, tmp_path, get_b_tools.constants, start_services=False) as _: + async with setup_farmer_solver_multi_harvester( + get_b_tools, 3, tmp_path, get_b_tools.constants, start_services=False + ) as _: yield _ @@ -1301,10 +1307,13 @@ async def farmer_harvester_2_simulators_zero_bits_plot_filter( ) for index in range(len(bts)) ] - - [harvester_service], farmer_service, _ = await async_exit_stack.enter_async_context( - setup_farmer_multi_harvester(bt, 1, tmp_path, bt.constants, start_services=True) - ) + async with setup_solver(tmp_path / "solver", bt, bt.constants) as solver_service: + solver_peer = UnresolvedPeerInfo(bt.config["self_hostname"], solver_service._server.get_port()) + [harvester_service], farmer_service, _ = await async_exit_stack.enter_async_context( + setup_farmer_solver_multi_harvester( + bt, 1, tmp_path, bt.constants, start_services=True, solver_peer=solver_peer + ) + ) yield farmer_service, harvester_service, simulators[0], simulators[1], bt diff --git a/chia/_tests/core/test_farmer_harvester_rpc.py b/chia/_tests/core/test_farmer_harvester_rpc.py index 0052e5471eeb..77ceb597b63f 100644 --- a/chia/_tests/core/test_farmer_harvester_rpc.py +++ b/chia/_tests/core/test_farmer_harvester_rpc.py @@ -30,11 +30,15 @@ plot_matches_filter, ) from chia.farmer.farmer_rpc_client import FarmerRpcClient +from chia.farmer.farmer_service import FarmerService +from chia.harvester.harvester_service import HarvesterService from chia.plot_sync.receiver import Receiver, get_list_or_len from chia.plotting.util import add_plot_directory from chia.protocols import farmer_protocol from chia.protocols.harvester_protocol import Plot -from chia.simulator.block_tools import get_plot_dir +from chia.rpc.rpc_client import ResponseFailureError +from chia.simulator.block_tools import BlockTools, get_plot_dir +from chia.solver.solver_service import SolverService from chia.util.bech32m import decode_puzzle_hash, encode_puzzle_hash from chia.util.config import load_config, lock_and_load_config, save_config from chia.util.hash import std_hash @@ -503,3 +507,32 @@ async def assert_added(path: Path) -> None: added_directories = await harvester_rpc_client.get_plot_directories() assert str(test_path) in added_directories assert str(test_path_other) in added_directories + + +@pytest.mark.anyio +async def test_farmer_connect_to_solver( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, solver_service, bt = farmer_one_harvester_solver + assert farmer_service.rpc_server is not None + farmer_rpc_client = await FarmerRpcClient.create( + bt.config["self_hostname"], + farmer_service.rpc_server.listen_port, + farmer_service.root_path, + farmer_service.config, + ) + + try: + # Test successful connection to existing solver + solver_host = bt.config["self_hostname"] + solver_port = solver_service._server.get_port() + result = await farmer_rpc_client.connect_to_solver(solver_host, solver_port) + assert result["success"] is True + + # Test connection failure to non-existent solver + with pytest.raises(ResponseFailureError) as exc_info: + await farmer_rpc_client.connect_to_solver("localhost", 65000) + assert "Could not connect to solver" in exc_info.value.response["error"] + finally: + farmer_rpc_client.close() + await farmer_rpc_client.await_closed() diff --git a/chia/_tests/farmer_harvester/test_filter_prefix_bits.py b/chia/_tests/farmer_harvester/test_filter_prefix_bits.py index b4bfedd016f0..78941d9535dd 100644 --- a/chia/_tests/farmer_harvester/test_filter_prefix_bits.py +++ b/chia/_tests/farmer_harvester/test_filter_prefix_bits.py @@ -11,7 +11,7 @@ from chia._tests.conftest import ConsensusMode from chia._tests.core.test_farmer_harvester_rpc import wait_for_plot_sync -from chia._tests.util.setup_nodes import setup_farmer_multi_harvester +from chia._tests.util.setup_nodes import setup_farmer_solver_multi_harvester from chia._tests.util.time_out_assert import time_out_assert from chia.farmer.farmer_api import FarmerAPI from chia.farmer.farmer_rpc_client import FarmerRpcClient @@ -60,7 +60,7 @@ async def have_connections() -> bool: ) new_config = local_b_tools._config local_b_tools.change_config(new_config) - async with setup_farmer_multi_harvester( + async with setup_farmer_solver_multi_harvester( local_b_tools, 1, tmp_path, local_b_tools.constants, start_services=True ) as (harvesters, farmer_service, _): harvester_service = harvesters[0] diff --git a/chia/_tests/farmer_harvester/test_third_party_harvesters.py b/chia/_tests/farmer_harvester/test_third_party_harvesters.py index 99cec77f8699..68211046e1ff 100644 --- a/chia/_tests/farmer_harvester/test_third_party_harvesters.py +++ b/chia/_tests/farmer_harvester/test_third_party_harvesters.py @@ -84,17 +84,16 @@ async def test_harvester_receive_source_signing_data( full_node_1: FullNode = full_node_service_1._node full_node_2: FullNode = full_node_service_2._node + await time_out_assert(60, node_type_connected, True, farmer.server, NodeType.HARVESTER) # Connect peers to each other farmer_service.add_peer( UnresolvedPeerInfo(str(full_node_service_2.self_hostname), full_node_service_2._server.get_port()) ) + await time_out_assert(60, node_type_connected, True, farmer.server, NodeType.FULL_NODE) full_node_service_2.add_peer( UnresolvedPeerInfo(str(full_node_service_1.self_hostname), full_node_service_1._server.get_port()) ) - - await wait_until_node_type_connected(farmer.server, NodeType.FULL_NODE) - await wait_until_node_type_connected(farmer.server, NodeType.HARVESTER) # Should already be connected - await wait_until_node_type_connected(full_node_1.server, NodeType.FULL_NODE) + await time_out_assert(60, node_type_connected, True, full_node_1.server, NodeType.FULL_NODE) # Prepare test data blocks: list[FullBlock] @@ -429,12 +428,11 @@ def log_has_new_text() -> bool: return False -async def wait_until_node_type_connected(server: ChiaServer, node_type: NodeType) -> WSChiaConnection: - while True: - for peer in server.all_connections.values(): - if peer.connection_type == node_type.value: - return peer - await asyncio.sleep(1) +def node_type_connected(server: ChiaServer, node_type: NodeType) -> bool: + for peer in server.all_connections.values(): + if peer.connection_type == node_type.value: + return True + return False def decode_sp( diff --git a/chia/_tests/util/setup_nodes.py b/chia/_tests/util/setup_nodes.py index 80a9c0d78487..be92bc4346db 100644 --- a/chia/_tests/util/setup_nodes.py +++ b/chia/_tests/util/setup_nodes.py @@ -303,13 +303,14 @@ async def setup_simulators_and_wallets_inner( @asynccontextmanager -async def setup_farmer_multi_harvester( +async def setup_farmer_solver_multi_harvester( block_tools: BlockTools, harvester_count: int, temp_dir: Path, consensus_constants: ConsensusConstants, *, start_services: bool, + solver_peer: Optional[UnresolvedPeerInfo] = None, ) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, BlockTools]]: async with AsyncExitStack() as async_exit_stack: farmer_service = await async_exit_stack.enter_async_context( @@ -320,6 +321,7 @@ async def setup_farmer_multi_harvester( consensus_constants, port=uint16(0), start_service=start_services, + solver_peer=solver_peer, ) ) if start_services: @@ -342,74 +344,6 @@ async def setup_farmer_multi_harvester( yield harvester_services, farmer_service, block_tools -@asynccontextmanager -async def setup_farmer_multi_harvester_with_solver( - block_tools: BlockTools, - harvester_count: int, - temp_dir: Path, - consensus_constants: ConsensusConstants, - *, - start_services: bool, -) -> AsyncIterator[tuple[list[HarvesterService], FarmerService, SolverService, BlockTools]]: - async with AsyncExitStack() as async_exit_stack: - farmer_service = await async_exit_stack.enter_async_context( - setup_farmer( - block_tools, - temp_dir / "farmer", - block_tools.config["self_hostname"], - consensus_constants, - port=uint16(0), - start_service=start_services, - ) - ) - if start_services: - farmer_peer = UnresolvedPeerInfo(block_tools.config["self_hostname"], farmer_service._server.get_port()) - else: - farmer_peer = None - harvester_services = [ - await async_exit_stack.enter_async_context( - setup_harvester( - block_tools, - temp_dir / f"harvester_{i}", - farmer_peer, - consensus_constants, - start_service=start_services, - ) - ) - for i in range(harvester_count) - ] - - # Setup solver with farmer peer - CRITICAL: use same BlockTools root path for SSL CA consistency - solver_service = await async_exit_stack.enter_async_context( - setup_solver( - temp_dir / "solver", # Use temp_dir like harvester, not block_tools.root_path - block_tools, # Pass BlockTools so SSL CA can be consistent - consensus_constants, - start_service=start_services, - farmer_peer=farmer_peer, - ) - ) - - # Wait for farmer to be fully started before expecting solver connection - if start_services: - import asyncio - - # Wait for farmer to be fully initialized - timeout = 30 - for i in range(timeout): - if farmer_service._node.started: - print(f"Farmer fully started after {i} seconds") - break - await asyncio.sleep(1) - else: - print(f"WARNING: Farmer not started after {timeout} seconds") - - # Give solver additional time to connect - await asyncio.sleep(3) - - yield harvester_services, farmer_service, solver_service, block_tools - - @asynccontextmanager async def setup_full_system( consensus_constants: ConsensusConstants, diff --git a/chia/cmds/configure.py b/chia/cmds/configure.py index 4af54772098e..1e29c853137f 100644 --- a/chia/cmds/configure.py +++ b/chia/cmds/configure.py @@ -22,6 +22,7 @@ def configure( root_path: Path, set_farmer_peer: str, + set_solver_peer: str, set_node_introducer: str, set_fullnode_port: str, set_harvester_port: str, @@ -35,6 +36,7 @@ def configure( crawler_minimum_version_count: Optional[int], seeder_domain_name: str, seeder_nameserver: str, + set_solver_trusted_peers_only: str, ) -> None: config_yaml = "config.yaml" with lock_and_load_config(root_path, config_yaml, fill_missing_services=True) as config: @@ -59,6 +61,25 @@ def configure( change_made = True except ValueError: print("Farmer address must be in format [IP:Port]") + if set_solver_peer: + try: + host, port = parse_host_port(set_solver_peer) + # ensure farmer section has solver_peers field + if "solver_peers" not in config["farmer"]: + config["farmer"]["solver_peers"] = [] + # Set single solver peer (overrides any existing) + config["farmer"]["solver_peers"] = [{"host": host, "port": port}] + print(f"Solver peer updated to {host}:{port}") + change_made = True + except ValueError: + print("Solver address must be in format [IP:Port]") + if set_solver_trusted_peers_only: + config["solver"]["trusted_peers_only"] = str2bool(set_solver_trusted_peers_only) + if str2bool(set_solver_trusted_peers_only): + print("Solver will only accept trusted peer connections") + else: + print("Solver will accept connections from all peers") + change_made = True if set_fullnode_port: config["full_node"]["port"] = int(set_fullnode_port) config["full_node"]["introducer_peer"]["port"] = int(set_fullnode_port) @@ -237,6 +258,12 @@ def configure( ) @click.option("--set-node-introducer", help="Set the introducer for node - IP:Port", type=str) @click.option("--set-farmer-peer", help="Set the farmer peer for harvester - IP:Port", type=str) +@click.option("--set-solver-peer", help="Set the solver peer for farmer - IP:Port", type=str) +@click.option( + "--set-solver-trusted-peers-only", + help="Enable/disable trusted peer requirement for solver connections", + type=click.Choice(["true", "t", "false", "f"]), +) @click.option( "--set-fullnode-port", help="Set the port to use for the fullnode, useful for testing", @@ -292,6 +319,7 @@ def configure( def configure_cmd( ctx: click.Context, set_farmer_peer: str, + set_solver_peer: str, set_node_introducer: str, set_fullnode_port: str, set_harvester_port: str, @@ -305,10 +333,12 @@ def configure_cmd( crawler_minimum_version_count: int, seeder_domain_name: str, seeder_nameserver: str, + set_solver_trusted_peers_only: str, ) -> None: configure( ChiaCliContext.set_default(ctx).root_path, set_farmer_peer, + set_solver_peer, set_node_introducer, set_fullnode_port, set_harvester_port, @@ -322,4 +352,5 @@ def configure_cmd( crawler_minimum_version_count, seeder_domain_name, seeder_nameserver, + set_solver_trusted_peers_only, ) diff --git a/chia/cmds/farm.py b/chia/cmds/farm.py index 2fd131e39866..25fe03bed7b6 100644 --- a/chia/cmds/farm.py +++ b/chia/cmds/farm.py @@ -105,3 +105,26 @@ def challenges_cmd(ctx: click.Context, farmer_rpc_port: Optional[int], limit: in from chia.cmds.farm_funcs import challenges asyncio.run(challenges(ChiaCliContext.set_default(ctx).root_path, farmer_rpc_port, limit)) + + +@farm_cmd.command("connect-solver", help="Connect to a solver") +@click.option( + "-fp", + "--farmer-rpc-port", + help="Set the port where the Farmer is hosting the RPC interface", + type=int, + default=None, + show_default=True, +) +@click.argument("solver_address", required=True) +@click.pass_context +def connect_solver_cmd( + ctx: click.Context, + farmer_rpc_port: Optional[int], + solver_address: str, +) -> None: + import asyncio + + from chia.cmds.farm_funcs import solver_connect + + asyncio.run(solver_connect(ChiaCliContext.set_default(ctx).root_path, farmer_rpc_port, solver_address)) diff --git a/chia/cmds/farm_funcs.py b/chia/cmds/farm_funcs.py index 560a65b39b76..f64d0cfbba7b 100644 --- a/chia/cmds/farm_funcs.py +++ b/chia/cmds/farm_funcs.py @@ -11,6 +11,7 @@ from chia.cmds.units import units from chia.farmer.farmer_rpc_client import FarmerRpcClient from chia.full_node.full_node_rpc_client import FullNodeRpcClient +from chia.util.config import lock_and_load_config, save_config from chia.util.errors import CliRpcConnectionError from chia.util.network import is_localhost from chia.wallet.wallet_rpc_client import WalletRpcClient @@ -217,3 +218,33 @@ def process_harvesters(harvester_peers_in: dict[str, dict[str, Any]]) -> None: print("For details on farmed rewards and fees you should run 'chia wallet show'") else: print("Note: log into your key using 'chia wallet show' to see rewards for each key") + + +async def solver_connect(root_path: Path, farmer_rpc_port: Optional[int], solver_address: str) -> None: + from chia.util.network import parse_host_port + + try: + host, port = parse_host_port(solver_address) + except ValueError: + print("Solver address must be in format [IP:Port]") + return + try: + with lock_and_load_config(root_path, "config.yaml") as config: + config["farmer"]["solver_peers"] = [{"host": host, "port": port}] + save_config(root_path, "config.yaml", config) + print(f"✓ Updated config with solver peer {host}:{port}") + except Exception as e: + print(f"✗ Failed to update config: {e}") + return + try: + async with get_any_service_client(FarmerRpcClient, root_path, farmer_rpc_port) as (farmer_client, _): + result = await farmer_client.connect_to_solver(host, port) + if result.get("success"): + print(f"✓ Connected to solver at {host}:{port}") + else: + error = result.get("error", "Unknown error") + print(f"✗ Failed to connect to solver: {error}") + except CliRpcConnectionError: + print("✗ Could not connect to farmer. Make sure farmer is running.") + except Exception as e: + print(f"✗ Error connecting to solver: {e}") diff --git a/chia/cmds/init_funcs.py b/chia/cmds/init_funcs.py index 1c6b4faaefa3..283abf8d66c7 100644 --- a/chia/cmds/init_funcs.py +++ b/chia/cmds/init_funcs.py @@ -298,6 +298,8 @@ def chia_init( crawler_minimum_version_count=None, seeder_domain_name="", seeder_nameserver="", + set_solver_peer="", + set_solver_trusted_peers_only="", ) if fix_ssl_permissions: fix_ssl(root_path) @@ -324,6 +326,8 @@ def chia_init( crawler_minimum_version_count=None, seeder_domain_name="", seeder_nameserver="", + set_solver_peer="", + set_solver_trusted_peers_only="", ) create_all_ssl(root_path) if fix_ssl_permissions: diff --git a/chia/farmer/farmer_rpc_api.py b/chia/farmer/farmer_rpc_api.py index d889c82f0655..64fce4486fef 100644 --- a/chia/farmer/farmer_rpc_api.py +++ b/chia/farmer/farmer_rpc_api.py @@ -11,7 +11,10 @@ from chia.farmer.farmer import Farmer from chia.plot_sync.receiver import Receiver from chia.protocols.harvester_protocol import Plot +from chia.protocols.outbound_message import NodeType from chia.rpc.rpc_server import Endpoint, EndpointResult +from chia.types.peer_info import PeerInfo +from chia.util.network import resolve from chia.util.paginator import Paginator from chia.util.streamable import Streamable, streamable from chia.util.ws_message import WsRpcMessage, create_payload_dict @@ -102,6 +105,7 @@ def get_routes(self) -> dict[str, Endpoint]: "/get_harvester_plots_keys_missing": self.get_harvester_plots_keys_missing, "/get_harvester_plots_duplicates": self.get_harvester_plots_duplicates, "/get_pool_login_link": self.get_pool_login_link, + "/connect_to_solver": self.connect_to_solver, } async def _state_changed(self, change: str, change_data: Optional[dict[str, Any]]) -> list[WsRpcMessage]: @@ -363,3 +367,19 @@ async def get_pool_login_link(self, request: dict[str, Any]) -> EndpointResult: if login_link is None: raise ValueError(f"Failed to generate login link for {launcher_id.hex()}") return {"login_link": login_link} + + async def connect_to_solver(self, request: dict[str, Any]) -> EndpointResult: + for connection in self.service.server.get_connections(NodeType.SOLVER): + host = connection.peer_info.host + port = connection.peer_server_port + await connection.close() + self.service.log.info(f"Disconnected from solver at {host}:{port}") + host = request["host"] + port = request["port"] + target_node = PeerInfo(await resolve(host), port) + on_connect = getattr(self.service, "on_connect", None) + if await self.service.server.start_client(target_node, on_connect): + self.service.log.info(f"Connected to solver at {host}:{port}") + return {"success": True} + else: + return {"success": False, "error": f"Could not connect to solver at {host}:{port}"} diff --git a/chia/farmer/farmer_rpc_client.py b/chia/farmer/farmer_rpc_client.py index 94483444c7a3..8f5d8efc78d3 100644 --- a/chia/farmer/farmer_rpc_client.py +++ b/chia/farmer/farmer_rpc_client.py @@ -85,3 +85,7 @@ async def get_pool_login_link(self, launcher_id: bytes32) -> Optional[str]: return cast(Optional[str], result["login_link"]) except ValueError: # not connected to pool. return None + + async def connect_to_solver(self, host: str, port: int) -> dict[str, Any]: + """Connect farmer to specific solver""" + return await self.fetch("connect_to_solver", {"host": host, "port": port}) diff --git a/chia/farmer/start_farmer.py b/chia/farmer/start_farmer.py index bd12c4e466a9..2818fcd16ad3 100644 --- a/chia/farmer/start_farmer.py +++ b/chia/farmer/start_farmer.py @@ -62,7 +62,8 @@ def create_farmer_service( node_type=NodeType.FARMER, advertised_port=service_config["port"], service_name=SERVICE_NAME, - connect_peers=get_unresolved_peer_infos(service_config, NodeType.FULL_NODE), + connect_peers=get_unresolved_peer_infos(service_config, NodeType.FULL_NODE) + | get_unresolved_peer_infos(service_config, NodeType.SOLVER), on_connect_callback=node.on_connect, network_id=network_id, rpc_info=rpc_info, @@ -73,7 +74,7 @@ def create_farmer_service( async def async_main(root_path: pathlib.Path) -> int: # TODO: refactor to avoid the double load - config = load_config(root_path, "config.yaml") + config = load_config(root_path, "config.yaml", fill_missing_services=True) service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME) config[SERVICE_NAME] = service_config config_pool = load_config_cli(root_path, "config.yaml", "pool") diff --git a/chia/server/resolve_peer_info.py b/chia/server/resolve_peer_info.py index d4f8a585ca2f..aa5139ba561f 100644 --- a/chia/server/resolve_peer_info.py +++ b/chia/server/resolve_peer_info.py @@ -11,6 +11,7 @@ PEER_INFO_MAPPING: dict[NodeType, str] = { NodeType.FULL_NODE: "full_node_peer", NodeType.FARMER: "farmer_peer", + NodeType.SOLVER: "solver_peer", } @@ -21,6 +22,10 @@ def get_unresolved_peer_infos(service_config: dict[str, Any], peer_type: NodeTyp if peer_info is not None: peer_infos.append(peer_info) + # Default solver peer if none configured + if peer_type == NodeType.SOLVER and not peer_infos: + peer_infos = [{"host": "localhost", "port": 8666}] + return {UnresolvedPeerInfo(host=peer["host"], port=peer["port"]) for peer in peer_infos} diff --git a/chia/server/server.py b/chia/server/server.py index f6f97b50791c..ef823163b50c 100644 --- a/chia/server/server.py +++ b/chia/server/server.py @@ -172,13 +172,12 @@ def create( private_cert_path, private_key_path = None, None public_cert_path, public_key_path = None, None - authenticated_client_types = {NodeType.HARVESTER, NodeType.SOLVER} + authenticated_client_types = {NodeType.HARVESTER} authenticated_server_types = { NodeType.HARVESTER, NodeType.FARMER, NodeType.WALLET, NodeType.DATA_LAYER, - NodeType.SOLVER, } if local_type in authenticated_client_types: diff --git a/chia/simulator/setup_services.py b/chia/simulator/setup_services.py index 9b95e02acfbc..8f412da22652 100644 --- a/chia/simulator/setup_services.py +++ b/chia/simulator/setup_services.py @@ -359,6 +359,7 @@ async def setup_farmer( full_node_port: Optional[uint16] = None, start_service: bool = True, port: uint16 = uint16(0), + solver_peer: Optional[UnresolvedPeerInfo] = None, ) -> AsyncGenerator[FarmerService, None]: with create_lock_and_load_config(b_tools.root_path / "config" / "ssl" / "ca", root_path) as root_config: root_config["logging"]["log_stdout"] = True @@ -386,6 +387,16 @@ async def setup_farmer( service_config.pop("full_node_peer", None) service_config.pop("full_node_peers", None) + if solver_peer: + service_config["solver_peers"] = [ + { + "host": solver_peer.host, + "port": solver_peer.port, + }, + ] + else: + service_config.pop("solver_peers", None) + service = create_farmer_service( root_path, root_config, @@ -514,7 +525,6 @@ async def setup_solver( b_tools: BlockTools, consensus_constants: ConsensusConstants, start_service: bool = True, - farmer_peer: Optional[UnresolvedPeerInfo] = None, ) -> AsyncGenerator[SolverService, None]: with create_lock_and_load_config(b_tools.root_path / "config" / "ssl" / "ca", root_path) as config: config["logging"]["log_stdout"] = True @@ -528,7 +538,6 @@ async def setup_solver( root_path, config, consensus_constants, - farmer_peers={farmer_peer} if farmer_peer is not None else set(), ) async with service.manage(start=start_service): diff --git a/chia/solver/solver.py b/chia/solver/solver.py index 8b213a80be83..2483bb0c903f 100644 --- a/chia/solver/solver.py +++ b/chia/solver/solver.py @@ -44,6 +44,7 @@ def server(self) -> ChiaServer: def __init__(self, root_path: Path, config: dict[str, Any], constants: ConsensusConstants): self.log = log self.root_path = root_path + self.config = config self._shut_down = False num_threads = config["num_threads"] self.log.info(f"Initializing solver with {num_threads} threads") @@ -78,7 +79,16 @@ def get_connections(self, request_node_type: Optional[NodeType]) -> list[dict[st return default_get_connections(server=self.server, request_node_type=request_node_type) async def on_connect(self, connection: WSChiaConnection) -> None: - pass + if self.server.is_trusted_peer(connection, self.config.get("trusted_peers", {})): + self.log.info(f"Accepting connection from {connection.get_peer_logging()}") + return + if not self.config.get("trusted_peers_only", True): + self.log.info( + f"trusted peers check disabled, Accepting connection from untrusted {connection.get_peer_logging()}" + ) + return + self.log.warning(f"Rejecting untrusted connection from {connection.get_peer_logging()}") + await connection.close() async def on_disconnect(self, connection: WSChiaConnection) -> None: self.log.info(f"peer disconnected {connection.get_peer_logging()}") diff --git a/chia/solver/start_solver.py b/chia/solver/start_solver.py index 78b0901502be..4f34225fd586 100644 --- a/chia/solver/start_solver.py +++ b/chia/solver/start_solver.py @@ -19,7 +19,6 @@ from chia.solver.solver_api import SolverAPI from chia.solver.solver_rpc_api import SolverRpcApi from chia.solver.solver_service import SolverService -from chia.types.peer_info import UnresolvedPeerInfo 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 resolve_root_path @@ -35,7 +34,6 @@ def create_solver_service( root_path: pathlib.Path, config: dict[str, Any], consensus_constants: ConsensusConstants, - farmer_peers: set[UnresolvedPeerInfo] = set(), connect_to_daemon: bool = True, override_capabilities: Optional[list[tuple[uint16, str]]] = None, ) -> SolverService: @@ -64,7 +62,6 @@ def create_solver_service( service_name=SERVICE_NAME, upnp_ports=upnp_list, on_connect_callback=node.on_connect, - connect_peers=farmer_peers, network_id=network_id, rpc_info=rpc_info, connect_to_daemon=connect_to_daemon, @@ -74,7 +71,7 @@ def create_solver_service( async def async_main(service_config: dict[str, Any], root_path: pathlib.Path) -> int: - config = load_config(root_path, "config.yaml") + config = load_config(root_path, "config.yaml", fill_missing_services=True) config[SERVICE_NAME] = service_config network_id = service_config["selected_network"] overrides = service_config["network_overrides"]["constants"][network_id] diff --git a/chia/ssl/create_ssl.py b/chia/ssl/create_ssl.py index bd4589f2fa11..411b40a87f3b 100644 --- a/chia/ssl/create_ssl.py +++ b/chia/ssl/create_ssl.py @@ -26,7 +26,7 @@ "daemon", "solver", ] -_all_public_node_names: list[str] = ["full_node", "wallet", "farmer", "introducer", "timelord", "data_layer"] +_all_public_node_names: list[str] = ["full_node", "wallet", "farmer", "introducer", "timelord", "data_layer", "solver"] def get_chia_ca_crt_key() -> tuple[Any, Any]: diff --git a/chia/util/config.py b/chia/util/config.py index 96a6720d4206..f7615572cc3e 100644 --- a/chia/util/config.py +++ b/chia/util/config.py @@ -305,14 +305,12 @@ def selected_network_address_prefix(config: dict[str, Any]) -> str: def load_defaults_for_missing_services(config: dict[str, Any], config_name: str) -> dict[str, Any]: - services = ["data_layer"] + services = ["data_layer", "solver"] missing_services = [service for service in services if service not in config] defaulted = {} if len(missing_services) > 0: marshalled_default_config: str = initial_config_file(config_name) - unmarshalled_default_config = yaml.safe_load(marshalled_default_config) - for service in missing_services: defaulted[service] = unmarshalled_default_config[service] diff --git a/chia/util/initial-config.yaml b/chia/util/initial-config.yaml index 13bd21a98c7d..a8c641e10327 100644 --- a/chia/util/initial-config.yaml +++ b/chia/util/initial-config.yaml @@ -209,6 +209,11 @@ farmer: - host: *self_hostname port: 8444 + # The farmer will attempt to connect to these solvers for V2 plot solving + solver_peers: + - host: *self_hostname + port: 8666 + pool_public_keys: !!set {} # Replace this with a real receive address @@ -636,6 +641,14 @@ solver: # Enable or disable UPnP port forwarding enable_upnp: False + # Node IDs of trusted solver peers, only these can connect by default + trusted_peers: + 0ThisisanexampleNodeID7ff9d60f1c3fa270c213c0ad0cb89c01274634a7c3cb7: Does_not_matter + + # If False, accepts connections from all peers (not just trusted_peers) + # If True (default), only accepts localhost and trusted_peers connections + trusted_peers_only: True + # Logging configuration logging: *logging