diff --git a/chia/_tests/conftest.py b/chia/_tests/conftest.py index 0f386c9c1c45..8ce767056c49 100644 --- a/chia/_tests/conftest.py +++ b/chia/_tests/conftest.py @@ -65,12 +65,14 @@ setup_full_node, setup_introducer, setup_seeder, + setup_solver, setup_timelord, ) from chia.simulator.start_simulator import SimulatorFullNodeService from chia.simulator.wallet_tools import WalletTool +from chia.solver.solver_service import SolverService from chia.timelord.timelord_service import TimelordService -from chia.types.peer_info import PeerInfo +from chia.types.peer_info import PeerInfo, UnresolvedPeerInfo from chia.util.config import create_default_chia_config, lock_and_load_config from chia.util.db_wrapper import generate_in_memory_db_uri from chia.util.keychain import Keychain @@ -881,6 +883,23 @@ async def farmer_one_harvester(tmp_path: Path, get_b_tools: BlockTools) -> Async yield _ +FarmerOneHarvesterSolver = tuple[list[HarvesterService], FarmerService, SolverService, BlockTools] + + +@pytest.fixture(scope="function") +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: + yield harvester_services, farmer_service, solver_service, bt + + @pytest.fixture(scope="function") async def farmer_one_harvester_not_started( tmp_path: Path, get_b_tools: BlockTools diff --git a/chia/_tests/farmer_harvester/test_farmer_harvester.py b/chia/_tests/farmer_harvester/test_farmer_harvester.py index bf5badc19979..9d25e6815f5a 100644 --- a/chia/_tests/farmer_harvester/test_farmer_harvester.py +++ b/chia/_tests/farmer_harvester/test_farmer_harvester.py @@ -1,9 +1,11 @@ from __future__ import annotations import asyncio +import unittest.mock from math import floor from pathlib import Path from typing import Any, Optional +from unittest.mock import AsyncMock, Mock import pytest from chia_rs import G1Element @@ -19,16 +21,37 @@ from chia.harvester.harvester_rpc_client import HarvesterRpcClient from chia.harvester.harvester_service import HarvesterService from chia.plotting.util import PlotsRefreshParameter -from chia.protocols import farmer_protocol, harvester_protocol +from chia.protocols import farmer_protocol, harvester_protocol, solver_protocol from chia.protocols.outbound_message import NodeType, make_msg from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.simulator.block_tools import BlockTools +from chia.solver.solver_service import SolverService from chia.types.peer_info import UnresolvedPeerInfo from chia.util.config import load_config from chia.util.hash import std_hash from chia.util.keychain import generate_mnemonic +async def get_harvester_peer(farmer: Farmer) -> Any: + """wait for harvester connection and return the peer""" + + def has_harvester_connection() -> bool: + return len(farmer.server.get_connections(NodeType.HARVESTER)) > 0 + + await time_out_assert(10, has_harvester_connection, True) + return farmer.server.get_connections(NodeType.HARVESTER)[0] + + +async def get_solver_peer(farmer: Farmer) -> Any: + """wait for solver connection and return the peer""" + + def has_solver_connection() -> bool: + return len(farmer.server.get_connections(NodeType.SOLVER)) > 0 + + await time_out_assert(60, has_solver_connection, True) + return farmer.server.get_connections(NodeType.SOLVER)[0] + + def farmer_is_started(farmer: Farmer) -> bool: return farmer.started @@ -144,9 +167,6 @@ async def test_farmer_respond_signatures( # messages even though it didn't request them, to cover when the farmer doesn't know # about an sp_hash, so it fails at the sp record check. - def log_is_ready() -> bool: - return len(caplog.text) > 0 - _, _, harvester_service, _, _ = harvester_farmer_environment # We won't have an sp record for this one challenge_hash = bytes32(b"1" * 32) @@ -161,11 +181,16 @@ def log_is_ready() -> bool: include_source_signature_data=False, farmer_reward_address_override=None, ) + + expected_error = f"Do not have challenge hash {challenge_hash}" + + def expected_log_is_ready() -> bool: + return expected_error in caplog.text + msg = make_msg(ProtocolMessageTypes.respond_signatures, response) await harvester_service._node.server.send_to_all([msg], NodeType.FARMER) - await time_out_assert(5, log_is_ready) - # We fail the sps record check - expected_error = f"Do not have challenge hash {challenge_hash}" + await time_out_assert(10, expected_log_is_ready) + # We should find the error message assert expected_error in caplog.text @@ -298,3 +323,275 @@ async def test_harvester_has_no_server( harvester_server = harvesters[0]._server assert harvester_server.webserver is None + + +@pytest.mark.anyio +async def test_v2_partial_proofs_new_sp_hash( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _solver_service, _bt = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + sp_hash = bytes32(b"1" * 32) + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=bytes32(b"2" * 32), + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_1"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=None, + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = await get_harvester_peer(farmer) + await farmer_api.partial_proofs(partial_proofs, harvester_peer) + + assert sp_hash in farmer.number_of_responses + assert farmer.number_of_responses[sp_hash] == 0 + assert sp_hash in farmer.cache_add_time + + +@pytest.mark.anyio +async def test_v2_partial_proofs_missing_sp_hash( + caplog: pytest.LogCaptureFixture, + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + + sp_hash = bytes32(b"1" * 32) + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=bytes32(b"2" * 32), + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_1"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=None, + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = await get_harvester_peer(farmer_api.farmer) + await farmer_api.partial_proofs(partial_proofs, harvester_peer) + + assert f"Received partial proofs for a signage point that we do not have {sp_hash}" in caplog.text + + +@pytest.mark.anyio +async def test_v2_partial_proofs_with_existing_sp( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + sp_hash = bytes32(b"1" * 32) + challenge_hash = bytes32(b"2" * 32) + + sp = farmer_protocol.NewSignagePoint( + challenge_hash=challenge_hash, + challenge_chain_sp=sp_hash, + reward_chain_sp=std_hash(b"1"), + difficulty=uint64(1000), + sub_slot_iters=uint64(1000), + signage_point_index=uint8(0), + peak_height=uint32(1), + last_tx_height=uint32(0), + ) + + farmer.sps[sp_hash] = [sp] + + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=challenge_hash, + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_1", b"test_partial_proof_2"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=G1Element(), + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = await get_harvester_peer(farmer) + await farmer_api.partial_proofs(partial_proofs, harvester_peer) + + # should store 2 pending requests (one per partial proof) + assert len(farmer.pending_solver_requests) == 2 + assert sp_hash in farmer.cache_add_time + + +@pytest.mark.anyio +async def test_solution_response_handler( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + # set up a pending request + sp_hash = bytes32(b"1" * 32) + challenge_hash = bytes32(b"2" * 32) + + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=challenge_hash, + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_for_quality"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=G1Element(), + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = await get_harvester_peer(farmer) + + # manually add pending request + farmer.pending_solver_requests[partial_proofs.partial_proofs[0]] = { + "proof_data": partial_proofs, + "peer": harvester_peer, + } + + # create solution response + solution_response = solver_protocol.SolverResponse( + partial_proof=partial_proofs.partial_proofs[0], proof=b"test_proof_from_solver" + ) + solver_peer = Mock() + solver_peer.peer_node_id = "solver_peer" + + with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof: + await farmer_api.solution_response(solution_response, solver_peer) + + # verify new_proof_of_space was called with correct proof + mock_new_proof.assert_called_once() + call_args = mock_new_proof.call_args[0] + new_proof_of_space = call_args[0] + original_peer = call_args[1] + + assert new_proof_of_space.proof.proof == b"test_proof_from_solver" + assert original_peer == harvester_peer + + # verify pending request was removed + assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests + + +@pytest.mark.anyio +async def test_solution_response_unknown_quality( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + # get real solver peer connection + solver_peer = await get_solver_peer(farmer) + + # create solution response with unknown quality + solution_response = solver_protocol.SolverResponse(partial_proof=bytes(b"1" * 32), proof=b"test_proof") + + with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof: + await farmer_api.solution_response(solution_response, solver_peer) + # verify new_proof_of_space was NOT called + mock_new_proof.assert_not_called() + # verify pending requests unchanged + assert len(farmer.pending_solver_requests) == 0 + + +@pytest.mark.anyio +async def test_solution_response_empty_proof( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _solver_service, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + # set up a pending request + sp_hash = bytes32(b"1" * 32) + challenge_hash = bytes32(b"2" * 32) + + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=challenge_hash, + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_for_quality"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=G1Element(), + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = Mock() + harvester_peer.peer_node_id = "harvester_peer" + + # manually add pending request + farmer.pending_solver_requests[partial_proofs.partial_proofs[0]] = { + "proof_data": partial_proofs.partial_proofs[0], + "peer": harvester_peer, + } + + # get real solver peer connection + solver_peer = await get_solver_peer(farmer) + + # create solution response with empty proof + solution_response = solver_protocol.SolverResponse(partial_proof=partial_proofs.partial_proofs[0], proof=b"") + + with unittest.mock.patch.object(farmer_api, "new_proof_of_space", new_callable=AsyncMock) as mock_new_proof: + await farmer_api.solution_response(solution_response, solver_peer) + + # verify new_proof_of_space was NOT called + mock_new_proof.assert_not_called() + + # verify pending request was removed (cleanup still happens) + assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests + + +@pytest.mark.anyio +async def test_v2_partial_proofs_solver_exception( + farmer_one_harvester_solver: tuple[list[HarvesterService], FarmerService, SolverService, BlockTools], +) -> None: + _, farmer_service, _solver_service, _ = farmer_one_harvester_solver + farmer_api = farmer_service._api + farmer = farmer_api.farmer + + sp_hash = bytes32(b"1" * 32) + challenge_hash = bytes32(b"2" * 32) + + sp = farmer_protocol.NewSignagePoint( + challenge_hash=challenge_hash, + challenge_chain_sp=sp_hash, + reward_chain_sp=std_hash(b"1"), + difficulty=uint64(1000), + sub_slot_iters=uint64(1000), + signage_point_index=uint8(0), + peak_height=uint32(1), + last_tx_height=uint32(0), + ) + + farmer.sps[sp_hash] = [sp] + + partial_proofs = harvester_protocol.PartialProofsData( + challenge_hash=challenge_hash, + sp_hash=sp_hash, + plot_identifier="test_plot_id", + partial_proofs=[b"test_partial_proof_1"], + signage_point_index=uint8(0), + plot_size=uint8(32), + pool_public_key=G1Element(), + pool_contract_puzzle_hash=bytes32(b"4" * 32), + plot_public_key=G1Element(), + ) + + harvester_peer = await get_harvester_peer(farmer) + + # Mock send_to_all to raise an exception + with unittest.mock.patch.object(farmer.server, "send_to_all", side_effect=Exception("Solver connection failed")): + await farmer_api.partial_proofs(partial_proofs, harvester_peer) + + # verify pending request was cleaned up after exception + assert partial_proofs.partial_proofs[0] not in farmer.pending_solver_requests diff --git a/chia/_tests/harvester/__init__.py b/chia/_tests/harvester/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chia/_tests/harvester/config.py b/chia/_tests/harvester/config.py new file mode 100644 index 000000000000..b593bfe59ade --- /dev/null +++ b/chia/_tests/harvester/config.py @@ -0,0 +1,4 @@ +from __future__ import annotations + +job_timeout = 70 +checkout_blocks_and_plots = True diff --git a/chia/_tests/harvester/test_harvester_api.py b/chia/_tests/harvester/test_harvester_api.py new file mode 100644 index 000000000000..479adb69e1cb --- /dev/null +++ b/chia/_tests/harvester/test_harvester_api.py @@ -0,0 +1,157 @@ +from __future__ import annotations + +from collections.abc import AsyncGenerator, Iterator +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from chia_rs import ConsensusConstants, FullBlock, ProofOfSpace +from chia_rs.sized_bytes import bytes32 +from chia_rs.sized_ints import uint64 + +from chia._tests.conftest import HarvesterFarmerEnvironment +from chia._tests.plotting.util import get_test_plots +from chia._tests.util.time_out_assert import time_out_assert +from chia.harvester.harvester_api import HarvesterAPI +from chia.plotting.util import PlotInfo +from chia.protocols import harvester_protocol +from chia.protocols.harvester_protocol import PoolDifficulty +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.server.ws_connection import WSChiaConnection + + +@dataclass +class HarvesterTestEnvironment: + """Test environment with real plots loaded for harvester testing.""" + + harvester_api: HarvesterAPI + plot_info: PlotInfo + plot_path: Path + + +@pytest.fixture(scope="function") +async def harvester_environment( + harvester_farmer_environment: HarvesterFarmerEnvironment, +) -> AsyncGenerator[HarvesterTestEnvironment, None]: + """Create a test environment with real plots loaded.""" + _, _, harvester_service, _, _ = harvester_farmer_environment + harvester_api = harvester_service._server.api + assert isinstance(harvester_api, HarvesterAPI) + test_plots = get_test_plots() + assert len(test_plots) > 0, "no test plots available" + plot_manager = harvester_api.harvester.plot_manager + plot_manager.start_refreshing() + await time_out_assert(10, lambda: len(plot_manager.plots) > 0, True) + plot_path, plot_info = next(iter(plot_manager.plots.items())) + yield HarvesterTestEnvironment(harvester_api, plot_info, plot_path) + plot_manager.stop_refreshing() + + +def signage_point_from_block( + block: FullBlock, constants: ConsensusConstants +) -> harvester_protocol.NewSignagePointHarvester: + sp_index = block.reward_chain_block.signage_point_index + challenge_hash = block.reward_chain_block.pos_ss_cc_challenge_hash + sp_hash = ( + block.reward_chain_block.reward_chain_sp_vdf.output.get_hash() + if block.reward_chain_block.reward_chain_sp_vdf + else challenge_hash + ) + return harvester_protocol.NewSignagePointHarvester( + challenge_hash=challenge_hash, + difficulty=uint64(constants.DIFFICULTY_STARTING), + sub_slot_iters=uint64(constants.SUB_SLOT_ITERS_STARTING), + signage_point_index=sp_index, + sp_hash=sp_hash, + pool_difficulties=[], + peak_height=block.height, + last_tx_height=block.height, + ) + + +def create_test_setup( + harvester_environment: HarvesterTestEnvironment, + default_400_blocks: list[FullBlock], + blockchain_constants: ConsensusConstants, +) -> tuple[HarvesterTestEnvironment, harvester_protocol.NewSignagePointHarvester, MagicMock]: + env = harvester_environment + block = default_400_blocks[2] + new_challenge = signage_point_from_block(block, blockchain_constants) + mock_peer = MagicMock(spec=WSChiaConnection) + return env, new_challenge, mock_peer + + +@contextmanager +def mock_successful_proof(plot_info: PlotInfo) -> Iterator[None]: + with patch.object(plot_info.prover, "get_full_proof") as mock_get_proof: + mock_proof = MagicMock(spec=ProofOfSpace) + mock_get_proof.return_value = mock_proof, None + yield + + +def assert_farming_info_sent(mock_peer: MagicMock) -> None: + mock_peer.send_message.assert_called() + farming_info_calls = [ + call + for call in mock_peer.send_message.call_args_list + if call[0][0].type == ProtocolMessageTypes.farming_info.value + ] + assert len(farming_info_calls) == 1 + + +@pytest.mark.anyio +async def test_new_signage_point_harvester( + harvester_environment: HarvesterTestEnvironment, + default_400_blocks: list[FullBlock], + blockchain_constants: ConsensusConstants, +) -> None: + env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants) + with mock_successful_proof(env.plot_info): + await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer) + assert_farming_info_sent(mock_peer) + + +@pytest.mark.anyio +async def test_new_signage_point_harvester_pool_difficulty( + harvester_environment: HarvesterTestEnvironment, + default_400_blocks: list[FullBlock], + blockchain_constants: ConsensusConstants, +) -> None: + env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants) + pool_puzzle_hash = bytes32(b"pool" + b"0" * 28) + env.plot_info.pool_contract_puzzle_hash = pool_puzzle_hash + pool_difficulty = PoolDifficulty( + pool_contract_puzzle_hash=pool_puzzle_hash, + difficulty=uint64(500), + sub_slot_iters=uint64(67108864), + ) + + new_challenge = harvester_protocol.NewSignagePointHarvester( + challenge_hash=new_challenge.challenge_hash, + difficulty=new_challenge.difficulty, + sub_slot_iters=new_challenge.sub_slot_iters, + signage_point_index=new_challenge.signage_point_index, + sp_hash=new_challenge.sp_hash, + pool_difficulties=[pool_difficulty], # add pool difficulty + peak_height=new_challenge.peak_height, + last_tx_height=new_challenge.last_tx_height, + ) + + with mock_successful_proof(env.plot_info): + await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer) + + assert_farming_info_sent(mock_peer) + + +@pytest.mark.anyio +async def test_new_signage_point_harvester_prover_error( + harvester_environment: HarvesterTestEnvironment, + default_400_blocks: list[FullBlock], + blockchain_constants: ConsensusConstants, +) -> None: + env, new_challenge, mock_peer = create_test_setup(harvester_environment, default_400_blocks, blockchain_constants) + with patch.object(env.plot_info.prover, "get_qualities_for_challenge", side_effect=RuntimeError("test error")): + # should not raise exception, should handle error gracefully + await env.harvester_api.new_signage_point_harvester(new_challenge, mock_peer) diff --git a/chia/_tests/plotting/test_prover.py b/chia/_tests/plotting/test_prover.py index 592280d2df52..c776841cce87 100644 --- a/chia/_tests/plotting/test_prover.py +++ b/chia/_tests/plotting/test_prover.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, patch import pytest +from chia_rs.sized_bytes import bytes32 from chia.plotting.prover import PlotVersion, V1Prover, V2Prover, get_prover_from_bytes, get_prover_from_file @@ -25,9 +26,9 @@ def test_v2_prover_get_memo_raises_error(self) -> None: with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"): prover.get_memo() - def test_v2_prover_get_compression_level_raises_error(self) -> None: + def test_v2_prover_get_compression_level_raises_assertion_error(self) -> None: prover = V2Prover("/nonexistent/path/test.plot2") - with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"): + with pytest.raises(AssertionError, match="get_compression_level\\(\\) should never be called on V2 plots"): prover.get_compression_level() def test_v2_prover_get_id_raises_error(self) -> None: @@ -38,12 +39,12 @@ def test_v2_prover_get_id_raises_error(self) -> None: def test_v2_prover_get_qualities_for_challenge_raises_error(self) -> None: prover = V2Prover("/nonexistent/path/test.plot2") with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"): - prover.get_qualities_for_challenge(b"challenge") + prover.get_qualities_for_challenge(bytes32(b"1" * 32)) def test_v2_prover_get_full_proof_raises_error(self) -> None: prover = V2Prover("/nonexistent/path/test.plot2") - with pytest.raises(NotImplementedError, match="V2 plot format is not yet implemented"): - prover.get_full_proof(b"challenge", 0) + with pytest.raises(AssertionError, match="V2 plot format require solver to get full proof"): + prover.get_full_proof(bytes32(b"1" * 32), 0) def test_v2_prover_bytes_raises_error(self) -> None: prover = V2Prover("/nonexistent/path/test.plot2") diff --git a/chia/_tests/solver/__init__.py b/chia/_tests/solver/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chia/_tests/solver/config.py b/chia/_tests/solver/config.py new file mode 100644 index 000000000000..b593bfe59ade --- /dev/null +++ b/chia/_tests/solver/config.py @@ -0,0 +1,4 @@ +from __future__ import annotations + +job_timeout = 70 +checkout_blocks_and_plots = True diff --git a/chia/_tests/solver/test_solver_service.py b/chia/_tests/solver/test_solver_service.py new file mode 100644 index 000000000000..ccb683a9ef39 --- /dev/null +++ b/chia/_tests/solver/test_solver_service.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from pathlib import Path +from unittest.mock import patch + +import pytest +from chia_rs import ConsensusConstants + +from chia.protocols.outbound_message import Message +from chia.protocols.solver_protocol import SolverInfo +from chia.simulator.block_tools import create_block_tools_async +from chia.simulator.keyring import TempKeyring +from chia.simulator.setup_services import setup_solver + + +@pytest.mark.anyio +async def test_solver_api_methods(blockchain_constants: ConsensusConstants, tmp_path: Path) -> None: + with TempKeyring(populate=True) as keychain: + bt = await create_block_tools_async(constants=blockchain_constants, keychain=keychain) + async with setup_solver(tmp_path, bt, blockchain_constants) as solver_service: + solver = solver_service._node + solver_api = solver_service._api + assert solver_api.ready() is True + test_info = SolverInfo(partial_proof=b"test_partial_proof_42") + expected_proof = b"test_proof_data_12345" + with patch.object(solver, "solve", return_value=expected_proof): + api_result = await solver_api.solve(test_info) + assert api_result is not None + assert isinstance(api_result, Message) diff --git a/chia/_tests/util/build_network_protocol_files.py b/chia/_tests/util/build_network_protocol_files.py index 8e2f6b34c217..4de90f8478ae 100644 --- a/chia/_tests/util/build_network_protocol_files.py +++ b/chia/_tests/util/build_network_protocol_files.py @@ -33,6 +33,12 @@ def visit_farmer_protocol(visitor: Callable[[Any, str], None]) -> None: visitor(request_signed_values, "request_signed_values") visitor(farming_info, "farming_info") visitor(signed_values, "signed_values") + visitor(partial_proof, "partial_proof") + + +def visit_solver_protocol(visitor: Callable[[Any, str], None]) -> None: + visitor(solver_info, "solver_info") + visitor(solver_response, "solver_response") def visit_full_node(visitor: Callable[[Any, str], None]) -> None: @@ -170,6 +176,7 @@ def visit_all_messages(visitor: Callable[[Any, str], None]) -> None: visit_pool_protocol(visitor) visit_timelord_protocol(visitor) visit_shared_protocol(visitor) + visit_solver_protocol(visitor) def get_protocol_bytes() -> bytes: diff --git a/chia/_tests/util/network_protocol_data.py b/chia/_tests/util/network_protocol_data.py index 7e0ca731697c..9b0bd7efec68 100644 --- a/chia/_tests/util/network_protocol_data.py +++ b/chia/_tests/util/network_protocol_data.py @@ -37,6 +37,7 @@ harvester_protocol, introducer_protocol, pool_protocol, + solver_protocol, timelord_protocol, wallet_protocol, ) @@ -150,6 +151,26 @@ ), ) +partial_proof = harvester_protocol.PartialProofsData( + bytes32.fromhex("42743566108589c11bb3811b347900b6351fd3e25bad6c956c0bf1c05a4d93fb"), + bytes32.fromhex("8a346e8dc02e9b44c0571caa74fd99f163d4c5d7deaedac87125528721493f7a"), + "plot-filename", + [b"partial-proof1", b"partial-proof2"], + uint8(4), + uint8(32), + G1Element.from_bytes( + bytes.fromhex( + "a04c6b5ac7dfb935f6feecfdd72348ccf1d4be4fe7e26acf271ea3b7d308da61e0a308f7a62495328a81f5147b66634c" + ), + ), + bytes32.fromhex("91240fbacdf93b44c0571caa74fd99f163d4c5d7deaedac87125528721493f7a"), + G1Element.from_bytes( + bytes.fromhex( + "a04c6b5ac7dfb935f6feecfdd72348ccf1d4be4fe7e26acf271ea3b7d308da61e0a308f7a62495328a81f5147b66634c" + ), + ), +) + # FULL NODE PROTOCOL. new_peak = full_node_protocol.NewPeak( @@ -1082,3 +1103,8 @@ uint32(386395693), uint8(224), ) + +# SOLVER PROTOCOL +solver_info = solver_protocol.SolverInfo(partial_proof=b"partial-proof") + +solver_response = solver_protocol.SolverResponse(b"partial-proof", b"full-proof") diff --git a/chia/_tests/util/protocol_messages_bytes-v1.0 b/chia/_tests/util/protocol_messages_bytes-v1.0 index 0d9160933489..c14f08a3d12f 100644 Binary files a/chia/_tests/util/protocol_messages_bytes-v1.0 and b/chia/_tests/util/protocol_messages_bytes-v1.0 differ diff --git a/chia/_tests/util/protocol_messages_json.py b/chia/_tests/util/protocol_messages_json.py index c89601f7c90e..c21b0157b68e 100644 --- a/chia/_tests/util/protocol_messages_json.py +++ b/chia/_tests/util/protocol_messages_json.py @@ -65,6 +65,18 @@ "foliage_transaction_block_signature": "0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", } +partial_proof_json: dict[str, Any] = { + "challenge_hash": "0x42743566108589c11bb3811b347900b6351fd3e25bad6c956c0bf1c05a4d93fb", + "sp_hash": "0x8a346e8dc02e9b44c0571caa74fd99f163d4c5d7deaedac87125528721493f7a", + "plot_identifier": "plot-filename", + "partial_proofs": ["0x7061727469616c2d70726f6f6631", "0x7061727469616c2d70726f6f6632"], + "signage_point_index": 4, + "plot_size": 32, + "pool_public_key": "0xa04c6b5ac7dfb935f6feecfdd72348ccf1d4be4fe7e26acf271ea3b7d308da61e0a308f7a62495328a81f5147b66634c", + "pool_contract_puzzle_hash": "0x91240fbacdf93b44c0571caa74fd99f163d4c5d7deaedac87125528721493f7a", + "plot_public_key": "0xa04c6b5ac7dfb935f6feecfdd72348ccf1d4be4fe7e26acf271ea3b7d308da61e0a308f7a62495328a81f5147b66634c", +} + new_peak_json: dict[str, Any] = { "header_hash": "0x8a346e8dc02e9b44c0571caa74fd99f163d4c5d7deae9f8ddb00528721493f7a", "height": 2653549198, @@ -2701,3 +2713,10 @@ error_without_data_json: dict[str, Any] = {"code": 1, "message": "Unknown", "data": None} error_with_data_json: dict[str, Any] = {"code": 1, "message": "Unknown", "data": "0x65787472612064617461"} + +solver_info_json: dict[str, Any] = {"partial_proof": "0x7061727469616c2d70726f6f66"} + +solver_response_json: dict[str, Any] = { + "partial_proof": "0x7061727469616c2d70726f6f66", + "proof": "0x66756c6c2d70726f6f66", +} diff --git a/chia/_tests/util/setup_nodes.py b/chia/_tests/util/setup_nodes.py index a133e2baa4c1..80a9c0d78487 100644 --- a/chia/_tests/util/setup_nodes.py +++ b/chia/_tests/util/setup_nodes.py @@ -35,6 +35,7 @@ setup_full_node, setup_harvester, setup_introducer, + setup_solver, setup_timelord, setup_vdf_client, setup_vdf_clients, @@ -42,6 +43,7 @@ ) from chia.simulator.socket import find_available_listen_port from chia.simulator.start_simulator import SimulatorFullNodeService +from chia.solver.solver_service import SolverService from chia.timelord.timelord_service import TimelordService from chia.types.peer_info import UnresolvedPeerInfo from chia.util.hash import std_hash @@ -64,6 +66,7 @@ class FullSystem: introducer: IntroducerAPI timelord: TimelordService timelord_bluebox: TimelordService + solver: SolverService daemon: WebSocketServer @@ -339,6 +342,74 @@ 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, @@ -473,6 +544,15 @@ async def setup_full_system_inner( await asyncio.sleep(backoff) + solver_service = await async_exit_stack.enter_async_context( + setup_solver( + shared_b_tools.root_path / "solver", + shared_b_tools, + consensus_constants, + True, + ) + ) + full_system = FullSystem( node_1=node_1, node_2=node_2, @@ -481,6 +561,7 @@ async def setup_full_system_inner( introducer=introducer, timelord=timelord, timelord_bluebox=timelord_bluebox_service, + solver=solver_service, daemon=daemon_ws, ) yield full_system diff --git a/chia/_tests/util/test_network_protocol_files.py b/chia/_tests/util/test_network_protocol_files.py index 071a263e8366..279a45f2008c 100644 --- a/chia/_tests/util/test_network_protocol_files.py +++ b/chia/_tests/util/test_network_protocol_files.py @@ -51,528 +51,543 @@ def test_protocol_bytes() -> None: assert bytes(message_4) == bytes(signed_values) message_bytes, input_bytes = parse_blob(input_bytes) - message_5 = type(new_peak).from_bytes(message_bytes) - assert message_5 == new_peak - assert bytes(message_5) == bytes(new_peak) + message_5 = type(partial_proof).from_bytes(message_bytes) + assert message_5 == partial_proof + assert bytes(message_5) == bytes(partial_proof) message_bytes, input_bytes = parse_blob(input_bytes) - message_6 = type(new_transaction).from_bytes(message_bytes) - assert message_6 == new_transaction - assert bytes(message_6) == bytes(new_transaction) + message_6 = type(new_peak).from_bytes(message_bytes) + assert message_6 == new_peak + assert bytes(message_6) == bytes(new_peak) message_bytes, input_bytes = parse_blob(input_bytes) - message_7 = type(request_transaction).from_bytes(message_bytes) - assert message_7 == request_transaction - assert bytes(message_7) == bytes(request_transaction) + message_7 = type(new_transaction).from_bytes(message_bytes) + assert message_7 == new_transaction + assert bytes(message_7) == bytes(new_transaction) message_bytes, input_bytes = parse_blob(input_bytes) - message_8 = type(respond_transaction).from_bytes(message_bytes) - assert message_8 == respond_transaction - assert bytes(message_8) == bytes(respond_transaction) + message_8 = type(request_transaction).from_bytes(message_bytes) + assert message_8 == request_transaction + assert bytes(message_8) == bytes(request_transaction) message_bytes, input_bytes = parse_blob(input_bytes) - message_9 = type(request_proof_of_weight).from_bytes(message_bytes) - assert message_9 == request_proof_of_weight - assert bytes(message_9) == bytes(request_proof_of_weight) + message_9 = type(respond_transaction).from_bytes(message_bytes) + assert message_9 == respond_transaction + assert bytes(message_9) == bytes(respond_transaction) message_bytes, input_bytes = parse_blob(input_bytes) - message_10 = type(respond_proof_of_weight).from_bytes(message_bytes) - assert message_10 == respond_proof_of_weight - assert bytes(message_10) == bytes(respond_proof_of_weight) + message_10 = type(request_proof_of_weight).from_bytes(message_bytes) + assert message_10 == request_proof_of_weight + assert bytes(message_10) == bytes(request_proof_of_weight) message_bytes, input_bytes = parse_blob(input_bytes) - message_11 = type(request_block).from_bytes(message_bytes) - assert message_11 == request_block - assert bytes(message_11) == bytes(request_block) + message_11 = type(respond_proof_of_weight).from_bytes(message_bytes) + assert message_11 == respond_proof_of_weight + assert bytes(message_11) == bytes(respond_proof_of_weight) message_bytes, input_bytes = parse_blob(input_bytes) - message_12 = type(reject_block).from_bytes(message_bytes) - assert message_12 == reject_block - assert bytes(message_12) == bytes(reject_block) + message_12 = type(request_block).from_bytes(message_bytes) + assert message_12 == request_block + assert bytes(message_12) == bytes(request_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_13 = type(request_blocks).from_bytes(message_bytes) - assert message_13 == request_blocks - assert bytes(message_13) == bytes(request_blocks) + message_13 = type(reject_block).from_bytes(message_bytes) + assert message_13 == reject_block + assert bytes(message_13) == bytes(reject_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_14 = type(respond_blocks).from_bytes(message_bytes) - assert message_14 == respond_blocks - assert bytes(message_14) == bytes(respond_blocks) + message_14 = type(request_blocks).from_bytes(message_bytes) + assert message_14 == request_blocks + assert bytes(message_14) == bytes(request_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_15 = type(reject_blocks).from_bytes(message_bytes) - assert message_15 == reject_blocks - assert bytes(message_15) == bytes(reject_blocks) + message_15 = type(respond_blocks).from_bytes(message_bytes) + assert message_15 == respond_blocks + assert bytes(message_15) == bytes(respond_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_16 = type(respond_block).from_bytes(message_bytes) - assert message_16 == respond_block - assert bytes(message_16) == bytes(respond_block) + message_16 = type(reject_blocks).from_bytes(message_bytes) + assert message_16 == reject_blocks + assert bytes(message_16) == bytes(reject_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_17 = type(new_unfinished_block).from_bytes(message_bytes) - assert message_17 == new_unfinished_block - assert bytes(message_17) == bytes(new_unfinished_block) + message_17 = type(respond_block).from_bytes(message_bytes) + assert message_17 == respond_block + assert bytes(message_17) == bytes(respond_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_18 = type(request_unfinished_block).from_bytes(message_bytes) - assert message_18 == request_unfinished_block - assert bytes(message_18) == bytes(request_unfinished_block) + message_18 = type(new_unfinished_block).from_bytes(message_bytes) + assert message_18 == new_unfinished_block + assert bytes(message_18) == bytes(new_unfinished_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_19 = type(respond_unfinished_block).from_bytes(message_bytes) - assert message_19 == respond_unfinished_block - assert bytes(message_19) == bytes(respond_unfinished_block) + message_19 = type(request_unfinished_block).from_bytes(message_bytes) + assert message_19 == request_unfinished_block + assert bytes(message_19) == bytes(request_unfinished_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_20 = type(new_signage_point_or_end_of_subslot).from_bytes(message_bytes) - assert message_20 == new_signage_point_or_end_of_subslot - assert bytes(message_20) == bytes(new_signage_point_or_end_of_subslot) + message_20 = type(respond_unfinished_block).from_bytes(message_bytes) + assert message_20 == respond_unfinished_block + assert bytes(message_20) == bytes(respond_unfinished_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_21 = type(request_signage_point_or_end_of_subslot).from_bytes(message_bytes) - assert message_21 == request_signage_point_or_end_of_subslot - assert bytes(message_21) == bytes(request_signage_point_or_end_of_subslot) + message_21 = type(new_signage_point_or_end_of_subslot).from_bytes(message_bytes) + assert message_21 == new_signage_point_or_end_of_subslot + assert bytes(message_21) == bytes(new_signage_point_or_end_of_subslot) message_bytes, input_bytes = parse_blob(input_bytes) - message_22 = type(respond_signage_point).from_bytes(message_bytes) - assert message_22 == respond_signage_point - assert bytes(message_22) == bytes(respond_signage_point) + message_22 = type(request_signage_point_or_end_of_subslot).from_bytes(message_bytes) + assert message_22 == request_signage_point_or_end_of_subslot + assert bytes(message_22) == bytes(request_signage_point_or_end_of_subslot) message_bytes, input_bytes = parse_blob(input_bytes) - message_23 = type(respond_end_of_subslot).from_bytes(message_bytes) - assert message_23 == respond_end_of_subslot - assert bytes(message_23) == bytes(respond_end_of_subslot) + message_23 = type(respond_signage_point).from_bytes(message_bytes) + assert message_23 == respond_signage_point + assert bytes(message_23) == bytes(respond_signage_point) message_bytes, input_bytes = parse_blob(input_bytes) - message_24 = type(request_mempool_transaction).from_bytes(message_bytes) - assert message_24 == request_mempool_transaction - assert bytes(message_24) == bytes(request_mempool_transaction) + message_24 = type(respond_end_of_subslot).from_bytes(message_bytes) + assert message_24 == respond_end_of_subslot + assert bytes(message_24) == bytes(respond_end_of_subslot) message_bytes, input_bytes = parse_blob(input_bytes) - message_25 = type(new_compact_vdf).from_bytes(message_bytes) - assert message_25 == new_compact_vdf - assert bytes(message_25) == bytes(new_compact_vdf) + message_25 = type(request_mempool_transaction).from_bytes(message_bytes) + assert message_25 == request_mempool_transaction + assert bytes(message_25) == bytes(request_mempool_transaction) message_bytes, input_bytes = parse_blob(input_bytes) - message_26 = type(request_compact_vdf).from_bytes(message_bytes) - assert message_26 == request_compact_vdf - assert bytes(message_26) == bytes(request_compact_vdf) + message_26 = type(new_compact_vdf).from_bytes(message_bytes) + assert message_26 == new_compact_vdf + assert bytes(message_26) == bytes(new_compact_vdf) message_bytes, input_bytes = parse_blob(input_bytes) - message_27 = type(respond_compact_vdf).from_bytes(message_bytes) - assert message_27 == respond_compact_vdf - assert bytes(message_27) == bytes(respond_compact_vdf) + message_27 = type(request_compact_vdf).from_bytes(message_bytes) + assert message_27 == request_compact_vdf + assert bytes(message_27) == bytes(request_compact_vdf) message_bytes, input_bytes = parse_blob(input_bytes) - message_28 = type(request_peers).from_bytes(message_bytes) - assert message_28 == request_peers - assert bytes(message_28) == bytes(request_peers) + message_28 = type(respond_compact_vdf).from_bytes(message_bytes) + assert message_28 == respond_compact_vdf + assert bytes(message_28) == bytes(respond_compact_vdf) message_bytes, input_bytes = parse_blob(input_bytes) - message_29 = type(respond_peers).from_bytes(message_bytes) - assert message_29 == respond_peers - assert bytes(message_29) == bytes(respond_peers) + message_29 = type(request_peers).from_bytes(message_bytes) + assert message_29 == request_peers + assert bytes(message_29) == bytes(request_peers) message_bytes, input_bytes = parse_blob(input_bytes) - message_30 = type(new_unfinished_block2).from_bytes(message_bytes) - assert message_30 == new_unfinished_block2 - assert bytes(message_30) == bytes(new_unfinished_block2) + message_30 = type(respond_peers).from_bytes(message_bytes) + assert message_30 == respond_peers + assert bytes(message_30) == bytes(respond_peers) message_bytes, input_bytes = parse_blob(input_bytes) - message_31 = type(request_unfinished_block2).from_bytes(message_bytes) - assert message_31 == request_unfinished_block2 - assert bytes(message_31) == bytes(request_unfinished_block2) + message_31 = type(new_unfinished_block2).from_bytes(message_bytes) + assert message_31 == new_unfinished_block2 + assert bytes(message_31) == bytes(new_unfinished_block2) message_bytes, input_bytes = parse_blob(input_bytes) - message_32 = type(request_puzzle_solution).from_bytes(message_bytes) - assert message_32 == request_puzzle_solution - assert bytes(message_32) == bytes(request_puzzle_solution) + message_32 = type(request_unfinished_block2).from_bytes(message_bytes) + assert message_32 == request_unfinished_block2 + assert bytes(message_32) == bytes(request_unfinished_block2) message_bytes, input_bytes = parse_blob(input_bytes) - message_33 = type(puzzle_solution_response).from_bytes(message_bytes) - assert message_33 == puzzle_solution_response - assert bytes(message_33) == bytes(puzzle_solution_response) + message_33 = type(request_puzzle_solution).from_bytes(message_bytes) + assert message_33 == request_puzzle_solution + assert bytes(message_33) == bytes(request_puzzle_solution) message_bytes, input_bytes = parse_blob(input_bytes) - message_34 = type(respond_puzzle_solution).from_bytes(message_bytes) - assert message_34 == respond_puzzle_solution - assert bytes(message_34) == bytes(respond_puzzle_solution) + message_34 = type(puzzle_solution_response).from_bytes(message_bytes) + assert message_34 == puzzle_solution_response + assert bytes(message_34) == bytes(puzzle_solution_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_35 = type(reject_puzzle_solution).from_bytes(message_bytes) - assert message_35 == reject_puzzle_solution - assert bytes(message_35) == bytes(reject_puzzle_solution) + message_35 = type(respond_puzzle_solution).from_bytes(message_bytes) + assert message_35 == respond_puzzle_solution + assert bytes(message_35) == bytes(respond_puzzle_solution) message_bytes, input_bytes = parse_blob(input_bytes) - message_36 = type(send_transaction).from_bytes(message_bytes) - assert message_36 == send_transaction - assert bytes(message_36) == bytes(send_transaction) + message_36 = type(reject_puzzle_solution).from_bytes(message_bytes) + assert message_36 == reject_puzzle_solution + assert bytes(message_36) == bytes(reject_puzzle_solution) message_bytes, input_bytes = parse_blob(input_bytes) - message_37 = type(transaction_ack).from_bytes(message_bytes) - assert message_37 == transaction_ack - assert bytes(message_37) == bytes(transaction_ack) + message_37 = type(send_transaction).from_bytes(message_bytes) + assert message_37 == send_transaction + assert bytes(message_37) == bytes(send_transaction) message_bytes, input_bytes = parse_blob(input_bytes) - message_38 = type(new_peak_wallet).from_bytes(message_bytes) - assert message_38 == new_peak_wallet - assert bytes(message_38) == bytes(new_peak_wallet) + message_38 = type(transaction_ack).from_bytes(message_bytes) + assert message_38 == transaction_ack + assert bytes(message_38) == bytes(transaction_ack) message_bytes, input_bytes = parse_blob(input_bytes) - message_39 = type(request_block_header).from_bytes(message_bytes) - assert message_39 == request_block_header - assert bytes(message_39) == bytes(request_block_header) + message_39 = type(new_peak_wallet).from_bytes(message_bytes) + assert message_39 == new_peak_wallet + assert bytes(message_39) == bytes(new_peak_wallet) message_bytes, input_bytes = parse_blob(input_bytes) - message_40 = type(request_block_headers).from_bytes(message_bytes) - assert message_40 == request_block_headers - assert bytes(message_40) == bytes(request_block_headers) + message_40 = type(request_block_header).from_bytes(message_bytes) + assert message_40 == request_block_header + assert bytes(message_40) == bytes(request_block_header) message_bytes, input_bytes = parse_blob(input_bytes) - message_41 = type(respond_header_block).from_bytes(message_bytes) - assert message_41 == respond_header_block - assert bytes(message_41) == bytes(respond_header_block) + message_41 = type(request_block_headers).from_bytes(message_bytes) + assert message_41 == request_block_headers + assert bytes(message_41) == bytes(request_block_headers) message_bytes, input_bytes = parse_blob(input_bytes) - message_42 = type(respond_block_headers).from_bytes(message_bytes) - assert message_42 == respond_block_headers - assert bytes(message_42) == bytes(respond_block_headers) + message_42 = type(respond_header_block).from_bytes(message_bytes) + assert message_42 == respond_header_block + assert bytes(message_42) == bytes(respond_header_block) message_bytes, input_bytes = parse_blob(input_bytes) - message_43 = type(reject_header_request).from_bytes(message_bytes) - assert message_43 == reject_header_request - assert bytes(message_43) == bytes(reject_header_request) + message_43 = type(respond_block_headers).from_bytes(message_bytes) + assert message_43 == respond_block_headers + assert bytes(message_43) == bytes(respond_block_headers) message_bytes, input_bytes = parse_blob(input_bytes) - message_44 = type(request_removals).from_bytes(message_bytes) - assert message_44 == request_removals - assert bytes(message_44) == bytes(request_removals) + message_44 = type(reject_header_request).from_bytes(message_bytes) + assert message_44 == reject_header_request + assert bytes(message_44) == bytes(reject_header_request) message_bytes, input_bytes = parse_blob(input_bytes) - message_45 = type(respond_removals).from_bytes(message_bytes) - assert message_45 == respond_removals - assert bytes(message_45) == bytes(respond_removals) + message_45 = type(request_removals).from_bytes(message_bytes) + assert message_45 == request_removals + assert bytes(message_45) == bytes(request_removals) message_bytes, input_bytes = parse_blob(input_bytes) - message_46 = type(reject_removals_request).from_bytes(message_bytes) - assert message_46 == reject_removals_request - assert bytes(message_46) == bytes(reject_removals_request) + message_46 = type(respond_removals).from_bytes(message_bytes) + assert message_46 == respond_removals + assert bytes(message_46) == bytes(respond_removals) message_bytes, input_bytes = parse_blob(input_bytes) - message_47 = type(request_additions).from_bytes(message_bytes) - assert message_47 == request_additions - assert bytes(message_47) == bytes(request_additions) + message_47 = type(reject_removals_request).from_bytes(message_bytes) + assert message_47 == reject_removals_request + assert bytes(message_47) == bytes(reject_removals_request) message_bytes, input_bytes = parse_blob(input_bytes) - message_48 = type(respond_additions).from_bytes(message_bytes) - assert message_48 == respond_additions - assert bytes(message_48) == bytes(respond_additions) + message_48 = type(request_additions).from_bytes(message_bytes) + assert message_48 == request_additions + assert bytes(message_48) == bytes(request_additions) message_bytes, input_bytes = parse_blob(input_bytes) - message_49 = type(reject_additions).from_bytes(message_bytes) - assert message_49 == reject_additions - assert bytes(message_49) == bytes(reject_additions) + message_49 = type(respond_additions).from_bytes(message_bytes) + assert message_49 == respond_additions + assert bytes(message_49) == bytes(respond_additions) message_bytes, input_bytes = parse_blob(input_bytes) - message_50 = type(request_header_blocks).from_bytes(message_bytes) - assert message_50 == request_header_blocks - assert bytes(message_50) == bytes(request_header_blocks) + message_50 = type(reject_additions).from_bytes(message_bytes) + assert message_50 == reject_additions + assert bytes(message_50) == bytes(reject_additions) message_bytes, input_bytes = parse_blob(input_bytes) - message_51 = type(reject_header_blocks).from_bytes(message_bytes) - assert message_51 == reject_header_blocks - assert bytes(message_51) == bytes(reject_header_blocks) + message_51 = type(request_header_blocks).from_bytes(message_bytes) + assert message_51 == request_header_blocks + assert bytes(message_51) == bytes(request_header_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_52 = type(respond_header_blocks).from_bytes(message_bytes) - assert message_52 == respond_header_blocks - assert bytes(message_52) == bytes(respond_header_blocks) + message_52 = type(reject_header_blocks).from_bytes(message_bytes) + assert message_52 == reject_header_blocks + assert bytes(message_52) == bytes(reject_header_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_53 = type(coin_state).from_bytes(message_bytes) - assert message_53 == coin_state - assert bytes(message_53) == bytes(coin_state) + message_53 = type(respond_header_blocks).from_bytes(message_bytes) + assert message_53 == respond_header_blocks + assert bytes(message_53) == bytes(respond_header_blocks) message_bytes, input_bytes = parse_blob(input_bytes) - message_54 = type(register_for_ph_updates).from_bytes(message_bytes) - assert message_54 == register_for_ph_updates - assert bytes(message_54) == bytes(register_for_ph_updates) + message_54 = type(coin_state).from_bytes(message_bytes) + assert message_54 == coin_state + assert bytes(message_54) == bytes(coin_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_55 = type(reject_block_headers).from_bytes(message_bytes) - assert message_55 == reject_block_headers - assert bytes(message_55) == bytes(reject_block_headers) + message_55 = type(register_for_ph_updates).from_bytes(message_bytes) + assert message_55 == register_for_ph_updates + assert bytes(message_55) == bytes(register_for_ph_updates) message_bytes, input_bytes = parse_blob(input_bytes) - message_56 = type(respond_to_ph_updates).from_bytes(message_bytes) - assert message_56 == respond_to_ph_updates - assert bytes(message_56) == bytes(respond_to_ph_updates) + message_56 = type(reject_block_headers).from_bytes(message_bytes) + assert message_56 == reject_block_headers + assert bytes(message_56) == bytes(reject_block_headers) message_bytes, input_bytes = parse_blob(input_bytes) - message_57 = type(register_for_coin_updates).from_bytes(message_bytes) - assert message_57 == register_for_coin_updates - assert bytes(message_57) == bytes(register_for_coin_updates) + message_57 = type(respond_to_ph_updates).from_bytes(message_bytes) + assert message_57 == respond_to_ph_updates + assert bytes(message_57) == bytes(respond_to_ph_updates) message_bytes, input_bytes = parse_blob(input_bytes) - message_58 = type(respond_to_coin_updates).from_bytes(message_bytes) - assert message_58 == respond_to_coin_updates - assert bytes(message_58) == bytes(respond_to_coin_updates) + message_58 = type(register_for_coin_updates).from_bytes(message_bytes) + assert message_58 == register_for_coin_updates + assert bytes(message_58) == bytes(register_for_coin_updates) message_bytes, input_bytes = parse_blob(input_bytes) - message_59 = type(coin_state_update).from_bytes(message_bytes) - assert message_59 == coin_state_update - assert bytes(message_59) == bytes(coin_state_update) + message_59 = type(respond_to_coin_updates).from_bytes(message_bytes) + assert message_59 == respond_to_coin_updates + assert bytes(message_59) == bytes(respond_to_coin_updates) message_bytes, input_bytes = parse_blob(input_bytes) - message_60 = type(request_children).from_bytes(message_bytes) - assert message_60 == request_children - assert bytes(message_60) == bytes(request_children) + message_60 = type(coin_state_update).from_bytes(message_bytes) + assert message_60 == coin_state_update + assert bytes(message_60) == bytes(coin_state_update) message_bytes, input_bytes = parse_blob(input_bytes) - message_61 = type(respond_children).from_bytes(message_bytes) - assert message_61 == respond_children - assert bytes(message_61) == bytes(respond_children) + message_61 = type(request_children).from_bytes(message_bytes) + assert message_61 == request_children + assert bytes(message_61) == bytes(request_children) message_bytes, input_bytes = parse_blob(input_bytes) - message_62 = type(request_ses_info).from_bytes(message_bytes) - assert message_62 == request_ses_info - assert bytes(message_62) == bytes(request_ses_info) + message_62 = type(respond_children).from_bytes(message_bytes) + assert message_62 == respond_children + assert bytes(message_62) == bytes(respond_children) message_bytes, input_bytes = parse_blob(input_bytes) - message_63 = type(respond_ses_info).from_bytes(message_bytes) - assert message_63 == respond_ses_info - assert bytes(message_63) == bytes(respond_ses_info) + message_63 = type(request_ses_info).from_bytes(message_bytes) + assert message_63 == request_ses_info + assert bytes(message_63) == bytes(request_ses_info) message_bytes, input_bytes = parse_blob(input_bytes) - message_64 = type(coin_state_filters).from_bytes(message_bytes) - assert message_64 == coin_state_filters - assert bytes(message_64) == bytes(coin_state_filters) + message_64 = type(respond_ses_info).from_bytes(message_bytes) + assert message_64 == respond_ses_info + assert bytes(message_64) == bytes(respond_ses_info) message_bytes, input_bytes = parse_blob(input_bytes) - message_65 = type(request_remove_puzzle_subscriptions).from_bytes(message_bytes) - assert message_65 == request_remove_puzzle_subscriptions - assert bytes(message_65) == bytes(request_remove_puzzle_subscriptions) + message_65 = type(coin_state_filters).from_bytes(message_bytes) + assert message_65 == coin_state_filters + assert bytes(message_65) == bytes(coin_state_filters) message_bytes, input_bytes = parse_blob(input_bytes) - message_66 = type(respond_remove_puzzle_subscriptions).from_bytes(message_bytes) - assert message_66 == respond_remove_puzzle_subscriptions - assert bytes(message_66) == bytes(respond_remove_puzzle_subscriptions) + message_66 = type(request_remove_puzzle_subscriptions).from_bytes(message_bytes) + assert message_66 == request_remove_puzzle_subscriptions + assert bytes(message_66) == bytes(request_remove_puzzle_subscriptions) message_bytes, input_bytes = parse_blob(input_bytes) - message_67 = type(request_remove_coin_subscriptions).from_bytes(message_bytes) - assert message_67 == request_remove_coin_subscriptions - assert bytes(message_67) == bytes(request_remove_coin_subscriptions) + message_67 = type(respond_remove_puzzle_subscriptions).from_bytes(message_bytes) + assert message_67 == respond_remove_puzzle_subscriptions + assert bytes(message_67) == bytes(respond_remove_puzzle_subscriptions) message_bytes, input_bytes = parse_blob(input_bytes) - message_68 = type(respond_remove_coin_subscriptions).from_bytes(message_bytes) - assert message_68 == respond_remove_coin_subscriptions - assert bytes(message_68) == bytes(respond_remove_coin_subscriptions) + message_68 = type(request_remove_coin_subscriptions).from_bytes(message_bytes) + assert message_68 == request_remove_coin_subscriptions + assert bytes(message_68) == bytes(request_remove_coin_subscriptions) message_bytes, input_bytes = parse_blob(input_bytes) - message_69 = type(request_puzzle_state).from_bytes(message_bytes) - assert message_69 == request_puzzle_state - assert bytes(message_69) == bytes(request_puzzle_state) + message_69 = type(respond_remove_coin_subscriptions).from_bytes(message_bytes) + assert message_69 == respond_remove_coin_subscriptions + assert bytes(message_69) == bytes(respond_remove_coin_subscriptions) message_bytes, input_bytes = parse_blob(input_bytes) - message_70 = type(reject_puzzle_state).from_bytes(message_bytes) - assert message_70 == reject_puzzle_state - assert bytes(message_70) == bytes(reject_puzzle_state) + message_70 = type(request_puzzle_state).from_bytes(message_bytes) + assert message_70 == request_puzzle_state + assert bytes(message_70) == bytes(request_puzzle_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_71 = type(respond_puzzle_state).from_bytes(message_bytes) - assert message_71 == respond_puzzle_state - assert bytes(message_71) == bytes(respond_puzzle_state) + message_71 = type(reject_puzzle_state).from_bytes(message_bytes) + assert message_71 == reject_puzzle_state + assert bytes(message_71) == bytes(reject_puzzle_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_72 = type(request_coin_state).from_bytes(message_bytes) - assert message_72 == request_coin_state - assert bytes(message_72) == bytes(request_coin_state) + message_72 = type(respond_puzzle_state).from_bytes(message_bytes) + assert message_72 == respond_puzzle_state + assert bytes(message_72) == bytes(respond_puzzle_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_73 = type(respond_coin_state).from_bytes(message_bytes) - assert message_73 == respond_coin_state - assert bytes(message_73) == bytes(respond_coin_state) + message_73 = type(request_coin_state).from_bytes(message_bytes) + assert message_73 == request_coin_state + assert bytes(message_73) == bytes(request_coin_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_74 = type(reject_coin_state).from_bytes(message_bytes) - assert message_74 == reject_coin_state - assert bytes(message_74) == bytes(reject_coin_state) + message_74 = type(respond_coin_state).from_bytes(message_bytes) + assert message_74 == respond_coin_state + assert bytes(message_74) == bytes(respond_coin_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_75 = type(request_cost_info).from_bytes(message_bytes) - assert message_75 == request_cost_info - assert bytes(message_75) == bytes(request_cost_info) + message_75 = type(reject_coin_state).from_bytes(message_bytes) + assert message_75 == reject_coin_state + assert bytes(message_75) == bytes(reject_coin_state) message_bytes, input_bytes = parse_blob(input_bytes) - message_76 = type(respond_cost_info).from_bytes(message_bytes) - assert message_76 == respond_cost_info - assert bytes(message_76) == bytes(respond_cost_info) + message_76 = type(request_cost_info).from_bytes(message_bytes) + assert message_76 == request_cost_info + assert bytes(message_76) == bytes(request_cost_info) message_bytes, input_bytes = parse_blob(input_bytes) - message_77 = type(pool_difficulty).from_bytes(message_bytes) - assert message_77 == pool_difficulty - assert bytes(message_77) == bytes(pool_difficulty) + message_77 = type(respond_cost_info).from_bytes(message_bytes) + assert message_77 == respond_cost_info + assert bytes(message_77) == bytes(respond_cost_info) message_bytes, input_bytes = parse_blob(input_bytes) - message_78 = type(harvester_handhsake).from_bytes(message_bytes) - assert message_78 == harvester_handhsake - assert bytes(message_78) == bytes(harvester_handhsake) + message_78 = type(pool_difficulty).from_bytes(message_bytes) + assert message_78 == pool_difficulty + assert bytes(message_78) == bytes(pool_difficulty) message_bytes, input_bytes = parse_blob(input_bytes) - message_79 = type(new_signage_point_harvester).from_bytes(message_bytes) - assert message_79 == new_signage_point_harvester - assert bytes(message_79) == bytes(new_signage_point_harvester) + message_79 = type(harvester_handhsake).from_bytes(message_bytes) + assert message_79 == harvester_handhsake + assert bytes(message_79) == bytes(harvester_handhsake) message_bytes, input_bytes = parse_blob(input_bytes) - message_80 = type(new_proof_of_space).from_bytes(message_bytes) - assert message_80 == new_proof_of_space - assert bytes(message_80) == bytes(new_proof_of_space) + message_80 = type(new_signage_point_harvester).from_bytes(message_bytes) + assert message_80 == new_signage_point_harvester + assert bytes(message_80) == bytes(new_signage_point_harvester) message_bytes, input_bytes = parse_blob(input_bytes) - message_81 = type(request_signatures).from_bytes(message_bytes) - assert message_81 == request_signatures - assert bytes(message_81) == bytes(request_signatures) + message_81 = type(new_proof_of_space).from_bytes(message_bytes) + assert message_81 == new_proof_of_space + assert bytes(message_81) == bytes(new_proof_of_space) message_bytes, input_bytes = parse_blob(input_bytes) - message_82 = type(respond_signatures).from_bytes(message_bytes) - assert message_82 == respond_signatures - assert bytes(message_82) == bytes(respond_signatures) + message_82 = type(request_signatures).from_bytes(message_bytes) + assert message_82 == request_signatures + assert bytes(message_82) == bytes(request_signatures) message_bytes, input_bytes = parse_blob(input_bytes) - message_83 = type(plot).from_bytes(message_bytes) - assert message_83 == plot - assert bytes(message_83) == bytes(plot) + message_83 = type(respond_signatures).from_bytes(message_bytes) + assert message_83 == respond_signatures + assert bytes(message_83) == bytes(respond_signatures) message_bytes, input_bytes = parse_blob(input_bytes) - message_84 = type(request_plots).from_bytes(message_bytes) - assert message_84 == request_plots - assert bytes(message_84) == bytes(request_plots) + message_84 = type(plot).from_bytes(message_bytes) + assert message_84 == plot + assert bytes(message_84) == bytes(plot) message_bytes, input_bytes = parse_blob(input_bytes) - message_85 = type(respond_plots).from_bytes(message_bytes) - assert message_85 == respond_plots - assert bytes(message_85) == bytes(respond_plots) + message_85 = type(request_plots).from_bytes(message_bytes) + assert message_85 == request_plots + assert bytes(message_85) == bytes(request_plots) message_bytes, input_bytes = parse_blob(input_bytes) - message_86 = type(request_peers_introducer).from_bytes(message_bytes) - assert message_86 == request_peers_introducer - assert bytes(message_86) == bytes(request_peers_introducer) + message_86 = type(respond_plots).from_bytes(message_bytes) + assert message_86 == respond_plots + assert bytes(message_86) == bytes(respond_plots) message_bytes, input_bytes = parse_blob(input_bytes) - message_87 = type(respond_peers_introducer).from_bytes(message_bytes) - assert message_87 == respond_peers_introducer - assert bytes(message_87) == bytes(respond_peers_introducer) + message_87 = type(request_peers_introducer).from_bytes(message_bytes) + assert message_87 == request_peers_introducer + assert bytes(message_87) == bytes(request_peers_introducer) message_bytes, input_bytes = parse_blob(input_bytes) - message_88 = type(authentication_payload).from_bytes(message_bytes) - assert message_88 == authentication_payload - assert bytes(message_88) == bytes(authentication_payload) + message_88 = type(respond_peers_introducer).from_bytes(message_bytes) + assert message_88 == respond_peers_introducer + assert bytes(message_88) == bytes(respond_peers_introducer) message_bytes, input_bytes = parse_blob(input_bytes) - message_89 = type(get_pool_info_response).from_bytes(message_bytes) - assert message_89 == get_pool_info_response - assert bytes(message_89) == bytes(get_pool_info_response) + message_89 = type(authentication_payload).from_bytes(message_bytes) + assert message_89 == authentication_payload + assert bytes(message_89) == bytes(authentication_payload) message_bytes, input_bytes = parse_blob(input_bytes) - message_90 = type(post_partial_payload).from_bytes(message_bytes) - assert message_90 == post_partial_payload - assert bytes(message_90) == bytes(post_partial_payload) + message_90 = type(get_pool_info_response).from_bytes(message_bytes) + assert message_90 == get_pool_info_response + assert bytes(message_90) == bytes(get_pool_info_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_91 = type(post_partial_request).from_bytes(message_bytes) - assert message_91 == post_partial_request - assert bytes(message_91) == bytes(post_partial_request) + message_91 = type(post_partial_payload).from_bytes(message_bytes) + assert message_91 == post_partial_payload + assert bytes(message_91) == bytes(post_partial_payload) message_bytes, input_bytes = parse_blob(input_bytes) - message_92 = type(post_partial_response).from_bytes(message_bytes) - assert message_92 == post_partial_response - assert bytes(message_92) == bytes(post_partial_response) + message_92 = type(post_partial_request).from_bytes(message_bytes) + assert message_92 == post_partial_request + assert bytes(message_92) == bytes(post_partial_request) message_bytes, input_bytes = parse_blob(input_bytes) - message_93 = type(get_farmer_response).from_bytes(message_bytes) - assert message_93 == get_farmer_response - assert bytes(message_93) == bytes(get_farmer_response) + message_93 = type(post_partial_response).from_bytes(message_bytes) + assert message_93 == post_partial_response + assert bytes(message_93) == bytes(post_partial_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_94 = type(post_farmer_payload).from_bytes(message_bytes) - assert message_94 == post_farmer_payload - assert bytes(message_94) == bytes(post_farmer_payload) + message_94 = type(get_farmer_response).from_bytes(message_bytes) + assert message_94 == get_farmer_response + assert bytes(message_94) == bytes(get_farmer_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_95 = type(post_farmer_request).from_bytes(message_bytes) - assert message_95 == post_farmer_request - assert bytes(message_95) == bytes(post_farmer_request) + message_95 = type(post_farmer_payload).from_bytes(message_bytes) + assert message_95 == post_farmer_payload + assert bytes(message_95) == bytes(post_farmer_payload) message_bytes, input_bytes = parse_blob(input_bytes) - message_96 = type(post_farmer_response).from_bytes(message_bytes) - assert message_96 == post_farmer_response - assert bytes(message_96) == bytes(post_farmer_response) + message_96 = type(post_farmer_request).from_bytes(message_bytes) + assert message_96 == post_farmer_request + assert bytes(message_96) == bytes(post_farmer_request) message_bytes, input_bytes = parse_blob(input_bytes) - message_97 = type(put_farmer_payload).from_bytes(message_bytes) - assert message_97 == put_farmer_payload - assert bytes(message_97) == bytes(put_farmer_payload) + message_97 = type(post_farmer_response).from_bytes(message_bytes) + assert message_97 == post_farmer_response + assert bytes(message_97) == bytes(post_farmer_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_98 = type(put_farmer_request).from_bytes(message_bytes) - assert message_98 == put_farmer_request - assert bytes(message_98) == bytes(put_farmer_request) + message_98 = type(put_farmer_payload).from_bytes(message_bytes) + assert message_98 == put_farmer_payload + assert bytes(message_98) == bytes(put_farmer_payload) message_bytes, input_bytes = parse_blob(input_bytes) - message_99 = type(put_farmer_response).from_bytes(message_bytes) - assert message_99 == put_farmer_response - assert bytes(message_99) == bytes(put_farmer_response) + message_99 = type(put_farmer_request).from_bytes(message_bytes) + assert message_99 == put_farmer_request + assert bytes(message_99) == bytes(put_farmer_request) message_bytes, input_bytes = parse_blob(input_bytes) - message_100 = type(error_response).from_bytes(message_bytes) - assert message_100 == error_response - assert bytes(message_100) == bytes(error_response) + message_100 = type(put_farmer_response).from_bytes(message_bytes) + assert message_100 == put_farmer_response + assert bytes(message_100) == bytes(put_farmer_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_101 = type(new_peak_timelord).from_bytes(message_bytes) - assert message_101 == new_peak_timelord - assert bytes(message_101) == bytes(new_peak_timelord) + message_101 = type(error_response).from_bytes(message_bytes) + assert message_101 == error_response + assert bytes(message_101) == bytes(error_response) message_bytes, input_bytes = parse_blob(input_bytes) - message_102 = type(new_unfinished_block_timelord).from_bytes(message_bytes) - assert message_102 == new_unfinished_block_timelord - assert bytes(message_102) == bytes(new_unfinished_block_timelord) + message_102 = type(new_peak_timelord).from_bytes(message_bytes) + assert message_102 == new_peak_timelord + assert bytes(message_102) == bytes(new_peak_timelord) message_bytes, input_bytes = parse_blob(input_bytes) - message_103 = type(new_infusion_point_vdf).from_bytes(message_bytes) - assert message_103 == new_infusion_point_vdf - assert bytes(message_103) == bytes(new_infusion_point_vdf) + message_103 = type(new_unfinished_block_timelord).from_bytes(message_bytes) + assert message_103 == new_unfinished_block_timelord + assert bytes(message_103) == bytes(new_unfinished_block_timelord) message_bytes, input_bytes = parse_blob(input_bytes) - message_104 = type(new_signage_point_vdf).from_bytes(message_bytes) - assert message_104 == new_signage_point_vdf - assert bytes(message_104) == bytes(new_signage_point_vdf) + message_104 = type(new_infusion_point_vdf).from_bytes(message_bytes) + assert message_104 == new_infusion_point_vdf + assert bytes(message_104) == bytes(new_infusion_point_vdf) message_bytes, input_bytes = parse_blob(input_bytes) - message_105 = type(new_end_of_sub_slot_bundle).from_bytes(message_bytes) - assert message_105 == new_end_of_sub_slot_bundle - assert bytes(message_105) == bytes(new_end_of_sub_slot_bundle) + message_105 = type(new_signage_point_vdf).from_bytes(message_bytes) + assert message_105 == new_signage_point_vdf + assert bytes(message_105) == bytes(new_signage_point_vdf) message_bytes, input_bytes = parse_blob(input_bytes) - message_106 = type(request_compact_proof_of_time).from_bytes(message_bytes) - assert message_106 == request_compact_proof_of_time - assert bytes(message_106) == bytes(request_compact_proof_of_time) + message_106 = type(new_end_of_sub_slot_bundle).from_bytes(message_bytes) + assert message_106 == new_end_of_sub_slot_bundle + assert bytes(message_106) == bytes(new_end_of_sub_slot_bundle) message_bytes, input_bytes = parse_blob(input_bytes) - message_107 = type(respond_compact_proof_of_time).from_bytes(message_bytes) - assert message_107 == respond_compact_proof_of_time - assert bytes(message_107) == bytes(respond_compact_proof_of_time) + message_107 = type(request_compact_proof_of_time).from_bytes(message_bytes) + assert message_107 == request_compact_proof_of_time + assert bytes(message_107) == bytes(request_compact_proof_of_time) message_bytes, input_bytes = parse_blob(input_bytes) - message_108 = type(error_without_data).from_bytes(message_bytes) - assert message_108 == error_without_data - assert bytes(message_108) == bytes(error_without_data) + message_108 = type(respond_compact_proof_of_time).from_bytes(message_bytes) + assert message_108 == respond_compact_proof_of_time + assert bytes(message_108) == bytes(respond_compact_proof_of_time) message_bytes, input_bytes = parse_blob(input_bytes) - message_109 = type(error_with_data).from_bytes(message_bytes) - assert message_109 == error_with_data - assert bytes(message_109) == bytes(error_with_data) + message_109 = type(error_without_data).from_bytes(message_bytes) + assert message_109 == error_without_data + assert bytes(message_109) == bytes(error_without_data) + + message_bytes, input_bytes = parse_blob(input_bytes) + message_110 = type(error_with_data).from_bytes(message_bytes) + assert message_110 == error_with_data + assert bytes(message_110) == bytes(error_with_data) + + message_bytes, input_bytes = parse_blob(input_bytes) + message_111 = type(solver_info).from_bytes(message_bytes) + assert message_111 == solver_info + assert bytes(message_111) == bytes(solver_info) + + message_bytes, input_bytes = parse_blob(input_bytes) + message_112 = type(solver_response).from_bytes(message_bytes) + assert message_112 == solver_response + assert bytes(message_112) == bytes(solver_response) assert input_bytes == b"" diff --git a/chia/_tests/util/test_network_protocol_json.py b/chia/_tests/util/test_network_protocol_json.py index 3acb9240a83e..cea4321ed030 100644 --- a/chia/_tests/util/test_network_protocol_json.py +++ b/chia/_tests/util/test_network_protocol_json.py @@ -18,6 +18,8 @@ def test_protocol_json() -> None: assert type(farming_info).from_json_dict(farming_info_json) == farming_info assert str(signed_values_json) == str(signed_values.to_json_dict()) assert type(signed_values).from_json_dict(signed_values_json) == signed_values + assert str(partial_proof_json) == str(partial_proof.to_json_dict()) + assert type(partial_proof).from_json_dict(partial_proof_json) == partial_proof assert str(new_peak_json) == str(new_peak.to_json_dict()) assert type(new_peak).from_json_dict(new_peak_json) == new_peak assert str(new_transaction_json) == str(new_transaction.to_json_dict()) @@ -265,3 +267,7 @@ def test_protocol_json() -> None: assert type(error_without_data).from_json_dict(error_without_data_json) == error_without_data assert str(error_with_data_json) == str(error_with_data.to_json_dict()) assert type(error_with_data).from_json_dict(error_with_data_json) == error_with_data + assert str(solver_info_json) == str(solver_info.to_json_dict()) + assert type(solver_info).from_json_dict(solver_info_json) == solver_info + assert str(solver_response_json) == str(solver_response.to_json_dict()) + assert type(solver_response).from_json_dict(solver_response_json) == solver_response diff --git a/chia/_tests/util/test_network_protocol_test.py b/chia/_tests/util/test_network_protocol_test.py index ca7ecdc38605..b81e063b994b 100644 --- a/chia/_tests/util/test_network_protocol_test.py +++ b/chia/_tests/util/test_network_protocol_test.py @@ -12,6 +12,7 @@ pool_protocol, protocol_message_types, shared_protocol, + solver_protocol, timelord_protocol, wallet_protocol, ) @@ -188,6 +189,7 @@ def test_missing_messages() -> None: "RequestSignatures", "RespondPlots", "RespondSignatures", + "PartialProofsData", } introducer_msgs = {"RequestPeersIntroducer", "RespondPeersIntroducer"} @@ -219,6 +221,8 @@ def test_missing_messages() -> None: "RespondCompactProofOfTime", } + solver_msgs = {"SolverInfo", "SolverResponse"} + shared_msgs = {"Handshake", "Capability", "Error"} # if these asserts fail, make sure to add the new network protocol messages @@ -252,6 +256,10 @@ def test_missing_messages() -> None: f"message types were added or removed from timelord_protocol. {STANDARD_ADVICE}" ) + assert types_in_module(solver_protocol) == solver_msgs, ( + f"message types were added or removed from shared_protocol. {STANDARD_ADVICE}" + ) + assert types_in_module(shared_protocol) == shared_msgs, ( f"message types were added or removed from shared_protocol. {STANDARD_ADVICE}" ) diff --git a/chia/apis.py b/chia/apis.py index 3445b7dd15c6..f30eac27fafb 100644 --- a/chia/apis.py +++ b/chia/apis.py @@ -6,6 +6,7 @@ from chia.introducer.introducer_api import IntroducerAPI from chia.protocols.outbound_message import NodeType from chia.server.api_protocol import ApiProtocol +from chia.solver.solver_api import SolverAPI from chia.timelord.timelord_api import TimelordAPI from chia.wallet.wallet_node_api import WalletNodeAPI @@ -16,4 +17,5 @@ NodeType.TIMELORD: TimelordAPI, NodeType.FARMER: FarmerAPI, NodeType.HARVESTER: HarvesterAPI, + NodeType.SOLVER: SolverAPI, } diff --git a/chia/cmds/chia.py b/chia/cmds/chia.py index 988d95f78a8d..0420676e2672 100644 --- a/chia/cmds/chia.py +++ b/chia/cmds/chia.py @@ -24,6 +24,7 @@ from chia.cmds.plotters import plotters_cmd from chia.cmds.rpc import rpc_cmd from chia.cmds.show import show_cmd +from chia.cmds.solver import solver_cmd from chia.cmds.start import start_cmd from chia.cmds.stop import stop_cmd from chia.cmds.wallet import wallet_cmd @@ -127,6 +128,7 @@ def run_daemon_cmd(ctx: click.Context, wait_for_unlock: bool) -> None: cli.add_command(init_cmd) cli.add_command(rpc_cmd) cli.add_command(show_cmd) +cli.add_command(solver_cmd) cli.add_command(start_cmd) cli.add_command(stop_cmd) cli.add_command(netspace_cmd) diff --git a/chia/cmds/cmds_util.py b/chia/cmds/cmds_util.py index 1362a3b83529..0ba57ea62717 100644 --- a/chia/cmds/cmds_util.py +++ b/chia/cmds/cmds_util.py @@ -23,6 +23,7 @@ from chia.harvester.harvester_rpc_client import HarvesterRpcClient from chia.rpc.rpc_client import ResponseFailureError, RpcClient from chia.simulator.simulator_full_node_rpc_client import SimulatorFullNodeRpcClient +from chia.solver.solver_rpc_client import SolverRpcClient from chia.types.mempool_submission_status import MempoolSubmissionStatus from chia.util.config import load_config from chia.util.errors import CliRpcConnectionError, InvalidPathError @@ -42,6 +43,7 @@ "harvester": HarvesterRpcClient, "data_layer": DataLayerRpcClient, "simulator": SimulatorFullNodeRpcClient, + "solver": SolverRpcClient, } node_config_section_names: dict[type[RpcClient], str] = { @@ -52,6 +54,7 @@ HarvesterRpcClient: "harvester", DataLayerRpcClient: "data_layer", SimulatorFullNodeRpcClient: "full_node", + SolverRpcClient: "solver", } _T_RpcClient = TypeVar("_T_RpcClient", bound=RpcClient) diff --git a/chia/cmds/rpc.py b/chia/cmds/rpc.py index d04cdb41e346..0b2f474ab037 100644 --- a/chia/cmds/rpc.py +++ b/chia/cmds/rpc.py @@ -13,7 +13,17 @@ from chia.cmds.cmd_classes import ChiaCliContext from chia.util.config import load_config -services: list[str] = ["crawler", "daemon", "farmer", "full_node", "harvester", "timelord", "wallet", "data_layer"] +services: list[str] = [ + "crawler", + "daemon", + "farmer", + "full_node", + "harvester", + "timelord", + "wallet", + "data_layer", + "solver", +] async def call_endpoint( diff --git a/chia/cmds/solver.py b/chia/cmds/solver.py new file mode 100644 index 000000000000..4b34a5e7b14e --- /dev/null +++ b/chia/cmds/solver.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import Optional + +import click + +from chia.cmds.cmd_classes import ChiaCliContext + + +@click.group("solver", help="Manage your solver") +def solver_cmd() -> None: + pass + + +@solver_cmd.command("get_state", help="Get current solver state") +@click.option( + "-sp", + "--solver-rpc-port", + help="Set the port where the Solver is hosting the RPC interface. See the rpc_port under solver in config.yaml", + type=int, + default=None, + show_default=True, +) +@click.pass_context +def get_state_cmd( + ctx: click.Context, + solver_rpc_port: Optional[int], +) -> None: + import asyncio + + from chia.cmds.solver_funcs import get_state + + asyncio.run(get_state(ChiaCliContext.set_default(ctx), solver_rpc_port)) diff --git a/chia/cmds/solver_funcs.py b/chia/cmds/solver_funcs.py new file mode 100644 index 000000000000..91da321ba5ae --- /dev/null +++ b/chia/cmds/solver_funcs.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +import json +from typing import Optional + +from chia.cmds.cmd_classes import ChiaCliContext +from chia.cmds.cmds_util import get_any_service_client +from chia.solver.solver_rpc_client import SolverRpcClient + + +async def get_state( + ctx: ChiaCliContext, + solver_rpc_port: Optional[int] = None, +) -> None: + """Get solver state via RPC.""" + try: + async with get_any_service_client(SolverRpcClient, ctx.root_path, solver_rpc_port) as (client, _): + response = await client.get_state() + print(json.dumps(response, indent=2)) + except Exception as e: + print(f"Failed to get solver state: {e}") diff --git a/chia/consensus/pos_quality.py b/chia/consensus/pos_quality.py index 5d7e78db7e49..2213b7d1ce6c 100644 --- a/chia/consensus/pos_quality.py +++ b/chia/consensus/pos_quality.py @@ -1,6 +1,7 @@ from __future__ import annotations from chia_rs import PlotSize +from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint64 # The actual space in bytes of a plot, is _expected_plot_size(k) * UI_ACTUAL_SPACE_CONSTANT_FACTO @@ -15,6 +16,11 @@ } +def quality_for_partial_proof(partial_proof: bytes, challenge: bytes32) -> bytes32: + # TODO todo_v2_plots real implementaion + return challenge + + def _expected_plot_size(size: PlotSize) -> uint64: """ Given the plot size parameter k (which is between 32 and 59), computes the diff --git a/chia/farmer/farmer.py b/chia/farmer/farmer.py index 5c15be8a4ffc..1634eff2619c 100644 --- a/chia/farmer/farmer.py +++ b/chia/farmer/farmer.py @@ -143,6 +143,9 @@ def __init__( # Quality string to plot identifier and challenge_hash, for use with harvester.RequestSignatures self.quality_str_to_identifiers: dict[bytes32, tuple[str, bytes32, bytes32, bytes32]] = {} + # Track pending solver requests, keyed by partial proof + self.pending_solver_requests: dict[bytes, dict[str, Any]] = {} + # number of responses to each signage point self.number_of_responses: dict[bytes32, int] = {} diff --git a/chia/farmer/farmer_api.py b/chia/farmer/farmer_api.py index 3a084212964d..406615444147 100644 --- a/chia/farmer/farmer_api.py +++ b/chia/farmer/farmer_api.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast import aiohttp -from chia_rs import AugSchemeMPL, G2Element, PoolTarget, PrivateKey +from chia_rs import AugSchemeMPL, G2Element, PoolTarget, PrivateKey, ProofOfSpace from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint8, uint16, uint32, uint64 @@ -17,6 +17,7 @@ from chia.protocols import farmer_protocol, harvester_protocol from chia.protocols.farmer_protocol import DeclareProofOfSpace, SignedValues from chia.protocols.harvester_protocol import ( + PartialProofsData, PlotSyncDone, PlotSyncPathList, PlotSyncPlotList, @@ -33,6 +34,7 @@ get_current_authentication_token, ) from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.protocols.solver_protocol import SolverInfo, SolverResponse from chia.server.api_protocol import ApiMetadata from chia.server.server import ssl_context_for_root from chia.server.ws_connection import WSChiaConnection @@ -478,6 +480,103 @@ async def new_proof_of_space( return + @metadata.request(peer_required=True) + async def partial_proofs(self, partial_proof_data: PartialProofsData, peer: WSChiaConnection) -> None: + """ + This is a response from the harvester for V2 plots, containing only partial proof data. + We send these to the solver service and wait for a response with the full proof. + """ + if partial_proof_data.sp_hash not in self.farmer.number_of_responses: + self.farmer.number_of_responses[partial_proof_data.sp_hash] = 0 + self.farmer.cache_add_time[partial_proof_data.sp_hash] = uint64(time.time()) + + if partial_proof_data.sp_hash not in self.farmer.sps: + self.farmer.log.warning( + f"Received partial proofs for a signage point that we do not have {partial_proof_data.sp_hash}" + ) + return None + + self.farmer.cache_add_time[partial_proof_data.sp_hash] = uint64(time.time()) + + self.farmer.log.info( + f"Received V2 partial proof collection with {len(partial_proof_data.partial_proofs)} partail proofs " + f"for plot {partial_proof_data.plot_identifier[:10]}... from {peer.peer_node_id}" + ) + + # Process each partial proof chain through solver service to get full proofs + for partial_proof in partial_proof_data.partial_proofs: + solver_info = SolverInfo(partial_proof=partial_proof) + + try: + # store pending request data for matching with response + self.farmer.pending_solver_requests[partial_proof] = { + "proof_data": partial_proof_data, + "peer": peer, + } + + # send solve request to all solver connections + msg = make_msg(ProtocolMessageTypes.solve, solver_info) + await self.farmer.server.send_to_all([msg], NodeType.SOLVER) + self.farmer.log.debug(f"Sent solve request for partial proof {partial_proof.hex()[:10]}...") + + except Exception as e: + self.farmer.log.error( + f"Failed to call solver service for partial proof {partial_proof.hex()[:10]}...: {e}" + ) + # clean up pending request + if partial_proof in self.farmer.pending_solver_requests: + del self.farmer.pending_solver_requests[partial_proof] + + @metadata.request() + async def solution_response(self, response: SolverResponse, peer: WSChiaConnection) -> None: + """ + Handle solution response from solver service. + This is called when a solver responds to a solve request. + """ + self.farmer.log.debug(f"Received solution response: {len(response.proof)} bytes from {peer.peer_node_id}") + + # find the matching pending request using partial_proof + + if response.partial_proof not in self.farmer.pending_solver_requests: + self.farmer.log.warning( + f"Received solver response for unknown partial proof {response.partial_proof.hex()}" + ) + return + + # get the original request data + request_data = self.farmer.pending_solver_requests.pop(response.partial_proof) + proof_data = request_data["proof_data"] + original_peer = request_data["peer"] + partial_proof = response.partial_proof + + # create the proof of space with the solver's proof + proof_bytes = response.proof + if proof_bytes is None or len(proof_bytes) == 0: + self.farmer.log.warning(f"Received empty proof from solver for proof {partial_proof.hex()}...") + return + + sp_challenge_hash = proof_data.challenge_hash + new_proof_of_space = harvester_protocol.NewProofOfSpace( + proof_data.challenge_hash, + proof_data.sp_hash, + proof_data.plot_identifier, + ProofOfSpace( + sp_challenge_hash, + proof_data.pool_public_key, + proof_data.pool_contract_puzzle_hash, + proof_data.plot_public_key, + proof_data.plot_size, + proof_bytes, + ), + proof_data.signage_point_index, + include_source_signature_data=False, + farmer_reward_address_override=None, + fee_info=None, + ) + + # process the proof of space + await self.new_proof_of_space(new_proof_of_space, original_peer) + @metadata.request() async def respond_signatures(self, response: harvester_protocol.RespondSignatures) -> None: request = self._process_respond_signatures(response) diff --git a/chia/harvester/harvester_api.py b/chia/harvester/harvester_api.py index 74621f51376d..0e4c981efd40 100644 --- a/chia/harvester/harvester_api.py +++ b/chia/harvester/harvester_api.py @@ -3,22 +3,25 @@ import asyncio import logging import time +from collections.abc import Awaitable, Sequence from pathlib import Path from typing import TYPE_CHECKING, ClassVar, Optional, cast from chia_rs import AugSchemeMPL, G1Element, G2Element, ProofOfSpace from chia_rs.sized_bytes import bytes32 -from chia_rs.sized_ints import uint8, uint32, uint64 +from chia_rs.sized_ints import uint32, uint64 +from chia.consensus.pos_quality import quality_for_partial_proof from chia.consensus.pot_iterations import ( calculate_iterations_quality, calculate_sp_interval_iters, ) from chia.harvester.harvester import Harvester +from chia.plotting.prover import PlotVersion from chia.plotting.util import PlotInfo, parse_plot_info from chia.protocols import harvester_protocol from chia.protocols.farmer_protocol import FarmingInfo -from chia.protocols.harvester_protocol import Plot, PlotSyncResponse +from chia.protocols.harvester_protocol import PartialProofsData, Plot, PlotSyncResponse from chia.protocols.outbound_message import Message, make_msg from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.server.api_protocol import ApiMetadata @@ -50,6 +53,60 @@ def __init__(self, harvester: Harvester): def ready(self) -> bool: return True + def _plot_passes_filter(self, plot_info: PlotInfo, challenge: harvester_protocol.NewSignagePointHarvester) -> bool: + filter_prefix_bits = calculate_prefix_bits( + self.harvester.constants, + challenge.peak_height, + plot_info.prover.get_size(), + ) + return passes_plot_filter( + filter_prefix_bits, + plot_info.prover.get_id(), + challenge.challenge_hash, + challenge.sp_hash, + ) + + async def _handle_v1_responses( + self, + awaitables: Sequence[Awaitable[tuple[Path, list[harvester_protocol.NewProofOfSpace]]]], + start_time: float, + peer: WSChiaConnection, + ) -> int: + proofs_found = 0 + for filename_sublist_awaitable in asyncio.as_completed(awaitables): + filename, sublist = await filename_sublist_awaitable + time_taken = time.monotonic() - start_time + if time_taken > 8: + self.harvester.log.warning( + f"Looking up qualities on {filename} took: {time_taken}. This should be below 8 seconds" + f" to minimize risk of losing rewards." + ) + for response in sublist: + proofs_found += 1 + msg = make_msg(ProtocolMessageTypes.new_proof_of_space, response) + await peer.send_message(msg) + return proofs_found + + async def _handle_v2_responses( + self, v2_awaitables: Sequence[Awaitable[Optional[PartialProofsData]]], start_time: float, peer: WSChiaConnection + ) -> int: + partial_proofs_found = 0 + for quality_awaitable in asyncio.as_completed(v2_awaitables): + partial_proofs_data = await quality_awaitable + if partial_proofs_data is None: + continue + time_taken = time.monotonic() - start_time + if time_taken > 8: + self.harvester.log.warning( + f"Looking up partial proofs on {partial_proofs_data.plot_identifier}" + f"took: {time_taken}. This should be below 8 seconds" + f"to minimize risk of losing rewards." + ) + partial_proofs_found += len(partial_proofs_data.partial_proofs) + msg = make_msg(ProtocolMessageTypes.partial_proofs, partial_proofs_data) + await peer.send_message(msg) + return partial_proofs_found + @metadata.request(peer_required=True) async def harvester_handshake( self, harvester_handshake: harvester_protocol.HarvesterHandshake, peer: WSChiaConnection @@ -97,6 +154,72 @@ async def new_signage_point_harvester( loop = asyncio.get_running_loop() + def blocking_lookup_v2_partial_proofs(filename: Path, plot_info: PlotInfo) -> Optional[PartialProofsData]: + # Uses the V2 Prover object to lookup qualities only. No full proofs generated. + try: + plot_id = plot_info.prover.get_id() + sp_challenge_hash = calculate_pos_challenge( + plot_id, + new_challenge.challenge_hash, + new_challenge.sp_hash, + ) + partial_proofs = plot_info.prover.get_partial_proofs_for_challenge(sp_challenge_hash) + + # If no partial proofs are found, return None + if len(partial_proofs) == 0: + return None + + # Get the appropriate difficulty for this plot + difficulty = new_challenge.difficulty + sub_slot_iters = new_challenge.sub_slot_iters + if plot_info.pool_contract_puzzle_hash is not None: + # Check for pool-specific difficulty + for pool_difficulty in new_challenge.pool_difficulties: + if pool_difficulty.pool_contract_puzzle_hash == plot_info.pool_contract_puzzle_hash: + difficulty = pool_difficulty.difficulty + sub_slot_iters = pool_difficulty.sub_slot_iters + break + + # Filter qualities that pass the required_iters check (same as V1 flow) + good_partial_proofs = [] + sp_interval_iters = calculate_sp_interval_iters(self.harvester.constants, sub_slot_iters) + + for partial_proof in partial_proofs: + quality_str = quality_for_partial_proof(partial_proof, new_challenge.challenge_hash) + required_iters: uint64 = calculate_iterations_quality( + self.harvester.constants, + quality_str, + plot_info.prover.get_size(), + difficulty, + new_challenge.sp_hash, + sub_slot_iters, + new_challenge.last_tx_height, + ) + + if required_iters < sp_interval_iters: + good_partial_proofs.append(partial_proof) + + if len(good_partial_proofs) == 0: + return None + + size = plot_info.prover.get_size().size_v2 + assert size is not None + return PartialProofsData( + new_challenge.challenge_hash, + new_challenge.sp_hash, + good_partial_proofs[0].hex() + str(filename.resolve()), + good_partial_proofs, + new_challenge.signage_point_index, + size, + plot_info.pool_public_key, + plot_info.pool_contract_puzzle_hash, + plot_info.plot_public_key, + ) + return None + except Exception: + self.harvester.log.exception("Failed V2 partial proof lookup") + return None + def blocking_lookup(filename: Path, plot_info: PlotInfo) -> list[tuple[bytes32, ProofOfSpace]]: # Uses the Prover object to lookup qualities. This is a blocking call, # so it should be run in a thread pool. @@ -241,6 +364,7 @@ async def lookup_challenge( return filename, all_responses awaitables = [] + v2_awaitables = [] passed = 0 total = 0 with self.harvester.plot_manager: @@ -249,20 +373,19 @@ async def lookup_challenge( # Passes the plot filter (does not check sp filter yet though, since we have not reached sp) # This is being executed at the beginning of the slot total += 1 - - filter_prefix_bits = uint8( - calculate_prefix_bits( - self.harvester.constants, - new_challenge.peak_height, - try_plot_info.prover.get_size(), + if not self._plot_passes_filter(try_plot_info, new_challenge): + continue + if try_plot_info.prover.get_version() == PlotVersion.V2: + v2_awaitables.append( + loop.run_in_executor( + self.harvester.executor, + blocking_lookup_v2_partial_proofs, + try_plot_filename, + try_plot_info, + ) ) - ) - if passes_plot_filter( - filter_prefix_bits, - try_plot_info.prover.get_id(), - new_challenge.challenge_hash, - new_challenge.sp_hash, - ): + passed += 1 + else: passed += 1 awaitables.append(lookup_challenge(try_plot_filename, try_plot_info)) self.harvester.log.debug(f"new_signage_point_harvester {passed} plots passed the plot filter") @@ -270,21 +393,24 @@ async def lookup_challenge( # Concurrently executes all lookups on disk, to take advantage of multiple disk parallelism time_taken = time.monotonic() - start total_proofs_found = 0 - for filename_sublist_awaitable in asyncio.as_completed(awaitables): - filename, sublist = await filename_sublist_awaitable - time_taken = time.monotonic() - start - if time_taken > 8: - self.harvester.log.warning( - f"Looking up qualities on {filename} took: {time_taken}. This should be below 8 seconds" - f" to minimize risk of losing rewards." - ) - else: - pass - # self.harvester.log.info(f"Looking up qualities on {filename} took: {time_taken}") - for response in sublist: - total_proofs_found += 1 - msg = make_msg(ProtocolMessageTypes.new_proof_of_space, response) - await peer.send_message(msg) + total_v2_partial_proofs_found = 0 + + # run both concurrently + tasks = [] + if awaitables: + tasks.append(self._handle_v1_responses(awaitables, start, peer)) + if v2_awaitables: + tasks.append(self._handle_v2_responses(v2_awaitables, start, peer)) + + if tasks: + results = await asyncio.gather(*tasks) + if len(results) == 2: + total_proofs_found, total_v2_partial_proofs_found = results + elif len(results) == 1: + if awaitables: + total_proofs_found = results[0] + else: + total_v2_partial_proofs_found = results[0] now = uint64(time.time()) @@ -301,9 +427,10 @@ async def lookup_challenge( await peer.send_message(pass_msg) self.harvester.log.info( - f"{len(awaitables)} plots were eligible for farming {new_challenge.challenge_hash.hex()[:10]}..." - f" Found {total_proofs_found} proofs. Time: {time_taken:.5f} s. " - f"Total {self.harvester.plot_manager.plot_count()} plots" + f"challenge_hash: {new_challenge.challenge_hash.hex()[:10]} ..." + f"{len(awaitables) + len(v2_awaitables)} plots were eligible for farming challenge" + f"Found {total_proofs_found} V1 proofs and {total_v2_partial_proofs_found} V2 qualities." + f" Time: {time_taken:.5f} s. Total {self.harvester.plot_manager.plot_count()} plots" ) self.harvester.state_changed( "farming_info", @@ -311,7 +438,8 @@ async def lookup_challenge( "challenge_hash": new_challenge.challenge_hash.hex(), "total_plots": self.harvester.plot_manager.plot_count(), "found_proofs": total_proofs_found, - "eligible_plots": len(awaitables), + "found_v2_partial_proofs": total_v2_partial_proofs_found, + "eligible_plots": len(awaitables) + len(v2_awaitables), "time": time_taken, }, ) diff --git a/chia/plotting/check_plots.py b/chia/plotting/check_plots.py index 9bbe42735144..4c8f63a45c49 100644 --- a/chia/plotting/check_plots.py +++ b/chia/plotting/check_plots.py @@ -186,6 +186,7 @@ def process_plot(plot_path: Path, plot_info: PlotInfo, num_start: int, num_end: # Other plot errors cause get_full_proof or validate_proof to throw an AssertionError try: proof_start_time = round(time() * 1000) + # TODO : todo_v2_plots handle v2 plots proof = pr.get_full_proof(challenge, index, parallel_read) proof_spent_time = round(time() * 1000) - proof_start_time if proof_spent_time > 15000: diff --git a/chia/plotting/prover.py b/chia/plotting/prover.py index 83d5c1c73282..3a171dba451d 100644 --- a/chia/plotting/prover.py +++ b/chia/plotting/prover.py @@ -28,7 +28,8 @@ def get_version(self) -> PlotVersion: ... def __bytes__(self) -> bytes: ... def get_id(self) -> bytes32: ... def get_qualities_for_challenge(self, challenge: bytes32) -> list[bytes32]: ... - def get_full_proof(self, challenge: bytes, index: int, parallel_read: bool = True) -> bytes: ... + def get_partial_proofs_for_challenge(self, challenge: bytes32) -> list[bytes]: ... + def get_full_proof(self, challenge: bytes32, index: int, parallel_read: bool = True) -> bytes: ... @classmethod def from_bytes(cls, data: bytes) -> ProverProtocol: ... @@ -55,8 +56,7 @@ def get_memo(self) -> bytes: raise NotImplementedError("V2 plot format is not yet implemented") def get_compression_level(self) -> uint8: - # TODO: todo_v2_plots implement compression level retrieval - raise NotImplementedError("V2 plot format is not yet implemented") + raise AssertionError("get_compression_level() should never be called on V2 plots") def get_version(self) -> PlotVersion: return PlotVersion.V2 @@ -69,14 +69,17 @@ def get_id(self) -> bytes32: # TODO: Extract plot ID from V2 plot file raise NotImplementedError("V2 plot format is not yet implemented") - def get_qualities_for_challenge(self, challenge: bytes) -> list[bytes32]: + def get_qualities_for_challenge(self, challenge: bytes32) -> list[bytes32]: # TODO: todo_v2_plots Implement plot quality lookup raise NotImplementedError("V2 plot format is not yet implemented") - def get_full_proof(self, challenge: bytes, index: int, parallel_read: bool = True) -> bytes: - # TODO: todo_v2_plots Implement plot proof generation + def get_partial_proofs_for_challenge(self, challenge: bytes32) -> list[bytes]: + # TODO: todo_v2_plots Implement quality chain lookup (16 * k bits blobs) raise NotImplementedError("V2 plot format is not yet implemented") + def get_full_proof(self, challenge: bytes32, index: int, parallel_read: bool = True) -> bytes: + raise AssertionError("V2 plot format require solver to get full proof") + @classmethod def from_bytes(cls, data: bytes) -> V2Prover: # TODO: todo_v2_plots Implement prover deserialization from cache @@ -116,7 +119,10 @@ def get_id(self) -> bytes32: def get_qualities_for_challenge(self, challenge: bytes32) -> list[bytes32]: return [bytes32(quality) for quality in self._disk_prover.get_qualities_for_challenge(challenge)] - def get_full_proof(self, challenge: bytes, index: int, parallel_read: bool = True) -> bytes: + def get_partial_proofs_for_challenge(self, challenge: bytes32) -> list[bytes]: + raise AssertionError("V1 does not implement quality chains, only qualities") + + def get_full_proof(self, challenge: bytes32, index: int, parallel_read: bool = True) -> bytes: return bytes(self._disk_prover.get_full_proof(challenge, index, parallel_read)) @classmethod diff --git a/chia/protocols/harvester_protocol.py b/chia/protocols/harvester_protocol.py index 5554d5c95f9a..e1c8414b813b 100644 --- a/chia/protocols/harvester_protocol.py +++ b/chia/protocols/harvester_protocol.py @@ -63,6 +63,20 @@ class NewProofOfSpace(Streamable): fee_info: Optional[ProofOfSpaceFeeInfo] +@streamable +@dataclass(frozen=True) +class PartialProofsData(Streamable): + challenge_hash: bytes32 + sp_hash: bytes32 + plot_identifier: str + partial_proofs: list[bytes] # 16 * k bits blobs instead of 32-byte quality strings + signage_point_index: uint8 + plot_size: uint8 + pool_public_key: Optional[G1Element] + pool_contract_puzzle_hash: Optional[bytes32] + plot_public_key: G1Element + + # Source data corresponding to the hash that is sent to the Harvester for signing class SigningDataKind(IntEnum): FOLIAGE_BLOCK_DATA = 1 diff --git a/chia/protocols/outbound_message.py b/chia/protocols/outbound_message.py index e3632fa459d4..bf75f2f6a9e0 100644 --- a/chia/protocols/outbound_message.py +++ b/chia/protocols/outbound_message.py @@ -18,6 +18,7 @@ class NodeType(IntEnum): INTRODUCER = 5 WALLET = 6 DATA_LAYER = 7 + SOLVER = 8 @streamable diff --git a/chia/protocols/protocol_message_types.py b/chia/protocols/protocol_message_types.py index 3aea02990a45..b3824dcffb2f 100644 --- a/chia/protocols/protocol_message_types.py +++ b/chia/protocols/protocol_message_types.py @@ -13,6 +13,7 @@ class ProtocolMessageTypes(Enum): new_proof_of_space = 5 request_signatures = 6 respond_signatures = 7 + partial_proofs = 110 # Farmer protocol (farmer <-> full_node) new_signage_point = 8 @@ -136,4 +137,10 @@ class ProtocolMessageTypes(Enum): request_cost_info = 106 respond_cost_info = 107 + # new farmer protocol messages + solution_response = 108 + + # solver protocol + solve = 109 + error = 255 diff --git a/chia/protocols/shared_protocol.py b/chia/protocols/shared_protocol.py index b628ca7ddb2b..ead02792fc79 100644 --- a/chia/protocols/shared_protocol.py +++ b/chia/protocols/shared_protocol.py @@ -17,6 +17,7 @@ NodeType.INTRODUCER: "0.0.36", NodeType.WALLET: "0.0.38", NodeType.DATA_LAYER: "0.0.36", + NodeType.SOLVER: "0.0.37", } """ @@ -65,6 +66,7 @@ class Capability(IntEnum): NodeType.INTRODUCER: _capabilities, NodeType.WALLET: _capabilities, NodeType.DATA_LAYER: _capabilities, + NodeType.SOLVER: _capabilities, } diff --git a/chia/protocols/solver_protocol.py b/chia/protocols/solver_protocol.py new file mode 100644 index 000000000000..891bfb846bb9 --- /dev/null +++ b/chia/protocols/solver_protocol.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from chia.util.streamable import Streamable, streamable + + +@streamable +@dataclass(frozen=True) +class SolverInfo(Streamable): + partial_proof: bytes # 16 * k bits blob, k (plot size) can be derived from this + + +@streamable +@dataclass(frozen=True) +class SolverResponse(Streamable): + partial_proof: bytes + proof: bytes diff --git a/chia/server/server.py b/chia/server/server.py index 876e79406a36..f6f97b50791c 100644 --- a/chia/server/server.py +++ b/chia/server/server.py @@ -172,8 +172,14 @@ def create( private_cert_path, private_key_path = None, None public_cert_path, public_key_path = None, None - authenticated_client_types = {NodeType.HARVESTER} - authenticated_server_types = {NodeType.HARVESTER, NodeType.FARMER, NodeType.WALLET, NodeType.DATA_LAYER} + authenticated_client_types = {NodeType.HARVESTER, NodeType.SOLVER} + authenticated_server_types = { + NodeType.HARVESTER, + NodeType.FARMER, + NodeType.WALLET, + NodeType.DATA_LAYER, + NodeType.SOLVER, + } if local_type in authenticated_client_types: # Authenticated clients diff --git a/chia/simulator/setup_services.py b/chia/simulator/setup_services.py index f96872617330..9b95e02acfbc 100644 --- a/chia/simulator/setup_services.py +++ b/chia/simulator/setup_services.py @@ -37,6 +37,8 @@ from chia.simulator.keyring import TempKeyring from chia.simulator.ssl_certs import get_next_nodes_certs_and_keys, get_next_private_ca_cert_and_key from chia.simulator.start_simulator import SimulatorFullNodeService, create_full_node_simulator_service +from chia.solver.solver_service import SolverService +from chia.solver.start_solver import create_solver_service from chia.ssl.create_ssl import create_all_ssl from chia.timelord.start_timelord import create_timelord_service from chia.timelord.timelord_launcher import VDFClientProcessMgr, find_vdf_client, spawn_process @@ -504,3 +506,30 @@ async def setup_timelord( async with service.manage(): yield service + + +@asynccontextmanager +async def setup_solver( + root_path: Path, + 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 + config["solver"]["enable_upnp"] = True + config["solver"]["selected_network"] = "testnet0" + config["solver"]["port"] = 0 + config["solver"]["rpc_port"] = 0 + config["solver"]["num_threads"] = 1 + save_config(root_path, "config.yaml", config) + service = create_solver_service( + root_path, + config, + consensus_constants, + farmer_peers={farmer_peer} if farmer_peer is not None else set(), + ) + + async with service.manage(start=start_service): + yield service diff --git a/chia/solver/__init__.py b/chia/solver/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chia/solver/solver.py b/chia/solver/solver.py new file mode 100644 index 000000000000..fa7efea4a1f0 --- /dev/null +++ b/chia/solver/solver.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +import asyncio +import contextlib +import logging +from collections.abc import AsyncIterator +from concurrent.futures.thread import ThreadPoolExecutor +from pathlib import Path +from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast + +from chia_rs import ConsensusConstants + +from chia.protocols.outbound_message import NodeType +from chia.rpc.rpc_server import StateChangedProtocol, default_get_connections +from chia.server.server import ChiaServer +from chia.server.ws_connection import WSChiaConnection + +log = logging.getLogger(__name__) + + +class Solver: + if TYPE_CHECKING: + from chia.rpc.rpc_server import RpcServiceProtocol + + _protocol_check: ClassVar[RpcServiceProtocol] = cast("Solver", None) + + root_path: Path + _server: Optional[ChiaServer] + _shut_down: bool + started: bool = False + executor: ThreadPoolExecutor + state_changed_callback: Optional[StateChangedProtocol] = None + constants: ConsensusConstants + event_loop: asyncio.events.AbstractEventLoop + + @property + def server(self) -> ChiaServer: + if self._server is None: + raise RuntimeError("server not assigned") + + return self._server + + def __init__(self, root_path: Path, config: dict[str, Any], constants: ConsensusConstants): + self.log = log + self.root_path = root_path + self._shut_down = False + num_threads = config["num_threads"] + self.log.info(f"Initializing solver with {num_threads} threads") + self.executor = ThreadPoolExecutor(max_workers=num_threads, thread_name_prefix="solver-") + self._server = None + self.constants = constants + self.state_changed_callback: Optional[StateChangedProtocol] = None + self.log.info("Solver initialization complete") + + @contextlib.asynccontextmanager + async def manage(self) -> AsyncIterator[None]: + try: + self.log.info("Starting solver service") + self.started = True + self.log.info("Solver service started successfully") + yield + finally: + self.log.info("Shutting down solver service") + self._shut_down = True + self.executor.shutdown(wait=True) + self.log.info("Solver service shutdown complete") + + def solve(self, partial_proof: bytes) -> Optional[bytes]: + self.log.debug(f"Solve request: partial={partial_proof.hex()}") + # TODO todo_v2_plots implement actualy calling the solver + return None + + def get_connections(self, request_node_type: Optional[NodeType]) -> list[dict[str, Any]]: + return default_get_connections(server=self.server, request_node_type=request_node_type) + + async def on_connect(self, connection: WSChiaConnection) -> None: + pass + + async def on_disconnect(self, connection: WSChiaConnection) -> None: + self.log.info(f"peer disconnected {connection.get_peer_logging()}") + + def set_server(self, server: ChiaServer) -> None: + self._server = server + + def _set_state_changed_callback(self, callback: StateChangedProtocol) -> None: + self.state_changed_callback = callback diff --git a/chia/solver/solver_api.py b/chia/solver/solver_api.py new file mode 100644 index 000000000000..4ae365fc17c1 --- /dev/null +++ b/chia/solver/solver_api.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols.outbound_message import Message, make_msg +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.protocols.solver_protocol import SolverInfo, SolverResponse +from chia.server.api_protocol import ApiMetadata +from chia.solver.solver import Solver + + +class SolverAPI: + if TYPE_CHECKING: + from chia.server.api_protocol import ApiProtocol + + _protocol_check: ClassVar[ApiProtocol] = cast("SolverAPI", None) + + log: logging.Logger + solver: Solver + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + def __init__(self, solver: Solver) -> None: + self.log = logging.getLogger(__name__) + self.solver = solver + + def ready(self) -> bool: + return self.solver.started + + @metadata.request(peer_required=False, reply_types=[ProtocolMessageTypes.solution_response]) + async def solve( + self, + request: SolverInfo, + ) -> Optional[Message]: + """ + Solve a V2 plot partial proof to get the full proof of space. + This is called by the farmer when it receives V2 parital proofs from harvester. + """ + if not self.solver.started: + self.log.error("Solver is not started") + return None + + self.log.debug(f"Solving partial {request.partial_proof.hex()}") + + try: + proof = self.solver.solve(request.partial_proof) + if proof is None: + self.log.warning(f"Solver returned no proof for parital {request.partial_proof.hex()}") + return None + + self.log.debug(f"Successfully solved partial proof, returning {len(proof)} byte proof") + return make_msg( + ProtocolMessageTypes.solution_response, + SolverResponse(proof=proof, partial_proof=request.partial_proof), + ) + + except Exception as e: + self.log.error(f"Error solving parital {request.partial_proof.hex()}: {e}") + return None diff --git a/chia/solver/solver_rpc_api.py b/chia/solver/solver_rpc_api.py new file mode 100644 index 000000000000..b427964371df --- /dev/null +++ b/chia/solver/solver_rpc_api.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast + +from chia.rpc.rpc_server import Endpoint, EndpointResult +from chia.solver.solver import Solver +from chia.util.ws_message import WsRpcMessage + + +class SolverRpcApi: + if TYPE_CHECKING: + from chia.rpc.rpc_server import RpcApiProtocol + + _protocol_check: ClassVar[RpcApiProtocol] = cast("SolverRpcApi", None) + + def __init__(self, solver: Solver): + self.service = solver + self.service_name = "chia_solver" + + def get_routes(self) -> dict[str, Endpoint]: + return { + "/get_state": self.get_state, + } + + async def _state_changed(self, change: str, change_data: Optional[dict[str, Any]] = None) -> list[WsRpcMessage]: + return [] + + async def get_state(self, _: dict[str, Any]) -> EndpointResult: + return { + "started": self.service.started, + } diff --git a/chia/solver/solver_rpc_client.py b/chia/solver/solver_rpc_client.py new file mode 100644 index 000000000000..44c2a8201ba0 --- /dev/null +++ b/chia/solver/solver_rpc_client.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Any + +from chia.rpc.rpc_client import RpcClient + + +class SolverRpcClient(RpcClient): + """ + Client to Chia RPC, connects to a local solver. Uses HTTP/JSON, and converts back from + JSON into native python objects before returning. All api calls use POST requests. + """ + + async def get_state(self) -> dict[str, Any]: + """Get solver state.""" + return await self.fetch("get_state", {}) diff --git a/chia/solver/solver_service.py b/chia/solver/solver_service.py new file mode 100644 index 000000000000..aa3fdf67f71d --- /dev/null +++ b/chia/solver/solver_service.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from chia.server.start_service import Service +from chia.solver.solver import Solver +from chia.solver.solver_api import SolverAPI +from chia.solver.solver_rpc_api import SolverRpcApi + +SolverService = Service[Solver, SolverAPI, SolverRpcApi] diff --git a/chia/solver/start_solver.py b/chia/solver/start_solver.py new file mode 100644 index 000000000000..a73fe789678d --- /dev/null +++ b/chia/solver/start_solver.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import os +import pathlib +import sys +from multiprocessing import freeze_support +from typing import Any, Optional + +from chia_rs import ConsensusConstants +from chia_rs.sized_ints import uint16 + +from chia.apis import ApiProtocolRegistry +from chia.consensus.constants import replace_str_to_bytes +from chia.consensus.default_constants import DEFAULT_CONSTANTS, update_testnet_overrides +from chia.protocols.outbound_message import NodeType +from chia.server.signal_handlers import SignalHandlers +from chia.server.start_service import Service, async_run +from chia.solver.solver import Solver +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 +from chia.util.task_timing import maybe_manage_task_instrumentation + +# See: https://bugs.python.org/issue29288 +"".encode("idna") + +SERVICE_NAME = "solver" + + +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: + service_config = config[SERVICE_NAME] + + network_id = service_config["selected_network"] + upnp_list = [] + if service_config["enable_upnp"]: + upnp_list = [service_config["port"]] + + node = Solver(root_path, service_config, consensus_constants) + peer_api = SolverAPI(node) + network_id = service_config["selected_network"] + + rpc_info = None + if service_config.get("start_rpc_server", True): + rpc_info = (SolverRpcApi, service_config["rpc_port"]) + + return Service( + root_path=root_path, + config=config, + node=node, + peer_api=peer_api, + node_type=NodeType.SOLVER, + advertised_port=service_config["port"], + 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, + override_capabilities=override_capabilities, + class_for_type=ApiProtocolRegistry, + ) + + +async def async_main(service_config: dict[str, Any], root_path: pathlib.Path) -> int: + # TODO: refactor to avoid the double load + 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, root_path=root_path) + + service = create_solver_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() + + return 0 + + +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(root_path, "config.yaml", SERVICE_NAME) + return async_run(coro=async_main(service_config, root_path=root_path)) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/chia/ssl/create_ssl.py b/chia/ssl/create_ssl.py index fc2f63b35975..bd4589f2fa11 100644 --- a/chia/ssl/create_ssl.py +++ b/chia/ssl/create_ssl.py @@ -24,6 +24,7 @@ "crawler", "data_layer", "daemon", + "solver", ] _all_public_node_names: list[str] = ["full_node", "wallet", "farmer", "introducer", "timelord", "data_layer"] diff --git a/chia/util/initial-config.yaml b/chia/util/initial-config.yaml index 4ea7b63894c6..13bd21a98c7d 100644 --- a/chia/util/initial-config.yaml +++ b/chia/util/initial-config.yaml @@ -629,6 +629,34 @@ wallet: auto_sign_txs: True +solver: + # The solver server will run on this port + port: 8666 + + # Enable or disable UPnP port forwarding + enable_upnp: False + + # Logging configuration + logging: *logging + + # Network overrides and selected network + network_overrides: *network_overrides + selected_network: *selected_network + + # Number of threads for solver operations + num_threads: 4 + + # RPC server configuration + rpc_port: 8667 + start_rpc_server: True + + # SSL configuration + ssl: + private_crt: "config/ssl/solver/private_solver.crt" + private_key: "config/ssl/solver/private_solver.key" + public_crt: "config/ssl/solver/public_solver.crt" + public_key: "config/ssl/solver/public_solver.key" + data_layer: # TODO: consider name # TODO: organize consistently with other sections diff --git a/chia/util/service_groups.py b/chia/util/service_groups.py index b5c05ddce360..2c73906e069d 100644 --- a/chia/util/service_groups.py +++ b/chia/util/service_groups.py @@ -12,6 +12,7 @@ "chia_wallet", "chia_data_layer", "chia_data_layer_http", + "chia_solver", ], "daemon": [], # TODO: should this be `data_layer`? @@ -31,6 +32,7 @@ "crawler": ["chia_crawler"], "seeder": ["chia_crawler", "chia_seeder"], "seeder-only": ["chia_seeder"], + "solver": ["chia_solver"], } diff --git a/pyproject.toml b/pyproject.toml index 7a52970e35eb..042db02d3937 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ chia_full_node_simulator = "chia.simulator.start_simulator:main" chia_data_layer = "chia.data_layer.start_data_layer:main" chia_data_layer_http = "chia.data_layer.data_layer_server:main" chia_data_layer_s3_plugin = "chia.data_layer.s3_plugin_service:run_server" +chia_solver = "chia.solver.start_solver:main" [[tool.poetry.source]] name = "chia"