mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
return RejectAdditionsRequest to wallets instead of raising an exception (#20555)
* return RejectAdditionsRequest to wallets instead of raising an exception * review comments
This commit is contained in:
@@ -40,14 +40,17 @@ from chia.consensus.augmented_chain import AugmentedBlockchain
|
||||
from chia.consensus.block_body_validation import ForkInfo
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward
|
||||
from chia.consensus.difficulty_adjustment import get_next_sub_slot_iters_and_difficulty
|
||||
from chia.full_node.full_node_api import FullNodeAPI
|
||||
from chia.full_node.full_node_api import MAX_COIN_HASHES_PER_REQUEST, FullNodeAPI
|
||||
from chia.full_node.weight_proof import WeightProofHandler
|
||||
from chia.protocols import full_node_protocol, wallet_protocol
|
||||
from chia.protocols.outbound_message import Message, make_msg
|
||||
from chia.protocols.protocol_message_types import ProtocolMessageTypes
|
||||
from chia.protocols.shared_protocol import Capability
|
||||
from chia.protocols.wallet_protocol import (
|
||||
RejectAdditionsRequest,
|
||||
RejectRemovalsRequest,
|
||||
RequestAdditions,
|
||||
RequestRemovals,
|
||||
RespondAdditions,
|
||||
RespondBlockHeader,
|
||||
RespondBlockHeaders,
|
||||
@@ -635,16 +638,44 @@ async def test_request_additions_errors(simulator_and_wallet: OldSimulatorsAndWa
|
||||
last_block: BlockRecord | None = full_node_api.full_node.blockchain.get_peak()
|
||||
assert last_block is not None
|
||||
|
||||
# Invalid height
|
||||
with pytest.raises(ValueError):
|
||||
await full_node_api.request_additions(RequestAdditions(uint32(100), last_block.header_hash, [ph]))
|
||||
# Invalid height (with header_hash specified)
|
||||
res = await full_node_api.request_additions(RequestAdditions(uint32(100), last_block.header_hash, [ph]))
|
||||
assert res is not None
|
||||
reject = RejectAdditionsRequest.from_bytes(res.data)
|
||||
assert reject.height == 100
|
||||
assert reject.header_hash == last_block.header_hash
|
||||
|
||||
# Invalid height (no header_hash, returns zeros)
|
||||
res = await full_node_api.request_additions(RequestAdditions(uint32(100), None, [ph]))
|
||||
assert res is not None
|
||||
reject = RejectAdditionsRequest.from_bytes(res.data)
|
||||
assert reject.height == 100
|
||||
assert reject.header_hash == bytes32.zeros
|
||||
|
||||
# Invalid header hash
|
||||
with pytest.raises(ValueError):
|
||||
await full_node_api.request_additions(RequestAdditions(last_block.height, std_hash(b""), [ph]))
|
||||
res = await full_node_api.request_additions(RequestAdditions(last_block.height, std_hash(b""), [ph]))
|
||||
assert res is not None
|
||||
reject = RejectAdditionsRequest.from_bytes(res.data)
|
||||
assert reject.height == last_block.height
|
||||
assert reject.header_hash == std_hash(b"")
|
||||
|
||||
# Too many puzzle hashes
|
||||
too_many = [bytes32.random() for _ in range(MAX_COIN_HASHES_PER_REQUEST + 1)]
|
||||
res = await full_node_api.request_additions(RequestAdditions(last_block.height, last_block.header_hash, too_many))
|
||||
assert res is not None
|
||||
reject = RejectAdditionsRequest.from_bytes(res.data)
|
||||
assert reject.height == last_block.height
|
||||
assert reject.header_hash == last_block.header_hash
|
||||
|
||||
# Exactly at the limit is allowed
|
||||
at_limit = [bytes32.random() for _i in range(MAX_COIN_HASHES_PER_REQUEST)]
|
||||
res = await full_node_api.request_additions(RequestAdditions(last_block.height, last_block.header_hash, at_limit))
|
||||
assert res is not None
|
||||
response = RespondAdditions.from_bytes(res.data)
|
||||
assert response.height == last_block.height
|
||||
|
||||
# No results
|
||||
fake_coin = std_hash(b"")
|
||||
fake_coin = bytes32.random()
|
||||
assert ph != fake_coin
|
||||
res1 = await full_node_api.request_additions(
|
||||
RequestAdditions(last_block.height, last_block.header_hash, [fake_coin])
|
||||
@@ -667,7 +698,7 @@ async def test_request_additions_errors(simulator_and_wallet: OldSimulatorsAndWa
|
||||
# all coin names are concatenated and hashed into one entry in the merkle set for proof_2
|
||||
# the response contains the list of coins so you can check the proof_2
|
||||
|
||||
assert response.proofs[0][0] == std_hash(b"")
|
||||
assert response.proofs[0][0] == fake_coin
|
||||
assert response.proofs[0][1] is not None
|
||||
assert response.proofs[0][2] is None
|
||||
|
||||
@@ -763,6 +794,35 @@ async def test_request_additions_success(simulator_and_wallet: OldSimulatorsAndW
|
||||
assert len(response.coins) == 0
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_request_removals_too_many_coin_names(
|
||||
simulator_and_wallet: OldSimulatorsAndWallets, self_hostname: str
|
||||
) -> None:
|
||||
full_nodes, wallets, _ = simulator_and_wallet
|
||||
wallet_node, wallet_server = wallets[0]
|
||||
wallet = wallet_node.wallet_state_manager.main_wallet
|
||||
async with wallet.wallet_state_manager.new_action_scope(DEFAULT_TX_CONFIG, push=True) as action_scope:
|
||||
ph = await action_scope.get_puzzle_hash(wallet.wallet_state_manager)
|
||||
|
||||
full_node_api = full_nodes[0]
|
||||
await wallet_server.start_client(PeerInfo(self_hostname, full_node_api.full_node.server.get_port()), None)
|
||||
|
||||
for _ in range(2):
|
||||
await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph))
|
||||
|
||||
await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node, timeout=20)
|
||||
|
||||
last_block = full_node_api.full_node.blockchain.get_peak()
|
||||
assert last_block is not None
|
||||
|
||||
too_many = [bytes32.random() for _ in range(MAX_COIN_HASHES_PER_REQUEST + 1)]
|
||||
res = await full_node_api.request_removals(RequestRemovals(last_block.height, last_block.header_hash, too_many))
|
||||
assert res is not None
|
||||
reject = RejectRemovalsRequest.from_bytes(res.data)
|
||||
assert reject.height == last_block.height
|
||||
assert reject.header_hash == last_block.header_hash
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_get_wp_fork_point(
|
||||
default_10000_blocks: list[FullBlock], blockchain_constants: ConsensusConstants
|
||||
|
||||
@@ -83,6 +83,9 @@ if TYPE_CHECKING:
|
||||
else:
|
||||
FullNode = object
|
||||
|
||||
MAX_COIN_HASHES_PER_REQUEST = 50
|
||||
MAX_COINS_MAP_SIZE = 100
|
||||
|
||||
|
||||
async def tx_request_and_timeout(full_node: FullNode, transaction_id: bytes32, task_id: bytes32) -> None:
|
||||
"""
|
||||
@@ -1303,18 +1306,31 @@ class FullNodeAPI:
|
||||
|
||||
@metadata.request()
|
||||
async def request_additions(self, request: wallet_protocol.RequestAdditions) -> Message | None:
|
||||
if request.puzzle_hashes is not None and len(request.puzzle_hashes) > MAX_COIN_HASHES_PER_REQUEST:
|
||||
reject = wallet_protocol.RejectAdditionsRequest(
|
||||
request.height, request.header_hash if request.header_hash is not None else bytes32.zeros
|
||||
)
|
||||
return make_msg(ProtocolMessageTypes.reject_additions_request, reject)
|
||||
|
||||
if request.header_hash is None:
|
||||
header_hash: bytes32 | None = self.full_node.blockchain.height_to_hash(request.height)
|
||||
else:
|
||||
header_hash = request.header_hash
|
||||
if header_hash is None:
|
||||
raise ValueError(f"Block at height {request.height} not found")
|
||||
|
||||
# Note: this might return bad data if there is a reorg in this time
|
||||
additions = await self.full_node.coin_store.get_coins_added_at_height(request.height)
|
||||
reject = wallet_protocol.RejectAdditionsRequest(request.height, bytes32.zeros)
|
||||
return make_msg(ProtocolMessageTypes.reject_additions_request, reject)
|
||||
|
||||
if self.full_node.blockchain.height_to_hash(request.height) != header_hash:
|
||||
raise ValueError(f"Block {header_hash} no longer in chain, or invalid header_hash")
|
||||
reject = wallet_protocol.RejectAdditionsRequest(request.height, header_hash)
|
||||
return make_msg(ProtocolMessageTypes.reject_additions_request, reject)
|
||||
|
||||
additions = await self.full_node.coin_store.get_coins_added_at_height(request.height)
|
||||
|
||||
# Note: this might return bad data if there is a reorg while waiting for
|
||||
# the DB. So check the height-to-hash again
|
||||
if self.full_node.blockchain.height_to_hash(request.height) != header_hash:
|
||||
reject = wallet_protocol.RejectAdditionsRequest(request.height, header_hash)
|
||||
return make_msg(ProtocolMessageTypes.reject_additions_request, reject)
|
||||
|
||||
puzzlehash_coins_map: dict[bytes32, list[Coin]] = {}
|
||||
for coin_record in additions:
|
||||
@@ -1327,6 +1343,9 @@ class FullNodeAPI:
|
||||
proofs_map: list[tuple[bytes32, bytes, bytes | None]] = []
|
||||
|
||||
if request.puzzle_hashes is None:
|
||||
if len(puzzlehash_coins_map) > MAX_COINS_MAP_SIZE:
|
||||
reject = wallet_protocol.RejectAdditionsRequest(request.height, header_hash)
|
||||
return make_msg(ProtocolMessageTypes.reject_additions_request, reject)
|
||||
for puzzle_hash, coins in puzzlehash_coins_map.items():
|
||||
coins_map.append((puzzle_hash, coins))
|
||||
response = wallet_protocol.RespondAdditions(request.height, header_hash, coins_map, None)
|
||||
@@ -1360,6 +1379,10 @@ class FullNodeAPI:
|
||||
|
||||
@metadata.request()
|
||||
async def request_removals(self, request: wallet_protocol.RequestRemovals) -> Message | None:
|
||||
if request.coin_names is not None and len(request.coin_names) > MAX_COIN_HASHES_PER_REQUEST:
|
||||
reject = wallet_protocol.RejectRemovalsRequest(request.height, request.header_hash)
|
||||
return make_msg(ProtocolMessageTypes.reject_removals_request, reject)
|
||||
|
||||
block: FullBlock | None = await self.full_node.block_store.get_full_block(request.header_hash)
|
||||
|
||||
# We lock so that the coin store does not get modified
|
||||
@@ -1377,11 +1400,14 @@ class FullNodeAPI:
|
||||
|
||||
assert block is not None and block.foliage_transaction_block is not None
|
||||
|
||||
# Note: this might return bad data if there is a reorg in this time
|
||||
all_removals: list[CoinRecord] = await self.full_node.coin_store.get_coins_removed_at_height(block.height)
|
||||
|
||||
# Note: this might return bad data if there is a reorg while waiting for
|
||||
# the DB. So check the height-to-hash again
|
||||
if self.full_node.blockchain.height_to_hash(block.height) != request.header_hash:
|
||||
raise ValueError(f"Block {block.header_hash} no longer in chain")
|
||||
reject = wallet_protocol.RejectRemovalsRequest(request.height, request.header_hash)
|
||||
msg = make_msg(ProtocolMessageTypes.reject_removals_request, reject)
|
||||
return msg
|
||||
|
||||
all_removals_dict: dict[bytes32, Coin] = {}
|
||||
for coin_record in all_removals:
|
||||
|
||||
Reference in New Issue
Block a user