diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01b7da6b4d62..0195abf7ddeb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,6 +13,13 @@ repos: entry: ./activated.py python chia/_tests/build-init-files.py -v --root . language: system pass_filenames: false + - repo: local + hooks: + - id: generate-service-peer-schemas + name: Generate service peer schemas + entry: ./activated.py chia dev generate-service-peer-schemas + language: system + pass_filenames: false - repo: local hooks: - id: ruff_format diff --git a/chia/apis.py b/chia/apis.py deleted file mode 100644 index f30eac27fafb..000000000000 --- a/chia/apis.py +++ /dev/null @@ -1,21 +0,0 @@ -from __future__ import annotations - -from chia.farmer.farmer_api import FarmerAPI -from chia.full_node.full_node_api import FullNodeAPI -from chia.harvester.harvester_api import HarvesterAPI -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 - -ApiProtocolRegistry: dict[NodeType, type[ApiProtocol]] = { - NodeType.FULL_NODE: FullNodeAPI, - NodeType.WALLET: WalletNodeAPI, - NodeType.INTRODUCER: IntroducerAPI, - NodeType.TIMELORD: TimelordAPI, - NodeType.FARMER: FarmerAPI, - NodeType.HARVESTER: HarvesterAPI, - NodeType.SOLVER: SolverAPI, -} diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py new file mode 100644 index 000000000000..2c2700020901 --- /dev/null +++ b/chia/apis/__init__.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from chia.apis.data_layer_api_schema import DataLayerApiSchema +from chia.apis.farmer_api_schema import FarmerApiSchema +from chia.apis.full_node_api_schema import FullNodeApiSchema +from chia.apis.harvester_api_schema import HarvesterApiSchema +from chia.apis.introducer_api_schema import IntroducerApiSchema +from chia.apis.solver_api_schema import SolverApiSchema +from chia.apis.timelord_api_schema import TimelordApiSchema +from chia.apis.wallet_api_schema import WalletNodeApiSchema +from chia.protocols.outbound_message import NodeType +from chia.server.api_protocol import ApiSchemaProtocol + +ApiProtocolRegistry: dict[NodeType, type[ApiSchemaProtocol]] = { + NodeType.DATA_LAYER: DataLayerApiSchema, + NodeType.FARMER: FarmerApiSchema, + NodeType.FULL_NODE: FullNodeApiSchema, + NodeType.HARVESTER: HarvesterApiSchema, + NodeType.INTRODUCER: IntroducerApiSchema, + NodeType.SOLVER: SolverApiSchema, + NodeType.TIMELORD: TimelordApiSchema, + NodeType.WALLET: WalletNodeApiSchema, +} diff --git a/chia/apis/data_layer_api_schema.py b/chia/apis/data_layer_api_schema.py new file mode 100644 index 000000000000..71e8b7086e4e --- /dev/null +++ b/chia/apis/data_layer_api_schema.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, cast + +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol + + +class DataLayerApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("DataLayerApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/farmer_api_schema.py b/chia/apis/farmer_api_schema.py new file mode 100644 index 000000000000..5bf5b284baac --- /dev/null +++ b/chia/apis/farmer_api_schema.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols import farmer_protocol, harvester_protocol +from chia.protocols.harvester_protocol import ( + PartialProofsData, + PlotSyncDone, + PlotSyncPathList, + PlotSyncPlotList, + PlotSyncStart, +) +from chia.protocols.outbound_message import Message +from chia.protocols.solver_protocol import SolverResponse +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol +from chia.server.ws_connection import WSChiaConnection + + +class FarmerApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("FarmerApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=True) + async def new_proof_of_space( + self, new_proof_of_space: harvester_protocol.NewProofOfSpace, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True) + async def partial_proofs(self, partial_proof_data: PartialProofsData, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def solution_response(self, response: SolverResponse, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def respond_signatures(self, response: harvester_protocol.RespondSignatures) -> None: ... + + @metadata.request() + async def new_signage_point(self, new_signage_point: farmer_protocol.NewSignagePoint) -> None: ... + + @metadata.request() + async def request_signed_values( + self, full_node_request: farmer_protocol.RequestSignedValues + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def farming_info(self, request: farmer_protocol.FarmingInfo, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def respond_plots(self, _: harvester_protocol.RespondPlots, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_start(self, message: PlotSyncStart, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_loaded(self, message: PlotSyncPlotList, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_removed(self, message: PlotSyncPathList, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_invalid(self, message: PlotSyncPathList, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_keys_missing(self, message: PlotSyncPathList, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_duplicates(self, message: PlotSyncPathList, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def plot_sync_done(self, message: PlotSyncDone, peer: WSChiaConnection) -> None: ... diff --git a/chia/apis/full_node_api_schema.py b/chia/apis/full_node_api_schema.py new file mode 100644 index 000000000000..7eed70b330a0 --- /dev/null +++ b/chia/apis/full_node_api_schema.py @@ -0,0 +1,264 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols import farmer_protocol, full_node_protocol, introducer_protocol, timelord_protocol, wallet_protocol +from chia.protocols.outbound_message import Message +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol +from chia.server.ws_connection import WSChiaConnection + + +class FullNodeApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("FullNodeApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_peers]) + async def request_peers( + self, _request: full_node_protocol.RequestPeers, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def respond_peers( + self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def respond_peers_introducer( + self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True, execute_task=True) + async def new_peak(self, request: full_node_protocol.NewPeak, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def new_transaction( + self, transaction: full_node_protocol.NewTransaction, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_transaction]) + async def request_transaction(self, request: full_node_protocol.RequestTransaction) -> Optional[Message]: ... + + @metadata.request(peer_required=True, bytes_required=True) + async def respond_transaction( + self, + tx: full_node_protocol.RespondTransaction, + peer: WSChiaConnection, + tx_bytes: bytes = b"", + test: bool = False, + ) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_proof_of_weight]) + async def request_proof_of_weight(self, request: full_node_protocol.RequestProofOfWeight) -> Optional[Message]: ... + + @metadata.request() + async def respond_proof_of_weight(self, request: full_node_protocol.RespondProofOfWeight) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_block, ProtocolMessageTypes.reject_block]) + async def request_block(self, request: full_node_protocol.RequestBlock) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_blocks, ProtocolMessageTypes.reject_blocks]) + async def request_blocks(self, request: full_node_protocol.RequestBlocks) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def reject_block( + self, + request: full_node_protocol.RejectBlock, + peer: WSChiaConnection, + ) -> None: ... + + @metadata.request(peer_required=True) + async def reject_blocks( + self, + request: full_node_protocol.RejectBlocks, + peer: WSChiaConnection, + ) -> None: ... + + @metadata.request(peer_required=True) + async def respond_blocks( + self, + request: full_node_protocol.RespondBlocks, + peer: WSChiaConnection, + ) -> None: ... + + @metadata.request(peer_required=True) + async def respond_block( + self, + respond_block: full_node_protocol.RespondBlock, + peer: WSChiaConnection, + ) -> Optional[Message]: ... + + @metadata.request() + async def new_unfinished_block( + self, new_unfinished_block: full_node_protocol.NewUnfinishedBlock + ) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_unfinished_block]) + async def request_unfinished_block( + self, request_unfinished_block: full_node_protocol.RequestUnfinishedBlock + ) -> Optional[Message]: ... + + @metadata.request() + async def new_unfinished_block2( + self, new_unfinished_block: full_node_protocol.NewUnfinishedBlock2 + ) -> Optional[Message]: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_unfinished_block]) + async def request_unfinished_block2( + self, request_unfinished_block: full_node_protocol.RequestUnfinishedBlock2 + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def respond_unfinished_block( + self, + respond_unfinished_block: full_node_protocol.RespondUnfinishedBlock, + peer: WSChiaConnection, + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def new_signage_point_or_end_of_sub_slot( + self, new_sp: full_node_protocol.NewSignagePointOrEndOfSubSlot, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request( + reply_types=[ProtocolMessageTypes.respond_signage_point, ProtocolMessageTypes.respond_end_of_sub_slot] + ) + async def request_signage_point_or_end_of_sub_slot( + self, request: full_node_protocol.RequestSignagePointOrEndOfSubSlot + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def respond_signage_point( + self, request: full_node_protocol.RespondSignagePoint, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def respond_end_of_sub_slot( + self, request: full_node_protocol.RespondEndOfSubSlot, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def request_mempool_transactions( + self, + request: full_node_protocol.RequestMempoolTransactions, + peer: WSChiaConnection, + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def declare_proof_of_space( + self, request: farmer_protocol.DeclareProofOfSpace, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def signed_values( + self, farmer_request: farmer_protocol.SignedValues, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def new_infusion_point_vdf( + self, request: timelord_protocol.NewInfusionPointVDF, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request(peer_required=True) + async def new_signage_point_vdf( + self, request: timelord_protocol.NewSignagePointVDF, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True) + async def new_end_of_sub_slot_vdf( + self, request: timelord_protocol.NewEndOfSubSlotVDF, peer: WSChiaConnection + ) -> Optional[Message]: ... + + @metadata.request() + async def request_block_header(self, request: wallet_protocol.RequestBlockHeader) -> Optional[Message]: ... + + @metadata.request() + async def request_additions(self, request: wallet_protocol.RequestAdditions) -> Optional[Message]: ... + + @metadata.request() + async def request_removals(self, request: wallet_protocol.RequestRemovals) -> Optional[Message]: ... + + @metadata.request() + async def send_transaction( + self, request: wallet_protocol.SendTransaction, *, test: bool = False + ) -> Optional[Message]: ... + + @metadata.request() + async def request_puzzle_solution(self, request: wallet_protocol.RequestPuzzleSolution) -> Optional[Message]: ... + + @metadata.request() + async def request_block_headers(self, request: wallet_protocol.RequestBlockHeaders) -> Optional[Message]: ... + + @metadata.request() + async def request_header_blocks(self, request: wallet_protocol.RequestHeaderBlocks) -> Optional[Message]: ... + + @metadata.request(bytes_required=True, execute_task=True) + async def respond_compact_proof_of_time( + self, request: timelord_protocol.RespondCompactProofOfTime, request_bytes: bytes = b"" + ) -> None: ... + + @metadata.request(peer_required=True, bytes_required=True, execute_task=True) + async def new_compact_vdf( + self, request: full_node_protocol.NewCompactVDF, peer: WSChiaConnection, request_bytes: bytes = b"" + ) -> None: ... + + @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_compact_vdf]) + async def request_compact_vdf( + self, request: full_node_protocol.RequestCompactVDF, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True) + async def respond_compact_vdf( + self, request: full_node_protocol.RespondCompactVDF, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True) + async def register_for_ph_updates( # type: ignore[empty-body] + self, request: wallet_protocol.RegisterForPhUpdates, peer: WSChiaConnection + ) -> Message: ... + + @metadata.request(peer_required=True) + async def register_for_coin_updates( # type: ignore[empty-body] + self, request: wallet_protocol.RegisterForCoinUpdates, peer: WSChiaConnection + ) -> Message: ... + + @metadata.request() + async def request_children(self, request: wallet_protocol.RequestChildren) -> Optional[Message]: ... + + @metadata.request() + async def request_ses_hashes(self, request: wallet_protocol.RequestSESInfo) -> Message: # type: ignore[empty-body] + ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_fee_estimates]) + async def request_fee_estimates(self, request: wallet_protocol.RequestFeeEstimates) -> Message: # type: ignore[empty-body] + ... + + @metadata.request( + peer_required=True, + reply_types=[ProtocolMessageTypes.respond_remove_puzzle_subscriptions], + ) + async def request_remove_puzzle_subscriptions( # type: ignore[empty-body] + self, request: wallet_protocol.RequestRemovePuzzleSubscriptions, peer: WSChiaConnection + ) -> Message: ... + + @metadata.request( + peer_required=True, + reply_types=[ProtocolMessageTypes.respond_remove_coin_subscriptions], + ) + async def request_remove_coin_subscriptions( # type: ignore[empty-body] + self, request: wallet_protocol.RequestRemoveCoinSubscriptions, peer: WSChiaConnection + ) -> Message: ... + + @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_puzzle_state]) + async def request_puzzle_state( # type: ignore[empty-body] + self, request: wallet_protocol.RequestPuzzleState, peer: WSChiaConnection + ) -> Message: ... + + @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_coin_state]) + async def request_coin_state(self, request: wallet_protocol.RequestCoinState, peer: WSChiaConnection) -> Message: # type: ignore[empty-body] + ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_cost_info]) + async def request_cost_info(self, _request: wallet_protocol.RequestCostInfo) -> Optional[Message]: ... diff --git a/chia/apis/harvester_api_schema.py b/chia/apis/harvester_api_schema.py new file mode 100644 index 000000000000..7c37852382bf --- /dev/null +++ b/chia/apis/harvester_api_schema.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols import harvester_protocol +from chia.protocols.harvester_protocol import PlotSyncResponse +from chia.protocols.outbound_message import Message +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol +from chia.server.ws_connection import WSChiaConnection + + +class HarvesterApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("HarvesterApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=True) + async def harvester_handshake( + self, harvester_handshake: harvester_protocol.HarvesterHandshake, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True, request_type=ProtocolMessageTypes.new_signage_point_harvester) + async def new_signage_point_harvester( + self, new_challenge: harvester_protocol.NewSignagePointHarvester2, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_signatures]) + async def request_signatures(self, request: harvester_protocol.RequestSignatures) -> Optional[Message]: ... + + @metadata.request() + async def request_plots(self, _: harvester_protocol.RequestPlots) -> Message: # type: ignore[empty-body] + ... + + @metadata.request() + async def plot_sync_response(self, response: PlotSyncResponse) -> None: ... diff --git a/chia/apis/introducer_api_schema.py b/chia/apis/introducer_api_schema.py new file mode 100644 index 000000000000..4e5ad08b0e29 --- /dev/null +++ b/chia/apis/introducer_api_schema.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols.introducer_protocol import RequestPeersIntroducer +from chia.protocols.outbound_message import Message +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol +from chia.server.ws_connection import WSChiaConnection + + +class IntroducerApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("IntroducerApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=True) + async def request_peers_introducer( + self, + request: RequestPeersIntroducer, + peer: WSChiaConnection, + ) -> Optional[Message]: ... diff --git a/chia/apis/solver_api_schema.py b/chia/apis/solver_api_schema.py new file mode 100644 index 000000000000..a45ccdf8b887 --- /dev/null +++ b/chia/apis/solver_api_schema.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, cast + +from chia.protocols.outbound_message import Message +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.protocols.solver_protocol import SolverInfo +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol + + +class SolverApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("SolverApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=False, reply_types=[ProtocolMessageTypes.solution_response]) + async def solve( + self, + request: SolverInfo, + ) -> Optional[Message]: ... diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py new file mode 100644 index 000000000000..d67d08e2a8c9 --- /dev/null +++ b/chia/apis/timelord_api_schema.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, cast + +from chia.protocols import timelord_protocol +from chia.protocols.timelord_protocol import NewPeakTimelord +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol + + +class TimelordApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("TimelordApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request() + async def new_peak_timelord(self, new_peak: NewPeakTimelord) -> None: ... + + @metadata.request() + async def new_unfinished_block_timelord( + self, new_unfinished_block: timelord_protocol.NewUnfinishedBlockTimelord + ) -> None: ... + + @metadata.request() + async def request_compact_proof_of_time(self, vdf_info: timelord_protocol.RequestCompactProofOfTime) -> None: ... diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py new file mode 100644 index 000000000000..ee4779d6dead --- /dev/null +++ b/chia/apis/wallet_api_schema.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, cast + +from chia_rs import RespondToPhUpdates + +from chia.protocols import full_node_protocol, introducer_protocol, wallet_protocol +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol +from chia.server.ws_connection import WSChiaConnection + + +class WalletNodeApiSchema: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("WalletNodeApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + + @metadata.request(peer_required=True) + async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest) -> None: ... + + @metadata.request(peer_required=True, execute_task=True) + async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest) -> None: ... + + @metadata.request() + async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader) -> None: ... + + @metadata.request(peer_required=True) + async def respond_additions(self, response: wallet_protocol.RespondAdditions, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight) -> None: ... + + @metadata.request(peer_required=True) + async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChiaConnection) -> None: ... + + @metadata.request(peer_required=True) + async def respond_peers_introducer( + self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection + ) -> None: ... + + @metadata.request(peer_required=True) + async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection) -> None: ... + + @metadata.request() + async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution) -> None: ... + + @metadata.request() + async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution) -> None: ... + + @metadata.request() + async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks) -> None: ... + + @metadata.request() + async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders) -> None: ... + + @metadata.request() + async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks) -> None: ... + + @metadata.request() + async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders) -> None: ... + + @metadata.request(peer_required=True, execute_task=True) + async def coin_state_update(self, request: wallet_protocol.CoinStateUpdate, peer: WSChiaConnection) -> None: ... + + @metadata.request() # type: ignore[type-var] + async def respond_to_ph_updates(self, request: RespondToPhUpdates) -> None: ... + + @metadata.request() + async def respond_to_coin_updates(self, request: wallet_protocol.RespondToCoinUpdates) -> None: ... + + @metadata.request() + async def respond_children(self, request: wallet_protocol.RespondChildren) -> None: ... + + @metadata.request() + async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo) -> None: ... + + @metadata.request() + async def respond_blocks(self, request: full_node_protocol.RespondBlocks) -> None: ... diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py new file mode 100644 index 000000000000..b7d43e92d99a --- /dev/null +++ b/chia/cmds/dev/generate_schemas.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import click + +import chia +from chia.data_layer.data_layer_api import DataLayerAPI +from chia.farmer.farmer_api import FarmerAPI +from chia.full_node.full_node_api import FullNodeAPI +from chia.harvester.harvester_api import HarvesterAPI +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 + + +class SchemaGenerationError(Exception): + """Base exception for schema generation errors.""" + + +class GitTrackingError(SchemaGenerationError): + """Exception raised when git tracking validation fails.""" + + +class FormattingError(SchemaGenerationError): + """Exception raised when file formatting fails.""" + + +class RegistryGenerationError(SchemaGenerationError): + """Exception raised when registry file generation fails.""" + + +# Registry of original implementation APIs for schema generation +source_api_registry: dict[NodeType, type[ApiProtocol]] = { + NodeType.FULL_NODE: FullNodeAPI, + NodeType.WALLET: WalletNodeAPI, + NodeType.INTRODUCER: IntroducerAPI, + NodeType.TIMELORD: TimelordAPI, + NodeType.FARMER: FarmerAPI, + NodeType.HARVESTER: HarvesterAPI, + NodeType.SOLVER: SolverAPI, + NodeType.DATA_LAYER: DataLayerAPI, +} + +d = set(source_api_registry.keys()).symmetric_difference(NodeType) +if len(d) != 0: + raise Exception(f"NodeType and source_api_registry out of sync: {d}") + +api_class_names: dict[NodeType, str] = { + **{node_type: node_type.name.title().replace("_", "") for node_type in source_api_registry.keys()}, + NodeType.WALLET: "WalletNode", + NodeType.FULL_NODE: "FullNode", +} + + +@click.command("generate-service-peer-schemas") +@click.option( + "--output-dir", + "-o", + type=click.Path(exists=False, path_type=Path), + # avoiding chia.apis.__file__ directly can help with overwriting broken output files + default=Path(chia.__file__).parent.joinpath("apis"), + help="Output directory for generated schema files", +) +@click.option( + "--service", + "-s", + type=click.Choice([node_type.name.lower() for node_type in NodeType]), + multiple=True, + help="""Generate schema for specific service(s). Can be used multiple times. + If not specified, generates for all services.""", +) +@click.option( + "--format-output/--no-format", + default=True, + help="Run ruff format and check --fix on generated files", +) +@click.option( + "--generate-registry/--no-registry", + default=True, + help="Generate ApiProtocolRegistry file", +) +def generate_service_peer_schemas_cmd( + output_dir: Path, + service: tuple[str, ...], + format_output: bool, + generate_registry: bool, +) -> None: + """Generate service peer API schemas from registered API protocols.""" + output_dir.mkdir(parents=True, exist_ok=True) + + # Determine which services to generate + if service: + selected_services = [NodeType[name.upper()] for name in service] + else: + selected_services = list(source_api_registry.keys()) + + generated_files = [] + has_errors = False + + for node_type in selected_services: + if node_type not in source_api_registry: + click.echo(f"Warning: No API registered for {node_type.name}", err=True) + has_errors = True + continue + + api_class = source_api_registry[node_type] + output_file = output_dir.joinpath(f"{node_type.name.lower()}_api_schema.py") + + # Generate schema content + try: + schema_content = api_class.metadata.create_schema(api_class) + + # Write to file + with output_file.open("w", encoding="utf-8", newline="\n") as file: + file.write(schema_content) + + generated_files.append(output_file) + click.echo(f"Generated {output_file} ({len(schema_content)} characters)") + + except Exception as e: + click.echo(f"Error generating schema for {node_type.name}: {type(e).__name__} -> {e}", err=True) + has_errors = True + continue + + # Generate ApiProtocolRegistry if requested + if generate_registry: + try: + registry_file = _generate_registry_file(output_dir, selected_services) + generated_files.append(registry_file) + except RegistryGenerationError as e: + click.echo(f"Registry generation failed: {e}", err=True) + has_errors = True + + try: + # Verify all generated files are tracked by git + if generated_files: + _verify_git_tracking(generated_files) + + # Format generated files if requested + if format_output and generated_files: + _format_files(generated_files) + + except (GitTrackingError, FormattingError) as e: + click.echo(f"Error: {e}", err=True) + has_errors = True + + if generated_files: + if has_errors: + click.echo(f"Generated {len(generated_files)} schema file(s) with errors", err=True) + sys.exit(1) + else: + click.echo(f"Successfully generated {len(generated_files)} schema file(s)") + else: + click.echo("No schema files were generated", err=True) + sys.exit(1) + + +def _format_files(files: list[Path]) -> None: + """Format files, raising FormattingError if any errors occurred.""" + formatting_errors = [] + + for file_path in files: + try: + # Run ruff format + subprocess.run( + [sys.executable, "-m", "ruff", "format", file_path], check=True, capture_output=True, text=True + ) + click.echo(f" [OK] Formatted {file_path.name}") + + except Exception as e: + error_msg = f"Failed to format {file_path.name}: {e}" + click.echo(f" [FAIL] {error_msg}", err=True) + formatting_errors.append(error_msg) + continue + + try: + # Run ruff check --fix + subprocess.run( + [sys.executable, "-m", "ruff", "check", "--fix", file_path], + check=True, + capture_output=True, + text=True, + ) + click.echo(f" [OK] No ruff check issues for {file_path.name}") + + except Exception as e: + error_msg = f"Failed to run ruff check on {file_path.name}: {e}" + click.echo(f" [FAIL] {error_msg}", err=True) + formatting_errors.append(error_msg) + + if formatting_errors: + raise FormattingError(f"Formatting failed for {len(formatting_errors)} file(s)") + + +def _verify_git_tracking(files: list[Path]) -> None: + """Verify all generated files are tracked by git. Raises GitTrackingError if issues found.""" + try: + # Get list of tracked files from git + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], check=True, capture_output=True, text=True, cwd=Path.cwd() + ) + repo_root = Path(result.stdout.strip()) + result = subprocess.run(["git", "ls-files"], check=True, capture_output=True, text=True, cwd=repo_root) + tracked_files = {Path(line) for line in result.stdout.strip().splitlines()} + + # Check each generated file + untracked_files = [file_path for file_path in files if file_path.relative_to(repo_root) not in tracked_files] + + if len(untracked_files) == 0: + click.echo("All generated files are tracked by git") + else: + click.echo("ERROR: The following generated files are not tracked by git:", err=True) + for file_path in untracked_files: + click.echo(f" {file_path}", err=True) + click.echo("Please add these files to git before proceeding.", err=True) + raise GitTrackingError(f"Found {len(untracked_files)} untracked generated files") + + except subprocess.CalledProcessError as e: + error_msg = f"Error checking git status: {e}" + click.echo(error_msg, err=True) + raise GitTrackingError(error_msg) from e + + +def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) -> Path: + """Generate the ApiProtocolRegistry __init__.py file. Raises RegistryGenerationError on failure.""" + registry_file = output_dir.joinpath("__init__.py") + + try: + # Generate imports for each service + imports = [] + registry_entries = [] + + for node_type in sorted(selected_services, key=lambda x: x.name): + schema_module = f"{node_type.name.lower()}_api_schema" + + # Convert FARMER -> FarmerApiSchema, HARVESTER -> HarvesterApiSchema, etc. + schema_class = f"{api_class_names[node_type]}ApiSchema" + + imports.append(f"from chia.apis.{schema_module} import {schema_class}") + registry_entries.append(f" NodeType.{node_type.name}: {schema_class},") + + joined_imports = "\n".join(imports) + joined_registry_entries = "\n".join(registry_entries) + # Generate the complete file content + content = f"""from __future__ import annotations + +{joined_imports} +from chia.protocols.outbound_message import NodeType +from chia.server.api_protocol import ApiSchemaProtocol + +ApiProtocolRegistry: dict[NodeType, type[ApiSchemaProtocol]] = {{ +{joined_registry_entries} +}} +""" + + with registry_file.open("w", encoding="utf-8", newline="\n") as file: + file.write(content) + + click.echo(f"Generated {registry_file} ({len(content)} characters)") + return registry_file + + except Exception as e: + error_msg = f"Error generating registry file: {e}" + click.echo(error_msg, err=True) + raise RegistryGenerationError(error_msg) from e diff --git a/chia/cmds/dev/main.py b/chia/cmds/dev/main.py index 1436096cf21d..8afcbeeecdb2 100644 --- a/chia/cmds/dev/main.py +++ b/chia/cmds/dev/main.py @@ -3,6 +3,7 @@ import click from chia.cmds.dev.data import data_group +from chia.cmds.dev.generate_schemas import generate_service_peer_schemas_cmd from chia.cmds.dev.gh import gh_group from chia.cmds.dev.installers import installers_group from chia.cmds.dev.mempool import mempool_cmd @@ -20,3 +21,4 @@ def dev_cmd(ctx: click.Context) -> None: dev_cmd.add_command(gh_group) dev_cmd.add_command(mempool_cmd) dev_cmd.add_command(data_group) +dev_cmd.add_command(generate_service_peer_schemas_cmd) diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index f12ac7c64eb5..b55de87ccd4a 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -1,7 +1,10 @@ from __future__ import annotations import functools +import inspect import logging +import re +import textwrap from collections.abc import Awaitable from dataclasses import dataclass, field from logging import Logger @@ -14,10 +17,13 @@ from chia.util.streamable import Streamable -class ApiProtocol(Protocol): - log: Logger +class ApiSchemaProtocol(Protocol): metadata: ClassVar[ApiMetadata] + +class ApiProtocol(ApiSchemaProtocol, Protocol): + log: Logger + def ready(self) -> bool: ... @@ -114,3 +120,98 @@ def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kw return wrapper return inner + + def create_schema(self, api: type[ApiProtocol]) -> str: + # ruff will fixup imports + import_lines = [ + "from __future__ import annotations", + "from typing import TYPE_CHECKING, ClassVar, Optional, cast", + "from chia.protocols.outbound_message import Message", + "from chia.protocols.protocol_message_types import ProtocolMessageTypes", + "from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol", + "from chia.server.ws_connection import WSChiaConnection", + ] + + schema_class_name = api.__name__.replace("API", "ApiSchema") + class_lines = ( + textwrap.dedent( + f""" + class {schema_class_name}: + if TYPE_CHECKING: + _protocol_check: ApiSchemaProtocol = cast("{schema_class_name}", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + """ + ) + .strip() + .splitlines() + ) + + method_lines = [] + + # First pass: collect method signatures and track usage + for request in self.message_type_to_request.values(): + type_hints = get_type_hints(request.method) + source = inspect.getsource(request.method).splitlines() + + # Collect the method signature lines + method_source = [] + for line in source: + method_source.append(line) + if line.rstrip().endswith(":"): + break + + this_method_schema_source = "\n".join(method_source) + + # Check for types used in parameters that appear in the signature + for param_name, hint in type_hints.items(): + if param_name in {"self", "return"}: + continue + + module = hint.__module__ + name = hint.__name__ + protocol_match = re.match(r"(?Pchia\.protocols)\.(?P[^. ]+_protocol)", module) + # Import from chia.protocols.* + if protocol_match is not None: + base = protocol_match.group("base") + protocol = protocol_match.group("protocol") + if re.search(rf"(? ChiaServer: log = logging.getLogger(name) diff --git a/chia/server/start_service.py b/chia/server/start_service.py index 2a23019f390d..154ab9bc64cb 100644 --- a/chia/server/start_service.py +++ b/chia/server/start_service.py @@ -17,7 +17,7 @@ from chia.protocols.outbound_message import NodeType from chia.protocols.shared_protocol import default_capabilities from chia.rpc.rpc_server import RpcApiProtocol, RpcServer, RpcServiceProtocol, start_rpc_server -from chia.server.api_protocol import ApiProtocol +from chia.server.api_protocol import ApiProtocol, ApiSchemaProtocol from chia.server.chia_policy import set_chia_policy from chia.server.server import ChiaServer from chia.server.signal_handlers import SignalHandlers @@ -62,7 +62,7 @@ def __init__( network_id: str, *, config: dict[str, Any], - class_for_type: dict[NodeType, type[ApiProtocol]], + class_for_type: dict[NodeType, type[ApiSchemaProtocol]], upnp_ports: Optional[list[int]] = None, connect_peers: Optional[set[UnresolvedPeerInfo]] = None, on_connect_callback: Optional[Callable[[WSChiaConnection], Awaitable[None]]] = None, diff --git a/chia/server/ws_connection.py b/chia/server/ws_connection.py index 5950a06044cc..e80b2dbf8f91 100644 --- a/chia/server/ws_connection.py +++ b/chia/server/ws_connection.py @@ -28,7 +28,7 @@ RATE_LIMITER_BAN_SECONDS, ) from chia.protocols.shared_protocol import Capability, Error, Handshake, protocol_version -from chia.server.api_protocol import ApiMetadata, ApiProtocol +from chia.server.api_protocol import ApiMetadata, ApiProtocol, ApiSchemaProtocol from chia.server.capabilities import known_active_capabilities from chia.server.rate_limits import RateLimiter from chia.types.peer_info import PeerInfo @@ -84,7 +84,7 @@ class WSChiaConnection: close_callback: Optional[ConnectionClosedCallbackProtocol] = field(repr=False) outbound_rate_limiter: RateLimiter inbound_rate_limiter: RateLimiter - class_for_type: dict[NodeType, type[ApiProtocol]] = field(repr=False) + class_for_type: dict[NodeType, type[ApiSchemaProtocol]] = field(repr=False) # connection properties is_outbound: bool @@ -140,7 +140,7 @@ def create( inbound_rate_limit_percent: int, outbound_rate_limit_percent: int, local_capabilities_for_handshake: list[tuple[uint16, str]], - class_for_type: dict[NodeType, type[ApiProtocol]], + class_for_type: dict[NodeType, type[ApiSchemaProtocol]], session: Optional[ClientSession] = None, ) -> WSChiaConnection: assert ws._writer is not None diff --git a/chia/timelord/timelord_api.py b/chia/timelord/timelord_api.py index 956953ca5c99..9892df349992 100644 --- a/chia/timelord/timelord_api.py +++ b/chia/timelord/timelord_api.py @@ -121,7 +121,9 @@ def check_orphaned_unfinished_block(self, new_peak: NewPeakTimelord): return False @metadata.request() - async def new_unfinished_block_timelord(self, new_unfinished_block: timelord_protocol.NewUnfinishedBlockTimelord): + async def new_unfinished_block_timelord( + self, new_unfinished_block: timelord_protocol.NewUnfinishedBlockTimelord + ) -> None: if self.timelord.last_state is None: return None async with self.timelord.lock: @@ -155,7 +157,7 @@ async def new_unfinished_block_timelord(self, new_unfinished_block: timelord_pro log.debug(f"Non-overflow unfinished block, total {self.timelord.total_unfinished}") @metadata.request() - async def request_compact_proof_of_time(self, vdf_info: timelord_protocol.RequestCompactProofOfTime): + async def request_compact_proof_of_time(self, vdf_info: timelord_protocol.RequestCompactProofOfTime) -> None: async with self.timelord.lock: if not self.timelord.bluebox_mode: return None diff --git a/chia/wallet/wallet_node_api.py b/chia/wallet/wallet_node_api.py index 5bfbbe7c3e4d..11462964347d 100644 --- a/chia/wallet/wallet_node_api.py +++ b/chia/wallet/wallet_node_api.py @@ -32,22 +32,24 @@ def ready(self) -> bool: return self.wallet_node.logged_in @metadata.request(peer_required=True) - async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection): + async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection) -> None: pass - async def reject_removals_request(self, response: wallet_protocol.RejectRemovalsRequest, peer: WSChiaConnection): + async def reject_removals_request( + self, response: wallet_protocol.RejectRemovalsRequest, peer: WSChiaConnection + ) -> None: """ The full node has rejected our request for removals. """ @metadata.request() - async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest): + async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest) -> None: """ The full node has rejected our request for additions. """ @metadata.request(peer_required=True, execute_task=True) - async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChiaConnection): + async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChiaConnection) -> None: """ The full node sent as a new peak """ @@ -77,25 +79,25 @@ async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChi await self.wallet_node.new_peak_queue.new_peak_wallet(peak, peer) @metadata.request() - async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest): + async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest) -> None: """ The full node has rejected our request for a header. """ @metadata.request() - async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader): + async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader) -> None: pass @metadata.request(peer_required=True) - async def respond_additions(self, response: wallet_protocol.RespondAdditions, peer: WSChiaConnection): + async def respond_additions(self, response: wallet_protocol.RespondAdditions, peer: WSChiaConnection) -> None: pass @metadata.request() - async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight): + async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight) -> None: pass @metadata.request(peer_required=True) - async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChiaConnection): + async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChiaConnection) -> None: """ This is an ack for our previous SendTransaction call. This removes the transaction from the send queue if we have sent it to enough nodes. @@ -137,7 +139,7 @@ async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChi @metadata.request(peer_required=True) async def respond_peers_introducer( self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection - ): + ) -> None: if self.wallet_node.wallet_peers is not None: await self.wallet_node.wallet_peers.add_peers(request.peer_list, peer.get_peer_info(), False) @@ -145,7 +147,7 @@ async def respond_peers_introducer( await peer.close() @metadata.request(peer_required=True) - async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection): + async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection) -> None: if self.wallet_node.wallet_peers is None: return None @@ -155,50 +157,50 @@ async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WS return None @metadata.request() - async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution): + async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution) -> None: self.log.error("Unexpected message `respond_puzzle_solution`. Peer might be slow to respond") @metadata.request() - async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution): + async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution) -> None: self.log.warning(f"Reject puzzle solution: {request}") @metadata.request() - async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks): + async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks) -> None: pass @metadata.request() - async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders): + async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders) -> None: pass @metadata.request() - async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks): + async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks) -> None: self.log.warning(f"Reject header blocks: {request}") @metadata.request() - async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders): + async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders) -> None: pass @metadata.request(peer_required=True, execute_task=True) - async def coin_state_update(self, request: wallet_protocol.CoinStateUpdate, peer: WSChiaConnection): + async def coin_state_update(self, request: wallet_protocol.CoinStateUpdate, peer: WSChiaConnection) -> None: await self.wallet_node.new_peak_queue.full_node_state_updated(request, peer) # TODO: Review this hinting issue around this rust type not being a Streamable # subclass, as you might expect it wouldn't be. Maybe we can get the # protocol working right back at the api.request definition. @metadata.request() # type: ignore[type-var] - async def respond_to_ph_updates(self, request: RespondToPhUpdates): + async def respond_to_ph_updates(self, request: RespondToPhUpdates) -> None: pass @metadata.request() - async def respond_to_coin_updates(self, request: wallet_protocol.RespondToCoinUpdates): + async def respond_to_coin_updates(self, request: wallet_protocol.RespondToCoinUpdates) -> None: pass @metadata.request() - async def respond_children(self, request: wallet_protocol.RespondChildren): + async def respond_children(self, request: wallet_protocol.RespondChildren) -> None: pass @metadata.request() - async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo): + async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo) -> None: pass @metadata.request() diff --git a/tach.toml b/tach.toml index e05edea91695..30127d6d216c 100644 --- a/tach.toml +++ b/tach.toml @@ -88,12 +88,6 @@ depends_on = [ [[modules]] path = "chia.apis" depends_on = [ - { path = "chia.harvester", deprecated = false }, - { path = "chia.farmer", deprecated = false }, - { path = "chia.introducer", deprecated = false }, - { path = "chia.wallet", deprecated = false }, - { path = "chia.full_node", deprecated = false }, - { path = "chia.timelord", deprecated = false }, { path = "chia.protocols", deprecated = false }, { path = "chia.server", deprecated = false }, ] @@ -212,6 +206,8 @@ depends_on = [ { path = "chia.simulator", deprecated = false }, { path = "chia.protocols", deprecated = false }, { path = "chia.data_layer", deprecated = false }, + { path = "chia.introducer", deprecated = false }, + { path = "chia.timelord", deprecated = false }, ] [[modules]]