full_node: Refactor `get_puzzle_and_solution_for_coin` (#15323)

This commit is contained in:
dustinface 2023-05-18 00:34:33 +02:00 committed by GitHub
parent eb7376a8c1
commit 9e9c75912f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 47 additions and 58 deletions

View File

@ -412,19 +412,14 @@ class SimClient:
removals: List[CoinRecord] = await self.service.coin_store.get_coins_removed_at_height(block_height)
return additions, removals
async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> Optional[CoinSpend]:
async def get_puzzle_and_solution(self, coin_id: bytes32, height: uint32) -> CoinSpend:
filtered_generators = list(filter(lambda block: block.height == height, self.service.blocks))
# real consideration should be made for the None cases instead of just hint ignoring
generator: BlockGenerator = filtered_generators[0].transactions_generator # type: ignore[assignment]
coin_record = await self.service.coin_store.get_coin_record(coin_id)
assert coin_record is not None
error, puzzle, solution = get_puzzle_and_solution_for_coin(generator, coin_record.coin)
if error:
return None
else:
assert puzzle is not None
assert solution is not None
return CoinSpend(coin_record.coin, puzzle, solution)
spend_info = get_puzzle_and_solution_for_coin(generator, coin_record.coin)
return CoinSpend(coin_record.coin, spend_info.puzzle, spend_info.solution)
async def get_all_mempool_tx_ids(self) -> List[bytes32]:
return self.service.mempool_manager.mempool.all_item_ids()

View File

@ -1302,17 +1302,13 @@ class FullNodeAPI:
block_generator: Optional[BlockGenerator] = await self.full_node.blockchain.get_block_generator(block)
assert block_generator is not None
error, puzzle, solution = await asyncio.get_running_loop().run_in_executor(
self.executor, get_puzzle_and_solution_for_coin, block_generator, coin_record.coin
)
if error is not None:
try:
spend_info = await asyncio.get_running_loop().run_in_executor(
self.executor, get_puzzle_and_solution_for_coin, block_generator, coin_record.coin
)
except ValueError:
return reject_msg
assert puzzle is not None
assert solution is not None
wrapper = PuzzleSolutionResponse(coin_name, height, puzzle, solution)
wrapper = PuzzleSolutionResponse(coin_name, height, spend_info.puzzle, spend_info.solution)
response = wallet_protocol.RespondPuzzleSolution(wrapper)
response_msg = make_msg(ProtocolMessageTypes.respond_puzzle_solution, response)
return response_msg

View File

@ -1,7 +1,7 @@
from __future__ import annotations
import logging
from typing import Dict, List, Optional, Tuple
from typing import Dict, List, Optional
from chia_rs import ENABLE_ASSERT_BEFORE, LIMIT_STACK, MEMPOOL_MODE, NO_RELATIVE_CONDITIONS_ON_EPHEMERAL
from chia_rs import get_puzzle_and_solution_for_coin as get_puzzle_and_solution_for_coin_rust
@ -16,7 +16,7 @@ from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.serialized_program import SerializedProgram
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_record import CoinRecord
from chia.types.coin_spend import CoinSpend
from chia.types.coin_spend import CoinSpend, SpendInfo
from chia.types.generator_types import BlockGenerator
from chia.types.spend_bundle_conditions import SpendBundleConditions
from chia.util.errors import Err
@ -62,9 +62,7 @@ def get_name_puzzle_conditions(
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), None, uint64(0))
def get_puzzle_and_solution_for_coin(
generator: BlockGenerator, coin: Coin
) -> Tuple[Optional[Exception], Optional[SerializedProgram], Optional[SerializedProgram]]:
def get_puzzle_and_solution_for_coin(generator: BlockGenerator, coin: Coin) -> SpendInfo:
try:
args = bytearray(b"\xff")
args += bytes(DESERIALIZE_MOD)
@ -80,10 +78,9 @@ def get_puzzle_and_solution_for_coin(
coin.amount,
coin.puzzle_hash,
)
return None, SerializedProgram.from_bytes(puzzle), SerializedProgram.from_bytes(solution)
return SpendInfo(SerializedProgram.from_bytes(puzzle), SerializedProgram.from_bytes(solution))
except Exception as e:
return e, None, None
raise ValueError(f"Failed to get puzzle and solution for coin {coin}, error: {e}") from e
def get_spends_for_block(generator: BlockGenerator) -> List[CoinSpend]:

View File

@ -694,14 +694,8 @@ class FullNodeRpcApi:
block_generator: Optional[BlockGenerator] = await self.service.blockchain.get_block_generator(block)
assert block_generator is not None
error, puzzle, solution = get_puzzle_and_solution_for_coin(block_generator, coin_record.coin)
if error is not None:
raise ValueError(f"Error: {error}")
assert puzzle is not None
assert solution is not None
return {"coin_solution": CoinSpend(coin_record.coin, puzzle, solution)}
spend_info = get_puzzle_and_solution_for_coin(block_generator, coin_record.coin)
return {"coin_solution": CoinSpend(coin_record.coin, spend_info.puzzle, spend_info.solution)}
async def get_additions_and_removals(self, request: Dict[str, Any]) -> EndpointResult:
if "header_hash" not in request:

View File

@ -65,3 +65,10 @@ def compute_additions_with_cost(
def compute_additions(cs: CoinSpend, *, max_cost: int = DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM) -> List[Coin]:
return compute_additions_with_cost(cs, max_cost=max_cost)[0]
@streamable
@dataclass(frozen=True)
class SpendInfo(Streamable):
puzzle: SerializedProgram
solution: SerializedProgram

View File

@ -128,8 +128,8 @@ class TestSimClient:
assert removals
# get_puzzle_and_solution
coin_solution = await sim_client.get_puzzle_and_solution(spendable_coin.name(), latest_block.height)
assert coin_solution
coin_spend = await sim_client.get_puzzle_and_solution(spendable_coin.name(), latest_block.height)
assert coin_spend == bundle.coin_spends[0]
# get_coin_records_by_parent_ids
new_coin = next(x.coin for x in additions if x.coin.puzzle_hash == puzzle_hash)

View File

@ -16,7 +16,7 @@ from chia.full_node.bitcoin_fee_estimator import create_bitcoin_fee_estimator
from chia.full_node.fee_estimation import EmptyMempoolInfo, MempoolInfo
from chia.full_node.full_node_api import FullNodeAPI
from chia.full_node.mempool import Mempool
from chia.full_node.mempool_check_conditions import get_name_puzzle_conditions
from chia.full_node.mempool_check_conditions import get_name_puzzle_conditions, get_puzzle_and_solution_for_coin
from chia.full_node.mempool_manager import MEMPOOL_MIN_FEE_INCREASE
from chia.full_node.pending_tx_cache import ConflictTxCache, PendingTxCache
from chia.protocols import full_node_protocol, wallet_protocol
@ -52,6 +52,7 @@ from tests.blockchain.blockchain_test_utils import _validate_and_add_block
from tests.connection_utils import add_dummy_connection, connect_and_get_peer
from tests.core.mempool.test_mempool_manager import (
IDENTITY_PUZZLE_HASH,
TEST_COIN,
make_test_coins,
mempool_item_from_spendbundle,
mk_item,
@ -2945,3 +2946,10 @@ def test_aggregating_on_a_solution_then_a_more_cost_saving_one_appears() -> None
# We ran with solution A and missed bigger savings on solution B
assert mempool.size() == 5
assert [c.coin for c in agg.coin_spends] == [coins[0], coins[1], coins[2]]
def test_get_puzzle_and_solution_for_coin_failure():
with pytest.raises(
ValueError, match=f"Failed to get puzzle and solution for coin {TEST_COIN}, error: failed to fill whole buffer"
):
get_puzzle_and_solution_for_coin(BlockGenerator(SerializedProgram(), [], []), TEST_COIN)

View File

@ -85,10 +85,9 @@ class TestCostCalculation:
coin_spend = spend_bundle.coin_spends[0]
assert coin_spend.coin.name() == npc_result.conds.spends[0].coin_id
error, puzzle, solution = get_puzzle_and_solution_for_coin(program, coin_spend.coin)
assert error is None
assert puzzle == coin_spend.puzzle_reveal
assert solution == coin_spend.solution
spend_info = get_puzzle_and_solution_for_coin(program, coin_spend.coin)
assert spend_info.puzzle == coin_spend.puzzle_reveal
assert spend_info.solution == coin_spend.solution
clvm_cost = 404560
byte_cost = len(bytes(program.program)) * test_constants.COST_PER_BYTE
@ -156,8 +155,8 @@ class TestCostCalculation:
bytes32.fromhex("14947eb0e69ee8fc8279190fc2d38cb4bbb61ba28f1a270cfd643a0e8d759576"),
300,
)
error, puzzle, solution = get_puzzle_and_solution_for_coin(generator, coin)
assert error is None
spend_info = get_puzzle_and_solution_for_coin(generator, coin)
assert spend_info.puzzle.to_program() == puzzle
@pytest.mark.asyncio
async def test_clvm_mempool_mode(self, softfork_height):
@ -274,6 +273,5 @@ async def test_get_puzzle_and_solution_for_coin_performance():
with assert_runtime(seconds=7, label="get_puzzle_and_solution_for_coin"):
for i in range(3):
for c in spends:
err, puzzle, solution = get_puzzle_and_solution_for_coin(generator, c)
assert err is None
assert puzzle.get_tree_hash() == c.puzzle_hash
spend_info = get_puzzle_and_solution_for_coin(generator, c)
assert spend_info.puzzle.get_tree_hash() == c.puzzle_hash

View File

@ -158,20 +158,14 @@ class TestCompression:
ca = CompressorArg(uint32(0), SerializedProgram.from_bytes(original_generator), start, end)
c = compressed_spend_bundle_solution(ca, sb)
removal = sb.coin_spends[0].coin
error, puzzle, solution = get_puzzle_and_solution_for_coin(c, removal)
assert error is None
assert puzzle is not None
assert solution is not None
assert bytes(puzzle) == bytes(sb.coin_spends[0].puzzle_reveal)
assert bytes(solution) == bytes(sb.coin_spends[0].solution)
spend_info = get_puzzle_and_solution_for_coin(c, removal)
assert bytes(spend_info.puzzle) == bytes(sb.coin_spends[0].puzzle_reveal)
assert bytes(spend_info.solution) == bytes(sb.coin_spends[0].solution)
# Test non compressed generator as well
s = simple_solution_generator(sb)
error, puzzle, solution = get_puzzle_and_solution_for_coin(s, removal)
assert error is None
assert puzzle is not None
assert solution is not None
assert bytes(puzzle) == bytes(sb.coin_spends[0].puzzle_reveal)
assert bytes(solution) == bytes(sb.coin_spends[0].solution)
spend_info = get_puzzle_and_solution_for_coin(s, removal)
assert bytes(spend_info.puzzle) == bytes(sb.coin_spends[0].puzzle_reveal)
assert bytes(spend_info.solution) == bytes(sb.coin_spends[0].solution)
def test_spend_byndle_coin_spend(self) -> None:
for i in range(0, 10):