From ada60efd8fdfa419dc50bb6a0e2be95f69140454 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 11:05:17 -0400 Subject: [PATCH 01/20] add chia dev command to generate api peer schema classes --- .pre-commit-config.yaml | 7 + chia/apis.py | 19 -- chia/apis/__init__.py | 19 ++ chia/apis/farmer_api_schema.py | 70 +++++++ chia/apis/full_node_api_schema.py | 312 +++++++++++++++++++++++++++++ chia/apis/harvester_api_schema.py | 35 ++++ chia/apis/introducer_api_schema.py | 17 ++ chia/apis/timelord_api_schema.py | 21 ++ chia/apis/wallet_api_schema.py | 100 +++++++++ chia/cmds/dev/generate_schemas.py | 125 ++++++++++++ chia/cmds/dev/main.py | 2 + chia/server/api_protocol.py | 127 ++++++++++++ chia/timelord/timelord_api.py | 4 +- 13 files changed, 838 insertions(+), 20 deletions(-) delete mode 100644 chia/apis.py create mode 100644 chia/apis/__init__.py create mode 100644 chia/apis/farmer_api_schema.py create mode 100644 chia/apis/full_node_api_schema.py create mode 100644 chia/apis/harvester_api_schema.py create mode 100644 chia/apis/introducer_api_schema.py create mode 100644 chia/apis/timelord_api_schema.py create mode 100644 chia/apis/wallet_api_schema.py create mode 100644 chia/cmds/dev/generate_schemas.py 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 3445b7dd15c6..000000000000 --- a/chia/apis.py +++ /dev/null @@ -1,19 +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.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, -} diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py new file mode 100644 index 000000000000..14d0f0519a8b --- /dev/null +++ b/chia/apis/__init__.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +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.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 ApiProtocol + +ApiProtocolRegistry: dict[NodeType, type[ApiProtocol]] = { + NodeType.FULL_NODE: FullNodeApiSchema, + NodeType.WALLET: WalletNodeApiSchema, + NodeType.INTRODUCER: IntroducerApiSchema, + NodeType.TIMELORD: TimelordApiSchema, + NodeType.FARMER: FarmerApiSchema, + NodeType.HARVESTER: HarvesterApiSchema, +} diff --git a/chia/apis/farmer_api_schema.py b/chia/apis/farmer_api_schema.py new file mode 100644 index 000000000000..752099ababfb --- /dev/null +++ b/chia/apis/farmer_api_schema.py @@ -0,0 +1,70 @@ +from __future__ import annotations +from chia.protocols import farmer_protocol +from chia.protocols import harvester_protocol +from chia.protocols.harvester_protocol import PlotSyncDone +from chia.protocols.harvester_protocol import PlotSyncPathList +from chia.protocols.harvester_protocol import PlotSyncPlotList +from chia.protocols.harvester_protocol import PlotSyncStart +from chia.protocols.outbound_message import Message +from chia.server.api_protocol import ApiMetadata +from chia.server.ws_connection import WSChiaConnection +from typing import Optional + +class FarmerApiSchema: + metadata = 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() + 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..969dd6a55a6b --- /dev/null +++ b/chia/apis/full_node_api_schema.py @@ -0,0 +1,312 @@ +from __future__ import annotations +from chia.protocols import farmer_protocol +from chia.protocols import full_node_protocol +from chia.protocols import introducer_protocol +from chia.protocols import timelord_protocol +from chia.protocols import wallet_protocol +from chia.protocols.outbound_message import Message +from chia.protocols.protocol_message_types import ProtocolMessageTypes +from chia.server.api_protocol import ApiMetadata +from chia.server.ws_connection import WSChiaConnection +from typing import Optional + +class FullNodeApiSchema: + metadata = 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( + self, request: wallet_protocol.RegisterForPhUpdates, peer: WSChiaConnection + ) -> Message: ... + ... + + @metadata.request(peer_required=True) + async def register_for_coin_updates( + 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: ... + ... + + @metadata.request(reply_types=[ProtocolMessageTypes.respond_fee_estimates]) + async def request_fee_estimates(self, request: wallet_protocol.RequestFeeEstimates) -> Message: ... + ... + + @metadata.request( + peer_required=True, + reply_types=[ProtocolMessageTypes.respond_remove_puzzle_subscriptions], + ) + async def request_remove_puzzle_subscriptions( + 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( + self, request: wallet_protocol.RequestRemoveCoinSubscriptions, peer: WSChiaConnection + ) -> Message: ... + ... + + @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_puzzle_state]) + async def request_puzzle_state( + 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: ... + ... + + @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..b70b182525ae --- /dev/null +++ b/chia/apis/harvester_api_schema.py @@ -0,0 +1,35 @@ +from __future__ import annotations +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 +from chia.server.ws_connection import WSChiaConnection +from typing import Optional + +class HarvesterApiSchema: + metadata = ApiMetadata() + + @metadata.request(peer_required=True) + async def harvester_handshake( + self, harvester_handshake: harvester_protocol.HarvesterHandshake, peer: WSChiaConnection + ) -> None: ... + ... + + @metadata.request(peer_required=True) + async def new_signage_point_harvester( + self, new_challenge: harvester_protocol.NewSignagePointHarvester, 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: ... + ... + + @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..a3c48813474f --- /dev/null +++ b/chia/apis/introducer_api_schema.py @@ -0,0 +1,17 @@ +from __future__ import annotations +from chia.protocols.introducer_protocol import RequestPeersIntroducer +from chia.protocols.outbound_message import Message +from chia.server.api_protocol import ApiMetadata +from chia.server.ws_connection import WSChiaConnection +from typing import Optional + +class IntroducerApiSchema: + metadata = ApiMetadata() + + @metadata.request(peer_required=True) + async def request_peers_introducer( + self, + request: RequestPeersIntroducer, + peer: WSChiaConnection, + ) -> Optional[Message]: ... + ... diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py new file mode 100644 index 000000000000..c8fed48a9602 --- /dev/null +++ b/chia/apis/timelord_api_schema.py @@ -0,0 +1,21 @@ +from __future__ import annotations +from chia.protocols import timelord_protocol +from chia.protocols.timelord_protocol import NewPeakTimelord +from chia.server.api_protocol import ApiMetadata + +class TimelordApiSchema: + metadata = 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): ... + ... diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py new file mode 100644 index 000000000000..fe80799c0cba --- /dev/null +++ b/chia/apis/wallet_api_schema.py @@ -0,0 +1,100 @@ +from __future__ import annotations +from chia.protocols import full_node_protocol +from chia.protocols import introducer_protocol +from chia.protocols import wallet_protocol +from chia.server.api_protocol import ApiMetadata +from chia.server.ws_connection import WSChiaConnection +from chia_rs import RespondToPhUpdates + +class WalletNodeApiSchema: + metadata = ApiMetadata() + + @metadata.request(peer_required=True) + async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection): ... + ... + + @metadata.request() + async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest): ... + ... + + @metadata.request(peer_required=True, execute_task=True) + async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChiaConnection): ... + ... + + @metadata.request() + async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest): ... + ... + + @metadata.request() + async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader): ... + ... + + @metadata.request(peer_required=True) + async def respond_additions(self, response: wallet_protocol.RespondAdditions, peer: WSChiaConnection): ... + ... + + @metadata.request() + async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight): ... + ... + + @metadata.request(peer_required=True) + async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChiaConnection): ... + ... + + @metadata.request(peer_required=True) + async def respond_peers_introducer( + self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection + ): ... + ... + + @metadata.request(peer_required=True) + async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection): ... + ... + + @metadata.request() + async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution): ... + ... + + @metadata.request() + async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution): ... + ... + + @metadata.request() + async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks): ... + ... + + @metadata.request() + async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders): ... + ... + + @metadata.request() + async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks): ... + ... + + @metadata.request() + async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders): ... + ... + + @metadata.request(peer_required=True, execute_task=True) + async def coin_state_update(self, request: wallet_protocol.CoinStateUpdate, peer: WSChiaConnection): ... + ... + + @metadata.request() # type: ignore[type-var] + async def respond_to_ph_updates(self, request: RespondToPhUpdates): ... + ... + + @metadata.request() + async def respond_to_coin_updates(self, request: wallet_protocol.RespondToCoinUpdates): ... + ... + + @metadata.request() + async def respond_children(self, request: wallet_protocol.RespondChildren): ... + ... + + @metadata.request() + async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo): ... + ... + + @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..5d00c7c9054d --- /dev/null +++ b/chia/cmds/dev/generate_schemas.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import io +import subprocess +import sys +from contextlib import redirect_stdout +from pathlib import Path + +import click + +import chia.apis +from chia.apis import ApiProtocolRegistry +from chia.protocols.outbound_message import NodeType + + +@click.command("generate-service-peer-schemas") +@click.option( + "--output-dir", + "-o", + type=click.Path(exists=False, path_type=Path), + default=Path(chia.apis.__file__).parent, + 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", +) +def generate_service_peer_schemas_cmd( + output_dir: Path, + service: tuple[str, ...], + format_output: 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(ApiProtocolRegistry.keys()) + + generated_files = [] + + for node_type in selected_services: + if node_type not in ApiProtocolRegistry: + click.echo(f"Warning: No API registered for {node_type.name}", err=True) + continue + + api_class = ApiProtocolRegistry[node_type] + output_file = output_dir / f"{node_type.name.lower()}_api_schema.py" + + # Generate schema content + output = io.StringIO() + try: + with redirect_stdout(output): + api_class.metadata.create_schema(api_class) + + schema_content = output.getvalue() + + # Write to file + with open(output_file, "w") as f: + f.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}: {e}", err=True) + continue + + # Format generated files if requested + if format_output and generated_files: + _format_files(generated_files) + + if generated_files: + 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 the generated files using ruff.""" + # Find python executable and ruff + python_exe = sys.executable + + for file_path in files: + try: + # Run ruff format + result = subprocess.run( + [python_exe, "-m", "ruff", "format", str(file_path)], check=False, capture_output=True, text=True + ) + if result.returncode == 0: + click.echo(f" ✓ Formatted {file_path.name}") + else: + click.echo(f" ✗ ruff format failed for {file_path.name}: {result.stderr}", err=True) + + except Exception as e: + click.echo(f" ✗ Failed to format {file_path.name}: {e}", err=True) + continue + + try: + # Run ruff check --fix + result = subprocess.run( + [python_exe, "-m", "ruff", "check", "--fix", str(file_path)], + check=False, + capture_output=True, + text=True, + ) + if result.returncode == 0: + click.echo(f" ✓ No ruff check issues for {file_path.name}") + else: + click.echo(f" ✓ Fixed ruff check issues for {file_path.name}") + + except Exception as e: + click.echo(f" ✗ Failed to run ruff check on {file_path.name}: {e}", err=True) 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..b87e1cb12ffc 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -1,7 +1,9 @@ from __future__ import annotations import functools +import inspect import logging +import re from collections.abc import Awaitable from dataclasses import dataclass, field from logging import Logger @@ -72,6 +74,7 @@ def request( non_optional_reply_types = reply_types def inner(f: Callable[Concatenate[Self, S, P], R]) -> Callable[Concatenate[Self, Union[bytes, S], P], R]: + # print(inspect.getsource(f)) @functools.wraps(f) def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kwargs) -> R: arg: S @@ -114,3 +117,127 @@ 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]) -> None: + imports = set() + imports.add("from __future__ import annotations") + imports.add("from chia.server.api_protocol import ApiMetadata") + + # Track what's actually referenced in the generated code + used_types = set() + 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.getsourcelines(request.method)[0] + + # Collect the method signature lines + method_source = [] + for line in source: + method_source.append(line) + if line.rstrip().endswith(":"): + break + method_lines.extend(method_source) + + # Check return type for Optional and Message + return_hint = type_hints.get("return") + if return_hint: + try: + if return_hint.__origin__ is Union: + args = return_hint.__args__ + if type(None) in args: + used_types.add("Optional") + for arg in args: + try: + if arg.__name__ == "Message": + used_types.add("Message") + except AttributeError: + pass + except AttributeError: + try: + if return_hint.__name__ == "Message": + used_types.add("Message") + except AttributeError: + pass + + # Check for types used in parameters that appear in the signature + for param_name, hint in type_hints.items(): + if param_name not in {"self", "return"}: + try: + module = hint.__module__ + name = hint.__name__ + if module and module.startswith("chia."): + # Check if the type name actually appears directly in the method signature + # (not as part of harvester_protocol.ClassName) + signature_text = "".join(method_source) + # Only import if used directly, not through module qualification + if f": {name}" in signature_text or f"-> {name}" in signature_text: + used_types.add(name) + imports.add(f"from {module} import {name}") + except AttributeError: + # Skip types that don't have the expected attributes + pass + + # Check decorators for ProtocolMessageTypes usage + decorator_line = source[0].strip() + if "ProtocolMessageTypes." in decorator_line: + used_types.add("ProtocolMessageTypes") + imports.add("from chia.protocols.protocol_message_types import ProtocolMessageTypes") + + # Check for protocol module usage in the signature + for line in method_source: + # Look for any protocol module usage (e.g., farmer_protocol.*, full_node_protocol.*, etc.) + protocol_matches = re.findall(r"(\w+_protocol)\.", line) + for protocol_module in protocol_matches: + imports.add(f"from chia.protocols import {protocol_module}") + + # Also check for types that might be from protocol modules but used directly + # Look for CamelCase types that aren't already imported + type_matches = re.findall(r": ([A-Z][a-zA-Z]+)", line) + for type_name in type_matches: + if type_name not in used_types and type_name not in {"Optional", "Message", "WSChiaConnection"}: + # This might be a direct import we missed + # Try to find it in the type hints + for param_name, hint in type_hints.items(): + try: + name = hint.__name__ + module = hint.__module__ + if name == type_name and module: + # Import from chia.protocols.* + if module.startswith("chia.protocols."): + imports.add(f"from {module} import {name}") + used_types.add(name) + # Special case: chia_rs types show up as builtins but are actually from chia_rs + elif module == "builtins" and type_name in {"RespondToPhUpdates"}: + imports.add(f"from chia_rs import {name}") + used_types.add(name) + except AttributeError: + # Skip types that don't have the expected attributes + pass + + # Add imports only for types that are actually used + if "Optional" in used_types: + imports.add("from typing import Optional") + if "Message" in used_types: + imports.add("from chia.protocols.outbound_message import Message") + + # Print imports + for import_line in sorted(imports): + print(import_line) + print() + + # Use *ApiSchema naming convention for generated schemas + schema_class_name = api.__name__.replace("API", "ApiSchema") + print(f"class {schema_class_name}:") + print(" metadata = ApiMetadata()") + print() + + for request in self.message_type_to_request.values(): + source = inspect.getsourcelines(request.method)[0] + for line in source: + print(line, end="") + if line.rstrip().endswith(":"): + break + print(" ...") + print() diff --git a/chia/timelord/timelord_api.py b/chia/timelord/timelord_api.py index 956953ca5c99..b96e520ac922 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: From e9efc86165f8c01ea219cb05682b2e122618e66c Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 11:24:25 -0400 Subject: [PATCH 02/20] ai --- chia/apis/farmer_api_schema.py | 26 +++---------- chia/apis/full_node_api_schema.py | 62 +++--------------------------- chia/apis/harvester_api_schema.py | 10 ++--- chia/apis/introducer_api_schema.py | 6 ++- chia/apis/timelord_api_schema.py | 5 +-- chia/apis/wallet_api_schema.py | 31 +++------------ chia/cmds/dev/generate_schemas.py | 27 ++++++++++--- 7 files changed, 47 insertions(+), 120 deletions(-) diff --git a/chia/apis/farmer_api_schema.py b/chia/apis/farmer_api_schema.py index 752099ababfb..9dc4238102da 100644 --- a/chia/apis/farmer_api_schema.py +++ b/chia/apis/farmer_api_schema.py @@ -1,14 +1,13 @@ from __future__ import annotations -from chia.protocols import farmer_protocol -from chia.protocols import harvester_protocol -from chia.protocols.harvester_protocol import PlotSyncDone -from chia.protocols.harvester_protocol import PlotSyncPathList -from chia.protocols.harvester_protocol import PlotSyncPlotList -from chia.protocols.harvester_protocol import PlotSyncStart + +from typing import Optional + +from chia.protocols import farmer_protocol, harvester_protocol +from chia.protocols.harvester_protocol import PlotSyncDone, PlotSyncPathList, PlotSyncPlotList, PlotSyncStart from chia.protocols.outbound_message import Message from chia.server.api_protocol import ApiMetadata from chia.server.ws_connection import WSChiaConnection -from typing import Optional + class FarmerApiSchema: metadata = ApiMetadata() @@ -17,54 +16,41 @@ class FarmerApiSchema: async def new_proof_of_space( self, new_proof_of_space: harvester_protocol.NewProofOfSpace, 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 index 969dd6a55a6b..66cd39ed424b 100644 --- a/chia/apis/full_node_api_schema.py +++ b/chia/apis/full_node_api_schema.py @@ -1,14 +1,13 @@ from __future__ import annotations -from chia.protocols import farmer_protocol -from chia.protocols import full_node_protocol -from chia.protocols import introducer_protocol -from chia.protocols import timelord_protocol -from chia.protocols import wallet_protocol + +from typing import Optional + +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 from chia.server.ws_connection import WSChiaConnection -from typing import Optional + class FullNodeApiSchema: metadata = ApiMetadata() @@ -17,33 +16,27 @@ class FullNodeApiSchema: 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( @@ -53,23 +46,18 @@ async def respond_transaction( 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( @@ -77,7 +65,6 @@ async def reject_block( request: full_node_protocol.RejectBlock, peer: WSChiaConnection, ) -> None: ... - ... @metadata.request(peer_required=True) async def reject_blocks( @@ -85,7 +72,6 @@ async def reject_blocks( request: full_node_protocol.RejectBlocks, peer: WSChiaConnection, ) -> None: ... - ... @metadata.request(peer_required=True) async def respond_blocks( @@ -93,7 +79,6 @@ async def respond_blocks( request: full_node_protocol.RespondBlocks, peer: WSChiaConnection, ) -> None: ... - ... @metadata.request(peer_required=True) async def respond_block( @@ -101,31 +86,26 @@ async def respond_block( 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( @@ -133,13 +113,11 @@ async def respond_unfinished_block( 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] @@ -147,19 +125,16 @@ async def new_signage_point_or_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( @@ -167,115 +142,93 @@ async def request_mempool_transactions( 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( self, request: wallet_protocol.RegisterForPhUpdates, peer: WSChiaConnection ) -> Message: ... - ... @metadata.request(peer_required=True) async def register_for_coin_updates( 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: ... - ... @metadata.request(reply_types=[ProtocolMessageTypes.respond_fee_estimates]) async def request_fee_estimates(self, request: wallet_protocol.RequestFeeEstimates) -> Message: ... - ... @metadata.request( peer_required=True, @@ -284,7 +237,6 @@ async def request_fee_estimates(self, request: wallet_protocol.RequestFeeEstimat async def request_remove_puzzle_subscriptions( self, request: wallet_protocol.RequestRemovePuzzleSubscriptions, peer: WSChiaConnection ) -> Message: ... - ... @metadata.request( peer_required=True, @@ -293,20 +245,16 @@ async def request_remove_puzzle_subscriptions( async def request_remove_coin_subscriptions( self, request: wallet_protocol.RequestRemoveCoinSubscriptions, peer: WSChiaConnection ) -> Message: ... - ... @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_puzzle_state]) async def request_puzzle_state( 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: ... - ... @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 index b70b182525ae..6a62bd61aad6 100644 --- a/chia/apis/harvester_api_schema.py +++ b/chia/apis/harvester_api_schema.py @@ -1,11 +1,14 @@ from __future__ import annotations + +from typing import Optional + 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 from chia.server.ws_connection import WSChiaConnection -from typing import Optional + class HarvesterApiSchema: metadata = ApiMetadata() @@ -14,22 +17,17 @@ class HarvesterApiSchema: async def harvester_handshake( self, harvester_handshake: harvester_protocol.HarvesterHandshake, peer: WSChiaConnection ) -> None: ... - ... @metadata.request(peer_required=True) async def new_signage_point_harvester( self, new_challenge: harvester_protocol.NewSignagePointHarvester, 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: ... - ... @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 index a3c48813474f..f638edbe7ac8 100644 --- a/chia/apis/introducer_api_schema.py +++ b/chia/apis/introducer_api_schema.py @@ -1,9 +1,12 @@ from __future__ import annotations + +from typing import Optional + from chia.protocols.introducer_protocol import RequestPeersIntroducer from chia.protocols.outbound_message import Message from chia.server.api_protocol import ApiMetadata from chia.server.ws_connection import WSChiaConnection -from typing import Optional + class IntroducerApiSchema: metadata = ApiMetadata() @@ -14,4 +17,3 @@ async def request_peers_introducer( request: RequestPeersIntroducer, peer: WSChiaConnection, ) -> Optional[Message]: ... - ... diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py index c8fed48a9602..e546bc9d317c 100644 --- a/chia/apis/timelord_api_schema.py +++ b/chia/apis/timelord_api_schema.py @@ -1,21 +1,20 @@ from __future__ import annotations + from chia.protocols import timelord_protocol from chia.protocols.timelord_protocol import NewPeakTimelord from chia.server.api_protocol import ApiMetadata + class TimelordApiSchema: metadata = 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): ... - ... diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py index fe80799c0cba..b93717c543fc 100644 --- a/chia/apis/wallet_api_schema.py +++ b/chia/apis/wallet_api_schema.py @@ -1,100 +1,79 @@ from __future__ import annotations -from chia.protocols import full_node_protocol -from chia.protocols import introducer_protocol -from chia.protocols import wallet_protocol + +from chia_rs import RespondToPhUpdates + +from chia.protocols import full_node_protocol, introducer_protocol, wallet_protocol from chia.server.api_protocol import ApiMetadata from chia.server.ws_connection import WSChiaConnection -from chia_rs import RespondToPhUpdates + class WalletNodeApiSchema: metadata = ApiMetadata() @metadata.request(peer_required=True) async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection): ... - ... @metadata.request() async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest): ... - ... @metadata.request(peer_required=True, execute_task=True) async def new_peak_wallet(self, peak: wallet_protocol.NewPeakWallet, peer: WSChiaConnection): ... - ... @metadata.request() async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest): ... - ... @metadata.request() async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader): ... - ... @metadata.request(peer_required=True) async def respond_additions(self, response: wallet_protocol.RespondAdditions, peer: WSChiaConnection): ... - ... @metadata.request() async def respond_proof_of_weight(self, response: full_node_protocol.RespondProofOfWeight): ... - ... @metadata.request(peer_required=True) async def transaction_ack(self, ack: wallet_protocol.TransactionAck, peer: WSChiaConnection): ... - ... @metadata.request(peer_required=True) async def respond_peers_introducer( self, request: introducer_protocol.RespondPeersIntroducer, peer: WSChiaConnection ): ... - ... @metadata.request(peer_required=True) async def respond_peers(self, request: full_node_protocol.RespondPeers, peer: WSChiaConnection): ... - ... @metadata.request() async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution): ... - ... @metadata.request() async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution): ... - ... @metadata.request() async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks): ... - ... @metadata.request() async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders): ... - ... @metadata.request() async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks): ... - ... @metadata.request() async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders): ... - ... @metadata.request(peer_required=True, execute_task=True) async def coin_state_update(self, request: wallet_protocol.CoinStateUpdate, peer: WSChiaConnection): ... - ... @metadata.request() # type: ignore[type-var] async def respond_to_ph_updates(self, request: RespondToPhUpdates): ... - ... @metadata.request() async def respond_to_coin_updates(self, request: wallet_protocol.RespondToCoinUpdates): ... - ... @metadata.request() async def respond_children(self, request: wallet_protocol.RespondChildren): ... - ... @metadata.request() async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo): ... - ... @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 index 5d00c7c9054d..28bfa4503c75 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -8,9 +8,24 @@ import click -import chia.apis -from chia.apis import ApiProtocolRegistry +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.timelord.timelord_api import TimelordAPI +from chia.wallet.wallet_node_api import WalletNodeAPI + +# Registry of original implementation APIs for schema generation +SourceApiRegistry: dict[NodeType, type[ApiProtocol]] = { + NodeType.FULL_NODE: FullNodeAPI, + NodeType.WALLET: WalletNodeAPI, + NodeType.INTRODUCER: IntroducerAPI, + NodeType.TIMELORD: TimelordAPI, + NodeType.FARMER: FarmerAPI, + NodeType.HARVESTER: HarvesterAPI, +} @click.command("generate-service-peer-schemas") @@ -18,7 +33,7 @@ "--output-dir", "-o", type=click.Path(exists=False, path_type=Path), - default=Path(chia.apis.__file__).parent, + default=lambda: Path(__file__).parent.parent.parent / "apis", help="Output directory for generated schema files", ) @click.option( @@ -46,16 +61,16 @@ def generate_service_peer_schemas_cmd( if service: selected_services = [NodeType[name.upper()] for name in service] else: - selected_services = list(ApiProtocolRegistry.keys()) + selected_services = list(SourceApiRegistry.keys()) generated_files = [] for node_type in selected_services: - if node_type not in ApiProtocolRegistry: + if node_type not in SourceApiRegistry: click.echo(f"Warning: No API registered for {node_type.name}", err=True) continue - api_class = ApiProtocolRegistry[node_type] + api_class = SourceApiRegistry[node_type] output_file = output_dir / f"{node_type.name.lower()}_api_schema.py" # Generate schema content From 540b93894b8998a02531b28166703eb96ab42c6f Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 11:31:34 -0400 Subject: [PATCH 03/20] tidy --- chia/cmds/dev/generate_schemas.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 28bfa4503c75..14e598e37df1 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -8,6 +8,7 @@ import click +import chia.apis from chia.farmer.farmer_api import FarmerAPI from chia.full_node.full_node_api import FullNodeAPI from chia.harvester.harvester_api import HarvesterAPI @@ -33,7 +34,7 @@ "--output-dir", "-o", type=click.Path(exists=False, path_type=Path), - default=lambda: Path(__file__).parent.parent.parent / "apis", + default=Path(chia.apis.__file__).parent, help="Output directory for generated schema files", ) @click.option( From d639014019458becc287cae59a111e5401829e12 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 11:36:04 -0400 Subject: [PATCH 04/20] more tidy --- chia/apis/__init__.py | 3 +-- chia/apis/timelord_api_schema.py | 2 +- chia/apis/wallet_api_schema.py | 42 ++++++++++++++--------------- chia/timelord/timelord_api.py | 2 +- chia/wallet/wallet_node_api.py | 46 +++++++++++++++++--------------- 5 files changed, 48 insertions(+), 47 deletions(-) diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index 14d0f0519a8b..8a6690e3df66 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -7,9 +7,8 @@ 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 ApiProtocol -ApiProtocolRegistry: dict[NodeType, type[ApiProtocol]] = { +ApiProtocolRegistry: dict[NodeType, type[object]] = { NodeType.FULL_NODE: FullNodeApiSchema, NodeType.WALLET: WalletNodeApiSchema, NodeType.INTRODUCER: IntroducerApiSchema, diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py index e546bc9d317c..687d9757b900 100644 --- a/chia/apis/timelord_api_schema.py +++ b/chia/apis/timelord_api_schema.py @@ -17,4 +17,4 @@ async def new_unfinished_block_timelord( ) -> None: ... @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: ... diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py index b93717c543fc..22291e59f256 100644 --- a/chia/apis/wallet_api_schema.py +++ b/chia/apis/wallet_api_schema.py @@ -11,69 +11,69 @@ class WalletNodeApiSchema: metadata = ApiMetadata() @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: ... @metadata.request() - async def reject_additions_request(self, response: wallet_protocol.RejectAdditionsRequest): ... + 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): ... + 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): ... + async def reject_header_request(self, response: wallet_protocol.RejectHeaderRequest) -> None: ... @metadata.request() - async def respond_block_header(self, response: wallet_protocol.RespondBlockHeader): ... + 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): ... + 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): ... + 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): ... + 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): ... + 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): ... + async def respond_puzzle_solution(self, request: wallet_protocol.RespondPuzzleSolution) -> None: ... @metadata.request() - async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution): ... + async def reject_puzzle_solution(self, request: wallet_protocol.RejectPuzzleSolution) -> None: ... @metadata.request() - async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks): ... + async def respond_header_blocks(self, request: wallet_protocol.RespondHeaderBlocks) -> None: ... @metadata.request() - async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders): ... + async def respond_block_headers(self, request: wallet_protocol.RespondBlockHeaders) -> None: ... @metadata.request() - async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks): ... + async def reject_header_blocks(self, request: wallet_protocol.RejectHeaderBlocks) -> None: ... @metadata.request() - async def reject_block_headers(self, request: wallet_protocol.RejectBlockHeaders): ... + 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): ... + 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): ... + async def respond_to_ph_updates(self, request: RespondToPhUpdates) -> None: ... @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: ... @metadata.request() - async def respond_children(self, request: wallet_protocol.RespondChildren): ... + async def respond_children(self, request: wallet_protocol.RespondChildren) -> None: ... @metadata.request() - async def respond_ses_hashes(self, request: wallet_protocol.RespondSESInfo): ... + 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/timelord/timelord_api.py b/chia/timelord/timelord_api.py index b96e520ac922..9892df349992 100644 --- a/chia/timelord/timelord_api.py +++ b/chia/timelord/timelord_api.py @@ -157,7 +157,7 @@ async def new_unfinished_block_timelord( 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() From fb20a4fa052a1652f59fff8e095ed78b66f25ca8 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 12:31:16 -0400 Subject: [PATCH 05/20] green pre-commit --- chia/apis/__init__.py | 3 +- chia/apis/farmer_api_schema.py | 9 +++-- chia/apis/full_node_api_schema.py | 30 ++++++++------ chia/apis/harvester_api_schema.py | 12 ++++-- chia/apis/introducer_api_schema.py | 9 +++-- chia/apis/timelord_api_schema.py | 9 ++++- chia/apis/wallet_api_schema.py | 9 ++++- chia/cmds/dev/generate_schemas.py | 7 +++- chia/server/api_protocol.py | 64 +++++++++++++++++++++++++----- chia/server/server.py | 6 +-- chia/server/start_service.py | 4 +- chia/server/ws_connection.py | 6 +-- tach.toml | 6 --- 13 files changed, 120 insertions(+), 54 deletions(-) diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index 8a6690e3df66..97ef97d36655 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -7,8 +7,9 @@ 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 ApiProtocolSchema -ApiProtocolRegistry: dict[NodeType, type[object]] = { +ApiProtocolRegistry: dict[NodeType, type[ApiProtocolSchema]] = { NodeType.FULL_NODE: FullNodeApiSchema, NodeType.WALLET: WalletNodeApiSchema, NodeType.INTRODUCER: IntroducerApiSchema, diff --git a/chia/apis/farmer_api_schema.py b/chia/apis/farmer_api_schema.py index 9dc4238102da..e8033e885933 100644 --- a/chia/apis/farmer_api_schema.py +++ b/chia/apis/farmer_api_schema.py @@ -1,16 +1,19 @@ from __future__ import annotations -from typing import Optional +from typing import TYPE_CHECKING, ClassVar, Optional, cast from chia.protocols import farmer_protocol, harvester_protocol from chia.protocols.harvester_protocol import PlotSyncDone, PlotSyncPathList, PlotSyncPlotList, PlotSyncStart from chia.protocols.outbound_message import Message -from chia.server.api_protocol import ApiMetadata +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema from chia.server.ws_connection import WSChiaConnection class FarmerApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("FarmerApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request(peer_required=True) async def new_proof_of_space( diff --git a/chia/apis/full_node_api_schema.py b/chia/apis/full_node_api_schema.py index 66cd39ed424b..cdaf0b2690ca 100644 --- a/chia/apis/full_node_api_schema.py +++ b/chia/apis/full_node_api_schema.py @@ -1,16 +1,19 @@ from __future__ import annotations -from typing import Optional +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 +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema from chia.server.ws_connection import WSChiaConnection class FullNodeApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("FullNodeApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request(peer_required=True, reply_types=[ProtocolMessageTypes.respond_peers]) async def request_peers( @@ -212,12 +215,12 @@ async def respond_compact_vdf( ) -> None: ... @metadata.request(peer_required=True) - async def register_for_ph_updates( + 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( + async def register_for_coin_updates( # type: ignore[empty-body] self, request: wallet_protocol.RegisterForCoinUpdates, peer: WSChiaConnection ) -> Message: ... @@ -225,16 +228,18 @@ async def register_for_coin_updates( async def request_children(self, request: wallet_protocol.RequestChildren) -> Optional[Message]: ... @metadata.request() - async def request_ses_hashes(self, request: wallet_protocol.RequestSESInfo) -> Message: ... + 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: ... + 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( + async def request_remove_puzzle_subscriptions( # type: ignore[empty-body] self, request: wallet_protocol.RequestRemovePuzzleSubscriptions, peer: WSChiaConnection ) -> Message: ... @@ -242,19 +247,18 @@ async def request_remove_puzzle_subscriptions( peer_required=True, reply_types=[ProtocolMessageTypes.respond_remove_coin_subscriptions], ) - async def request_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( + 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: ... + 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 index 6a62bd61aad6..4b6e81077e83 100644 --- a/chia/apis/harvester_api_schema.py +++ b/chia/apis/harvester_api_schema.py @@ -1,17 +1,20 @@ from __future__ import annotations -from typing import Optional +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 +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema from chia.server.ws_connection import WSChiaConnection class HarvesterApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("HarvesterApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request(peer_required=True) async def harvester_handshake( @@ -27,7 +30,8 @@ async def new_signage_point_harvester( async def request_signatures(self, request: harvester_protocol.RequestSignatures) -> Optional[Message]: ... @metadata.request() - async def request_plots(self, _: harvester_protocol.RequestPlots) -> Message: ... + 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 index f638edbe7ac8..a0bc1cc60495 100644 --- a/chia/apis/introducer_api_schema.py +++ b/chia/apis/introducer_api_schema.py @@ -1,15 +1,18 @@ from __future__ import annotations -from typing import Optional +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 +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema from chia.server.ws_connection import WSChiaConnection class IntroducerApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("IntroducerApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request(peer_required=True) async def request_peers_introducer( diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py index 687d9757b900..aad524d69161 100644 --- a/chia/apis/timelord_api_schema.py +++ b/chia/apis/timelord_api_schema.py @@ -1,12 +1,17 @@ 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 +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema class TimelordApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("TimelordApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request() async def new_peak_timelord(self, new_peak: NewPeakTimelord) -> None: ... diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py index 22291e59f256..985e79351a34 100644 --- a/chia/apis/wallet_api_schema.py +++ b/chia/apis/wallet_api_schema.py @@ -1,14 +1,19 @@ 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 +from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema from chia.server.ws_connection import WSChiaConnection class WalletNodeApiSchema: - metadata = ApiMetadata() + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("WalletNodeApiSchema", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() @metadata.request(peer_required=True) async def respond_removals(self, response: wallet_protocol.RespondRemovals, peer: WSChiaConnection) -> None: ... diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 14e598e37df1..505c06ce7262 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -8,7 +8,9 @@ import click -import chia.apis +import chia + +# import chia.apis from chia.farmer.farmer_api import FarmerAPI from chia.full_node.full_node_api import FullNodeAPI from chia.harvester.harvester_api import HarvesterAPI @@ -34,7 +36,8 @@ "--output-dir", "-o", type=click.Path(exists=False, path_type=Path), - default=Path(chia.apis.__file__).parent, + # default=Path(chia.apis.__file__).parent, + default=Path(chia.__file__).parent.joinpath("apis"), help="Output directory for generated schema files", ) @click.option( diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index b87e1cb12ffc..0bc17bd20b7a 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -4,6 +4,7 @@ import inspect import logging import re +import textwrap from collections.abc import Awaitable from dataclasses import dataclass, field from logging import Logger @@ -16,10 +17,13 @@ from chia.util.streamable import Streamable -class ApiProtocol(Protocol): - log: Logger +class ApiProtocolSchema(Protocol): metadata: ClassVar[ApiMetadata] + +class ApiProtocol(ApiProtocolSchema, Protocol): + log: Logger + def ready(self) -> bool: ... @@ -121,7 +125,7 @@ def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kw def create_schema(self, api: type[ApiProtocol]) -> None: imports = set() imports.add("from __future__ import annotations") - imports.add("from chia.server.api_protocol import ApiMetadata") + imports.add("from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema") # Track what's actually referenced in the generated code used_types = set() @@ -218,7 +222,10 @@ def create_schema(self, api: type[ApiProtocol]) -> None: # Add imports only for types that are actually used if "Optional" in used_types: - imports.add("from typing import Optional") + imports.add("from typing import TYPE_CHECKING, ClassVar, Optional, cast") + else: + imports.add("from typing import TYPE_CHECKING, ClassVar, cast") + if "Message" in used_types: imports.add("from chia.protocols.outbound_message import Message") @@ -229,15 +236,52 @@ def create_schema(self, api: type[ApiProtocol]) -> None: # Use *ApiSchema naming convention for generated schemas schema_class_name = api.__name__.replace("API", "ApiSchema") - print(f"class {schema_class_name}:") - print(" metadata = ApiMetadata()") - print() + print( + textwrap.dedent( + f""" + class {schema_class_name}: + if TYPE_CHECKING: + _protocol_check: ApiProtocolSchema = cast("{schema_class_name}", None) + + metadata: ClassVar[ApiMetadata] = ApiMetadata() + """ + ) + ) for request in self.message_type_to_request.values(): - source = inspect.getsourcelines(request.method)[0] + source = inspect.getsource(request.method).splitlines() + + # Check if method has a non-None return type that requires an ignore comment + type_hints = get_type_hints(request.method) + return_hint = type_hints.get("return") + needs_ignore = False + + if return_hint and return_hint is not type(None): + # Check if it's Optional[something] - Optional types are fine with "..." + try: + if hasattr(return_hint, "__origin__") and return_hint.__origin__ is Union: + args = return_hint.__args__ + if type(None) not in args: + # Not Optional (e.g., just Message), needs ignore + needs_ignore = True + # else: is Optional, no ignore needed + else: + # Direct return type (not Optional), needs ignore + needs_ignore = True + except AttributeError: + # If we can't analyze it, assume it needs ignore + needs_ignore = True + for line in source: - print(line, end="") - if line.rstrip().endswith(":"): + stripped = line.strip() + final_line = line + if stripped.startswith(("async def", "def")): + if needs_ignore: + final_line = final_line.rstrip() + " # type: ignore[empty-body]" + + print(final_line) + if stripped.endswith(":"): break + print(" ...") print() diff --git a/chia/server/server.py b/chia/server/server.py index 876e79406a36..531f12b4ec99 100644 --- a/chia/server/server.py +++ b/chia/server/server.py @@ -31,7 +31,7 @@ from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.protocols.protocol_state_machine import message_requires_reply from chia.protocols.protocol_timing import INVALID_PROTOCOL_BAN_SECONDS -from chia.server.api_protocol import ApiProtocol +from chia.server.api_protocol import ApiProtocol, ApiProtocolSchema from chia.server.introducer_peers import IntroducerPeers from chia.server.ssl_context import private_ssl_paths, public_ssl_paths from chia.server.ws_connection import ConnectionCallback, WSChiaConnection @@ -132,7 +132,7 @@ class ChiaServer: ssl_client_context: ssl.SSLContext node_id: bytes32 exempt_peer_networks: list[Union[IPv4Network, IPv6Network]] - class_for_type: dict[NodeType, type[ApiProtocol]] + class_for_type: dict[NodeType, type[ApiProtocolSchema]] all_connections: dict[bytes32, WSChiaConnection] = field(default_factory=dict) on_connect: Optional[ConnectionCallback] = None shut_down_event: asyncio.Event = field(default_factory=asyncio.Event) @@ -160,7 +160,7 @@ def create( config: dict[str, Any], private_ca_crt_key: tuple[Path, Path], chia_ca_crt_key: tuple[Path, Path], - class_for_type: dict[NodeType, type[ApiProtocol]], + class_for_type: dict[NodeType, type[ApiProtocolSchema]], name: str = __name__, ) -> ChiaServer: log = logging.getLogger(name) diff --git a/chia/server/start_service.py b/chia/server/start_service.py index 2a23019f390d..5837302dc148 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, ApiProtocolSchema 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[ApiProtocolSchema]], 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..416af0ecc00b 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, ApiProtocolSchema 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[ApiProtocolSchema]] = 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[ApiProtocolSchema]], session: Optional[ClientSession] = None, ) -> WSChiaConnection: assert ws._writer is not None diff --git a/tach.toml b/tach.toml index e05edea91695..3f4f2d9d6b5d 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 }, ] From 2ce5e96e7ca87631e374cff169d94a15090252a6 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 12:40:41 -0400 Subject: [PATCH 06/20] tweak --- tach.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tach.toml b/tach.toml index 3f4f2d9d6b5d..30127d6d216c 100644 --- a/tach.toml +++ b/tach.toml @@ -206,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]] From 53d5496b319860c1d44707303b6b26be6bc7c004 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 13:11:01 -0400 Subject: [PATCH 07/20] newlines --- chia/cmds/dev/generate_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 505c06ce7262..a4ac5f8f5913 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -86,7 +86,7 @@ def generate_service_peer_schemas_cmd( schema_content = output.getvalue() # Write to file - with open(output_file, "w") as f: + with open(output_file, "w", newline="\n") as f: f.write(schema_content) generated_files.append(output_file) From e479de308dc5a720f8ecab2bb11ed7b907d7e11c Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 13:51:12 -0400 Subject: [PATCH 08/20] and utf8 --- chia/cmds/dev/generate_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index a4ac5f8f5913..fb8930492755 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -86,7 +86,7 @@ def generate_service_peer_schemas_cmd( schema_content = output.getvalue() # Write to file - with open(output_file, "w", newline="\n") as f: + with open(output_file, "w", encoding="utf-8", newline="\n") as f: f.write(schema_content) generated_files.append(output_file) From 68f955dd035711535bf13070870903ffade82ce4 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 14:05:46 -0400 Subject: [PATCH 09/20] and generate the api file itself --- chia/apis/__init__.py | 6 ++-- chia/cmds/dev/generate_schemas.py | 60 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index 97ef97d36655..9a80d52384d1 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -10,10 +10,10 @@ from chia.server.api_protocol import ApiProtocolSchema ApiProtocolRegistry: dict[NodeType, type[ApiProtocolSchema]] = { + NodeType.FARMER: FarmerApiSchema, NodeType.FULL_NODE: FullNodeApiSchema, - NodeType.WALLET: WalletNodeApiSchema, + NodeType.HARVESTER: HarvesterApiSchema, NodeType.INTRODUCER: IntroducerApiSchema, NodeType.TIMELORD: TimelordApiSchema, - NodeType.FARMER: FarmerApiSchema, - NodeType.HARVESTER: HarvesterApiSchema, + NodeType.WALLET: WalletNodeApiSchema, } diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index fb8930492755..ea557e54eea0 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -53,10 +53,16 @@ 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) @@ -96,6 +102,12 @@ def generate_service_peer_schemas_cmd( click.echo(f"Error generating schema for {node_type.name}: {e}", err=True) continue + # Generate ApiProtocolRegistry if requested + if generate_registry: + registry_file = _generate_registry_file(output_dir, selected_services) + if registry_file: + generated_files.append(registry_file) + # Format generated files if requested if format_output and generated_files: _format_files(generated_files) @@ -142,3 +154,51 @@ def _format_files(files: list[Path]) -> None: except Exception as e: click.echo(f" ✗ Failed to run ruff check on {file_path.name}: {e}", err=True) + + +def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) -> Path | None: + """Generate the ApiProtocolRegistry __init__.py file.""" + registry_file = output_dir / "__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" + + # Handle special cases for class names + if node_type == NodeType.WALLET: + schema_class = "WalletNodeApiSchema" + elif node_type == NodeType.FULL_NODE: + schema_class = "FullNodeApiSchema" + else: + # Convert FARMER -> FarmerApiSchema, HARVESTER -> HarvesterApiSchema, etc. + schema_class = f"{node_type.name.title()}ApiSchema" + + imports.append(f"from chia.apis.{schema_module} import {schema_class}") + registry_entries.append(f" NodeType.{node_type.name}: {schema_class},") + + # Generate the complete file content + content = f"""from __future__ import annotations + +{chr(10).join(imports)} +from chia.protocols.outbound_message import NodeType +from chia.server.api_protocol import ApiProtocolSchema + +ApiProtocolRegistry: dict[NodeType, type[ApiProtocolSchema]] = {{ +{chr(10).join(registry_entries)} +}} +""" + + # Write to file + with open(registry_file, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + + click.echo(f"Generated {registry_file} ({len(content)} characters)") + return registry_file + + except Exception as e: + click.echo(f"Error generating registry file: {e}", err=True) + return None From 0e1fd7c8a85f9300ca68986109117bb93c9622e1 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 22 Aug 2025 16:05:38 -0400 Subject: [PATCH 10/20] less unicode --- chia/cmds/dev/generate_schemas.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index ea557e54eea0..eaf26273832a 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -131,12 +131,12 @@ def _format_files(files: list[Path]) -> None: [python_exe, "-m", "ruff", "format", str(file_path)], check=False, capture_output=True, text=True ) if result.returncode == 0: - click.echo(f" ✓ Formatted {file_path.name}") + click.echo(f" [OK] Formatted {file_path.name}") else: - click.echo(f" ✗ ruff format failed for {file_path.name}: {result.stderr}", err=True) + click.echo(f" [FAIL] ruff format failed for {file_path.name}: {result.stderr}", err=True) except Exception as e: - click.echo(f" ✗ Failed to format {file_path.name}: {e}", err=True) + click.echo(f" [FAIL] Failed to format {file_path.name}: {e}", err=True) continue try: @@ -148,12 +148,12 @@ def _format_files(files: list[Path]) -> None: text=True, ) if result.returncode == 0: - click.echo(f" ✓ No ruff check issues for {file_path.name}") + click.echo(f" [OK] No ruff check issues for {file_path.name}") else: - click.echo(f" ✓ Fixed ruff check issues for {file_path.name}") + click.echo(f" [OK] Fixed ruff check issues for {file_path.name}") except Exception as e: - click.echo(f" ✗ Failed to run ruff check on {file_path.name}: {e}", err=True) + click.echo(f" [FAIL] Failed to run ruff check on {file_path.name}: {e}", err=True) def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) -> Path | None: From f63c8099ed7dfbb7c6ce9ea73aadf3e721b05c3b Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 09:30:55 -0400 Subject: [PATCH 11/20] add solver --- chia/apis/__init__.py | 2 ++ chia/cmds/dev/generate_schemas.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index 9a80d52384d1..a0049a68df5d 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -4,6 +4,7 @@ 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 @@ -14,6 +15,7 @@ NodeType.FULL_NODE: FullNodeApiSchema, NodeType.HARVESTER: HarvesterApiSchema, NodeType.INTRODUCER: IntroducerApiSchema, + NodeType.SOLVER: SolverApiSchema, NodeType.TIMELORD: TimelordApiSchema, NodeType.WALLET: WalletNodeApiSchema, } diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index eaf26273832a..549dc9c6761c 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -17,6 +17,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 @@ -28,6 +29,7 @@ NodeType.TIMELORD: TimelordAPI, NodeType.FARMER: FarmerAPI, NodeType.HARVESTER: HarvesterAPI, + NodeType.SOLVER: SolverAPI, } From 6f1e93cc5003e59c5d1cfe166cab93b4ff66ef4c Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 09:35:58 -0400 Subject: [PATCH 12/20] rename schema protocol --- chia/apis/__init__.py | 4 ++-- chia/apis/farmer_api_schema.py | 4 ++-- chia/apis/full_node_api_schema.py | 4 ++-- chia/apis/harvester_api_schema.py | 4 ++-- chia/apis/introducer_api_schema.py | 4 ++-- chia/apis/timelord_api_schema.py | 4 ++-- chia/apis/wallet_api_schema.py | 4 ++-- chia/cmds/dev/generate_schemas.py | 4 ++-- chia/server/api_protocol.py | 8 ++++---- chia/server/server.py | 6 +++--- chia/server/start_service.py | 4 ++-- chia/server/ws_connection.py | 6 +++--- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index a0049a68df5d..89f7b2323ef4 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -8,9 +8,9 @@ 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 ApiProtocolSchema +from chia.server.api_protocol import ApiSchemaProtocol -ApiProtocolRegistry: dict[NodeType, type[ApiProtocolSchema]] = { +ApiProtocolRegistry: dict[NodeType, type[ApiSchemaProtocol]] = { NodeType.FARMER: FarmerApiSchema, NodeType.FULL_NODE: FullNodeApiSchema, NodeType.HARVESTER: HarvesterApiSchema, diff --git a/chia/apis/farmer_api_schema.py b/chia/apis/farmer_api_schema.py index 0505a073fe16..5bf5b284baac 100644 --- a/chia/apis/farmer_api_schema.py +++ b/chia/apis/farmer_api_schema.py @@ -12,13 +12,13 @@ ) from chia.protocols.outbound_message import Message from chia.protocols.solver_protocol import SolverResponse -from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol from chia.server.ws_connection import WSChiaConnection class FarmerApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("FarmerApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("FarmerApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/full_node_api_schema.py b/chia/apis/full_node_api_schema.py index cdaf0b2690ca..7eed70b330a0 100644 --- a/chia/apis/full_node_api_schema.py +++ b/chia/apis/full_node_api_schema.py @@ -5,13 +5,13 @@ 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, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol from chia.server.ws_connection import WSChiaConnection class FullNodeApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("FullNodeApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("FullNodeApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/harvester_api_schema.py b/chia/apis/harvester_api_schema.py index 4b6e81077e83..6dde8b03deeb 100644 --- a/chia/apis/harvester_api_schema.py +++ b/chia/apis/harvester_api_schema.py @@ -6,13 +6,13 @@ 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, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol from chia.server.ws_connection import WSChiaConnection class HarvesterApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("HarvesterApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("HarvesterApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/introducer_api_schema.py b/chia/apis/introducer_api_schema.py index a0bc1cc60495..4e5ad08b0e29 100644 --- a/chia/apis/introducer_api_schema.py +++ b/chia/apis/introducer_api_schema.py @@ -4,13 +4,13 @@ from chia.protocols.introducer_protocol import RequestPeersIntroducer from chia.protocols.outbound_message import Message -from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol from chia.server.ws_connection import WSChiaConnection class IntroducerApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("IntroducerApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("IntroducerApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/timelord_api_schema.py b/chia/apis/timelord_api_schema.py index aad524d69161..d67d08e2a8c9 100644 --- a/chia/apis/timelord_api_schema.py +++ b/chia/apis/timelord_api_schema.py @@ -4,12 +4,12 @@ from chia.protocols import timelord_protocol from chia.protocols.timelord_protocol import NewPeakTimelord -from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol class TimelordApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("TimelordApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("TimelordApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/apis/wallet_api_schema.py b/chia/apis/wallet_api_schema.py index 985e79351a34..ee4779d6dead 100644 --- a/chia/apis/wallet_api_schema.py +++ b/chia/apis/wallet_api_schema.py @@ -5,13 +5,13 @@ from chia_rs import RespondToPhUpdates from chia.protocols import full_node_protocol, introducer_protocol, wallet_protocol -from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema +from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol from chia.server.ws_connection import WSChiaConnection class WalletNodeApiSchema: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("WalletNodeApiSchema", None) + _protocol_check: ApiSchemaProtocol = cast("WalletNodeApiSchema", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 549dc9c6761c..d3b1f45300dd 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -187,9 +187,9 @@ def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) {chr(10).join(imports)} from chia.protocols.outbound_message import NodeType -from chia.server.api_protocol import ApiProtocolSchema +from chia.server.api_protocol import ApiSchemaProtocol -ApiProtocolRegistry: dict[NodeType, type[ApiProtocolSchema]] = {{ +ApiProtocolRegistry: dict[NodeType, type[ApiSchemaProtocol]] = {{ {chr(10).join(registry_entries)} }} """ diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index 0bc17bd20b7a..e3deca241afa 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -17,11 +17,11 @@ from chia.util.streamable import Streamable -class ApiProtocolSchema(Protocol): +class ApiSchemaProtocol(Protocol): metadata: ClassVar[ApiMetadata] -class ApiProtocol(ApiProtocolSchema, Protocol): +class ApiProtocol(ApiSchemaProtocol, Protocol): log: Logger def ready(self) -> bool: ... @@ -125,7 +125,7 @@ def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kw def create_schema(self, api: type[ApiProtocol]) -> None: imports = set() imports.add("from __future__ import annotations") - imports.add("from chia.server.api_protocol import ApiMetadata, ApiProtocolSchema") + imports.add("from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol") # Track what's actually referenced in the generated code used_types = set() @@ -241,7 +241,7 @@ def create_schema(self, api: type[ApiProtocol]) -> None: f""" class {schema_class_name}: if TYPE_CHECKING: - _protocol_check: ApiProtocolSchema = cast("{schema_class_name}", None) + _protocol_check: ApiSchemaProtocol = cast("{schema_class_name}", None) metadata: ClassVar[ApiMetadata] = ApiMetadata() """ diff --git a/chia/server/server.py b/chia/server/server.py index 12930ae4e45a..00faba35c2b3 100644 --- a/chia/server/server.py +++ b/chia/server/server.py @@ -31,7 +31,7 @@ from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.protocols.protocol_state_machine import message_requires_reply from chia.protocols.protocol_timing import INVALID_PROTOCOL_BAN_SECONDS -from chia.server.api_protocol import ApiProtocol, ApiProtocolSchema +from chia.server.api_protocol import ApiProtocol, ApiSchemaProtocol from chia.server.introducer_peers import IntroducerPeers from chia.server.ssl_context import private_ssl_paths, public_ssl_paths from chia.server.ws_connection import ConnectionCallback, WSChiaConnection @@ -132,7 +132,7 @@ class ChiaServer: ssl_client_context: ssl.SSLContext node_id: bytes32 exempt_peer_networks: list[Union[IPv4Network, IPv6Network]] - class_for_type: dict[NodeType, type[ApiProtocolSchema]] + class_for_type: dict[NodeType, type[ApiSchemaProtocol]] all_connections: dict[bytes32, WSChiaConnection] = field(default_factory=dict) on_connect: Optional[ConnectionCallback] = None shut_down_event: asyncio.Event = field(default_factory=asyncio.Event) @@ -160,7 +160,7 @@ def create( config: dict[str, Any], private_ca_crt_key: tuple[Path, Path], chia_ca_crt_key: tuple[Path, Path], - class_for_type: dict[NodeType, type[ApiProtocolSchema]], + class_for_type: dict[NodeType, type[ApiSchemaProtocol]], name: str = __name__, ) -> ChiaServer: log = logging.getLogger(name) diff --git a/chia/server/start_service.py b/chia/server/start_service.py index 5837302dc148..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, ApiProtocolSchema +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[ApiProtocolSchema]], + 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 416af0ecc00b..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, ApiProtocolSchema +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[ApiProtocolSchema]] = 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[ApiProtocolSchema]], + class_for_type: dict[NodeType, type[ApiSchemaProtocol]], session: Optional[ClientSession] = None, ) -> WSChiaConnection: assert ws._writer is not None From 8125f035d50034908aa2770c96b10b98ab97f9f9 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 09:42:15 -0400 Subject: [PATCH 13/20] add solver api schema --- chia/apis/solver_api_schema.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 chia/apis/solver_api_schema.py 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]: ... From c9a43c557b3d2f5031aa6e55ebea9cb6a51818af Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 09:49:30 -0400 Subject: [PATCH 14/20] tidy --- chia/apis/__init__.py | 2 + chia/apis/data_layer_api_schema.py | 12 ++ chia/cmds/dev/generate_schemas.py | 190 ++++++++++++++++++++--------- chia/server/api_protocol.py | 116 ++++++++---------- 4 files changed, 196 insertions(+), 124 deletions(-) create mode 100644 chia/apis/data_layer_api_schema.py diff --git a/chia/apis/__init__.py b/chia/apis/__init__.py index 89f7b2323ef4..2c2700020901 100644 --- a/chia/apis/__init__.py +++ b/chia/apis/__init__.py @@ -1,5 +1,6 @@ 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 @@ -11,6 +12,7 @@ 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, 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/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index d3b1f45300dd..290acc3a535f 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -1,16 +1,13 @@ from __future__ import annotations -import io import subprocess import sys -from contextlib import redirect_stdout from pathlib import Path import click import chia - -# import chia.apis +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 @@ -21,8 +18,25 @@ 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 -SourceApiRegistry: dict[NodeType, type[ApiProtocol]] = { +source_api_registry: dict[NodeType, type[ApiProtocol]] = { NodeType.FULL_NODE: FullNodeAPI, NodeType.WALLET: WalletNodeAPI, NodeType.INTRODUCER: IntroducerAPI, @@ -30,6 +44,17 @@ 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", } @@ -38,7 +63,7 @@ "--output-dir", "-o", type=click.Path(exists=False, path_type=Path), - # default=Path(chia.apis.__file__).parent, + # 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", ) @@ -73,94 +98,146 @@ def generate_service_peer_schemas_cmd( if service: selected_services = [NodeType[name.upper()] for name in service] else: - selected_services = list(SourceApiRegistry.keys()) + selected_services = list(source_api_registry.keys()) generated_files = [] + has_errors = False for node_type in selected_services: - if node_type not in SourceApiRegistry: + 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 = SourceApiRegistry[node_type] - output_file = output_dir / f"{node_type.name.lower()}_api_schema.py" + api_class = source_api_registry[node_type] + output_file = output_dir.joinpath(f"{node_type.name.lower()}_api_schema.py") # Generate schema content - output = io.StringIO() try: - with redirect_stdout(output): - api_class.metadata.create_schema(api_class) - - schema_content = output.getvalue() + schema_content = api_class.metadata.create_schema(api_class) # Write to file - with open(output_file, "w", encoding="utf-8", newline="\n") as f: - f.write(schema_content) + output_file.write_text(schema_content, encoding="utf-8", newline="\n") 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}: {e}", err=True) + has_errors = True continue # Generate ApiProtocolRegistry if requested if generate_registry: - registry_file = _generate_registry_file(output_dir, selected_services) - if registry_file: + 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 - # Format generated files if requested - if format_output and generated_files: - _format_files(generated_files) + 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: - click.echo(f"Successfully generated {len(generated_files)} schema file(s)") + 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 the generated files using ruff.""" - # Find python executable and ruff - python_exe = sys.executable + """Format files, raising FormattingError if any errors occurred.""" + formatting_errors = [] for file_path in files: try: # Run ruff format - result = subprocess.run( - [python_exe, "-m", "ruff", "format", str(file_path)], check=False, capture_output=True, text=True + subprocess.run( + [sys.executable, "-m", "ruff", "format", file_path], check=True, capture_output=True, text=True ) - if result.returncode == 0: - click.echo(f" [OK] Formatted {file_path.name}") - else: - click.echo(f" [FAIL] ruff format failed for {file_path.name}: {result.stderr}", err=True) + click.echo(f" [OK] Formatted {file_path.name}") except Exception as e: - click.echo(f" [FAIL] Failed to format {file_path.name}: {e}", err=True) + 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 - result = subprocess.run( - [python_exe, "-m", "ruff", "check", "--fix", str(file_path)], - check=False, + subprocess.run( + [sys.executable, "-m", "ruff", "check", "--fix", file_path], + check=True, capture_output=True, text=True, ) - if result.returncode == 0: - click.echo(f" [OK] No ruff check issues for {file_path.name}") - else: - click.echo(f" [OK] Fixed ruff check issues for {file_path.name}") + click.echo(f" [OK] No ruff check issues for {file_path.name}") except Exception as e: - click.echo(f" [FAIL] Failed to run ruff check on {file_path.name}: {e}", err=True) + 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 _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) -> Path | None: - """Generate the ApiProtocolRegistry __init__.py file.""" - registry_file = output_dir / "__init__.py" + +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", "ls-files"], check=True, capture_output=True, text=True, cwd=Path.cwd()) + tracked_files = set(result.stdout.strip().split("\n")) + + # Check each generated file + untracked_files = [] + warnings = [] + + for file_path in files: + # Convert to relative path from repo root + try: + relative_path = file_path.relative_to(Path.cwd()) + if str(relative_path) not in tracked_files: + untracked_files.append(file_path) + except ValueError: + # File is outside repo root, can't check tracking + warning = f"Cannot check git tracking for file outside repo: {file_path}" + click.echo(f"Warning: {warning}", err=True) + warnings.append(warning) + + if untracked_files: + 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") + else: + click.echo("All generated files are tracked by git") + + 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 @@ -170,37 +247,32 @@ def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) for node_type in sorted(selected_services, key=lambda x: x.name): schema_module = f"{node_type.name.lower()}_api_schema" - # Handle special cases for class names - if node_type == NodeType.WALLET: - schema_class = "WalletNodeApiSchema" - elif node_type == NodeType.FULL_NODE: - schema_class = "FullNodeApiSchema" - else: - # Convert FARMER -> FarmerApiSchema, HARVESTER -> HarvesterApiSchema, etc. - schema_class = f"{node_type.name.title()}ApiSchema" + # 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 -{chr(10).join(imports)} +{joined_imports} from chia.protocols.outbound_message import NodeType from chia.server.api_protocol import ApiSchemaProtocol ApiProtocolRegistry: dict[NodeType, type[ApiSchemaProtocol]] = {{ -{chr(10).join(registry_entries)} +{joined_registry_entries} }} """ - # Write to file - with open(registry_file, "w", encoding="utf-8", newline="\n") as f: - f.write(content) + registry_file.write_text(content, encoding="utf-8", newline="\n") click.echo(f"Generated {registry_file} ({len(content)} characters)") return registry_file except Exception as e: - click.echo(f"Error generating registry file: {e}", err=True) - return None + error_msg = f"Error generating registry file: {e}" + click.echo(error_msg, err=True) + raise RegistryGenerationError(error_msg) from e diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index e3deca241afa..428bb77796f2 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -122,7 +122,7 @@ def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kw return inner - def create_schema(self, api: type[ApiProtocol]) -> None: + def create_schema(self, api: type[ApiProtocol]) -> str: imports = set() imports.add("from __future__ import annotations") imports.add("from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol") @@ -147,41 +147,30 @@ def create_schema(self, api: type[ApiProtocol]) -> None: # Check return type for Optional and Message return_hint = type_hints.get("return") if return_hint: - try: - if return_hint.__origin__ is Union: - args = return_hint.__args__ - if type(None) in args: - used_types.add("Optional") - for arg in args: - try: - if arg.__name__ == "Message": - used_types.add("Message") - except AttributeError: - pass - except AttributeError: - try: - if return_hint.__name__ == "Message": + origin = getattr(return_hint, "__origin__", None) + if origin is Union: + args = return_hint.__args__ + if type(None) in args: + used_types.add("Optional") + for arg in args: + if arg.__name__ == "Message": used_types.add("Message") - except AttributeError: - pass + elif return_hint.__name__ == "Message": + used_types.add("Message") # Check for types used in parameters that appear in the signature for param_name, hint in type_hints.items(): if param_name not in {"self", "return"}: - try: - module = hint.__module__ - name = hint.__name__ - if module and module.startswith("chia."): - # Check if the type name actually appears directly in the method signature - # (not as part of harvester_protocol.ClassName) - signature_text = "".join(method_source) - # Only import if used directly, not through module qualification - if f": {name}" in signature_text or f"-> {name}" in signature_text: - used_types.add(name) - imports.add(f"from {module} import {name}") - except AttributeError: - # Skip types that don't have the expected attributes - pass + module = hint.__module__ + name = hint.__name__ + if module and module.startswith("chia."): + # Check if the type name actually appears directly in the method signature + # (not as part of harvester_protocol.ClassName) + signature_text = "".join(method_source) + # Only import if used directly, not through module qualification + if f": {name}" in signature_text or f"-> {name}" in signature_text: + used_types.add(name) + imports.add(f"from {module} import {name}") # Check decorators for ProtocolMessageTypes usage decorator_line = source[0].strip() @@ -204,21 +193,17 @@ def create_schema(self, api: type[ApiProtocol]) -> None: # This might be a direct import we missed # Try to find it in the type hints for param_name, hint in type_hints.items(): - try: - name = hint.__name__ - module = hint.__module__ - if name == type_name and module: - # Import from chia.protocols.* - if module.startswith("chia.protocols."): - imports.add(f"from {module} import {name}") - used_types.add(name) - # Special case: chia_rs types show up as builtins but are actually from chia_rs - elif module == "builtins" and type_name in {"RespondToPhUpdates"}: - imports.add(f"from chia_rs import {name}") - used_types.add(name) - except AttributeError: - # Skip types that don't have the expected attributes - pass + name = hint.__name__ + module = hint.__module__ + if name == type_name and module: + # Import from chia.protocols.* + if module.startswith("chia.protocols."): + imports.add(f"from {module} import {name}") + used_types.add(name) + # Special case: chia_rs types show up as builtins but are actually from chia_rs + elif module == "builtins" and type_name in {"RespondToPhUpdates"}: + imports.add(f"from chia_rs import {name}") + used_types.add(name) # Add imports only for types that are actually used if "Optional" in used_types: @@ -229,14 +214,17 @@ def create_schema(self, api: type[ApiProtocol]) -> None: if "Message" in used_types: imports.add("from chia.protocols.outbound_message import Message") - # Print imports + # Build the schema content as a string + lines = [] + + # Add imports for import_line in sorted(imports): - print(import_line) - print() + lines.append(import_line) + lines.append("") # Use *ApiSchema naming convention for generated schemas schema_class_name = api.__name__.replace("API", "ApiSchema") - print( + lines.append( textwrap.dedent( f""" class {schema_class_name}: @@ -245,7 +233,7 @@ class {schema_class_name}: metadata: ClassVar[ApiMetadata] = ApiMetadata() """ - ) + ).strip() ) for request in self.message_type_to_request.values(): @@ -258,18 +246,14 @@ class {schema_class_name}: if return_hint and return_hint is not type(None): # Check if it's Optional[something] - Optional types are fine with "..." - try: - if hasattr(return_hint, "__origin__") and return_hint.__origin__ is Union: - args = return_hint.__args__ - if type(None) not in args: - # Not Optional (e.g., just Message), needs ignore - needs_ignore = True - # else: is Optional, no ignore needed - else: - # Direct return type (not Optional), needs ignore + if hasattr(return_hint, "__origin__") and return_hint.__origin__ is Union: + args = return_hint.__args__ + if type(None) not in args: + # Not Optional (e.g., just Message), needs ignore needs_ignore = True - except AttributeError: - # If we can't analyze it, assume it needs ignore + # else: is Optional, no ignore needed + else: + # Direct return type (not Optional), needs ignore needs_ignore = True for line in source: @@ -279,9 +263,11 @@ class {schema_class_name}: if needs_ignore: final_line = final_line.rstrip() + " # type: ignore[empty-body]" - print(final_line) + lines.append(final_line.rstrip()) if stripped.endswith(":"): break - print(" ...") - print() + lines.append(" ...") + lines.append("") + + return "\n".join(lines) From 5ede2bca4b5fa934120b3d83aa9e7ac9a8383636 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 11:54:32 -0400 Subject: [PATCH 15/20] purge --- chia/server/api_protocol.py | 156 ++++++++++++------------------------ 1 file changed, 50 insertions(+), 106 deletions(-) diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index 428bb77796f2..7fccb39cef6c 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -123,18 +123,38 @@ def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kw return inner def create_schema(self, api: type[ApiProtocol]) -> str: - imports = set() - imports.add("from __future__ import annotations") - imports.add("from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol") + # ruff will fixup imports + import_lines = [ + "from __future__ import annotations", + "from typing import TYPE_CHECKING, ClassVar, Optional, cast", + "from chia_rs import RespondToPhUpdates", + "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() + ) - # Track what's actually referenced in the generated code - used_types = set() 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.getsourcelines(request.method)[0] + source = inspect.getsource(request.method).splitlines() # Collect the method signature lines method_source = [] @@ -142,113 +162,35 @@ def create_schema(self, api: type[ApiProtocol]) -> str: method_source.append(line) if line.rstrip().endswith(":"): break - method_lines.extend(method_source) - # Check return type for Optional and Message - return_hint = type_hints.get("return") - if return_hint: - origin = getattr(return_hint, "__origin__", None) - if origin is Union: - args = return_hint.__args__ - if type(None) in args: - used_types.add("Optional") - for arg in args: - if arg.__name__ == "Message": - used_types.add("Message") - elif return_hint.__name__ == "Message": - used_types.add("Message") + 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 not in {"self", "return"}: - module = hint.__module__ - name = hint.__name__ - if module and module.startswith("chia."): - # Check if the type name actually appears directly in the method signature - # (not as part of harvester_protocol.ClassName) - signature_text = "".join(method_source) - # Only import if used directly, not through module qualification - if f": {name}" in signature_text or f"-> {name}" in signature_text: - used_types.add(name) - imports.add(f"from {module} import {name}") - - # Check decorators for ProtocolMessageTypes usage - decorator_line = source[0].strip() - if "ProtocolMessageTypes." in decorator_line: - used_types.add("ProtocolMessageTypes") - imports.add("from chia.protocols.protocol_message_types import ProtocolMessageTypes") - - # Check for protocol module usage in the signature - for line in method_source: - # Look for any protocol module usage (e.g., farmer_protocol.*, full_node_protocol.*, etc.) - protocol_matches = re.findall(r"(\w+_protocol)\.", line) - for protocol_module in protocol_matches: - imports.add(f"from chia.protocols import {protocol_module}") - - # Also check for types that might be from protocol modules but used directly - # Look for CamelCase types that aren't already imported - type_matches = re.findall(r": ([A-Z][a-zA-Z]+)", line) - for type_name in type_matches: - if type_name not in used_types and type_name not in {"Optional", "Message", "WSChiaConnection"}: - # This might be a direct import we missed - # Try to find it in the type hints - for param_name, hint in type_hints.items(): - name = hint.__name__ - module = hint.__module__ - if name == type_name and module: - # Import from chia.protocols.* - if module.startswith("chia.protocols."): - imports.add(f"from {module} import {name}") - used_types.add(name) - # Special case: chia_rs types show up as builtins but are actually from chia_rs - elif module == "builtins" and type_name in {"RespondToPhUpdates"}: - imports.add(f"from chia_rs import {name}") - used_types.add(name) - - # Add imports only for types that are actually used - if "Optional" in used_types: - imports.add("from typing import TYPE_CHECKING, ClassVar, Optional, cast") - else: - imports.add("from typing import TYPE_CHECKING, ClassVar, cast") - - if "Message" in used_types: - imports.add("from chia.protocols.outbound_message import Message") - - # Build the schema content as a string - lines = [] - - # Add imports - for import_line in sorted(imports): - lines.append(import_line) - lines.append("") - - # Use *ApiSchema naming convention for generated schemas - schema_class_name = api.__name__.replace("API", "ApiSchema") - lines.append( - textwrap.dedent( - f""" - class {schema_class_name}: - if TYPE_CHECKING: - _protocol_check: ApiSchemaProtocol = cast("{schema_class_name}", None) - - metadata: ClassVar[ApiMetadata] = ApiMetadata() - """ - ).strip() - ) - - for request in self.message_type_to_request.values(): - source = inspect.getsource(request.method).splitlines() + 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"(? Date: Tue, 26 Aug 2025 12:20:49 -0400 Subject: [PATCH 16/20] fixup for 3.9 --- chia/cmds/dev/generate_schemas.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 290acc3a535f..13f2d3bc1a4b 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -117,7 +117,8 @@ def generate_service_peer_schemas_cmd( schema_content = api_class.metadata.create_schema(api_class) # Write to file - output_file.write_text(schema_content, encoding="utf-8", newline="\n") + 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)") @@ -267,7 +268,8 @@ def _generate_registry_file(output_dir: Path, selected_services: list[NodeType]) }} """ - registry_file.write_text(content, encoding="utf-8", newline="\n") + 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 From 76478da295e8462dfb4ac178fd9d2fed9437c1d0 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 12:34:36 -0400 Subject: [PATCH 17/20] fixup for windows --- chia/cmds/dev/generate_schemas.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 13f2d3bc1a4b..9e883fb330dd 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -202,33 +202,24 @@ 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", "ls-files"], check=True, capture_output=True, text=True, cwd=Path.cwd()) - tracked_files = set(result.stdout.strip().split("\n")) + 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 = [] - warnings = [] - - for file_path in files: - # Convert to relative path from repo root - try: - relative_path = file_path.relative_to(Path.cwd()) - if str(relative_path) not in tracked_files: - untracked_files.append(file_path) - except ValueError: - # File is outside repo root, can't check tracking - warning = f"Cannot check git tracking for file outside repo: {file_path}" - click.echo(f"Warning: {warning}", err=True) - warnings.append(warning) - - if untracked_files: + 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") - else: - click.echo("All generated files are tracked by git") except subprocess.CalledProcessError as e: error_msg = f"Error checking git status: {e}" From 192d75aa16b0f5404c46e40183ab4b2d6b043313 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 26 Aug 2025 14:23:15 -0400 Subject: [PATCH 18/20] tidy --- chia/server/api_protocol.py | 1 - 1 file changed, 1 deletion(-) diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index 7fccb39cef6c..d846935b161c 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -78,7 +78,6 @@ def request( non_optional_reply_types = reply_types def inner(f: Callable[Concatenate[Self, S, P], R]) -> Callable[Concatenate[Self, Union[bytes, S], P], R]: - # print(inspect.getsource(f)) @functools.wraps(f) def wrapper(self: Self, original: Union[bytes, S], *args: P.args, **kwargs: P.kwargs) -> R: arg: S From 97790b8d365a6e2803a24a81a8e4ae86fb487c83 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 9 Sep 2025 11:57:23 -0400 Subject: [PATCH 19/20] catchup --- chia/apis/harvester_api_schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chia/apis/harvester_api_schema.py b/chia/apis/harvester_api_schema.py index 6dde8b03deeb..7c37852382bf 100644 --- a/chia/apis/harvester_api_schema.py +++ b/chia/apis/harvester_api_schema.py @@ -21,9 +21,9 @@ async def harvester_handshake( self, harvester_handshake: harvester_protocol.HarvesterHandshake, peer: WSChiaConnection ) -> None: ... - @metadata.request(peer_required=True) + @metadata.request(peer_required=True, request_type=ProtocolMessageTypes.new_signage_point_harvester) async def new_signage_point_harvester( - self, new_challenge: harvester_protocol.NewSignagePointHarvester, peer: WSChiaConnection + self, new_challenge: harvester_protocol.NewSignagePointHarvester2, peer: WSChiaConnection ) -> None: ... @metadata.request(reply_types=[ProtocolMessageTypes.respond_signatures]) From d13af1b421ac37961a25444f48b1beab3a895f70 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Tue, 9 Sep 2025 12:15:49 -0400 Subject: [PATCH 20/20] stop hacking for chia_rs --- chia/cmds/dev/generate_schemas.py | 2 +- chia/server/api_protocol.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chia/cmds/dev/generate_schemas.py b/chia/cmds/dev/generate_schemas.py index 9e883fb330dd..b7d43e92d99a 100644 --- a/chia/cmds/dev/generate_schemas.py +++ b/chia/cmds/dev/generate_schemas.py @@ -124,7 +124,7 @@ def generate_service_peer_schemas_cmd( click.echo(f"Generated {output_file} ({len(schema_content)} characters)") except Exception as e: - click.echo(f"Error generating schema for {node_type.name}: {e}", err=True) + click.echo(f"Error generating schema for {node_type.name}: {type(e).__name__} -> {e}", err=True) has_errors = True continue diff --git a/chia/server/api_protocol.py b/chia/server/api_protocol.py index d846935b161c..b55de87ccd4a 100644 --- a/chia/server/api_protocol.py +++ b/chia/server/api_protocol.py @@ -126,7 +126,6 @@ def create_schema(self, api: type[ApiProtocol]) -> str: import_lines = [ "from __future__ import annotations", "from typing import TYPE_CHECKING, ClassVar, Optional, cast", - "from chia_rs import RespondToPhUpdates", "from chia.protocols.outbound_message import Message", "from chia.protocols.protocol_message_types import ProtocolMessageTypes", "from chia.server.api_protocol import ApiMetadata, ApiSchemaProtocol", @@ -180,6 +179,8 @@ class {schema_class_name}: import_lines.append(f"from {base}.{protocol} import {name}") if re.search(rf"(?