Skip to content

Commit b0aac77

Browse files
committed
add solver service
1 parent 3d4edb3 commit b0aac77

File tree

7 files changed

+285
-0
lines changed

7 files changed

+285
-0
lines changed

chia/protocols/farmer_protocol.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,10 @@ class SignedValues(Streamable):
105105
quality_string: bytes32
106106
foliage_block_data_signature: G2Element
107107
foliage_transaction_block_signature: G2Element
108+
109+
110+
111+
@streamable
112+
@dataclass(frozen=True)
113+
class SolutionResponse(Streamable):
114+
proof: bytes

chia/protocols/outbound_message.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class NodeType(IntEnum):
1818
INTRODUCER = 5
1919
WALLET = 6
2020
DATA_LAYER = 7
21+
SOLVER = 8
2122

2223

2324
@streamable

chia/protocols/protocol_message_types.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,10 @@ class ProtocolMessageTypes(Enum):
136136
request_cost_info = 106
137137
respond_cost_info = 107
138138

139+
# new farmer protocol messages
140+
solution_resonse = 108
141+
142+
# solver protocol
143+
solve = 109
144+
139145
error = 255

chia/protocols/solver_protocol.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
from dataclasses import dataclass
3+
4+
from chia_rs import PlotSize
5+
from chia_rs.sized_bytes import bytes32
6+
from chia_rs.sized_ints import uint64
7+
from chia.util.streamable import Streamable, streamable
8+
9+
10+
11+
12+
@streamable
13+
@dataclass(frozen=True)
14+
class SolverInfo(Streamable):
15+
plot_size: PlotSize
16+
plot_diffculty: uint64
17+
quality_string: bytes32

chia/server/start_solver.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import pathlib
5+
import sys
6+
from multiprocessing import freeze_support
7+
from typing import Any, Optional
8+
9+
from chia_rs import ConsensusConstants
10+
from chia_rs.sized_ints import uint16
11+
12+
from chia.apis import ApiProtocolRegistry
13+
from chia.consensus.constants import replace_str_to_bytes
14+
from chia.consensus.default_constants import DEFAULT_CONSTANTS, update_testnet_overrides
15+
from chia.full_node.full_node import FullNode
16+
from chia.full_node.full_node_api import FullNodeAPI
17+
from chia.full_node.full_node_rpc_api import FullNodeRpcApi
18+
from chia.protocols.outbound_message import NodeType
19+
from chia.server.aliases import FullNodeService
20+
from chia.server.signal_handlers import SignalHandlers
21+
from chia.server.start_service import RpcInfo, Service, async_run
22+
from chia.util.chia_logging import initialize_service_logging
23+
from chia.util.config import load_config, load_config_cli
24+
from chia.util.default_root import resolve_root_path
25+
from chia.util.task_timing import maybe_manage_task_instrumentation
26+
27+
# See: https://bugs.python.org/issue29288
28+
"".encode("idna")
29+
30+
SERVICE_NAME = "solver"
31+
32+
33+
async def create_solver_service(
34+
root_path: pathlib.Path,
35+
config: dict[str, Any],
36+
consensus_constants: ConsensusConstants,
37+
connect_to_daemon: bool = True,
38+
override_capabilities: Optional[list[tuple[uint16, str]]] = None,
39+
) -> FullNodeService:
40+
service_config = config[SERVICE_NAME]
41+
42+
network_id = service_config["selected_network"]
43+
upnp_list = []
44+
if service_config["enable_upnp"]:
45+
upnp_list = [service_config["port"]]
46+
47+
node = await FullNode.create(
48+
service_config,
49+
root_path=root_path,
50+
consensus_constants=consensus_constants,
51+
)
52+
peer_api = FullNodeAPI(node)
53+
54+
rpc_info: Optional[RpcInfo[FullNodeRpcApi]] = None
55+
if service_config.get("start_rpc_server", True):
56+
rpc_info = (FullNodeRpcApi, service_config["rpc_port"])
57+
58+
return Service(
59+
root_path=root_path,
60+
config=config,
61+
node=node,
62+
peer_api=peer_api,
63+
node_type=NodeType.SOLVER,
64+
advertised_port=service_config["port"],
65+
service_name=SERVICE_NAME,
66+
upnp_ports=upnp_list,
67+
# connect_peers=get_unresolved_peer_infos(service_config, NodeType.SOLVER),
68+
on_connect_callback=node.on_connect,
69+
network_id=network_id,
70+
rpc_info=rpc_info,
71+
connect_to_daemon=connect_to_daemon,
72+
override_capabilities=override_capabilities,
73+
class_for_type=ApiProtocolRegistry,
74+
)
75+
76+
77+
async def async_main(service_config: dict[str, Any], root_path: pathlib.Path) -> int:
78+
# TODO: refactor to avoid the double load
79+
config = load_config(root_path, "config.yaml")
80+
config[SERVICE_NAME] = service_config
81+
network_id = service_config["selected_network"]
82+
overrides = service_config["network_overrides"]["constants"][network_id]
83+
update_testnet_overrides(network_id, overrides)
84+
updated_constants = replace_str_to_bytes(DEFAULT_CONSTANTS, **overrides)
85+
initialize_service_logging(service_name=SERVICE_NAME, config=config, root_path=root_path)
86+
87+
service = await create_solver_service(root_path, config, updated_constants)
88+
async with SignalHandlers.manage() as signal_handlers:
89+
await service.setup_process_global_state(signal_handlers=signal_handlers)
90+
await service.run()
91+
92+
return 0
93+
94+
95+
def main() -> int:
96+
freeze_support()
97+
root_path = resolve_root_path(override=None)
98+
99+
with maybe_manage_task_instrumentation(
100+
enable=os.environ.get(f"CHIA_INSTRUMENT_{SERVICE_NAME.upper()}") is not None
101+
):
102+
service_config = load_config_cli(root_path, "config.yaml", SERVICE_NAME)
103+
# target_peer_count = service_config.get("target_peer_count", 40) - service_config.get(
104+
# "target_outbound_peer_count", 8
105+
# )
106+
# if target_peer_count < 0:
107+
# target_peer_count = None
108+
# if not service_config.get("use_chia_loop_policy", True):
109+
# target_peer_count = None
110+
return async_run(coro=async_main(service_config, root_path=root_path))
111+
112+
113+
if __name__ == "__main__":
114+
sys.exit(main())

chia/solver/solver.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from __future__ import annotations
2+
3+
import asyncio
4+
import concurrent
5+
import contextlib
6+
import logging
7+
from collections.abc import AsyncIterator
8+
from concurrent.futures.thread import ThreadPoolExecutor
9+
from pathlib import Path
10+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast
11+
from chia_rs import ConsensusConstants
12+
from chia.protocols.solver_protocol import SolverInfo
13+
from chia.protocols.outbound_message import NodeType
14+
from chia.rpc.rpc_server import StateChangedProtocol, default_get_connections
15+
from chia.server.server import ChiaServer
16+
from chia.server.ws_connection import WSChiaConnection
17+
18+
19+
log = logging.getLogger(__name__)
20+
21+
22+
class Solver:
23+
if TYPE_CHECKING:
24+
from chia.rpc.rpc_server import RpcServiceProtocol
25+
_protocol_check: ClassVar[RpcServiceProtocol] = cast("Solver", None)
26+
27+
root_path: Path
28+
_server: Optional[ChiaServer]
29+
_shut_down: bool
30+
started: bool = False
31+
executor: ThreadPoolExecutor
32+
state_changed_callback: Optional[StateChangedProtocol] = None
33+
constants: ConsensusConstants
34+
event_loop: asyncio.events.AbstractEventLoop
35+
36+
37+
38+
@property
39+
def server(self) -> ChiaServer:
40+
if self._server is None:
41+
raise RuntimeError("server not assigned")
42+
43+
return self._server
44+
45+
def __init__(self, root_path: Path, config: dict[str, Any], constants: ConsensusConstants):
46+
self.log = log
47+
self.root_path = root_path
48+
self._shut_down = False
49+
self.executor = concurrent.futures.ThreadPoolExecutor(
50+
max_workers=config["num_threads"], thread_name_prefix="solver-"
51+
)
52+
self._server = None
53+
self.constants = constants
54+
self.state_changed_callback: Optional[StateChangedProtocol] = None
55+
56+
57+
58+
@contextlib.asynccontextmanager
59+
async def manage(self) -> AsyncIterator[None]:
60+
try:
61+
self.started = True
62+
yield
63+
finally:
64+
self._shut_down = True
65+
66+
def solve(self, info: SolverInfo) -> Optional[bytes]:
67+
return None
68+
69+
def get_connections(self, request_node_type: Optional[NodeType]) -> list[dict[str, Any]]:
70+
return default_get_connections(server=self.server, request_node_type=request_node_type)
71+
72+
async def on_connect(self, connection: WSChiaConnection) -> None:
73+
pass
74+
75+
76+
async def on_disconnect(self, connection: WSChiaConnection) -> None:
77+
self.log.info(f"peer disconnected {connection.get_peer_logging()}")
78+
79+
80+
def set_server(self, server: ChiaServer) -> None:
81+
self._server = server
82+
83+
def _set_state_changed_callback(self, callback: StateChangedProtocol) -> None:
84+
self.state_changed_callback = callback
85+

chia/solver/solver_api.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from __future__ import annotations
2+
3+
import json
4+
import logging
5+
import time
6+
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union, cast
7+
8+
import aiohttp
9+
from chia.protocols.outbound_message import make_msg
10+
from chia_rs.sized_bytes import bytes32
11+
from chia_rs.sized_ints import uint8, uint16, uint32, uint64
12+
13+
from chia import __version__
14+
from chia.protocols.farmer_protocol import SolutionResponse
15+
from chia.protocols.outbound_message import Message
16+
from chia.protocols.protocol_message_types import ProtocolMessageTypes
17+
from chia.protocols.solver_protocol import SolverInfo
18+
from chia.server.api_protocol import ApiMetadata
19+
from chia.solver.solver import Solver
20+
21+
22+
23+
class SolverAPI:
24+
if TYPE_CHECKING:
25+
from chia.server.api_protocol import ApiProtocol
26+
_protocol_check: ClassVar[ApiProtocol] = cast("SolverAPI", None)
27+
28+
log: logging.Logger
29+
solver: Solver
30+
metadata: ClassVar[ApiMetadata] = ApiMetadata()
31+
32+
def __init__(self, solver: Solver) -> None:
33+
self.log = logging.getLogger(__name__)
34+
self.solver = solver
35+
36+
def ready(self) -> bool:
37+
return self.solver.started
38+
39+
@metadata.request()
40+
async def solve(
41+
self,
42+
request: SolverInfo,
43+
) -> Optional[Message]:
44+
if not self.solver.started:
45+
raise RuntimeError("Solver is not started")
46+
47+
proof = self.solver.solve(request)
48+
if proof is None:
49+
return None
50+
51+
response: SolutionResponse = SolutionResponse(
52+
proof=proof,
53+
)
54+
return make_msg(ProtocolMessageTypes.solution_resonse,response)
55+

0 commit comments

Comments
 (0)