From b7967d3e3a9902347891f149b3fc593d39f3aa7e Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Sun, 10 Aug 2025 14:25:23 +0200 Subject: [PATCH] optimize get_spends RPC --- .../core/full_node/stores/test_block_store.py | 5 ++- chia/_tests/util/test_full_block_utils.py | 1 + chia/full_node/block_store.py | 7 ++-- chia/full_node/full_block_utils.py | 25 +++++++++++-- chia/full_node/full_node_rpc_api.py | 35 +++++++++++-------- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/chia/_tests/core/full_node/stores/test_block_store.py b/chia/_tests/core/full_node/stores/test_block_store.py index 5cb22d158f7d..5aaa40d9e167 100644 --- a/chia/_tests/core/full_node/stores/test_block_store.py +++ b/chia/_tests/core/full_node/stores/test_block_store.py @@ -91,7 +91,10 @@ async def test_block_store(tmp_dir: Path, db_version: int, bt: BlockTools, use_c assert block == await store.get_full_block(block.header_hash) assert bytes(block) == await store.get_full_block_bytes(block.header_hash) assert GeneratorBlockInfo( - block.foliage.prev_block_hash, block.transactions_generator, block.transactions_generator_ref_list + block.foliage.prev_block_hash, + block.height, + block.transactions_generator, + block.transactions_generator_ref_list, ) == await store.get_block_info(block.header_hash) assert maybe_serialize(block.transactions_generator) == await store.get_generator(block.header_hash) assert block_record == (await store.get_block_record(block_record_hh)) diff --git a/chia/_tests/util/test_full_block_utils.py b/chia/_tests/util/test_full_block_utils.py index 3a3b3ed23970..48a816cffda3 100644 --- a/chia/_tests/util/test_full_block_utils.py +++ b/chia/_tests/util/test_full_block_utils.py @@ -275,6 +275,7 @@ async def test_parser(): assert block.transactions_generator == bi.transactions_generator assert block.prev_header_hash == bi.prev_header_hash assert block.transactions_generator_ref_list == bi.transactions_generator_ref_list + assert block.height == bi.height # this doubles the run-time of this test, with questionable utility # assert gen == FullBlock.from_bytes(block_bytes).transactions_generator diff --git a/chia/full_node/block_store.py b/chia/full_node/block_store.py index 4fef5b9a4006..41fcd948edd8 100644 --- a/chia/full_node/block_store.py +++ b/chia/full_node/block_store.py @@ -251,7 +251,10 @@ async def get_block_info(self, header_hash: bytes32) -> Optional[GeneratorBlockI cached = self.block_cache.get(header_hash) if cached is not None: return GeneratorBlockInfo( - cached.foliage.prev_block_hash, cached.transactions_generator, cached.transactions_generator_ref_list + cached.foliage.prev_block_hash, + cached.height, + cached.transactions_generator, + cached.transactions_generator_ref_list, ) formatted_str = "SELECT block, height from full_blocks WHERE header_hash=?" @@ -270,7 +273,7 @@ async def get_block_info(self, header_hash: bytes32) -> Optional[GeneratorBlockI # definition of parsing a block b = FullBlock.from_bytes(block_bytes) return GeneratorBlockInfo( - b.foliage.prev_block_hash, b.transactions_generator, b.transactions_generator_ref_list + b.foliage.prev_block_hash, b.height, b.transactions_generator, b.transactions_generator_ref_list ) async def get_generator(self, header_hash: bytes32) -> Optional[bytes]: diff --git a/chia/full_node/full_block_utils.py b/chia/full_node/full_block_utils.py index 97f52fd23202..305358b51c77 100644 --- a/chia/full_node/full_block_utils.py +++ b/chia/full_node/full_block_utils.py @@ -143,6 +143,26 @@ def skip_reward_chain_block(buf: memoryview) -> memoryview: return skip_bool(buf) # is_transaction_block +def height_from_reward_chain_block(buf: memoryview) -> tuple[memoryview, uint32]: + buf = skip_uint128(buf) # weight + height = uint32.from_bytes(buf[:4]) + buf = skip_uint32(buf) # height + buf = skip_uint128(buf) # total_iters + buf = skip_uint8(buf) # signage_point_index + buf = skip_bytes32(buf) # pos_ss_cc_challenge_hash + + buf = skip_proof_of_space(buf) # proof_of_space + buf = skip_optional(buf, skip_vdf_info) # challenge_chain_sp_vdf + buf = skip_g2_element(buf) # challenge_chain_sp_signature + buf = skip_vdf_info(buf) # challenge_chain_ip_vdf + buf = skip_optional(buf, skip_vdf_info) # reward_chain_sp_vdf + buf = skip_g2_element(buf) # reward_chain_sp_signature + buf = skip_vdf_info(buf) # reward_chain_ip_vdf + buf = skip_optional(buf, skip_vdf_info) # infused_challenge_chain_ip_vdf + buf = skip_bool(buf) # is_transaction_block + return buf, height + + def skip_pool_target(buf: memoryview) -> memoryview: # buf = skip_bytes32(buf) # puzzle_hash # return skip_uint32(buf) # max_height @@ -228,13 +248,14 @@ def generator_from_block(buf: memoryview) -> Optional[bytes]: @dataclass(frozen=True) class GeneratorBlockInfo: prev_header_hash: bytes32 + height: uint32 transactions_generator: Optional[SerializedProgram] transactions_generator_ref_list: list[uint32] def block_info_from_block(buf: memoryview) -> GeneratorBlockInfo: buf = skip_list(buf, skip_end_of_sub_slot_bundle) # finished_sub_slots - buf = skip_reward_chain_block(buf) # reward_chain_block + buf, height = height_from_reward_chain_block(buf) # reward_chain_block buf = skip_optional(buf, skip_vdf_proof) # challenge_chain_sp_proof buf = skip_vdf_proof(buf) # challenge_chain_ip_proof buf = skip_optional(buf, skip_vdf_proof) # reward_chain_sp_proof @@ -262,7 +283,7 @@ def block_info_from_block(buf: memoryview) -> GeneratorBlockInfo: refs.append(uint32.from_bytes(buf[:4])) buf = buf[4:] - return GeneratorBlockInfo(prev_hash, generator, refs) + return GeneratorBlockInfo(prev_hash, height, generator, refs) def header_block_from_block( diff --git a/chia/full_node/full_node_rpc_api.py b/chia/full_node/full_node_rpc_api.py index 3330583ee67a..f871bfa6c0a5 100644 --- a/chia/full_node/full_node_rpc_api.py +++ b/chia/full_node/full_node_rpc_api.py @@ -27,6 +27,7 @@ from chia.consensus.get_block_generator import get_block_generator from chia.consensus.pos_quality import UI_ACTUAL_SPACE_CONSTANT_FACTOR from chia.full_node.fee_estimator_interface import FeeEstimatorInterface +from chia.full_node.full_block_utils import GeneratorBlockInfo, get_height_and_tx_status_from_block from chia.full_node.full_node import FullNode from chia.protocols.outbound_message import NodeType from chia.rpc.rpc_server import Endpoint, EndpointResult @@ -481,15 +482,15 @@ async def get_block_spends(self, request: dict[str, Any]) -> EndpointResult: if "header_hash" not in request: raise ValueError("No header_hash in request") header_hash = bytes32.from_hexstr(request["header_hash"]) - full_block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash) - if full_block is None: + block_info: Optional[GeneratorBlockInfo] = await self.service.block_store.get_block_info(header_hash) + if block_info is None: raise ValueError(f"Block {header_hash.hex()} not found") - block_generator = await get_block_generator(self.service.blockchain.lookup_block_generators, full_block) + block_generator = await get_block_generator(self.service.blockchain.lookup_block_generators, block_info) if block_generator is None: # if block is not a transaction block. return {"block_spends": []} - flags = get_flags_for_height_and_constants(full_block.height, self.service.constants) + flags = get_flags_for_height_and_constants(block_info.height, self.service.constants) spends = await asyncio.get_running_loop().run_in_executor( self.executor, get_spends_for_trusted_block, @@ -505,15 +506,15 @@ async def get_block_spends_with_conditions(self, request: dict[str, Any]) -> End if "header_hash" not in request: raise ValueError("No header_hash in request") header_hash = bytes32.from_hexstr(request["header_hash"]) - full_block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash) - if full_block is None: + block_info: Optional[GeneratorBlockInfo] = await self.service.block_store.get_block_info(header_hash) + if block_info is None: raise ValueError(f"Block {header_hash.hex()} not found") - block_generator = await get_block_generator(self.service.blockchain.lookup_block_generators, full_block) + block_generator = await get_block_generator(self.service.blockchain.lookup_block_generators, block_info) if block_generator is None: # if block is not a transaction block. return {"block_spends_with_conditions": []} - flags = get_flags_for_height_and_constants(full_block.height, self.service.constants) + flags = get_flags_for_height_and_constants(block_info.height, self.service.constants) spends_with_conditions = await asyncio.get_running_loop().run_in_executor( self.executor, get_spends_for_trusted_block_with_conditions, @@ -792,7 +793,9 @@ async def get_puzzle_and_solution(self, request: dict[str, Any]) -> EndpointResu assert block_generator is not None try: - puzzle, solution = get_puzzle_and_solution_for_coin( + puzzle, solution = await asyncio.get_running_loop().run_in_executor( + self.executor, + get_puzzle_and_solution_for_coin, block_generator.program, block_generator.generator_refs, self.service.constants.MAX_BLOCK_COST_CLVM, @@ -807,16 +810,18 @@ async def get_additions_and_removals(self, request: dict[str, Any]) -> EndpointR if "header_hash" not in request: raise ValueError("No header_hash in request") header_hash = bytes32.from_hexstr(request["header_hash"]) - - block: Optional[FullBlock] = await self.service.block_store.get_full_block(header_hash) - if block is None: + block_bytes: Optional[bytes] = await self.service.block_store.get_full_block_bytes(header_hash) + if block_bytes is None: raise ValueError(f"Block {header_hash.hex()} not found") + block_view = memoryview(block_bytes) + height, _is_tx_block = get_height_and_tx_status_from_block(block_view) + async with self.service.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.low): - if self.service.blockchain.height_to_hash(block.height) != header_hash: + if self.service.blockchain.height_to_hash(height) != header_hash: raise ValueError(f"Block at {header_hash.hex()} is no longer in the blockchain (it's in a fork)") - additions: list[CoinRecord] = await self.service.coin_store.get_coins_added_at_height(block.height) - removals: list[CoinRecord] = await self.service.coin_store.get_coins_removed_at_height(block.height) + additions: list[CoinRecord] = await self.service.coin_store.get_coins_added_at_height(height) + removals: list[CoinRecord] = await self.service.coin_store.get_coins_removed_at_height(height) return { "additions": [coin_record_dict_backwards_compat(cr.to_json_dict()) for cr in additions],