make sure to pass in the correct previous transaction block height (#20237)

* make sure to pass in the correct previous transaction block height to get_flags_for_height_and_constants()

* review comments
This commit is contained in:
Arvid Norberg
2025-11-14 16:29:51 -06:00
committed by GitHub
parent 5cc6b5d409
commit 38d2d9f2d0
10 changed files with 73 additions and 25 deletions
@@ -59,7 +59,7 @@ def test_process_fast_forward_spends_nothing_to_do() -> None:
original_version = dataclasses.replace(internal_mempool_item)
singleton_ff = SingletonFastForward()
bundle_coin_spends = singleton_ff.process_fast_forward_spends(
mempool_item=internal_mempool_item, height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
mempool_item=internal_mempool_item, prev_tx_height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
)
assert singleton_ff == SingletonFastForward()
assert bundle_coin_spends == original_version.bundle_coin_spends
@@ -91,7 +91,7 @@ def test_process_fast_forward_spends_latest_unspent() -> None:
original_version = dataclasses.replace(internal_mempool_item)
singleton_ff = SingletonFastForward()
bundle_coin_spends = singleton_ff.process_fast_forward_spends(
mempool_item=internal_mempool_item, height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
mempool_item=internal_mempool_item, prev_tx_height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
)
child_coin = item.bundle_coin_spends[test_coin.name()].additions[0]
expected_fast_forward_spends = {
@@ -28,6 +28,8 @@ def get_name_puzzle_conditions(
height: uint32,
constants: ConsensusConstants,
) -> NPCResult:
# TODO: technically, this is supposed to be the previous transaction block
# height. But this is just for testing and it seems to be OK for now
flags = get_flags_for_height_and_constants(height, constants) | DONT_VALIDATE_SIGNATURE
if mempool_mode:
+2 -2
View File
@@ -83,11 +83,11 @@ async def ignore_block_validation(
return new_create
def run_block(
block: FullBlock, prev_generators: list[bytes], constants: ConsensusConstants
block: FullBlock, prev_generators: list[bytes], prev_tx_height: uint32, constants: ConsensusConstants
) -> tuple[Optional[int], Optional[SpendBundleConditions]]:
assert block.transactions_generator is not None
assert block.transactions_info is not None
flags = get_flags_for_height_and_constants(block.height, constants) | DONT_VALIDATE_SIGNATURE
flags = get_flags_for_height_and_constants(prev_tx_height, constants) | DONT_VALIDATE_SIGNATURE
if block.height >= constants.HARD_FORK_HEIGHT:
run_block = run_block_generator2
else:
+8 -1
View File
@@ -274,7 +274,14 @@ class Blockchain:
assert block_generator is not None
assert block.transactions_info is not None
assert block.foliage_transaction_block is not None
flags = get_flags_for_height_and_constants(block.height, self.constants)
prev_tx_height = pre_sp_tx_block_height(
constants=self.constants,
blocks=self,
prev_b_hash=block.prev_header_hash,
sp_index=block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(block.finished_sub_slots) > 0,
)
flags = get_flags_for_height_and_constants(prev_tx_height, self.constants)
additions, removals = additions_and_removals(
bytes(block.transactions_generator),
block_generator.generator_refs,
+10 -3
View File
@@ -60,11 +60,11 @@ class PreValidationResult(Streamable):
# this layer of abstraction is here to let wallet tests monkeypatch it
def _run_block(
block: FullBlock, prev_generators: list[bytes], constants: ConsensusConstants
block: FullBlock, prev_generators: list[bytes], prev_tx_height: uint32, constants: ConsensusConstants
) -> tuple[Optional[int], Optional[SpendBundleConditions]]:
assert block.transactions_generator is not None
assert block.transactions_info is not None
flags = get_flags_for_height_and_constants(block.height, constants)
flags = get_flags_for_height_and_constants(prev_tx_height, constants)
if block.height >= constants.HARD_FORK_HEIGHT:
run_block = run_block_generator2
else:
@@ -116,7 +116,14 @@ def _pre_validate_block(
uint16(Err.BLOCK_COST_EXCEEDS_MAX.value), None, None, uint32(validation_time * 1000)
)
err, conds = _run_block(block, prev_generators, constants)
prev_tx_height = pre_sp_tx_block_height(
constants=constants,
blocks=blockchain,
prev_b_hash=block.prev_header_hash,
sp_index=block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(block.finished_sub_slots) > 0,
)
err, conds = _run_block(block, prev_generators, prev_tx_height, constants)
assert (err is None) != (conds is None)
if err is not None:
+3 -3
View File
@@ -172,7 +172,7 @@ class SingletonFastForward:
fast_forward_spends: dict[bytes32, UnspentLineageInfo] = dataclasses.field(default_factory=dict)
def process_fast_forward_spends(
self, *, mempool_item: InternalMempoolItem, height: uint32, constants: ConsensusConstants
self, *, mempool_item: InternalMempoolItem, prev_tx_height: uint32, constants: ConsensusConstants
) -> dict[bytes32, BundleCoinSpend]:
"""
Provides the caller with a `bundle_coin_spends` map that has a proper
@@ -182,7 +182,7 @@ class SingletonFastForward:
Args:
mempool_item: The internal mempool item to process
constants: needed in order to refresh the mempool item if needed
height: needed in order to refresh the mempool item if needed
prev_tx_height: needed in order to refresh the mempool item if needed
Returns:
The resulting `bundle_coin_spends` map of coin IDs to coin spends
@@ -273,7 +273,7 @@ class SingletonFastForward:
try:
# Run the new spend bundle to make sure it remains valid. What we
# care about here is whether this call throws or not.
get_conditions_from_spendbundle(new_sb, mempool_item.conds.cost, constants, height)
get_conditions_from_spendbundle(new_sb, mempool_item.conds.cost, constants, prev_tx_height)
# get_conditions_from_spendbundle raises a ValueError with an error code
except ValueError as e:
# Convert that to a ValidationError
+9 -1
View File
@@ -39,6 +39,7 @@ from chiabip158 import PyBIP158
from chia.consensus.block_creation import create_unfinished_block
from chia.consensus.blockchain import BlockchainMutexPriority
from chia.consensus.generator_tools import get_block_header
from chia.consensus.get_block_challenge import pre_sp_tx_block_height
from chia.consensus.get_block_generator import get_block_generator
from chia.consensus.pot_iterations import calculate_ip_iters, calculate_iterations_quality, calculate_sp_iters
from chia.consensus.signage_point import SignagePoint
@@ -1274,7 +1275,14 @@ class FullNodeAPI:
# transactions_generator, so the block_generator should always be set
assert block_generator is not None, "failed to get block_generator for tx-block"
flags = get_flags_for_height_and_constants(request.height, self.full_node.constants)
prev_tx_height = pre_sp_tx_block_height(
constants=self.full_node.constants,
blocks=self.full_node.blockchain,
prev_b_hash=block.prev_header_hash,
sp_index=block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(block.finished_sub_slots) > 0,
)
flags = get_flags_for_height_and_constants(prev_tx_height, self.full_node.constants)
additions, removals = await asyncio.get_running_loop().run_in_executor(
self.executor,
additions_and_removals,
+25 -3
View File
@@ -24,6 +24,7 @@ from chia_rs.sized_bytes import bytes32
from chia_rs.sized_ints import uint32, uint64, uint128
from chia.consensus.blockchain import Blockchain, BlockchainMutexPriority
from chia.consensus.get_block_challenge import pre_sp_tx_block_height
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
@@ -489,7 +490,14 @@ class FullNodeRpcApi:
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)
prev_tx_height = pre_sp_tx_block_height(
constants=self.service.constants,
blocks=self.service.blockchain,
prev_b_hash=full_block.prev_header_hash,
sp_index=full_block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(full_block.finished_sub_slots) > 0,
)
flags = get_flags_for_height_and_constants(prev_tx_height, self.service.constants)
spends = await asyncio.get_running_loop().run_in_executor(
self.executor,
get_spends_for_trusted_block,
@@ -513,7 +521,14 @@ class FullNodeRpcApi:
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)
prev_tx_height = pre_sp_tx_block_height(
constants=self.service.constants,
blocks=self.service.blockchain,
prev_b_hash=full_block.prev_header_hash,
sp_index=full_block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(full_block.finished_sub_slots) > 0,
)
flags = get_flags_for_height_and_constants(prev_tx_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,12 +807,19 @@ class FullNodeRpcApi:
assert block_generator is not None
try:
prev_tx_height = pre_sp_tx_block_height(
constants=self.service.constants,
blocks=self.service.blockchain,
prev_b_hash=block.prev_header_hash,
sp_index=block.reward_chain_block.signage_point_index,
first_in_sub_slot=len(block.finished_sub_slots) > 0,
)
puzzle, solution = get_puzzle_and_solution_for_coin(
block_generator.program,
block_generator.generator_refs,
self.service.constants.MAX_BLOCK_COST_CLVM,
coin_record.coin,
get_flags_for_height_and_constants(block.height, self.service.constants),
get_flags_for_height_and_constants(prev_tx_height, self.service.constants),
)
return {"coin_solution": CoinSpend(coin_record.coin, puzzle, solution)}
except Exception as e:
+10 -10
View File
@@ -505,15 +505,15 @@ class Mempool:
def create_block_generator(
self,
constants: ConsensusConstants,
height: uint32,
prev_tx_height: uint32,
timeout: float,
) -> Optional[NewBlockGenerator]:
"""
height is needed in case we fast-forward a transaction and we need to
re-run its puzzle.
prev_tx_height is needed in case we fast-forward a transaction and we
need to re-run its puzzle.
"""
mempool_bundle = self.create_bundle_from_mempool_items(constants, height, timeout)
mempool_bundle = self.create_bundle_from_mempool_items(constants, prev_tx_height, timeout)
if mempool_bundle is None:
return None
@@ -535,7 +535,7 @@ class Mempool:
f"spends: {len(removals)} additions: {len(additions)}",
)
flags = get_flags_for_height_and_constants(height, constants) | MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE
flags = get_flags_for_height_and_constants(prev_tx_height, constants) | MEMPOOL_MODE | DONT_VALIDATE_SIGNATURE
err, conds = run_block_generator2(
block_program,
@@ -551,7 +551,7 @@ class Mempool:
if err is not None: # pragma: no cover
log.error(
f"Failed to compute block cost during farming: {err} "
f"height: {height} "
f"prev-tx-height: {prev_tx_height} "
f"generator: {bytes(block_program).hex()}"
)
return None
@@ -570,7 +570,7 @@ class Mempool:
)
def create_bundle_from_mempool_items(
self, constants: ConsensusConstants, height: uint32, timeout: float = 1.0
self, constants: ConsensusConstants, prev_tx_height: uint32, timeout: float = 1.0
) -> Optional[tuple[SpendBundle, list[Coin]]]:
cost_sum = 0 # Checks that total cost does not exceed block maximum
fee_sum = 0 # Checks that total fees don't exceed 64 bits
@@ -624,7 +624,7 @@ class Mempool:
cost_saving = 0
else:
bundle_coin_spends = singleton_ff.process_fast_forward_spends(
mempool_item=item, height=height, constants=constants
mempool_item=item, prev_tx_height=prev_tx_height, constants=constants
)
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=bundle_coin_spends
@@ -687,7 +687,7 @@ class Mempool:
return agg, additions
def create_block_generator2(
self, constants: ConsensusConstants, height: uint32, timeout: float
self, constants: ConsensusConstants, prev_tx_height: uint32, timeout: float
) -> Optional[NewBlockGenerator]:
fee_sum = 0 # Checks that total fees don't exceed 64 bits
additions: list[Coin] = []
@@ -723,7 +723,7 @@ class Mempool:
assert item.conds is not None
cost = item.conds.condition_cost + item.conds.execution_cost
bundle_coin_spends = singleton_ff.process_fast_forward_spends(
mempool_item=item, height=height, constants=constants
mempool_item=item, prev_tx_height=prev_tx_height, constants=constants
)
unique_coin_spends, cost_saving, unique_additions = dedup_coin_spends.get_deduplication_info(
bundle_coin_spends=bundle_coin_spends
+2
View File
@@ -307,6 +307,8 @@ class MempoolManager:
# cache of MempoolItems with height conditions making them not valid yet
_pending_cache: PendingTxCache
seen_cache_size: int
# the peak is only ever set to a transaction block. We use it to validate
# timelocks against, so it must have a timestamp.
peak: Optional[BlockRecordProtocol]
mempool: Mempool
_worker_queue_size: int