mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
* fix type annotation in configure.py and init.py * enable mypy redundant-expr and remove redundant expressions
280 lines
8.7 KiB
Python
Executable File
280 lines
8.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import time
|
|
from itertools import chain
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import aiofiles
|
|
import click
|
|
from chia_rs.sized_bytes import bytes32
|
|
|
|
from chia.cmds.cmds_util import get_any_service_client
|
|
from chia.full_node.full_node_rpc_client import FullNodeRpcClient
|
|
from chia.util.default_root import resolve_root_path
|
|
from chia.util.path import path_from_root
|
|
from chia.util.task_referencer import create_referenced_task
|
|
|
|
DEFAULT_PIPELINE_DEPTH: int = 10
|
|
log = logging.getLogger("validate_rpcs")
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)-8s %(message)s")
|
|
|
|
|
|
def get_height_to_hash_filename(root_path: Path, config: dict[str, Any]) -> Path:
|
|
"""
|
|
Utility function to get the path to the height-to-hash database file.
|
|
"""
|
|
db_path_replaced: Path = root_path / config["full_node"]["database_path"]
|
|
db_directory: Path = path_from_root(root_path, db_path_replaced).parent
|
|
selected_network: str = config["full_node"]["selected_network"]
|
|
suffix = "" if selected_network == "mainnet" else f"-{selected_network}"
|
|
return db_directory / f"height-to-hash{suffix}"
|
|
|
|
|
|
async def get_height_to_hash_bytes(root_path: Path, config: dict[str, Any]) -> bytes:
|
|
"""
|
|
Load the height-to-hash database file into a bytearray.
|
|
"""
|
|
height_to_hash_filename: Path = get_height_to_hash_filename(root_path, config)
|
|
async with aiofiles.open(height_to_hash_filename, "rb") as f:
|
|
return await f.read()
|
|
|
|
|
|
def get_block_hash_for_height(height: int, height_to_hash: bytes) -> bytes32:
|
|
"""
|
|
Get the block header hash from the height-to-hash database.
|
|
"""
|
|
idx = height * 32
|
|
assert idx + 32 <= len(height_to_hash)
|
|
return bytes32(height_to_hash[idx : idx + 32])
|
|
|
|
|
|
@click.command(help="Test RPC endpoints using chain", no_args_is_help=True)
|
|
@click.option(
|
|
"--root-path",
|
|
default=resolve_root_path(override=None),
|
|
help="Config file root",
|
|
type=click.Path(),
|
|
show_default=True,
|
|
)
|
|
@click.option(
|
|
"-p",
|
|
"--rpc-port",
|
|
help=(
|
|
"Set the port where the Full Node is hosting the RPC interface. See the rpc_port under full_node in config.yaml"
|
|
),
|
|
type=int,
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"-sc",
|
|
"--spends_with_conditions",
|
|
help="Test get_block_spends_with_conditions",
|
|
is_flag=True,
|
|
type=bool,
|
|
default=False,
|
|
)
|
|
@click.option(
|
|
"-sp",
|
|
"--block_spends",
|
|
help="Test get_block_spends",
|
|
is_flag=True,
|
|
type=bool,
|
|
default=False,
|
|
)
|
|
@click.option(
|
|
"-ar",
|
|
"--additions_and_removals",
|
|
help="Test get_additions_and_removals",
|
|
is_flag=True,
|
|
type=bool,
|
|
default=False,
|
|
)
|
|
@click.option(
|
|
"-s",
|
|
"--start-height",
|
|
help="Start height for the RPC calls",
|
|
type=int,
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"-e",
|
|
"--end-height",
|
|
help="End height for the RPC calls",
|
|
type=int,
|
|
default=None,
|
|
)
|
|
@click.option(
|
|
"-r",
|
|
"--pipeline-depth",
|
|
help="Set the number of concurrent RPC calls to make.",
|
|
type=int,
|
|
default=DEFAULT_PIPELINE_DEPTH,
|
|
)
|
|
def cli(
|
|
root_path: str,
|
|
rpc_port: int | None,
|
|
spends_with_conditions: bool,
|
|
block_spends: bool,
|
|
additions_and_removals: bool,
|
|
pipeline_depth: int,
|
|
start_height: int | None = None,
|
|
end_height: int | None = None,
|
|
) -> None:
|
|
root_path_path = Path(root_path)
|
|
requests_per_batch = 0
|
|
if spends_with_conditions:
|
|
requests_per_batch += 1
|
|
if block_spends:
|
|
requests_per_batch += 1
|
|
if additions_and_removals:
|
|
requests_per_batch += 1
|
|
if requests_per_batch == 0:
|
|
print("No RPC calls selected. Exiting.")
|
|
return
|
|
if start_height is None:
|
|
start_height = 0
|
|
asyncio.run(
|
|
cli_async(
|
|
root_path=root_path_path,
|
|
rpc_port=rpc_port,
|
|
spends_with_conditions=spends_with_conditions,
|
|
block_spends=block_spends,
|
|
additions_and_removals=additions_and_removals,
|
|
start_height=start_height,
|
|
end_height=end_height,
|
|
pipeline_depth=pipeline_depth,
|
|
)
|
|
)
|
|
|
|
|
|
async def node_spends_with_conditions(
|
|
node_client: FullNodeRpcClient,
|
|
block_hash: bytes32,
|
|
height: int,
|
|
) -> None:
|
|
try:
|
|
res = await node_client.get_block_spends_with_conditions(block_hash)
|
|
for c in res:
|
|
c.coin_spend.get_hash() # Ensure CoinSpend is valid
|
|
except Exception as e:
|
|
log.error(f"ERROR: [{height}] get_block_spends_with_conditions returned invalid result")
|
|
raise e
|
|
|
|
|
|
async def node_block_spends(
|
|
node_client: FullNodeRpcClient,
|
|
block_hash: bytes32,
|
|
height: int,
|
|
) -> None:
|
|
try:
|
|
res = await node_client.get_block_spends(block_hash)
|
|
for c in res:
|
|
c.get_hash() # Ensure CoinSpend is valid
|
|
except Exception as e:
|
|
log.error(f"ERROR: [{height}] get_block_spends returned invalid result")
|
|
raise e
|
|
|
|
|
|
async def node_additions_removals(
|
|
node_client: FullNodeRpcClient,
|
|
block_hash: bytes32,
|
|
height: int,
|
|
) -> None:
|
|
try:
|
|
add, rem = await node_client.get_additions_and_removals(block_hash)
|
|
for coin in chain(add, rem):
|
|
coin.get_hash()
|
|
|
|
except Exception as e:
|
|
log.error(f"ERROR: [{height}] get_additions_and_removals returned invalid result")
|
|
raise e
|
|
|
|
|
|
async def cli_async(
|
|
root_path: Path,
|
|
rpc_port: int | None,
|
|
spends_with_conditions: bool,
|
|
block_spends: bool,
|
|
additions_and_removals: bool,
|
|
pipeline_depth: int,
|
|
start_height: int,
|
|
end_height: int | None = None,
|
|
) -> None:
|
|
async with get_any_service_client(FullNodeRpcClient, root_path, rpc_port) as (
|
|
node_client,
|
|
config,
|
|
):
|
|
blockchain_state: dict[str, Any] = await node_client.get_blockchain_state()
|
|
if blockchain_state["peak"] is None:
|
|
# Peak height is required for the cache.
|
|
print("No blockchain found. Exiting.")
|
|
return
|
|
peak_height = blockchain_state["peak"].height
|
|
assert peak_height is not None, "Blockchain peak height is None"
|
|
if end_height is None:
|
|
end_height = peak_height
|
|
|
|
print("Connected to Full Node")
|
|
|
|
height_to_hash_bytes: bytes = await get_height_to_hash_bytes(root_path=root_path, config=config)
|
|
|
|
log.info("block header hashes loaded from height-to-hash file.")
|
|
|
|
# Set initial values for the loop
|
|
|
|
pipeline: set[asyncio.Task[None]] = set()
|
|
completed_requests: int = 0
|
|
# measure time for performance measurement per segment
|
|
cycle_start: float = time.monotonic()
|
|
# also measure time for the whole process
|
|
start_time: float = cycle_start
|
|
|
|
def add_tasks_for_height(height: int) -> None:
|
|
block_header_hash = get_block_hash_for_height(height, height_to_hash_bytes)
|
|
# Create tasks for each RPC call based on the flags
|
|
if spends_with_conditions:
|
|
pipeline.add(
|
|
create_referenced_task(node_spends_with_conditions(node_client, block_header_hash, height))
|
|
)
|
|
if block_spends:
|
|
pipeline.add(create_referenced_task(node_block_spends(node_client, block_header_hash, height)))
|
|
if additions_and_removals:
|
|
pipeline.add(create_referenced_task(node_additions_removals(node_client, block_header_hash, height)))
|
|
|
|
for i in range(start_height, end_height + 1):
|
|
add_tasks_for_height(height=i)
|
|
# Make Status Updates.
|
|
while len(pipeline) >= pipeline_depth:
|
|
done, pipeline = await asyncio.wait(pipeline, return_when=asyncio.FIRST_COMPLETED)
|
|
completed_requests += len(done)
|
|
now = time.monotonic()
|
|
if cycle_start + 5 < now:
|
|
time_taken = now - cycle_start
|
|
log.info(
|
|
f"Processed {completed_requests} RPCs in {time_taken:.2f}s, "
|
|
f"{time_taken / completed_requests:.4f}s per RPC "
|
|
f"({i - start_height} Blocks completed out of {end_height - start_height})"
|
|
)
|
|
completed_requests = 0
|
|
cycle_start = now
|
|
|
|
# Wait for any remaining tasks to complete
|
|
log.info(f"Waiting for {len(pipeline)} remaining tasks to complete...")
|
|
if pipeline:
|
|
await asyncio.gather(*pipeline)
|
|
|
|
print(f"Finished processing blocks from {start_height} to {end_height} (peak: {peak_height})")
|
|
print(
|
|
f"Time per block for the whole process: "
|
|
f"{(time.monotonic() - start_time) / (end_height - start_height):.4f} seconds"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|