From 9147629b7817171057a9f4a12060012dcff2d95c Mon Sep 17 00:00:00 2001 From: Arvid Norberg Date: Wed, 19 Mar 2025 19:37:16 +0100 Subject: [PATCH 1/7] NewBlockGenerator as an extension of BlockGenerator (#19390) --- chia/_tests/core/mempool/test_mempool.py | 23 ++- .../core/mempool/test_mempool_manager.py | 166 +++++++++++++++++ chia/consensus/block_creation.py | 67 +++---- chia/full_node/full_node_api.py | 24 +-- chia/full_node/mempool.py | 10 +- chia/full_node/mempool_manager.py | 5 +- chia/rpc/full_node_rpc_api.py | 32 ++-- chia/simulator/block_tools.py | 167 +++++++++--------- chia/types/generator_types.py | 26 +++ 9 files changed, 352 insertions(+), 168 deletions(-) diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 79a154233e..817548c0a9 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -2957,9 +2957,8 @@ async def test_skip_error_items() -> None: called += 1 raise RuntimeError("failed to find fast forward coin") - result = await mempool.create_block_generator(local_get_unspent_lineage_info, DEFAULT_CONSTANTS, uint32(10)) - assert result is not None - generator, _, _, _ = result + generator = await mempool.create_block_generator(local_get_unspent_lineage_info, DEFAULT_CONSTANTS, uint32(10)) + assert generator is not None assert called == 3 assert generator.program == SerializedProgram.from_bytes(bytes.fromhex("ff01ff8080")) @@ -3247,21 +3246,21 @@ async def test_create_block_generator() -> None: mempool.add_to_pool(mi) invariant_check_mempool(mempool) - block = await mempool.create_block_generator(get_unspent_lineage_info_for_puzzle_hash, test_constants, uint32(0)) - assert block is not None - generator, signature, additions, _ = block + generator = await mempool.create_block_generator( + get_unspent_lineage_info_for_puzzle_hash, test_constants, uint32(0) + ) + assert generator is not None - assert set(additions) == expected_additions - - assert len(additions) == len(expected_additions) - assert signature == expected_signature + assert set(generator.additions) == expected_additions + assert len(generator.additions) == len(expected_additions) + assert generator.signature == expected_signature err, conds = run_block_generator2( bytes(generator.program), generator.generator_refs, test_constants.MAX_BLOCK_COST_CLVM, 0, - signature, + generator.signature, None, test_constants, ) @@ -3278,7 +3277,7 @@ async def test_create_block_generator() -> None: assert Coin(spend.coin_id, add2[0], uint64(add2[1])) in expected_additions num_additions += 1 - assert num_additions == len(additions) + assert num_additions == len(generator.additions) invariant_check_mempool(mempool) diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index f982cd7d11..94d8602abf 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -2,6 +2,7 @@ from __future__ import annotations import dataclasses import logging +import random from collections.abc import Awaitable, Collection, Sequence from typing import Any, Callable, ClassVar, Optional, Union @@ -9,14 +10,17 @@ import pytest from chia_rs import ( ELIGIBLE_FOR_DEDUP, ELIGIBLE_FOR_FF, + MEMPOOL_MODE, AugSchemeMPL, ConsensusConstants, G2Element, get_conditions_from_spendbundle, + run_block_generator2, ) from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint8, uint32, uint64 from chiabip158 import PyBIP158 +from clvm.casts import int_to_bytes from chia._tests.conftest import ConsensusMode from chia._tests.util.misc import invariant_check_mempool @@ -41,6 +45,7 @@ from chia.protocols.full_node_protocol import RequestBlock, RespondBlock from chia.protocols.protocol_message_types import ProtocolMessageTypes from chia.simulator.full_node_simulator import FullNodeSimulator from chia.simulator.simulator_protocol import FarmNewBlockProtocol +from chia.simulator.wallet_tools import WalletTool from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import INFINITE_COST, Program from chia.types.blockchain_format.serialized_program import SerializedProgram @@ -48,6 +53,7 @@ from chia.types.clvm_cost import CLVMCost from chia.types.coin_record import CoinRecord from chia.types.coin_spend import CoinSpend, make_spend from chia.types.condition_opcodes import ConditionOpcode +from chia.types.condition_with_args import ConditionWithArgs from chia.types.eligible_coin_spends import ( DedupCoinSpend, EligibilityAndAdditions, @@ -63,6 +69,10 @@ from chia.types.spend_bundle import SpendBundle from chia.types.spend_bundle_conditions import SpendBundleConditions, SpendConditions from chia.util.errors import Err, ValidationError from chia.wallet.conditions import AssertCoinAnnouncement +from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( + DEFAULT_HIDDEN_PUZZLE_HASH, + calculate_synthetic_secret_key, +) from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG from chia.wallet.wallet import Wallet from chia.wallet.wallet_coin_record import WalletCoinRecord @@ -2501,3 +2511,159 @@ async def test_advancing_ff(use_optimization: bool) -> None: spend = item.bundle_coin_spends[spend_a.coin.name()] assert spend.eligible_for_fast_forward assert spend.latest_singleton_coin == spend_c.coin.name() + + +@pytest.fixture(name="test_wallet", autouse=True) +def test_wallet_fixture() -> WalletTool: + return WalletTool(DEFAULT_CONSTANTS) + + +@pytest.fixture(name="transactions_1000", autouse=True) +def transactions_1000_fixture(test_wallet: WalletTool, seeded_random: random.Random) -> list[SpendBundle]: + op = ConditionOpcode + bundles: list[SpendBundle] = [] + + test_conditions = [ + op.AGG_SIG_PARENT, + op.AGG_SIG_PUZZLE, + op.AGG_SIG_AMOUNT, + op.AGG_SIG_PUZZLE_AMOUNT, + op.AGG_SIG_PARENT_AMOUNT, + op.AGG_SIG_PARENT_PUZZLE, + op.AGG_SIG_UNSAFE, + op.AGG_SIG_ME, + op.CREATE_COIN, + op.CREATE_COIN_ANNOUNCEMENT, + op.CREATE_PUZZLE_ANNOUNCEMENT, + op.ASSERT_MY_COIN_ID, + op.ASSERT_MY_PARENT_ID, + op.ASSERT_MY_PUZZLEHASH, + op.ASSERT_MY_AMOUNT, + ] + + print("generating 1000 coins and spend bundles") + for i in range(1000): + # generate a coin with a dummy parent coin ID + puzzle = test_wallet.get_new_puzzle() + coin = Coin(bytes32(i.to_bytes(32, byteorder="big")), puzzle.get_tree_hash(), uint64(100 * i)) + + conditions: dict[ConditionOpcode, list[ConditionWithArgs]] = { + ConditionOpcode.CREATE_COIN: [ + ConditionWithArgs( + ConditionOpcode.CREATE_COIN, [test_wallet.get_new_puzzle().get_tree_hash(), int_to_bytes(25 * i)] + ) + ] + } + # generate a somewhat arbitrarty set of conditions for the spend, just + # to have some diversity and make the block interesting + num_conditions = seeded_random.randint(0, 10) + for c in seeded_random.sample(test_conditions, num_conditions): + if c in set( + [ + op.AGG_SIG_PARENT, + op.AGG_SIG_PUZZLE, + op.AGG_SIG_AMOUNT, + op.AGG_SIG_PUZZLE_AMOUNT, + op.AGG_SIG_PARENT_AMOUNT, + op.AGG_SIG_PARENT_PUZZLE, + op.AGG_SIG_UNSAFE, + op.AGG_SIG_ME, + ] + ): + secret_key = test_wallet.get_private_key_for_puzzle_hash(coin.puzzle_hash) + synthetic_secret_key = calculate_synthetic_secret_key(secret_key, DEFAULT_HIDDEN_PUZZLE_HASH) + cond = ConditionWithArgs(c, [bytes(synthetic_secret_key.get_g1()), b"foobar"]) + elif c == op.CREATE_COIN: + cond = ConditionWithArgs(c, [test_wallet.get_new_puzzle().get_tree_hash(), int_to_bytes(i)]) + elif c in set([op.CREATE_COIN_ANNOUNCEMENT, op.CREATE_PUZZLE_ANNOUNCEMENT]): + cond = ConditionWithArgs(c, [b"foobar"]) + elif c == op.ASSERT_MY_COIN_ID: + cond = ConditionWithArgs(c, [coin.name()]) + elif c == op.ASSERT_MY_PARENT_ID: + cond = ConditionWithArgs(c, [coin.parent_coin_info]) + elif c == op.ASSERT_MY_PUZZLEHASH: + cond = ConditionWithArgs(c, [coin.puzzle_hash]) + elif c == op.ASSERT_MY_AMOUNT: + cond = ConditionWithArgs(c, [int_to_bytes(coin.amount)]) + conditions.setdefault(c, []).append(cond) + + # generate a spend of that coin + bundle = test_wallet.generate_signed_transaction( + uint64(50 * i), test_wallet.get_new_puzzle().get_tree_hash(), coin, conditions + ) + bundles.append(bundle) + return bundles + + +# if we try to fill the mempool with more than 550, all spends won't +# necessarily fit in the block, which the test assumes +@pytest.mark.anyio +@pytest.mark.parametrize("mempool_size", [1, 2, 50, 100, 300, 400, 550]) +@pytest.mark.parametrize("seed", [0, 1, 2, 3, 4, 5, 6]) +async def test_create_block_generator(mempool_size: int, seed: int, transactions_1000: list[SpendBundle]) -> None: + bundles = transactions_1000 + all_coins = [s.coin for b in bundles for s in b.coin_spends] + coins = TestCoins(all_coins, {}) + + rng = random.Random(seed) + + # run the test multiple times, generating different combinations of mempools + mempool_manager = await setup_mempool(coins) + + included_bundles = rng.sample(bundles, mempool_size) + expected_additions: set[Coin] = set() + expected_removals: set[Coin] = set() + expected_signature = G2Element() + for sb in included_bundles: + pre_validation = await mempool_manager.pre_validate_spendbundle(sb) + bundle_add_info = await mempool_manager.add_spend_bundle( + sb, pre_validation, sb.name(), first_added_height=uint32(1) + ) + expected_additions.update(sb.additions()) + expected_removals.update(sb.removals()) + + expected_signature += sb.aggregated_signature + assert bundle_add_info.status == MempoolInclusionStatus.SUCCESS + item = mempool_manager.get_mempool_item(sb.name()) + assert item is not None + all_items = mempool_manager.mempool.all_items() + assert len(list(all_items)) == len(included_bundles) + + invariant_check_mempool(mempool_manager.mempool) + + assert mempool_manager.peak is not None + new_block_gen = await mempool_manager.create_block_generator(mempool_manager.peak.header_hash) + assert new_block_gen is not None + + # now, make sure the generator we got is valid + + assert len(expected_additions) == len(new_block_gen.additions) + assert expected_additions == set(new_block_gen.additions) + assert len(expected_removals) == len(new_block_gen.removals) + assert expected_removals == set(new_block_gen.removals) + assert expected_signature == new_block_gen.signature + + err, conds = run_block_generator2( + bytes(new_block_gen.program), + new_block_gen.generator_refs, + DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, + MEMPOOL_MODE, + new_block_gen.signature, + None, + DEFAULT_CONSTANTS, + ) + + assert err is None + assert conds is not None + + assert len(conds.spends) == len(expected_removals) + assert conds.cost < DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM + + num_additions = 0 + for spend in conds.spends: + assert Coin(spend.parent_id, spend.puzzle_hash, uint64(spend.coin_amount)) in expected_removals + for add2 in spend.create_coin: + assert Coin(spend.coin_id, add2[0], uint64(add2[1])) in expected_additions + num_additions += 1 + + assert num_additions == len(new_block_gen.additions) diff --git a/chia/consensus/block_creation.py b/chia/consensus/block_creation.py index 8c549aaf2a..15279e446c 100644 --- a/chia/consensus/block_creation.py +++ b/chia/consensus/block_creation.py @@ -38,7 +38,7 @@ from chia.types.blockchain_format.proof_of_space import ProofOfSpace from chia.types.blockchain_format.vdf import VDFInfo, VDFProof from chia.types.end_of_slot_bundle import EndOfSubSlotBundle from chia.types.full_block import FullBlock -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import BlockGenerator, NewBlockGenerator from chia.types.unfinished_block import UnfinishedBlock from chia.util.hash import std_hash from chia.util.prev_transaction_block import get_prev_transaction_block @@ -79,10 +79,7 @@ def compute_block_fee(additions: Sequence[Coin], removals: Sequence[Coin]) -> ui def create_foliage( constants: ConsensusConstants, reward_block_unfinished: RewardChainBlockUnfinished, - block_generator: Optional[BlockGenerator], - aggregate_sig: G2Element, - additions: list[Coin], - removals: list[Coin], + new_block_gen: Optional[NewBlockGenerator], prev_block: Optional[BlockRecord], blocks: BlockRecordsProtocol, total_iters_sp: uint128, @@ -103,8 +100,7 @@ def create_foliage( Args: constants: consensus constants being used for this chain reward_block_unfinished: the reward block to look at, potentially at the signage point - block_generator: transactions to add to the foliage block, if created - aggregate_sig: aggregate of all transactions (or infinity element) + new_block_gen: transactions to add to the foliage block, if created, including aggregate signature prev_block: the previous block at the signage point blocks: dict from header hash to blocks, of all ancestor blocks total_iters_sp: total iters at the signage point @@ -165,15 +161,16 @@ def create_foliage( foliage_transaction_block_hash: Optional[bytes32] if is_transaction_block: - cost = uint64(0) + cost: uint64 + spend_bundle_fees: uint64 # Calculate the cost of transactions - if block_generator is not None: - cost = compute_cost(block_generator, constants, height) - - spend_bundle_fees = compute_fees(additions, removals) + if new_block_gen is not None: + cost = compute_cost(new_block_gen, constants, height) + spend_bundle_fees = compute_fees(new_block_gen.additions, new_block_gen.removals) else: spend_bundle_fees = uint64(0) + cost = uint64(0) reward_claims_incorporated = [] if height > 0: @@ -215,14 +212,19 @@ def create_foliage( ) reward_claims_incorporated += [pool_coin, farmer_coin] curr = blocks.block_record(curr.prev_hash) - additions.extend(reward_claims_incorporated.copy()) - for coin in additions: + + for coin in reward_claims_incorporated: tx_additions.append(coin) byte_array_tx.append(bytearray(coin.puzzle_hash)) - for coin in removals: - cname = coin.name() - tx_removals.append(cname) - byte_array_tx.append(bytearray(cname)) + + if new_block_gen is not None: + for coin in new_block_gen.additions: + tx_additions.append(coin) + byte_array_tx.append(bytearray(coin.puzzle_hash)) + for coin in new_block_gen.removals: + cname = coin.name() + tx_removals.append(cname) + byte_array_tx.append(bytearray(cname)) bip158: PyBIP158 = PyBIP158(byte_array_tx) encoded = bytes(bip158.GetEncoded()) @@ -247,8 +249,8 @@ def create_foliage( removals_root = bytes32(compute_merkle_set_root(tx_removals)) generator_hash = bytes32.zeros - if block_generator is not None: - generator_hash = std_hash(block_generator.program) + if new_block_gen is not None: + generator_hash = std_hash(new_block_gen.program) generator_refs_hash = bytes32([1] * 32) filter_hash: bytes32 = std_hash(encoded) @@ -256,7 +258,7 @@ def create_foliage( transactions_info: Optional[TransactionsInfo] = TransactionsInfo( generator_hash, generator_refs_hash, - aggregate_sig, + new_block_gen.signature if new_block_gen else G2Element(), spend_bundle_fees, cost, reward_claims_incorporated, @@ -318,10 +320,7 @@ def create_unfinished_block( timestamp: uint64, blocks: BlockRecordsProtocol, seed: bytes = b"", - block_generator: Optional[BlockGenerator] = None, - aggregate_sig: G2Element = G2Element(), - additions: Optional[list[Coin]] = None, - removals: Optional[list[Coin]] = None, + new_block_gen: Optional[NewBlockGenerator] = None, prev_block: Optional[BlockRecord] = None, finished_sub_slots_input: Optional[list[EndOfSubSlotBundle]] = None, compute_cost: Callable[[BlockGenerator, ConsensusConstants, uint32], uint64] = compute_block_cost, @@ -347,10 +346,7 @@ def create_unfinished_block( signage_point: signage point information (VDFs) timestamp: timestamp to add to the foliage block, if created seed: seed to randomize chain - block_generator: transactions to add to the foliage block, if created - aggregate_sig: aggregate of all transactions (or infinity element) - additions: Coins added in spend_bundle - removals: Coins removed in spend_bundle + new_block_gen: transactions to add to the foliage block, if created, including aggregate signature prev_block: previous block (already in chain) from the signage point blocks: dictionary from header hash to SBR of all included SBR finished_sub_slots_input: finished_sub_slots at the signage point @@ -409,17 +405,10 @@ def create_unfinished_block( signage_point.rc_vdf, rc_sp_signature, ) - if additions is None: - additions = [] - if removals is None: - removals = [] (foliage, foliage_transaction_block, transactions_info) = create_foliage( constants, rc_block, - block_generator, - aggregate_sig, - additions, - removals, + new_block_gen, prev_block, blocks, total_iters_sp, @@ -440,8 +429,8 @@ def create_unfinished_block( foliage, foliage_transaction_block, transactions_info, - block_generator.program if block_generator else None, - [], # generator_refs + new_block_gen.program if new_block_gen else None, + new_block_gen.block_refs if new_block_gen else [], ) diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 8fc3d15cf2..114411fc64 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -62,7 +62,7 @@ from chia.types.blockchain_format.sub_epoch_summary import SubEpochSummary from chia.types.coin_record import CoinRecord from chia.types.end_of_slot_bundle import EndOfSubSlotBundle from chia.types.full_block import FullBlock -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import BlockGenerator, NewBlockGenerator from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.types.peer_info import PeerInfo from chia.types.spend_bundle import SpendBundle @@ -835,10 +835,7 @@ class FullNodeAPI: # 3. In a future sub-slot that we already know of # Grab best transactions from Mempool for given tip target - aggregate_signature: G2Element = G2Element() - block_generator: Optional[BlockGenerator] = None - additions: Optional[list[Coin]] = [] - removals: Optional[list[Coin]] = [] + new_block_gen: Optional[NewBlockGenerator] async with self.full_node.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.high): peak: Optional[BlockRecord] = self.full_node.blockchain.get_peak() @@ -863,12 +860,15 @@ class FullNodeAPI: while not curr_l_tb.is_transaction_block: curr_l_tb = self.full_node.blockchain.block_record(curr_l_tb.prev_hash) try: - block = await self.full_node.mempool_manager.create_block_generator(curr_l_tb.header_hash) - if block is not None: - block_generator, aggregate_signature, additions, removals = block + new_block_gen = await self.full_node.mempool_manager.create_block_generator( + curr_l_tb.header_hash + ) except Exception as e: self.log.error(f"Traceback: {traceback.format_exc()}") self.full_node.log.error(f"Error making spend bundle {e} peak: {peak}") + new_block_gen = None + else: + new_block_gen = None def get_plot_sig(to_sign: bytes32, _extra: G1Element) -> G2Element: if to_sign == request.challenge_chain_sp: @@ -1005,10 +1005,7 @@ class FullNodeAPI: timestamp, self.full_node.blockchain, b"", - block_generator, - aggregate_signature, - additions, - removals, + new_block_gen, prev_b, finished_sub_slots, ) @@ -1065,9 +1062,6 @@ class FullNodeAPI: self.full_node.blockchain, b"", None, - G2Element(), - None, - None, prev_b, finished_sub_slots, ) diff --git a/chia/full_node/mempool.py b/chia/full_node/mempool.py index 9033691a9a..02e90e2bdf 100644 --- a/chia/full_node/mempool.py +++ b/chia/full_node/mempool.py @@ -20,7 +20,7 @@ from chia.types.blockchain_format.serialized_program import SerializedProgram from chia.types.clvm_cost import CLVMCost from chia.types.coin_spend import CoinSpend from chia.types.eligible_coin_spends import EligibleCoinSpends, SkipDedup, UnspentLineageInfo -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import NewBlockGenerator from chia.types.internal_mempool_item import InternalMempoolItem from chia.types.mempool_item import MempoolItem from chia.types.spend_bundle import SpendBundle @@ -492,7 +492,7 @@ class Mempool: get_unspent_lineage_info_for_puzzle_hash: Callable[[bytes32], Awaitable[Optional[UnspentLineageInfo]]], constants: ConsensusConstants, height: uint32, - ) -> Optional[tuple[BlockGenerator, G2Element, list[Coin], list[Coin]]]: + ) -> Optional[NewBlockGenerator]: """ height is needed in case we fast-forward a transaction and we need to re-run its puzzle. @@ -520,8 +520,10 @@ class Mempool: logging.INFO if duration < 1 else logging.WARNING, f"serializing block generator took {duration:0.2f} seconds", ) - return ( - BlockGenerator(SerializedProgram.from_bytes(block_program), []), + return NewBlockGenerator( + SerializedProgram.from_bytes(block_program), + [], + [], spend_bundle.aggregated_signature, additions, removals, diff --git a/chia/full_node/mempool_manager.py b/chia/full_node/mempool_manager.py index 31de696a3a..70b067dc3a 100644 --- a/chia/full_node/mempool_manager.py +++ b/chia/full_node/mempool_manager.py @@ -13,7 +13,6 @@ from chia_rs import ( ELIGIBLE_FOR_FF, BLSCache, ConsensusConstants, - G2Element, supports_fast_forward, validate_clvm_and_signature, ) @@ -34,7 +33,7 @@ from chia.types.clvm_cost import CLVMCost from chia.types.coin_record import CoinRecord from chia.types.eligible_coin_spends import EligibilityAndAdditions, UnspentLineageInfo from chia.types.fee_rate import FeeRate -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import NewBlockGenerator from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.types.mempool_item import BundleCoinSpend, MempoolItem from chia.types.spend_bundle import SpendBundle @@ -244,7 +243,7 @@ class MempoolManager: async def create_block_generator( self, last_tb_header_hash: bytes32, - ) -> Optional[tuple[BlockGenerator, G2Element, list[Coin], list[Coin]]]: + ) -> Optional[NewBlockGenerator]: """ Returns a block generator program, the aggregate signature and all additions and removals, for a new block """ diff --git a/chia/rpc/full_node_rpc_api.py b/chia/rpc/full_node_rpc_api.py index fcbc13c6cf..19b23581f5 100644 --- a/chia/rpc/full_node_rpc_api.py +++ b/chia/rpc/full_node_rpc_api.py @@ -4,7 +4,6 @@ import time from datetime import datetime, timezone from typing import TYPE_CHECKING, Any, ClassVar, Optional, cast -from chia_rs import G2Element from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64, uint128 @@ -25,7 +24,7 @@ from chia.types.blockchain_format.proof_of_space import calculate_prefix_bits from chia.types.coin_record import CoinRecord from chia.types.coin_spend import CoinSpend from chia.types.full_block import FullBlock -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import BlockGenerator, NewBlockGenerator from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.types.spend_bundle import SpendBundle from chia.types.spend_bundle_conditions import SpendBundleConditions @@ -840,8 +839,7 @@ class FullNodeRpcApi: return {"mempool_items": [item.to_json_dict() for item in items]} async def create_block_generator(self, _: dict[str, Any]) -> EndpointResult: - mempool_generator = BlockGenerator() - signature = G2Element() + gen = NewBlockGenerator() # Grab best transactions from Mempool for given tip target async with self.service.blockchain.priority_mutex.acquire(priority=BlockchainMutexPriority.low): @@ -849,9 +847,11 @@ class FullNodeRpcApi: if peak is None: return { - "generator": mempool_generator.program, - "refs": mempool_generator.generator_refs, - "sig": signature, + "generator": gen.program, + "refs": gen.block_refs, + "additions": gen.additions, + "removals": gen.removals, + "sig": gen.signature, } # Finds the last transaction block before this one @@ -863,14 +863,22 @@ class FullNodeRpcApi: start_time = time.monotonic() try: - result = await self.service.mempool_manager.create_block_generator(curr_l_tb.header_hash) - assert result is not None - mempool_generator, signature, _additions, _removals = result + maybe_gen = await self.service.mempool_manager.create_block_generator(curr_l_tb.header_hash) + if maybe_gen is None: + self.service.log.error(f"failed to create block generator, peak: {peak}") + else: + gen = maybe_gen except Exception: - self.service.log.exception(f"Error making spend bundle peak: {peak}") + self.service.log.exception(f"Error creating block generator, peak: {peak}") self.service.log.info(f"Simulated block constructed in {time.monotonic() - start_time:0.2f} seconds") - return {"generator": mempool_generator.program, "refs": mempool_generator.generator_refs, "sig": signature} + return { + "generator": gen.program, + "refs": gen.block_refs, + "additions": gen.additions, + "removals": gen.removals, + "sig": gen.signature, + } def _get_spendbundle_type_cost(self, name: str) -> uint64: """ diff --git a/chia/simulator/block_tools.py b/chia/simulator/block_tools.py index 4e3967addd..6939084e63 100644 --- a/chia/simulator/block_tools.py +++ b/chia/simulator/block_tools.py @@ -97,7 +97,7 @@ from chia.types.blockchain_format.vdf import VDFInfo, VDFProof from chia.types.condition_opcodes import ConditionOpcode from chia.types.end_of_slot_bundle import EndOfSubSlotBundle from chia.types.full_block import FullBlock -from chia.types.generator_types import BlockGenerator +from chia.types.generator_types import BlockGenerator, NewBlockGenerator from chia.types.spend_bundle import SpendBundle from chia.types.unfinished_block import UnfinishedBlock from chia.util.bech32m import encode_puzzle_hash @@ -596,6 +596,8 @@ class BlockTools: skip_overflow: bool = False, min_signage_point: int = -1, ) -> list[FullBlock]: + # make a copy to not have different invocations affect each other + block_refs = block_refs[:] assert num_blocks > 0 if block_list_input is not None: block_list = block_list_input.copy() @@ -611,6 +613,9 @@ class BlockTools: tx_block_heights.append(b.height) constants = self.constants + + # this indicates whether the passed in transaction_data has been + # included in a transaction block yet transaction_data_included = False if time_per_block is None: time_per_block = float(constants.SUB_SLOT_TIME_TARGET) / float(constants.SLOT_BLOCKS_TARGET) @@ -767,20 +772,9 @@ class BlockTools: continue assert latest_block.header_hash in blocks - additions = None - removals = None if transaction_data_included: transaction_data = None block_refs = [] - if transaction_data is not None: - additions = compute_additions_unchecked(transaction_data) - removals = transaction_data.removals() - elif include_transactions: - assert wallet is not None - assert rng is not None - transaction_data, additions = make_spend_bundle(available_coins, wallet, rng) - removals = transaction_data.removals() - transaction_data_included = False assert last_timestamp is not None if proof_of_space.pool_contract_puzzle_hash is not None: @@ -795,32 +789,48 @@ class BlockTools: else: pool_target = PoolTarget(self.pool_ph, uint32(0)) - block_generator: Optional[BlockGenerator] + if dummy_block_references and len(tx_block_heights) > 4: + block_refs.extend( + [ + tx_block_heights[1], + tx_block_heights[len(tx_block_heights) // 2], + tx_block_heights[-2], + ] + ) + + new_gen: Optional[NewBlockGenerator] if transaction_data is not None: + # this means the caller passed in transaction_data + # to be included in the block. + additions = compute_additions_unchecked(transaction_data) + removals = transaction_data.removals() if start_height >= constants.HARD_FORK_HEIGHT: - block_generator = simple_solution_generator_backrefs(transaction_data) - block_refs = [] + program = simple_solution_generator_backrefs(transaction_data).program else: - block_generator = simple_solution_generator(transaction_data) - - aggregate_signature = transaction_data.aggregated_signature + program = simple_solution_generator(transaction_data).program + block_refs = [] + new_gen = NewBlockGenerator( + program, [], block_refs, transaction_data.aggregated_signature, additions, removals + ) + elif include_transactions: + # if the caller did not pass in specific + # transactions, this parameter means we just want + # some transactions + assert wallet is not None + assert rng is not None + transaction_data, additions = make_spend_bundle(available_coins, wallet, rng) + removals = transaction_data.removals() + program = simple_solution_generator(transaction_data).program + new_gen = NewBlockGenerator( + program, [], block_refs, transaction_data.aggregated_signature, additions, removals + ) + transaction_data_included = False + elif dummy_block_references: + program = SerializedProgram.from_bytes(solution_generator([])) + new_gen = NewBlockGenerator(program, [], block_refs, G2Element(), [], []) else: - block_generator = None - aggregate_signature = G2Element() + new_gen = None - if dummy_block_references: - if block_generator is None: - program = SerializedProgram.from_bytes(solution_generator([])) - block_generator = BlockGenerator(program, []) - - if len(tx_block_heights) > 4: - block_refs.extend( - [ - tx_block_heights[1], - tx_block_heights[len(tx_block_heights) // 2], - tx_block_heights[-2], - ] - ) ( full_block, block_record, @@ -838,10 +848,7 @@ class BlockTools: last_timestamp, start_height, time_per_block, - block_generator, - aggregate_signature, - additions, - removals, + new_gen, height_to_hash, difficulty, required_iters, @@ -854,7 +861,6 @@ class BlockTools: seed, normalized_to_identity_cc_ip=normalized_to_identity_cc_ip, current_time=current_time, - block_refs=block_refs, ) if block_record.is_transaction_block: transaction_data_included = True @@ -1033,19 +1039,8 @@ class BlockTools: latest_block_eos = latest_block overflow_cc_challenge = finished_sub_slots_at_ip[-1].challenge_chain.get_hash() overflow_rc_challenge = finished_sub_slots_at_ip[-1].reward_chain.get_hash() - additions = None - removals = None if transaction_data_included: transaction_data = None - if transaction_data is not None: - additions = compute_additions_unchecked(transaction_data) - removals = transaction_data.removals() - elif include_transactions: - assert wallet is not None - assert rng is not None - transaction_data, additions = make_spend_bundle(available_coins, wallet, rng) - removals = transaction_data.removals() - transaction_data_included = False sub_slots_finished += 1 self.log.info( f"Sub slot finished. blocks included: {blocks_added_this_sub_slot} blocks_per_slot: " @@ -1113,30 +1108,47 @@ class BlockTools: pool_target = PoolTarget(pool_reward_puzzle_hash, uint32(0)) else: pool_target = PoolTarget(self.pool_ph, uint32(0)) + + if dummy_block_references and len(tx_block_heights) > 4: + block_refs.extend( + [ + tx_block_heights[1], + tx_block_heights[len(tx_block_heights) // 2], + tx_block_heights[-2], + ] + ) + if transaction_data is not None: + # this means the caller passed in transaction_data + # to be included in the block. + additions = compute_additions_unchecked(transaction_data) + removals = transaction_data.removals() if start_height >= constants.HARD_FORK_HEIGHT: - block_generator = simple_solution_generator_backrefs(transaction_data) - block_refs = [] + program = simple_solution_generator_backrefs(transaction_data).program else: - block_generator = simple_solution_generator(transaction_data) - aggregate_signature = transaction_data.aggregated_signature + program = simple_solution_generator(transaction_data).program + block_refs = [] + new_gen = NewBlockGenerator( + program, [], block_refs, transaction_data.aggregated_signature, additions, removals + ) + elif include_transactions: + # if the caller did not pass in specific + # transactions, this parameter means we just want + # some transactions + assert wallet is not None + assert rng is not None + transaction_data, additions = make_spend_bundle(available_coins, wallet, rng) + removals = transaction_data.removals() + program = simple_solution_generator(transaction_data).program + new_gen = NewBlockGenerator( + program, [], block_refs, transaction_data.aggregated_signature, additions, removals + ) + transaction_data_included = False + elif dummy_block_references: + program = SerializedProgram.from_bytes(solution_generator([])) + new_gen = NewBlockGenerator(program, [], block_refs, G2Element(), [], []) else: - block_generator = None - aggregate_signature = G2Element() - - if dummy_block_references: - if block_generator is None: - program = SerializedProgram.from_bytes(solution_generator([])) - block_generator = BlockGenerator(program, []) - - if len(tx_block_heights) > 4: - block_refs.extend( - [ - tx_block_heights[1], - tx_block_heights[len(tx_block_heights) // 2], - tx_block_heights[-2], - ] - ) + new_gen = None ( full_block, @@ -1155,10 +1167,7 @@ class BlockTools: last_timestamp, start_height, time_per_block, - block_generator, - aggregate_signature, - additions, - removals, + new_gen, height_to_hash, difficulty, required_iters, @@ -1173,7 +1182,6 @@ class BlockTools: overflow_rc_challenge=overflow_rc_challenge, normalized_to_identity_cc_ip=normalized_to_identity_cc_ip, current_time=current_time, - block_refs=block_refs, ) if block_record.is_transaction_block: @@ -1788,10 +1796,7 @@ def get_full_block_and_block_record( last_timestamp: float, start_height: uint32, time_per_block: float, - block_generator: Optional[BlockGenerator], - aggregate_signature: G2Element, - additions: Optional[list[Coin]], - removals: Optional[list[Coin]], + new_gen: Optional[NewBlockGenerator], height_to_hash: dict[uint32, bytes32], difficulty: uint64, required_iters: uint64, @@ -1803,7 +1808,6 @@ def get_full_block_and_block_record( prev_block: BlockRecord, seed: bytes = b"", *, - block_refs: list[uint32] = [], overflow_cc_challenge: Optional[bytes32] = None, overflow_rc_challenge: Optional[bytes32] = None, normalized_to_identity_cc_ip: bool = False, @@ -1838,10 +1842,7 @@ def get_full_block_and_block_record( uint64(timestamp), BlockCache(blocks), seed, - block_generator, - aggregate_signature, - additions, - removals, + new_gen, prev_block, finished_sub_slots, compute_cost=compute_cost_test, diff --git a/chia/types/generator_types.py b/chia/types/generator_types.py index 5b20ed90ed..961b65a2c4 100644 --- a/chia/types/generator_types.py +++ b/chia/types/generator_types.py @@ -2,12 +2,38 @@ from __future__ import annotations from dataclasses import dataclass, field +from chia_rs import Coin, G2Element +from chia_rs.sized_ints import uint32 + from chia.types.blockchain_format.serialized_program import SerializedProgram from chia.util.streamable import Streamable, streamable +# This holds what we need to pre validate a block generator @streamable @dataclass(frozen=True) class BlockGenerator(Streamable): program: SerializedProgram = field(default_factory=SerializedProgram.default) + # to run the block generator, we need the actual bytes of the previous + # generators it may reference. These are parameters passed in to the block + # generator generator_refs: list[bytes] = field(default_factory=list) + + +# When we create a new block, this object holds the block generator and +# additional information we need to create the UnfinishedBlock from it. +# When creating a block, we still need to be able to run it, to compute its cost +# and validate it. Therefore, this is a superset of the BlockGenerator class. +@dataclass(frozen=True) +class NewBlockGenerator(BlockGenerator): + # when creating a block, we include the block heights of the generators we + # reference. Tese are block heights and generator_refs contain the + # corresponding bytes of the generator programs + block_refs: list[uint32] = field(default_factory=list) + # the aggregate signature of all AGG_SIG_* conditions returned by the block + # generator. + signature: G2Element = G2Element() + # all CREATE_COIN outputs created by the block generator + additions: list[Coin] = field(default_factory=list) + # all coins being spent by the block generator + removals: list[Coin] = field(default_factory=list) From e91335eff4258aeb386436fbab131d3d070f9cf6 Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Thu, 20 Mar 2025 03:30:45 +0100 Subject: [PATCH 2/7] CHIA-2570 Adapt to requiring the same amount across singleton fast forward versions (#19398) Adapt to requiring the same amount across singleton fast forward versions. --- .../core/full_node/stores/test_coin_store.py | 4 +- chia/_tests/core/mempool/test_mempool.py | 6 +- .../core/mempool/test_mempool_manager.py | 8 +- .../mempool/test_singleton_fast_forward.py | 88 +++++-------------- chia/full_node/coin_store.py | 14 +-- chia/types/eligible_coin_spends.py | 27 +++--- 6 files changed, 40 insertions(+), 107 deletions(-) diff --git a/chia/_tests/core/full_node/stores/test_coin_store.py b/chia/_tests/core/full_node/stores/test_coin_store.py index ab1501d060..edbc42777d 100644 --- a/chia/_tests/core/full_node/stores/test_coin_store.py +++ b/chia/_tests/core/full_node/stores/test_coin_store.py @@ -802,7 +802,7 @@ class UnspentLineageInfoCase: ), ], parent_with_diff_amount=True, - expected_success=True, + expected_success=False, ), UnspentLineageInfoCase( id="Unspent with parent that has same puzzlehash and amount but is also unspent", @@ -884,13 +884,11 @@ async def test_get_unspent_lineage_info_for_puzzle_hash(case: UnspentLineageInfo if case.expected_success: assert result == UnspentLineageInfo( coin_id=bytes32(TEST_COIN_ID), - coin_amount=TEST_AMOUNT, parent_id=( bytes32(TEST_PARENT_ID_DIFFERENT_AMOUNT) if case.parent_with_diff_amount else bytes32(TEST_PARENT_ID) ), - parent_amount=TEST_PARENT_DIFFERENT_AMOUNT if case.parent_with_diff_amount else TEST_AMOUNT, parent_parent_id=bytes32(TEST_PARENT_PARENT_ID), ) else: diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index 817548c0a9..12287406d2 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -3355,11 +3355,7 @@ async def test_lineage_cache(seeded_random: random.Random) -> None: called = 0 info1 = UnspentLineageInfo( - bytes32.random(seeded_random), - uint64.from_bytes(seeded_random.randbytes(8)), - bytes32.random(seeded_random), - uint64.from_bytes(seeded_random.randbytes(8)), - bytes32.random(seeded_random), + bytes32.random(seeded_random), bytes32.random(seeded_random), bytes32.random(seeded_random) ) async def callback1(ph: bytes32) -> Optional[UnspentLineageInfo]: diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index 94d8602abf..ad612e769c 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -2212,9 +2212,7 @@ class TestCoins: self.coin_records[c.name()] = CoinRecord(c, uint32(0), uint32(0), False, TEST_TIMESTAMP) self.lineage_info = {} for ph, c in lineage.items(): - self.lineage_info[ph] = UnspentLineageInfo( - c.name(), c.amount, c.parent_coin_info, uint64(1337), bytes32([42] * 32) - ) + self.lineage_info[ph] = UnspentLineageInfo(c.name(), c.parent_coin_info, bytes32([42] * 32)) def spend_coin(self, coin_id: bytes32, height: uint32 = uint32(10)) -> None: self.coin_records[coin_id] = dataclasses.replace(self.coin_records[coin_id], spent_block_index=height) @@ -2225,9 +2223,7 @@ class TestCoins: else: assert coin.puzzle_hash == puzzle_hash prev = self.lineage_info[puzzle_hash] - self.lineage_info[puzzle_hash] = UnspentLineageInfo( - coin.name(), coin.amount, coin.parent_coin_info, prev.coin_amount, prev.coin_id - ) + self.lineage_info[puzzle_hash] = UnspentLineageInfo(coin.name(), coin.parent_coin_info, prev.coin_id) async def get_coin_records(self, coin_ids: Collection[bytes32]) -> list[CoinRecord]: ret = [] diff --git a/chia/_tests/core/mempool/test_singleton_fast_forward.py b/chia/_tests/core/mempool/test_singleton_fast_forward.py index c6dba2e987..3dde1fda59 100644 --- a/chia/_tests/core/mempool/test_singleton_fast_forward.py +++ b/chia/_tests/core/mempool/test_singleton_fast_forward.py @@ -110,11 +110,7 @@ async def test_process_fast_forward_spends_latest_unspent() -> None: test_amount = uint64(3) test_coin = Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, test_amount) test_unspent_lineage_info = UnspentLineageInfo( - coin_id=test_coin.name(), - coin_amount=test_coin.amount, - parent_id=test_coin.parent_coin_info, - parent_amount=test_coin.amount, - parent_parent_id=TEST_COIN_ID, + coin_id=test_coin.name(), parent_id=test_coin.parent_coin_info, parent_parent_id=TEST_COIN_ID ) async def get_unspent_lineage_info_for_puzzle_hash(puzzle_hash: bytes32) -> Optional[UnspentLineageInfo]: @@ -141,11 +137,7 @@ async def test_process_fast_forward_spends_latest_unspent() -> None: child_coin = item.bundle_coin_spends[test_coin.name()].additions[0] expected_fast_forward_spends = { IDENTITY_PUZZLE_HASH: UnspentLineageInfo( - coin_id=child_coin.name(), - coin_amount=child_coin.amount, - parent_id=test_coin.name(), - parent_amount=test_coin.amount, - parent_parent_id=test_coin.parent_coin_info, + coin_id=child_coin.name(), parent_id=test_coin.name(), parent_parent_id=test_coin.parent_coin_info ) } # We have set the next version from our additions to chain ff spends @@ -202,9 +194,7 @@ def test_perform_the_fast_forward() -> None: test_spend_data = BundleCoinSpend(test_coin_spend, False, True, [test_child_coin]) test_unspent_lineage_info = UnspentLineageInfo( coin_id=latest_unspent_coin.name(), - coin_amount=latest_unspent_coin.amount, parent_id=latest_unspent_coin.parent_coin_info, - parent_amount=test_child_coin.amount, parent_parent_id=test_child_coin.parent_coin_info, ) # Start from a fresh state of fast forward spends @@ -227,9 +217,7 @@ def test_perform_the_fast_forward() -> None: # (previously latest unspent) expected_unspent_lineage_info = UnspentLineageInfo( coin_id=expected_child_coin.name(), - coin_amount=expected_child_coin.amount, parent_id=latest_unspent_coin.name(), - parent_amount=latest_unspent_coin.amount, parent_parent_id=latest_unspent_coin.parent_coin_info, ) assert fast_forward_spends == {test_ph: expected_unspent_lineage_info} @@ -301,7 +289,7 @@ def make_singleton_coin_spend( async def prepare_singleton_eve( - sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, start_amount: uint64, singleton_amount: uint64 + sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, singleton_amount: uint64 ) -> tuple[Program, CoinSpend, Program]: # Generate starting info key_lookup = KeyTool() @@ -319,11 +307,11 @@ async def prepare_singleton_eve( starting_coin = records[0].coin # Launching conditions, launcher_coin_spend = singleton_top_layer.launch_conditions_and_coinsol( - coin=starting_coin, inner_puzzle=inner_puzzle, comment=[], amount=start_amount + coin=starting_coin, inner_puzzle=inner_puzzle, comment=[], amount=singleton_amount ) # Keep a remaining coin with an even amount conditions.append( - Program.to([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, starting_coin.amount - start_amount - 1]) + Program.to([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, starting_coin.amount - singleton_amount - 1]) ) # Create a solution for standard transaction delegated_puzzle = p2_conditions.puzzle_for_conditions(conditions) @@ -351,10 +339,10 @@ async def prepare_singleton_eve( async def prepare_and_test_singleton( - sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, start_amount: uint64, singleton_amount: uint64 + sim: SpendSim, sim_client: SimClient, is_eligible_for_ff: bool, singleton_amount: uint64 ) -> tuple[Coin, CoinSpend, Program, Coin]: inner_puzzle, eve_coin_spend, eve_signing_puzzle = await prepare_singleton_eve( - sim, sim_client, is_eligible_for_ff, start_amount, singleton_amount + sim, sim_client, is_eligible_for_ff, singleton_amount ) # At this point we don't have any unspent singleton singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash @@ -374,11 +362,7 @@ async def prepare_and_test_singleton( singleton_puzzle_hash ) assert unspent_lineage_info == UnspentLineageInfo( - coin_id=singleton.name(), - coin_amount=singleton.amount, - parent_id=eve_coin.name(), - parent_amount=eve_coin.amount, - parent_parent_id=eve_coin.parent_coin_info, + coin_id=singleton.name(), parent_id=eve_coin.name(), parent_parent_id=eve_coin.parent_coin_info ) return singleton, eve_coin_spend, inner_puzzle, remaining_coin @@ -393,7 +377,7 @@ async def test_singleton_fast_forward_solo() -> None: SINGLETON_AMOUNT = uint64(1337) async with sim_and_client() as (sim, sim_client): singleton, eve_coin_spend, inner_puzzle, _ = await prepare_and_test_singleton( - sim, sim_client, True, SINGLETON_AMOUNT, SINGLETON_AMOUNT + sim, sim_client, True, SINGLETON_AMOUNT ) singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash inner_puzzle_hash = inner_puzzle.get_tree_hash() @@ -410,9 +394,7 @@ async def test_singleton_fast_forward_solo() -> None: assert singleton_child.amount == SINGLETON_AMOUNT assert unspent_lineage_info == UnspentLineageInfo( coin_id=singleton_child.name(), - coin_amount=singleton_child.amount, parent_id=eve_coin_spend.coin.name(), - parent_amount=singleton.amount, parent_parent_id=eve_coin_spend.coin.parent_coin_info, ) @@ -434,12 +416,10 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool) get properly fast forwarded to the latest unspent (when it's eligible) or get correctly rejected as a double spend (when it's not eligible) """ - START_AMOUNT = uint64(1337) - # We're decrementing the next iteration's amount for testing purposes - SINGLETON_AMOUNT = uint64(1335) + SINGLETON_AMOUNT = uint64(1337) async with sim_and_client() as (sim, sim_client): singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton( - sim, sim_client, is_eligible_for_ff, START_AMOUNT, SINGLETON_AMOUNT + sim, sim_client, is_eligible_for_ff, SINGLETON_AMOUNT ) # Let's spend this first version, to create a bigger singleton child singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash @@ -475,11 +455,7 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool) singleton_child, [remaining_coin] = await get_singleton_and_remaining_coins(sim) assert singleton_child.amount == SINGLETON_AMOUNT assert unspent_lineage_info == UnspentLineageInfo( - coin_id=singleton_child.name(), - coin_amount=singleton_child.amount, - parent_id=singleton.name(), - parent_amount=singleton.amount, - parent_parent_id=eve_coin_spend.coin.name(), + coin_id=singleton_child.name(), parent_id=singleton.name(), parent_parent_id=eve_coin_spend.coin.name() ) # Now let's spend the first version again (despite being already spent by now) remaining_spend_solution = SerializedProgram.from_program( @@ -506,11 +482,7 @@ async def test_singleton_fast_forward_different_block(is_eligible_for_ff: bool) ) singleton_grandchild, [remaining_coin] = await get_singleton_and_remaining_coins(sim) assert unspent_lineage_info == UnspentLineageInfo( - coin_id=singleton_grandchild.name(), - coin_amount=singleton_grandchild.amount, - parent_id=singleton_child.name(), - parent_amount=singleton_child.amount, - parent_parent_id=singleton.name(), + coin_id=singleton_grandchild.name(), parent_id=singleton_child.name(), parent_parent_id=singleton.name() ) else: # As this singleton is not eligible for fast forward, attempting to @@ -526,12 +498,10 @@ async def test_singleton_fast_forward_same_block() -> None: singleton version, all in the same block, to make sure they get properly fast forwarded and chained down to a latest unspent version """ - START_AMOUNT = uint64(1337) - # We're decrementing the next iteration's amount for testing purposes - SINGLETON_AMOUNT = uint64(1335) + SINGLETON_AMOUNT = uint64(1337) async with sim_and_client() as (sim, sim_client): singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton( - sim, sim_client, True, START_AMOUNT, SINGLETON_AMOUNT + sim, sim_client, True, SINGLETON_AMOUNT ) # Let's spend this first version, to create a bigger singleton child singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash @@ -558,11 +528,7 @@ async def test_singleton_fast_forward_same_block() -> None: singleton_child, [remaining_coin] = await get_singleton_and_remaining_coins(sim) assert singleton_child.amount == SINGLETON_AMOUNT assert unspent_lineage_info == UnspentLineageInfo( - coin_id=singleton_child.name(), - coin_amount=singleton_child.amount, - parent_id=singleton.name(), - parent_amount=singleton.amount, - parent_parent_id=eve_coin_spend.coin.name(), + coin_id=singleton_child.name(), parent_id=singleton.name(), parent_parent_id=eve_coin_spend.coin.name() ) # Now let's send 3 arbitrary spends of the already spent singleton in # one block. They should all properly fast forward @@ -596,14 +562,8 @@ async def test_singleton_fast_forward_same_block() -> None: assert unspent_lineage_info is not None # The unspent coin ID should reflect the latest version assert unspent_lineage_info.coin_id == latest_singleton.name() - # The latest version should have the last random amount - assert latest_singleton.amount == SINGLETON_AMOUNT - # The unspent coin amount should reflect the latest version - assert unspent_lineage_info.coin_amount == latest_singleton.amount # The unspent parent ID should reflect the latest version's parent assert unspent_lineage_info.parent_id == latest_singleton.parent_coin_info - # The one before it should have the second last random amount - assert unspent_lineage_info.parent_amount == SINGLETON_AMOUNT @pytest.mark.anyio @@ -615,7 +575,7 @@ async def test_mempool_items_immutability_on_ff() -> None: SINGLETON_AMOUNT = uint64(1337) async with sim_and_client() as (sim, sim_client): singleton, eve_coin_spend, inner_puzzle, remaining_coin = await prepare_and_test_singleton( - sim, sim_client, True, SINGLETON_AMOUNT, SINGLETON_AMOUNT + sim, sim_client, True, SINGLETON_AMOUNT ) singleton_name = singleton.name() singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash @@ -649,11 +609,7 @@ async def test_mempool_items_immutability_on_ff() -> None: singleton_child_name = singleton_child.name() assert singleton_child.amount == SINGLETON_AMOUNT assert unspent_lineage_info == UnspentLineageInfo( - coin_id=singleton_child_name, - coin_amount=singleton_child.amount, - parent_id=singleton_name, - parent_amount=singleton.amount, - parent_parent_id=eve_coin_spend.coin.name(), + coin_id=singleton_child_name, parent_id=singleton_name, parent_parent_id=eve_coin_spend.coin.name() ) # Now let's spend the first version again (despite being already spent # by now) to exercise its fast forward. @@ -694,11 +650,11 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None: This test covers the scenario where we receive a spend bundle with a singleton fast forward spend that has currently no unspent coin. """ - test_amount = uint64(1337) + singleton_amount = uint64(1337) async with sim_and_client() as (sim, sim_client): # Prepare a singleton spend singleton, eve_coin_spend, inner_puzzle, _ = await prepare_and_test_singleton( - sim, sim_client, True, start_amount=test_amount, singleton_amount=test_amount + sim, sim_client, True, singleton_amount=singleton_amount ) singleton_name = singleton.name() singleton_puzzle_hash = eve_coin_spend.coin.puzzle_hash @@ -708,7 +664,7 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None: sig = AugSchemeMPL.sign(sk, b"foobar", g1) inner_conditions: list[list[Any]] = [ [ConditionOpcode.AGG_SIG_UNSAFE, bytes(g1), b"foobar"], - [ConditionOpcode.CREATE_COIN, inner_puzzle_hash, test_amount], + [ConditionOpcode.CREATE_COIN, inner_puzzle_hash, singleton_amount], ] singleton_coin_spend, _ = make_singleton_coin_spend(eve_coin_spend, singleton, inner_puzzle, inner_conditions) # Get its current latest unspent info @@ -717,9 +673,7 @@ async def test_double_spend_ff_spend_no_latest_unspent() -> None: ) assert unspent_lineage_info == UnspentLineageInfo( coin_id=singleton_name, - coin_amount=test_amount, parent_id=eve_coin_spend.coin.name(), - parent_amount=eve_coin_spend.coin.amount, parent_parent_id=eve_coin_spend.coin.parent_coin_info, ) # Let's remove this latest unspent coin from the coin store diff --git a/chia/full_node/coin_store.py b/chia/full_node/coin_store.py index aa8c01920b..95c9b4db7a 100644 --- a/chia/full_node/coin_store.py +++ b/chia/full_node/coin_store.py @@ -11,7 +11,6 @@ import typing_extensions from aiosqlite import Cursor from chia_rs.sized_bytes import bytes32 from chia_rs.sized_ints import uint32, uint64 -from clvm.casts import int_from_bytes from chia.protocols.wallet_protocol import CoinState from chia.types.blockchain_format.coin import Coin @@ -613,27 +612,22 @@ class CoinStore: async with self.db_wrapper.reader_no_transaction() as conn: async with conn.execute( "SELECT unspent.coin_name, " - "unspent.amount, " "unspent.coin_parent, " - "parent.amount, " "parent.coin_parent " "FROM coin_record AS unspent INDEXED BY coin_puzzle_hash " "LEFT JOIN coin_record AS parent ON unspent.coin_parent = parent.coin_name " "WHERE unspent.spent_index = 0 " "AND parent.spent_index > 0 " "AND unspent.puzzle_hash = ? " - "AND parent.puzzle_hash = unspent.puzzle_hash", + "AND parent.puzzle_hash = unspent.puzzle_hash " + "AND parent.amount = unspent.amount", (puzzle_hash,), ) as cursor: rows = list(await cursor.fetchall()) if len(rows) != 1: log.debug("Expected 1 unspent with puzzle hash %s, but found %s", puzzle_hash.hex(), len(rows)) return None - coin_id, coin_amount, parent_id, parent_amount, parent_parent_id = rows[0] + coin_id, parent_id, parent_parent_id = rows[0] return UnspentLineageInfo( - coin_id=bytes32(coin_id), - coin_amount=uint64(int_from_bytes(coin_amount)), - parent_id=bytes32(parent_id), - parent_amount=uint64(int_from_bytes(parent_amount)), - parent_parent_id=bytes32(parent_parent_id), + coin_id=bytes32(coin_id), parent_id=bytes32(parent_id), parent_parent_id=bytes32(parent_parent_id) ) diff --git a/chia/types/eligible_coin_spends.py b/chia/types/eligible_coin_spends.py index e14c640d13..ed0ba602bc 100644 --- a/chia/types/eligible_coin_spends.py +++ b/chia/types/eligible_coin_spends.py @@ -47,9 +47,7 @@ class DedupCoinSpend: @dataclasses.dataclass(frozen=True) class UnspentLineageInfo: coin_id: bytes32 - coin_amount: uint64 parent_id: bytes32 - parent_amount: uint64 parent_parent_id: bytes32 @@ -70,16 +68,19 @@ def set_next_singleton_version( next iteration """ singleton_child = next( - (addition for addition in singleton_additions if addition.puzzle_hash == current_singleton.puzzle_hash), None + ( + addition + for addition in singleton_additions + if addition.puzzle_hash == current_singleton.puzzle_hash and addition.amount == current_singleton.amount + ), + None, ) if singleton_child is None: raise ValueError("Could not find fast forward child singleton.") # Keep track of this in order to chain the next ff fast_forward_spends[current_singleton.puzzle_hash] = UnspentLineageInfo( coin_id=singleton_child.name(), - coin_amount=singleton_child.amount, parent_id=singleton_child.parent_coin_info, - parent_amount=current_singleton.amount, parent_parent_id=current_singleton.parent_coin_info, ) @@ -107,14 +108,10 @@ def perform_the_fast_forward( ValueError if none of the additions are considered to be the singleton's next iteration """ - new_coin = Coin( - unspent_lineage_info.parent_id, spend_data.coin_spend.coin.puzzle_hash, unspent_lineage_info.coin_amount - ) - new_parent = Coin( - unspent_lineage_info.parent_parent_id, - spend_data.coin_spend.coin.puzzle_hash, - unspent_lineage_info.parent_amount, - ) + singleton_ph = spend_data.coin_spend.coin.puzzle_hash + singleton_amount = spend_data.coin_spend.coin.amount + new_coin = Coin(unspent_lineage_info.parent_id, singleton_ph, singleton_amount) + new_parent = Coin(unspent_lineage_info.parent_parent_id, singleton_ph, singleton_amount) # These hold because puzzle hash is not expected to change assert new_coin.name() == unspent_lineage_info.coin_id assert new_parent.name() == unspent_lineage_info.parent_id @@ -126,7 +123,7 @@ def perform_the_fast_forward( for addition in spend_data.additions: patched_addition = Coin(unspent_lineage_info.coin_id, addition.puzzle_hash, addition.amount) patched_additions.append(patched_addition) - if addition.puzzle_hash == spend_data.coin_spend.coin.puzzle_hash: + if addition.puzzle_hash == singleton_ph and addition.amount == singleton_amount: # We found the next version of this singleton singleton_child = patched_addition if singleton_child is None: @@ -135,9 +132,7 @@ def perform_the_fast_forward( # Keep track of this in order to chain the next ff fast_forward_spends[spend_data.coin_spend.coin.puzzle_hash] = UnspentLineageInfo( coin_id=singleton_child.name(), - coin_amount=singleton_child.amount, parent_id=singleton_child.parent_coin_info, - parent_amount=unspent_lineage_info.coin_amount, parent_parent_id=unspent_lineage_info.parent_id, ) return new_coin_spend, patched_additions From 05e9706c13654c3832b1f9217ad6c01e7ecc18d2 Mon Sep 17 00:00:00 2001 From: Almog De Paz Date: Thu, 20 Mar 2025 19:39:22 +0200 Subject: [PATCH 3/7] Check full node state in tl tests (#19392) * fix tl node peak sync * revert pytest change * add weight check, rearrange cases * comments, logs * logs * fix condition * add full node validations to tl test * add block --- chia/_tests/timelord/test_new_peak.py | 87 +++++++++++++++++++++------ 1 file changed, 69 insertions(+), 18 deletions(-) diff --git a/chia/_tests/timelord/test_new_peak.py b/chia/_tests/timelord/test_new_peak.py index 16b3f6ef82..1ea9ab47a7 100644 --- a/chia/_tests/timelord/test_new_peak.py +++ b/chia/_tests/timelord/test_new_peak.py @@ -27,41 +27,72 @@ from chia.types.unfinished_block import UnfinishedBlock class TestNewPeak: @pytest.mark.anyio async def test_timelord_new_peak_basic( - self, bt: BlockTools, timelord: tuple[TimelordAPI, ChiaServer], default_1000_blocks: list[FullBlock] + self, + timelord: tuple[TimelordAPI, ChiaServer], + default_1000_blocks: list[FullBlock], + one_node: tuple[list[FullNodeService], list[FullNodeSimulator], BlockTools], ) -> None: + [full_node_service], _, bt = one_node + full_node = full_node_service._node async with create_blockchain(bt.constants, 2) as (b1, _): async with create_blockchain(bt.constants, 2) as (b2, _): timelord_api, _ = timelord for block in default_1000_blocks: await _validate_and_add_block(b1, block) await _validate_and_add_block(b2, block) + await full_node.add_block(block) peak = timelord_peak_from_block(b1, default_1000_blocks[-1]) assert peak is not None assert timelord_api.timelord.new_peak is None await timelord_api.new_peak_timelord(peak) - assert timelord_api.timelord.new_peak is not None - assert timelord_api.timelord.new_peak.reward_chain_block.height == peak.reward_chain_block.height + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None + assert ( + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == peak.reward_chain_block.get_hash() + ) blocks = bt.get_consecutive_blocks(1, default_1000_blocks) await _validate_and_add_block(b1, blocks[-1]) await _validate_and_add_block(b2, blocks[-1]) await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks[-1])) - assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks[-1].height + await full_node.add_block(blocks[-1]) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None + assert ( + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == blocks[-1].reward_chain_block.get_hash() + ) + fn_peak = full_node.blockchain.get_peak() + assert fn_peak is not None and fn_peak.header_hash == blocks[-1].header_hash blocks_1 = bt.get_consecutive_blocks(2, blocks) await _validate_and_add_block(b1, blocks_1[-2]) await _validate_and_add_block(b1, blocks_1[-1]) await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks_1[-2])) + await full_node.add_block(blocks_1[-2]) await timelord_api.new_peak_timelord(timelord_peak_from_block(b1, blocks_1[-1])) - assert timelord_api.timelord.new_peak.reward_chain_block.height == blocks_1[-1].height + await full_node.add_block(blocks_1[-1]) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None + assert ( + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == blocks_1[-1].reward_chain_block.get_hash() + ) + fn_peak = full_node.blockchain.get_peak() + assert fn_peak is not None and fn_peak.header_hash == blocks_1[-1].header_hash # new unknown peak, weight less then curr peak blocks_2 = bt.get_consecutive_blocks(1, blocks) await _validate_and_add_block(b2, blocks_2[-1]) await timelord_api.new_peak_timelord(timelord_peak_from_block(b2, blocks_2[-1])) + await full_node.add_block(blocks_2[-1]) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) assert timelord_api.timelord.last_state.last_weight == blocks_1[-1].weight assert timelord_api.timelord.last_state.total_iters == blocks_1[-1].reward_chain_block.total_iters + fn_peak = full_node.blockchain.get_peak() + assert fn_peak is not None and fn_peak.header_hash == blocks_1[-1].header_hash @pytest.mark.anyio async def test_timelord_new_peak_unfinished_not_orphaned( @@ -106,7 +137,7 @@ class TestNewPeak: assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters await timelord_api.new_peak_timelord(new_peak) - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) assert ( timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() @@ -135,8 +166,11 @@ class TestNewPeak: assert timelord_api.timelord.new_peak is None await timelord_api.new_peak_timelord(peak) assert timelord_api.timelord.new_peak is not None + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None assert ( - timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash() + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == peak.reward_chain_block.get_hash() ) # make two new blocks on tip, block_2 has higher total iterations @@ -177,7 +211,7 @@ class TestNewPeak: # add block_2 peak and make sure we skip it and prefer to finish block_1 assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters await timelord_api.new_peak_timelord(new_peak) - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) # check that peak did not change assert ( @@ -238,9 +272,11 @@ class TestNewPeak: assert peak is not None assert timelord_api.timelord.new_peak is None await timelord_api.new_peak_timelord(peak) - assert timelord_api.timelord.new_peak is not None + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None assert ( - timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash() + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == peak.reward_chain_block.get_hash() ) # make two new blocks on tip @@ -277,7 +313,7 @@ class TestNewPeak: assert timelord_unf_block.reward_chain_block.total_iters <= new_peak.reward_chain_block.total_iters await timelord_api.new_peak_timelord(new_peak) - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) assert ( timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() @@ -286,22 +322,30 @@ class TestNewPeak: @pytest.mark.anyio async def test_timelord_new_peak_unfinished_eos( - self, bt: BlockTools, timelord: tuple[TimelordAPI, ChiaServer], default_1000_blocks: list[FullBlock] + self, + one_node: tuple[list[FullNodeService], list[FullNodeSimulator], BlockTools], + timelord: tuple[TimelordAPI, ChiaServer], + default_1000_blocks: list[FullBlock], ) -> None: + [full_node_service], _, bt = one_node + full_node = full_node_service._node async with create_blockchain(bt.constants, 2) as (b1, _): async with create_blockchain(bt.constants, 2) as (b2, _): timelord_api, _ = timelord for block in default_1000_blocks: await _validate_and_add_block(b1, block) await _validate_and_add_block(b2, block) + await full_node.add_block(block) peak = timelord_peak_from_block(b1, default_1000_blocks[-1]) assert peak is not None assert timelord_api.timelord.new_peak is None await timelord_api.new_peak_timelord(peak) - assert timelord_api.timelord.new_peak is not None + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) + assert timelord_api.timelord.last_state.peak is not None assert ( - timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash() + timelord_api.timelord.last_state.peak.reward_chain_block.get_hash() + == peak.reward_chain_block.get_hash() ) # make two new blocks on tip, block_2 is in a new slot @@ -315,6 +359,9 @@ class TestNewPeak: await _validate_and_add_block(b1, block_1) await _validate_and_add_block(b2, block_2) + await full_node.add_block(block_2) + fn_peak = full_node.blockchain.get_peak() + assert fn_peak is not None and fn_peak.header_hash == block_2.header_hash block_record = b2.block_record(block_2.header_hash) sub_slot_iters, difficulty = get_next_sub_slot_iters_and_difficulty( @@ -340,7 +387,8 @@ class TestNewPeak: new_peak = timelord_peak_from_block(b1, block_1) assert timelord_unf_block.reward_chain_block.total_iters >= new_peak.reward_chain_block.total_iters await timelord_api.new_peak_timelord(new_peak) - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await full_node.add_block(block_1) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) # make sure we switch to lower iteration peak assert ( @@ -348,6 +396,9 @@ class TestNewPeak: == new_peak.reward_chain_block.get_hash() ) + fn_peak = full_node.blockchain.get_peak() + assert fn_peak is not None and fn_peak.header_hash == block_1.header_hash + @pytest.mark.anyio async def test_timelord_new_peak_node_sync( self, @@ -373,7 +424,7 @@ class TestNewPeak: assert ( timelord_api.timelord.new_peak.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash() ) - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) # make two new blocks on tip, block_2 has higher total iterations block_1 = bt.get_consecutive_blocks(1, default_1000_blocks)[-1] block_2 = bt.get_consecutive_blocks( @@ -394,7 +445,7 @@ class TestNewPeak: assert timelord_api.timelord.new_peak is not None assert peak.header_hash == block_2.header_hash assert peak_tl.reward_chain_block.get_hash() == peak.reward_chain_block.get_hash() - await time_out_assert(60, peak_new_peak_is_none, True, timelord_api) + await time_out_assert(60, tl_new_peak_is_none, True, timelord_api) await full_node.add_block(block_1) await _validate_and_add_block(b1, block_1) @@ -491,5 +542,5 @@ def timelord_peak_from_block( ) -def peak_new_peak_is_none(timelord: TimelordAPI) -> bool: +def tl_new_peak_is_none(timelord: TimelordAPI) -> bool: return timelord.timelord.new_peak is None From bbb65220f1b23348f9c36c834622b4c6b6d215a9 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 21 Mar 2025 17:30:10 -0400 Subject: [PATCH 4/7] simplify chia command key root path handling (#19419) --- chia/cmds/chia.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/chia/cmds/chia.py b/chia/cmds/chia.py index ba20ee0c22..a754c330e4 100644 --- a/chia/cmds/chia.py +++ b/chia/cmds/chia.py @@ -58,7 +58,7 @@ CONTEXT_SETTINGS = { def cli( ctx: click.Context, root_path: str, - keys_root_path: Optional[str] = None, + keys_root_path: str, passphrase_file: Optional[TextIOWrapper] = None, ) -> None: from pathlib import Path @@ -66,11 +66,10 @@ def cli( context = ChiaCliContext.set_default(ctx=ctx) context.root_path = Path(root_path) - # keys_root_path and passphrase_file will be None if the passphrase options have been - # scrubbed from the CLI options - if keys_root_path is not None: - set_keys_root_path(Path(keys_root_path)) + set_keys_root_path(Path(keys_root_path)) + # passphrase_file will be None if the passphrase options have been + # scrubbed from the CLI options if passphrase_file is not None: import sys From 26fcbe70e6bb8c1f4820e0283879ab0a10246461 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Fri, 21 Mar 2025 17:31:13 -0400 Subject: [PATCH 5/7] subdirectory for chia dev commands (#19418) * move `chia dev` to its own subdirectory * rename `dev.py` to `main.py` * fixup * more --- chia/_tests/cmds/test_dev_gh.py | 2 +- chia/cmds/chia.py | 2 +- chia/cmds/dev/__init__.py | 0 chia/cmds/{ => dev}/gh.py | 0 chia/cmds/{ => dev}/installers.py | 0 chia/cmds/{dev.py => dev/main.py} | 8 ++++---- chia/cmds/{ => dev}/mempool.py | 2 +- chia/cmds/{ => dev}/mempool_funcs.py | 0 chia/cmds/{ => dev}/sim.py | 0 9 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 chia/cmds/dev/__init__.py rename chia/cmds/{ => dev}/gh.py (100%) rename chia/cmds/{ => dev}/installers.py (100%) rename chia/cmds/{dev.py => dev/main.py} (63%) rename chia/cmds/{ => dev}/mempool.py (95%) rename chia/cmds/{ => dev}/mempool_funcs.py (100%) rename chia/cmds/{ => dev}/sim.py (100%) diff --git a/chia/_tests/cmds/test_dev_gh.py b/chia/_tests/cmds/test_dev_gh.py index 1d397b1b63..163388fb6d 100644 --- a/chia/_tests/cmds/test_dev_gh.py +++ b/chia/_tests/cmds/test_dev_gh.py @@ -14,7 +14,7 @@ from _pytest.capture import CaptureFixture import chia._tests from chia._tests.util.misc import Marks, datacases -from chia.cmds.gh import Per, TestCMD, get_gh_token +from chia.cmds.dev.gh import Per, TestCMD, get_gh_token test_root = Path(chia._tests.__file__).parent diff --git a/chia/cmds/chia.py b/chia/cmds/chia.py index a754c330e4..140e04fedb 100644 --- a/chia/cmds/chia.py +++ b/chia/cmds/chia.py @@ -12,7 +12,7 @@ from chia.cmds.completion import completion from chia.cmds.configure import configure_cmd from chia.cmds.data import data_cmd from chia.cmds.db import db_cmd -from chia.cmds.dev import dev_cmd +from chia.cmds.dev.main import dev_cmd from chia.cmds.farm import farm_cmd from chia.cmds.init import init_cmd from chia.cmds.keys import keys_cmd diff --git a/chia/cmds/dev/__init__.py b/chia/cmds/dev/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/chia/cmds/gh.py b/chia/cmds/dev/gh.py similarity index 100% rename from chia/cmds/gh.py rename to chia/cmds/dev/gh.py diff --git a/chia/cmds/installers.py b/chia/cmds/dev/installers.py similarity index 100% rename from chia/cmds/installers.py rename to chia/cmds/dev/installers.py diff --git a/chia/cmds/dev.py b/chia/cmds/dev/main.py similarity index 63% rename from chia/cmds/dev.py rename to chia/cmds/dev/main.py index 6c0c2263a8..ea96c77eab 100644 --- a/chia/cmds/dev.py +++ b/chia/cmds/dev/main.py @@ -2,10 +2,10 @@ from __future__ import annotations import click -from chia.cmds.gh import gh_group -from chia.cmds.installers import installers_group -from chia.cmds.mempool import mempool_cmd -from chia.cmds.sim import sim_cmd +from chia.cmds.dev.gh import gh_group +from chia.cmds.dev.installers import installers_group +from chia.cmds.dev.mempool import mempool_cmd +from chia.cmds.dev.sim import sim_cmd @click.group("dev", help="Developer commands and tools") diff --git a/chia/cmds/mempool.py b/chia/cmds/dev/mempool.py similarity index 95% rename from chia/cmds/mempool.py rename to chia/cmds/dev/mempool.py index 0fc7717252..31c2da7aa2 100644 --- a/chia/cmds/mempool.py +++ b/chia/cmds/dev/mempool.py @@ -6,7 +6,7 @@ from typing import Optional import click from chia.cmds.cmd_classes import ChiaCliContext -from chia.cmds.mempool_funcs import create_block_async, export_mempool_async, import_mempool_async +from chia.cmds.dev.mempool_funcs import create_block_async, export_mempool_async, import_mempool_async @click.group("mempool", help="Debug the mempool") diff --git a/chia/cmds/mempool_funcs.py b/chia/cmds/dev/mempool_funcs.py similarity index 100% rename from chia/cmds/mempool_funcs.py rename to chia/cmds/dev/mempool_funcs.py diff --git a/chia/cmds/sim.py b/chia/cmds/dev/sim.py similarity index 100% rename from chia/cmds/sim.py rename to chia/cmds/dev/sim.py From b900985ddf72970267591e8cc29f94e653c7ea07 Mon Sep 17 00:00:00 2001 From: Amine Khaldi Date: Mon, 24 Mar 2025 22:40:45 +0100 Subject: [PATCH 6/7] CHIA-2580 Significantly speedup mempool manager tests by not forcing them to request unneeded fixtures (#19414) Don't force all mempool manager tests to request test_wallet and transactions_1000 fixtures. --- chia/_tests/core/mempool/test_mempool_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index ad612e769c..5736fea8b8 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -2509,12 +2509,12 @@ async def test_advancing_ff(use_optimization: bool) -> None: assert spend.latest_singleton_coin == spend_c.coin.name() -@pytest.fixture(name="test_wallet", autouse=True) +@pytest.fixture(name="test_wallet") def test_wallet_fixture() -> WalletTool: return WalletTool(DEFAULT_CONSTANTS) -@pytest.fixture(name="transactions_1000", autouse=True) +@pytest.fixture(name="transactions_1000") def transactions_1000_fixture(test_wallet: WalletTool, seeded_random: random.Random) -> list[SpendBundle]: op = ConditionOpcode bundles: list[SpendBundle] = [] From 99448387ada5dd492d06261ab823c59c2a55b231 Mon Sep 17 00:00:00 2001 From: Kyle Altendorf Date: Mon, 24 Mar 2025 17:41:06 -0400 Subject: [PATCH 7/7] add the keys root path to `ChiaCliContext` (#19420) * add the keys root path to `ChiaCliContext` * tidy --- chia/cmds/chia.py | 1 + chia/cmds/cmd_classes.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/chia/cmds/chia.py b/chia/cmds/chia.py index 140e04fedb..5f524db370 100644 --- a/chia/cmds/chia.py +++ b/chia/cmds/chia.py @@ -65,6 +65,7 @@ def cli( context = ChiaCliContext.set_default(ctx=ctx) context.root_path = Path(root_path) + context.keys_root_path = Path(keys_root_path) set_keys_root_path(Path(keys_root_path)) diff --git a/chia/cmds/cmd_classes.py b/chia/cmds/cmd_classes.py index d04b6270b0..1352f41693 100644 --- a/chia/cmds/cmd_classes.py +++ b/chia/cmds/cmd_classes.py @@ -25,7 +25,7 @@ from chia_rs.sized_bytes import bytes32 from typing_extensions import dataclass_transform from chia.util.byte_types import hexstr_to_bytes -from chia.util.default_root import DEFAULT_ROOT_PATH +from chia.util.default_root import DEFAULT_KEYS_ROOT_PATH, DEFAULT_ROOT_PATH from chia.util.streamable import is_type_SpecificOptional SyncCmd = Callable[..., None] @@ -68,6 +68,7 @@ class ChiaCliContext: context_dict_key: ClassVar[str] = "_chia_cli_context" root_path: pathlib.Path = DEFAULT_ROOT_PATH + keys_root_path: pathlib.Path = DEFAULT_KEYS_ROOT_PATH expected_prefix: Optional[str] = None rpc_port: Optional[int] = None keys_fingerprint: Optional[int] = None