From 8b5ce9fec8f0343c6e8c4ea942e90161696859d5 Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Tue, 15 Jul 2025 17:17:38 -0400 Subject: [PATCH 1/6] remove extra exception catching --- chia/farmer/farmer_rpc_client.py | 4 +- chia/full_node/full_node_rpc_client.py | 144 ++++++++++--------------- chia/wallet/wallet_rpc_client.py | 2 +- tools/validate_rpcs.py | 18 ++-- 4 files changed, 71 insertions(+), 97 deletions(-) diff --git a/chia/farmer/farmer_rpc_client.py b/chia/farmer/farmer_rpc_client.py index 0094d1f5f2ee..94483444c7a3 100644 --- a/chia/farmer/farmer_rpc_client.py +++ b/chia/farmer/farmer_rpc_client.py @@ -21,7 +21,7 @@ class FarmerRpcClient(RpcClient): async def get_signage_point(self, sp_hash: bytes32) -> Optional[dict[str, Any]]: try: return await self.fetch("get_signage_point", {"sp_hash": sp_hash.hex()}) - except ValueError: + except ValueError: # not synced return None async def get_signage_points(self) -> list[dict[str, Any]]: @@ -83,5 +83,5 @@ async def get_pool_login_link(self, launcher_id: bytes32) -> Optional[str]: try: result = await self.fetch("get_pool_login_link", {"launcher_id": launcher_id.hex()}) return cast(Optional[str], result["login_link"]) - except ValueError: + except ValueError: # not connected to pool. return None diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index 6bb6026e89b4..bd764901315b 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -36,11 +36,8 @@ async def get_blockchain_state(self) -> dict[str, Any]: response["blockchain_state"]["peak"] = BlockRecord.from_json_dict(response["blockchain_state"]["peak"]) return cast(dict[str, Any], response["blockchain_state"]) - async def get_block(self, header_hash: bytes32) -> Optional[FullBlock]: - try: - response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) - except Exception: - return None + async def get_block(self, header_hash: bytes32) -> FullBlock: + response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) return FullBlock.from_json_dict(response["block"]) async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) -> list[FullBlock]: @@ -52,16 +49,13 @@ async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) async def get_block_record_by_height(self, height: int) -> Optional[BlockRecord]: try: response = await self.fetch("get_block_record_by_height", {"height": height}) - except Exception: + except ValueError: # Block Height not found return None return BlockRecord.from_json_dict(response["block_record"]) async def get_block_record(self, header_hash: bytes32) -> Optional[BlockRecord]: - try: - response = await self.fetch("get_block_record", {"header_hash": header_hash.hex()}) - if response["block_record"] is None: - return None - except Exception: + response = await self.fetch("get_block_record", {"header_hash": header_hash.hex()}) + if response["block_record"] is None: return None return BlockRecord.from_json_dict(response["block_record"]) @@ -84,12 +78,8 @@ async def get_network_space(self, newer_block_header_hash: bytes32, older_block_ return cast(int, network_space_bytes_estimate["space"]) - async def get_coin_record_by_name(self, coin_id: bytes32) -> Optional[CoinRecord]: - try: - response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) - except Exception: - return None - + async def get_coin_record_by_name(self, coin_id: bytes32) -> CoinRecord: + response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) return CoinRecord.from_json_dict(coin_record_dict_backwards_compat(response["coin_record"])) async def get_coin_records_by_names( @@ -180,10 +170,7 @@ async def get_coin_records_by_hint( return [CoinRecord.from_json_dict(coin_record_dict_backwards_compat(coin)) for coin in response["coin_records"]] async def get_additions_and_removals(self, header_hash: bytes32) -> tuple[list[CoinRecord], list[CoinRecord]]: - try: - response = await self.fetch("get_additions_and_removals", {"header_hash": header_hash.hex()}) - except Exception: - return [], [] + response = await self.fetch("get_additions_and_removals", {"header_hash": header_hash.hex()}) removals = [] additions = [] for coin_record in response["removals"]: @@ -193,53 +180,40 @@ async def get_additions_and_removals(self, header_hash: bytes32) -> tuple[list[C return additions, removals async def get_block_records(self, start: int, end: int) -> list[dict[str, Any]]: - try: - response = await self.fetch("get_block_records", {"start": start, "end": end}) - if response["block_records"] is None: - return [] - except Exception: + response = await self.fetch("get_block_records", {"start": start, "end": end}) + if response["block_records"] is None: return [] # TODO: return block records return cast(list[dict[str, Any]], response["block_records"]) - async def get_block_spends(self, header_hash: bytes32) -> Optional[list[CoinSpend]]: - try: - response = await self.fetch("get_block_spends", {"header_hash": header_hash.hex()}) - output = [] - for block_spend in response["block_spends"]: - output.append(CoinSpend.from_json_dict(block_spend)) - return output - except Exception: - return None - - async def get_block_spends_with_conditions(self, header_hash: bytes32) -> Optional[list[CoinSpendWithConditions]]: - try: - response = await self.fetch("get_block_spends_with_conditions", {"header_hash": header_hash.hex()}) - block_spends: list[CoinSpendWithConditions] = [] - for block_spend in response["block_spends_with_conditions"]: - coin_spend = CoinSpend.from_json_dict(block_spend["coin_spend"]) - cond_tuples = block_spend["conditions"] - conditions = [] - for condition in cond_tuples: - cwa = ConditionWithArgs( - opcode=ConditionOpcode(bytes([condition[0]])), vars=[hexstr_to_bytes(b) for b in condition[1]] - ) - conditions.append(cwa) - block_spends.append(CoinSpendWithConditions(coin_spend=coin_spend, conditions=conditions)) - return block_spends - - except Exception: - return None + async def get_block_spends(self, header_hash: bytes32) -> list[CoinSpend]: + response = await self.fetch("get_block_spends", {"header_hash": header_hash.hex()}) + output = [] + for block_spend in response["block_spends"]: + output.append(CoinSpend.from_json_dict(block_spend)) + return output + + async def get_block_spends_with_conditions(self, header_hash: bytes32) -> list[CoinSpendWithConditions]: + response = await self.fetch("get_block_spends_with_conditions", {"header_hash": header_hash.hex()}) + block_spends: list[CoinSpendWithConditions] = [] + for block_spend in response["block_spends_with_conditions"]: + coin_spend = CoinSpend.from_json_dict(block_spend["coin_spend"]) + cond_tuples = block_spend["conditions"] + conditions = [] + for condition in cond_tuples: + cwa = ConditionWithArgs( + opcode=ConditionOpcode(bytes([condition[0]])), vars=[hexstr_to_bytes(b) for b in condition[1]] + ) + conditions.append(cwa) + block_spends.append(CoinSpendWithConditions(coin_spend=coin_spend, conditions=conditions)) + return block_spends async def push_tx(self, spend_bundle: SpendBundle) -> dict[str, Any]: return await self.fetch("push_tx", {"spend_bundle": spend_bundle.to_json_dict()}) - async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Optional[CoinSpend]: - try: - response = await self.fetch("get_puzzle_and_solution", {"coin_id": coin_id.hex(), "height": height}) - return CoinSpend.from_json_dict(response["coin_solution"]) - except Exception: - return None + async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> CoinSpend: + response = await self.fetch("get_puzzle_and_solution", {"coin_id": coin_id.hex(), "height": height}) + return CoinSpend.from_json_dict(response["coin_solution"]) async def get_all_mempool_tx_ids(self) -> list[bytes32]: response = await self.fetch("get_all_mempool_tx_ids", {}) @@ -256,14 +230,11 @@ async def get_mempool_item_by_tx_id( self, tx_id: bytes32, include_pending: bool = False, - ) -> Optional[dict[str, Any]]: - try: - response = await self.fetch( - "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} - ) - return cast(dict[str, Any], response["mempool_item"]) - except Exception: - return None + ) -> dict[str, Any]: + response = await self.fetch( + "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} + ) + return cast(dict[str, Any], response["mempool_item"]) async def get_mempool_items_by_coin_name(self, coin_name: bytes32) -> dict[str, Any]: response = await self.fetch("get_mempool_items_by_coin_name", {"coin_name": coin_name.hex()}) @@ -275,26 +246,23 @@ async def create_block_generator(self) -> Optional[dict[str, Any]]: async def get_recent_signage_point_or_eos( self, sp_hash: Optional[bytes32], challenge_hash: Optional[bytes32] - ) -> Optional[Any]: - try: - if sp_hash is not None: - assert challenge_hash is None - response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) - return { - "signage_point": SignagePoint.from_json_dict(response["signage_point"]), - "time_received": response["time_received"], - "reverted": response["reverted"], - } - else: - assert challenge_hash is not None - response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) - return { - "eos": EndOfSubSlotBundle.from_json_dict(response["eos"]), - "time_received": response["time_received"], - "reverted": response["reverted"], - } - except Exception: - return None + ) -> dict[str, Any]: + if sp_hash is not None: + assert challenge_hash is None + response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) + return { + "signage_point": SignagePoint.from_json_dict(response["signage_point"]), + "time_received": response["time_received"], + "reverted": response["reverted"], + } + else: + assert challenge_hash is not None + response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) + return { + "eos": EndOfSubSlotBundle.from_json_dict(response["eos"]), + "time_received": response["time_received"], + "reverted": response["reverted"], + } async def get_fee_estimate( self, diff --git a/chia/wallet/wallet_rpc_client.py b/chia/wallet/wallet_rpc_client.py index 88ae0084b14e..330c1e2b7264 100644 --- a/chia/wallet/wallet_rpc_client.py +++ b/chia/wallet/wallet_rpc_client.py @@ -767,7 +767,7 @@ async def cat_asset_id_to_name(self, asset_id: bytes32) -> Optional[tuple[Option request = {"asset_id": asset_id.hex()} try: res = await self.fetch("cat_asset_id_to_name", request) - except ValueError: + except ValueError: # This happens if the asset_id is unknown return None wallet_id: Optional[uint32] = None if res["wallet_id"] is None else uint32(int(res["wallet_id"])) diff --git a/tools/validate_rpcs.py b/tools/validate_rpcs.py index c375fd891caf..aa77347f458d 100755 --- a/tools/validate_rpcs.py +++ b/tools/validate_rpcs.py @@ -153,9 +153,11 @@ async def node_spends_with_conditions( block_hash: bytes32, height: int, ) -> None: - result = await node_client.get_block_spends_with_conditions(block_hash) - if result is None: + try: + await node_client.get_block_spends_with_conditions(block_hash) + except Exception as e: print(f"ERROR: [{height}] get_block_spends_with_conditions returned invalid result") + raise e async def node_block_spends( @@ -163,9 +165,11 @@ async def node_block_spends( block_hash: bytes32, height: int, ) -> None: - result = await node_client.get_block_spends(block_hash) - if result is None: + try: + await node_client.get_block_spends(block_hash) + except Exception as e: print(f"ERROR: [{height}] get_block_spends returned invalid result") + raise e async def node_additions_removals( @@ -173,9 +177,11 @@ async def node_additions_removals( block_hash: bytes32, height: int, ) -> None: - response = await node_client.get_additions_and_removals(block_hash) - if response is None: + try: + await node_client.get_additions_and_removals(block_hash) + except Exception as e: print(f"ERROR: [{height}] get_additions_and_removals returned invalid result") + raise e async def cli_async( From a99f2c53163128e77ce6e10ca6b2e496b7449a3e Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Tue, 15 Jul 2025 20:57:28 -0400 Subject: [PATCH 2/6] clean up node errors + tests --- chia/_tests/core/test_full_node_rpc.py | 80 +++++++++++++++++++------- chia/full_node/full_node_rpc_client.py | 64 ++++++++++++++++----- 2 files changed, 108 insertions(+), 36 deletions(-) diff --git a/chia/_tests/core/test_full_node_rpc.py b/chia/_tests/core/test_full_node_rpc.py index 38e7badc2703..4fb4e90394c7 100644 --- a/chia/_tests/core/test_full_node_rpc.py +++ b/chia/_tests/core/test_full_node_rpc.py @@ -88,8 +88,12 @@ async def test1( peak_block = await client.get_block(state["peak"].header_hash) assert peak_block == blocks[-1] - assert (await client.get_block(bytes32([1] * 32))) is None - + try: + await client.get_block(bytes32([1] * 32)) + except ValueError as e: + assert str(e).endswith("not found") + else: + assert False, "Expected ValueError for non-existent block" block_record = await client.get_block_record_by_height(2) assert block_record is not None assert block_record.header_hash == blocks[2].header_hash @@ -150,8 +154,13 @@ async def test1( assert len(await client.get_all_mempool_items()) == 0 assert len(await client.get_all_mempool_tx_ids()) == 0 - assert (await client.get_mempool_item_by_tx_id(spend_bundle.name())) is None - assert (await client.get_mempool_item_by_tx_id(spend_bundle.name(), False)) is None + try: + await client.get_mempool_item_by_tx_id(spend_bundle.name()) + await client.get_mempool_item_by_tx_id(spend_bundle.name(), False) + except ValueError as e: + assert str(e).endswith("not in the mempool") + else: + assert False, "Expected ValueError for non-existent mempool item" await client.push_tx(spend_bundle) coin = spend_bundle.additions()[0] @@ -168,7 +177,12 @@ async def test1( mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle.name()) assert mempool_item is not None assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle - assert (await client.get_coin_record_by_name(coin.name())) is None + try: + await client.get_coin_record_by_name(coin.name()) + except ValueError as e: + assert str(e).endswith("not found") + else: + assert False, "Expected ValueError for non-existent coin record" # Verify that the include_pending arg to get_mempool_item_by_tx_id works coin_to_spend_pending = included_reward_coins[1] @@ -181,11 +195,16 @@ async def test1( condition_dic=condition_dic, ) await client.push_tx(spend_bundle_pending) - # not strictly in the mempool - assert (await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), False)) is None + try: + # not strictly in the mempool + await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), False) + except ValueError as e: + assert str(e).endswith("not in the mempool") + else: + assert False, "Expected ValueError for non-existent mempool item" + # pending entry into mempool, so include_pending fetches mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), True) - assert mempool_item is not None assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle_pending await full_node_api_1.farm_new_transaction_block(FarmNewBlockProtocol(ph_2)) @@ -454,16 +473,27 @@ async def test_signage_points( full_node_service_1.config, ) as client: # Only provide one - res = await client.get_recent_signage_point_or_eos(None, None) - assert res is None - res = await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1")) - assert res is None - + try: + await client.get_recent_signage_point_or_eos(None, None) + except ValueError as e: + assert str(e) == "sp_hash or challenge_hash must be provided." + else: + assert False, "Expected ValueError" + + try: + await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1")) + except ValueError as e: + assert str(e) == "Either sp_hash or challenge_hash must be provided, not both." + else: + assert False, "Expected ValueError" # Not found - res = await client.get_recent_signage_point_or_eos(std_hash(b"0"), None) - assert res is None - res = await client.get_recent_signage_point_or_eos(None, std_hash(b"0")) - assert res is None + try: + await client.get_recent_signage_point_or_eos(std_hash(b"0"), None) + await client.get_recent_signage_point_or_eos(None, std_hash(b"0")) + except ValueError as e: + assert str(e).endswith("in cache") + else: + assert False, "Expected ValueError" blocks = bt.get_consecutive_blocks(5) for block in blocks: @@ -494,8 +524,12 @@ async def test_signage_points( assert sp.rc_proof is not None assert sp.rc_vdf is not None # Don't have SP yet - res = await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None) - assert res is None + try: + await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None) + except ValueError as e: + assert str(e).startswith("Did not find sp") + else: + assert False, "Expected ValueError" # Add the last block await full_node_api_1.full_node.add_block(blocks[-1]) @@ -517,8 +551,12 @@ async def test_signage_points( selected_eos = blocks[-1].finished_sub_slots[0] # Don't have EOS yet - res = await client.get_recent_signage_point_or_eos(None, selected_eos.challenge_chain.get_hash()) - assert res is None + try: + await client.get_recent_signage_point_or_eos(None, selected_eos.challenge_chain.get_hash()) + except ValueError as e: + assert str(e).startswith("Did not find eos") + else: + assert False, "Expected ValueError" # Properly fetch an EOS for eos in blocks[-1].finished_sub_slots: diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index bd764901315b..5710904d0642 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -7,7 +7,7 @@ from chia_rs.sized_ints import uint32 from chia.consensus.signage_point import SignagePoint -from chia.rpc.rpc_client import RpcClient +from chia.rpc.rpc_client import RpcClient, ResponseFailureError from chia.types.coin_record import CoinRecord from chia.types.coin_spend import CoinSpendWithConditions from chia.types.condition_opcodes import ConditionOpcode @@ -37,7 +37,12 @@ async def get_blockchain_state(self) -> dict[str, Any]: return cast(dict[str, Any], response["blockchain_state"]) async def get_block(self, header_hash: bytes32) -> FullBlock: - response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) + try: + response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) + except ResponseFailureError as e: + if "not found" in str(e): + raise ValueError(f"Block {header_hash.hex()} not found") from e + raise e return FullBlock.from_json_dict(response["block"]) async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) -> list[FullBlock]: @@ -49,8 +54,10 @@ async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) async def get_block_record_by_height(self, height: int) -> Optional[BlockRecord]: try: response = await self.fetch("get_block_record_by_height", {"height": height}) - except ValueError: # Block Height not found - return None + except ResponseFailureError as e: # Block Height not found + if "not found" in str(e): + return None + raise e return BlockRecord.from_json_dict(response["block_record"]) async def get_block_record(self, header_hash: bytes32) -> Optional[BlockRecord]: @@ -79,7 +86,12 @@ async def get_network_space(self, newer_block_header_hash: bytes32, older_block_ return cast(int, network_space_bytes_estimate["space"]) async def get_coin_record_by_name(self, coin_id: bytes32) -> CoinRecord: - response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) + try: + response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) + except ResponseFailureError as e: # Coin ID not found + if "Coin record" in str(e): + raise ValueError(f"Coin record {coin_id.hex()} not found") from e + raise e return CoinRecord.from_json_dict(coin_record_dict_backwards_compat(response["coin_record"])) async def get_coin_records_by_names( @@ -180,7 +192,12 @@ async def get_additions_and_removals(self, header_hash: bytes32) -> tuple[list[C return additions, removals async def get_block_records(self, start: int, end: int) -> list[dict[str, Any]]: - response = await self.fetch("get_block_records", {"start": start, "end": end}) + try: + response = await self.fetch("get_block_records", {"start": start, "end": end}) + except ResponseFailureError as e: # No Peak Yet + if "Peak is None" in str(e): + return [] + raise e if response["block_records"] is None: return [] # TODO: return block records @@ -231,9 +248,14 @@ async def get_mempool_item_by_tx_id( tx_id: bytes32, include_pending: bool = False, ) -> dict[str, Any]: - response = await self.fetch( - "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} - ) + try: + response = await self.fetch( + "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} + ) + except ResponseFailureError as e: + if "not in the mempool" in str(e): + raise ValueError(f"Tx id {tx_id.hex()} not in the mempool") from e + raise e return cast(dict[str, Any], response["mempool_item"]) async def get_mempool_items_by_coin_name(self, coin_name: bytes32) -> dict[str, Any]: @@ -247,22 +269,34 @@ async def create_block_generator(self) -> Optional[dict[str, Any]]: async def get_recent_signage_point_or_eos( self, sp_hash: Optional[bytes32], challenge_hash: Optional[bytes32] ) -> dict[str, Any]: - if sp_hash is not None: - assert challenge_hash is None - response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) + if sp_hash is not None and challenge_hash is not None: + raise ValueError("Either sp_hash or challenge_hash must be provided, not both.") + elif sp_hash is not None: + try: + response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) + except ResponseFailureError as e: + if "Did not find sp" in str(e): + raise ValueError(f"Did not find sp {sp_hash.hex()} in cache") from e + raise e return { "signage_point": SignagePoint.from_json_dict(response["signage_point"]), "time_received": response["time_received"], "reverted": response["reverted"], } - else: - assert challenge_hash is not None - response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) + elif challenge_hash is not None: + try: + response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) + except ResponseFailureError as e: + if "Did not find eos" in str(e): + raise ValueError(f"Did not find eos {challenge_hash.hex()} in cache") from e + raise e return { "eos": EndOfSubSlotBundle.from_json_dict(response["eos"]), "time_received": response["time_received"], "reverted": response["reverted"], } + else: + raise ValueError("sp_hash or challenge_hash must be provided.") async def get_fee_estimate( self, From 9366330c172a228fa82ee778805559355383761d Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Tue, 15 Jul 2025 21:09:58 -0400 Subject: [PATCH 3/6] forgot to pre-commit --- chia/full_node/full_node_rpc_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index 5710904d0642..59f0b9c42ed0 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -7,7 +7,7 @@ from chia_rs.sized_ints import uint32 from chia.consensus.signage_point import SignagePoint -from chia.rpc.rpc_client import RpcClient, ResponseFailureError +from chia.rpc.rpc_client import ResponseFailureError, RpcClient from chia.types.coin_record import CoinRecord from chia.types.coin_spend import CoinSpendWithConditions from chia.types.condition_opcodes import ConditionOpcode From e8df3a66a3c73c4da9361a6826d5c740a57810e5 Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Wed, 16 Jul 2025 15:32:57 -0400 Subject: [PATCH 4/6] address using pytest wrong --- chia/_tests/core/test_full_node_rpc.py | 58 ++++---------------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/chia/_tests/core/test_full_node_rpc.py b/chia/_tests/core/test_full_node_rpc.py index 4fb4e90394c7..e517afe1ada0 100644 --- a/chia/_tests/core/test_full_node_rpc.py +++ b/chia/_tests/core/test_full_node_rpc.py @@ -88,12 +88,8 @@ async def test1( peak_block = await client.get_block(state["peak"].header_hash) assert peak_block == blocks[-1] - try: + with pytest.raises(ValueError, match="not found"): await client.get_block(bytes32([1] * 32)) - except ValueError as e: - assert str(e).endswith("not found") - else: - assert False, "Expected ValueError for non-existent block" block_record = await client.get_block_record_by_height(2) assert block_record is not None assert block_record.header_hash == blocks[2].header_hash @@ -154,13 +150,9 @@ async def test1( assert len(await client.get_all_mempool_items()) == 0 assert len(await client.get_all_mempool_tx_ids()) == 0 - try: + with pytest.raises(ValueError, match="not in the mempool"): await client.get_mempool_item_by_tx_id(spend_bundle.name()) await client.get_mempool_item_by_tx_id(spend_bundle.name(), False) - except ValueError as e: - assert str(e).endswith("not in the mempool") - else: - assert False, "Expected ValueError for non-existent mempool item" await client.push_tx(spend_bundle) coin = spend_bundle.additions()[0] @@ -177,12 +169,8 @@ async def test1( mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle.name()) assert mempool_item is not None assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle - try: + with pytest.raises(ValueError, match="not found"): await client.get_coin_record_by_name(coin.name()) - except ValueError as e: - assert str(e).endswith("not found") - else: - assert False, "Expected ValueError for non-existent coin record" # Verify that the include_pending arg to get_mempool_item_by_tx_id works coin_to_spend_pending = included_reward_coins[1] @@ -195,14 +183,9 @@ async def test1( condition_dic=condition_dic, ) await client.push_tx(spend_bundle_pending) - try: + with pytest.raises(ValueError, match="not in the mempool"): # not strictly in the mempool await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), False) - except ValueError as e: - assert str(e).endswith("not in the mempool") - else: - assert False, "Expected ValueError for non-existent mempool item" - # pending entry into mempool, so include_pending fetches mempool_item = await client.get_mempool_item_by_tx_id(spend_bundle_pending.name(), True) assert WalletSpendBundle.from_json_dict(mempool_item["spend_bundle"]) == spend_bundle_pending @@ -473,28 +456,14 @@ async def test_signage_points( full_node_service_1.config, ) as client: # Only provide one - try: + with pytest.raises(ValueError, match="sp_hash or challenge_hash must be provided."): await client.get_recent_signage_point_or_eos(None, None) - except ValueError as e: - assert str(e) == "sp_hash or challenge_hash must be provided." - else: - assert False, "Expected ValueError" - - try: + with pytest.raises(ValueError, match="Either sp_hash or challenge_hash must be provided, not both."): await client.get_recent_signage_point_or_eos(std_hash(b"0"), std_hash(b"1")) - except ValueError as e: - assert str(e) == "Either sp_hash or challenge_hash must be provided, not both." - else: - assert False, "Expected ValueError" # Not found - try: + with pytest.raises(ValueError, match="in cache"): await client.get_recent_signage_point_or_eos(std_hash(b"0"), None) await client.get_recent_signage_point_or_eos(None, std_hash(b"0")) - except ValueError as e: - assert str(e).endswith("in cache") - else: - assert False, "Expected ValueError" - blocks = bt.get_consecutive_blocks(5) for block in blocks: await full_node_api_1.full_node.add_block(block) @@ -524,12 +493,8 @@ async def test_signage_points( assert sp.rc_proof is not None assert sp.rc_vdf is not None # Don't have SP yet - try: + with pytest.raises(ValueError, match="Did not find sp"): await client.get_recent_signage_point_or_eos(sp.cc_vdf.output.get_hash(), None) - except ValueError as e: - assert str(e).startswith("Did not find sp") - else: - assert False, "Expected ValueError" # Add the last block await full_node_api_1.full_node.add_block(blocks[-1]) @@ -551,13 +516,8 @@ async def test_signage_points( selected_eos = blocks[-1].finished_sub_slots[0] # Don't have EOS yet - try: + with pytest.raises(ValueError, match="Did not find eos"): await client.get_recent_signage_point_or_eos(None, selected_eos.challenge_chain.get_hash()) - except ValueError as e: - assert str(e).startswith("Did not find eos") - else: - assert False, "Expected ValueError" - # Properly fetch an EOS for eos in blocks[-1].finished_sub_slots: await full_node_api_1.full_node.add_end_of_sub_slot(eos, peer) From d8701c185031230f05bdb49d266b956ece441e37 Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Wed, 16 Jul 2025 17:47:16 -0400 Subject: [PATCH 5/6] clean up error handling --- chia/full_node/full_node_rpc_client.py | 43 ++++++-------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/chia/full_node/full_node_rpc_client.py b/chia/full_node/full_node_rpc_client.py index 59f0b9c42ed0..ead3bdfa3c95 100644 --- a/chia/full_node/full_node_rpc_client.py +++ b/chia/full_node/full_node_rpc_client.py @@ -37,12 +37,7 @@ async def get_blockchain_state(self) -> dict[str, Any]: return cast(dict[str, Any], response["blockchain_state"]) async def get_block(self, header_hash: bytes32) -> FullBlock: - try: - response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) - except ResponseFailureError as e: - if "not found" in str(e): - raise ValueError(f"Block {header_hash.hex()} not found") from e - raise e + response = await self.fetch("get_block", {"header_hash": header_hash.hex()}) return FullBlock.from_json_dict(response["block"]) async def get_blocks(self, start: int, end: int, exclude_reorged: bool = False) -> list[FullBlock]: @@ -55,7 +50,7 @@ async def get_block_record_by_height(self, height: int) -> Optional[BlockRecord] try: response = await self.fetch("get_block_record_by_height", {"height": height}) except ResponseFailureError as e: # Block Height not found - if "not found" in str(e): + if e.response["error"] == f"Block height {height} not found in chain": return None raise e return BlockRecord.from_json_dict(response["block_record"]) @@ -86,12 +81,7 @@ async def get_network_space(self, newer_block_header_hash: bytes32, older_block_ return cast(int, network_space_bytes_estimate["space"]) async def get_coin_record_by_name(self, coin_id: bytes32) -> CoinRecord: - try: - response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) - except ResponseFailureError as e: # Coin ID not found - if "Coin record" in str(e): - raise ValueError(f"Coin record {coin_id.hex()} not found") from e - raise e + response = await self.fetch("get_coin_record_by_name", {"name": coin_id.hex()}) return CoinRecord.from_json_dict(coin_record_dict_backwards_compat(response["coin_record"])) async def get_coin_records_by_names( @@ -195,7 +185,7 @@ async def get_block_records(self, start: int, end: int) -> list[dict[str, Any]]: try: response = await self.fetch("get_block_records", {"start": start, "end": end}) except ResponseFailureError as e: # No Peak Yet - if "Peak is None" in str(e): + if e.response["error"] == "Peak is None": return [] raise e if response["block_records"] is None: @@ -248,14 +238,9 @@ async def get_mempool_item_by_tx_id( tx_id: bytes32, include_pending: bool = False, ) -> dict[str, Any]: - try: - response = await self.fetch( - "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} - ) - except ResponseFailureError as e: - if "not in the mempool" in str(e): - raise ValueError(f"Tx id {tx_id.hex()} not in the mempool") from e - raise e + response = await self.fetch( + "get_mempool_item_by_tx_id", {"tx_id": tx_id.hex(), "include_pending": include_pending} + ) return cast(dict[str, Any], response["mempool_item"]) async def get_mempool_items_by_coin_name(self, coin_name: bytes32) -> dict[str, Any]: @@ -272,24 +257,14 @@ async def get_recent_signage_point_or_eos( if sp_hash is not None and challenge_hash is not None: raise ValueError("Either sp_hash or challenge_hash must be provided, not both.") elif sp_hash is not None: - try: - response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) - except ResponseFailureError as e: - if "Did not find sp" in str(e): - raise ValueError(f"Did not find sp {sp_hash.hex()} in cache") from e - raise e + response = await self.fetch("get_recent_signage_point_or_eos", {"sp_hash": sp_hash.hex()}) return { "signage_point": SignagePoint.from_json_dict(response["signage_point"]), "time_received": response["time_received"], "reverted": response["reverted"], } elif challenge_hash is not None: - try: - response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) - except ResponseFailureError as e: - if "Did not find eos" in str(e): - raise ValueError(f"Did not find eos {challenge_hash.hex()} in cache") from e - raise e + response = await self.fetch("get_recent_signage_point_or_eos", {"challenge_hash": challenge_hash.hex()}) return { "eos": EndOfSubSlotBundle.from_json_dict(response["eos"]), "time_received": response["time_received"], From 4c0a9c07511ae21492d1ff402416d01e8cac9b37 Mon Sep 17 00:00:00 2001 From: Jack Nelson Date: Wed, 16 Jul 2025 23:50:21 -0400 Subject: [PATCH 6/6] seperate out pytest tests --- chia/_tests/core/test_full_node_rpc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chia/_tests/core/test_full_node_rpc.py b/chia/_tests/core/test_full_node_rpc.py index e517afe1ada0..4df0ef30b2ee 100644 --- a/chia/_tests/core/test_full_node_rpc.py +++ b/chia/_tests/core/test_full_node_rpc.py @@ -152,6 +152,7 @@ async def test1( assert len(await client.get_all_mempool_tx_ids()) == 0 with pytest.raises(ValueError, match="not in the mempool"): await client.get_mempool_item_by_tx_id(spend_bundle.name()) + with pytest.raises(ValueError, match="not in the mempool"): await client.get_mempool_item_by_tx_id(spend_bundle.name(), False) await client.push_tx(spend_bundle) @@ -463,6 +464,7 @@ async def test_signage_points( # Not found with pytest.raises(ValueError, match="in cache"): await client.get_recent_signage_point_or_eos(std_hash(b"0"), None) + with pytest.raises(ValueError, match="in cache"): await client.get_recent_signage_point_or_eos(None, std_hash(b"0")) blocks = bt.get_consecutive_blocks(5) for block in blocks: