|
40 | 40 | from chia._tests.util.misc import wallet_height_at_least
|
41 | 41 | from chia._tests.util.setup_nodes import OldSimulatorsAndWallets, SimulatorsAndWalletsServices
|
42 | 42 | from chia._tests.util.time_out_assert import time_out_assert, time_out_assert_custom_interval, time_out_messages
|
| 43 | +from chia.consensus.augmented_chain import AugmentedBlockchain |
43 | 44 | from chia.consensus.block_body_validation import ForkInfo
|
44 | 45 | from chia.consensus.blockchain import Blockchain
|
45 | 46 | from chia.consensus.get_block_challenge import get_block_challenge
|
|
91 | 92 | from chia.types.condition_with_args import ConditionWithArgs
|
92 | 93 | from chia.types.mempool_inclusion_status import MempoolInclusionStatus
|
93 | 94 | from chia.types.peer_info import PeerInfo, TimestampedPeerInfo
|
94 |
| -from chia.types.spend_bundle import estimate_fees |
95 | 95 | from chia.types.validation_state import ValidationState
|
96 |
| -from chia.util.augmented_chain import AugmentedBlockchain |
97 | 96 | from chia.util.errors import ConsensusError, Err
|
98 | 97 | from chia.util.hash import std_hash
|
99 | 98 | from chia.util.limited_semaphore import LimitedSemaphore
|
100 | 99 | from chia.util.recursive_replace import recursive_replace
|
101 | 100 | from chia.util.task_referencer import create_referenced_task
|
102 | 101 | from chia.util.vdf_prover import get_vdf_info_and_proof
|
| 102 | +from chia.wallet.estimate_fees import estimate_fees |
103 | 103 | from chia.wallet.transaction_record import TransactionRecord
|
104 | 104 | from chia.wallet.util.tx_config import DEFAULT_TX_CONFIG
|
105 | 105 | from chia.wallet.wallet_node import WalletNode
|
@@ -2995,6 +2995,117 @@ async def test_declare_proof_of_space_overflow(
|
2995 | 2995 | assert full_node_api.full_node.blockchain.get_peak_height() == block.height
|
2996 | 2996 |
|
2997 | 2997 |
|
| 2998 | +@pytest.mark.anyio |
| 2999 | +async def test_add_unfinished_block_with_generator_refs( |
| 3000 | + wallet_nodes: tuple[ |
| 3001 | + FullNodeSimulator, FullNodeSimulator, ChiaServer, ChiaServer, WalletTool, WalletTool, BlockTools |
| 3002 | + ], |
| 3003 | +) -> None: |
| 3004 | + """ |
| 3005 | + Robustly test add_unfinished_block, including generator refs and edge cases. |
| 3006 | + Assert block height after each added block. |
| 3007 | + """ |
| 3008 | + full_node_1, _, _, _, wallet, wallet_receiver, bt = wallet_nodes |
| 3009 | + coinbase_puzzlehash = wallet.get_new_puzzlehash() |
| 3010 | + blocks = bt.get_consecutive_blocks( |
| 3011 | + 5, block_list_input=[], guarantee_transaction_block=True, farmer_reward_puzzle_hash=coinbase_puzzlehash |
| 3012 | + ) |
| 3013 | + for i in range(3): |
| 3014 | + blocks = bt.get_consecutive_blocks( |
| 3015 | + 1, |
| 3016 | + block_list_input=blocks, |
| 3017 | + guarantee_transaction_block=True, |
| 3018 | + transaction_data=wallet.generate_signed_transaction( |
| 3019 | + uint64(1000), |
| 3020 | + wallet_receiver.get_new_puzzlehash(), |
| 3021 | + blocks[-3].get_included_reward_coins()[0], |
| 3022 | + ), |
| 3023 | + block_refs=[blocks[-1].height, blocks[-2].height], |
| 3024 | + ) |
| 3025 | + |
| 3026 | + for idx, block in enumerate(blocks[:-1]): |
| 3027 | + await full_node_1.full_node.add_block(block) |
| 3028 | + # Assert block height after each add |
| 3029 | + peak = full_node_1.full_node.blockchain.get_peak() |
| 3030 | + assert peak is not None and peak.height == blocks[-2].height |
| 3031 | + block = blocks[-1] |
| 3032 | + unf = unfinished_from_full_block(block) |
| 3033 | + |
| 3034 | + # Test with missing generator ref (should raise ConsensusError) |
| 3035 | + bad_refs = [uint32(9999999)] |
| 3036 | + unf_bad = unf.replace(transactions_generator_ref_list=bad_refs) |
| 3037 | + with pytest.raises(Exception) as excinfo: |
| 3038 | + await full_node_1.full_node.add_unfinished_block(unf_bad, None) |
| 3039 | + assert excinfo.value.args[0] == Err.GENERATOR_REF_HAS_NO_GENERATOR |
| 3040 | + |
| 3041 | + unf_no_gen = unf.replace(transactions_generator_ref_list=bad_refs, transactions_generator=None) |
| 3042 | + with pytest.raises(Exception) as excinfo: |
| 3043 | + await full_node_1.full_node.add_unfinished_block(unf_no_gen, None) |
| 3044 | + assert isinstance(excinfo.value, ConsensusError) |
| 3045 | + assert excinfo.value.code == Err.INVALID_TRANSACTIONS_GENERATOR_HASH |
| 3046 | + |
| 3047 | + # Duplicate generator refs (should raise ConsensusError or be rejected) |
| 3048 | + dup_ref = blocks[-2].height |
| 3049 | + unf_dup_refs = unf.replace(transactions_generator_ref_list=[dup_ref, dup_ref]) |
| 3050 | + with pytest.raises(Exception) as excinfo: |
| 3051 | + await full_node_1.full_node.add_unfinished_block(unf_dup_refs, None) |
| 3052 | + assert isinstance(excinfo.value, ConsensusError) |
| 3053 | + assert excinfo.value.code == Err.INVALID_TRANSACTIONS_GENERATOR_REFS_ROOT |
| 3054 | + |
| 3055 | + # ref block with no generator |
| 3056 | + unf_bad_ref = unf.replace(transactions_generator_ref_list=[uint32(2)]) |
| 3057 | + with pytest.raises(Exception) as excinfo: |
| 3058 | + await full_node_1.full_node.add_unfinished_block(unf_bad_ref, None) |
| 3059 | + assert excinfo.value.args[0] == Err.GENERATOR_REF_HAS_NO_GENERATOR |
| 3060 | + |
| 3061 | + # Generator ref points to block not yet in store (simulate by using a future height) |
| 3062 | + unf_future_ref = unf.replace(transactions_generator_ref_list=[uint32(blocks[-1].height + 1000)]) |
| 3063 | + with pytest.raises(Exception) as excinfo: |
| 3064 | + await full_node_1.full_node.add_unfinished_block(unf_future_ref, None) |
| 3065 | + assert excinfo.value.args[0] == Err.GENERATOR_REF_HAS_NO_GENERATOR |
| 3066 | + |
| 3067 | + # Generator ref points to itself |
| 3068 | + unf_self_ref = unf.replace(transactions_generator_ref_list=[block.height]) |
| 3069 | + # Should raise ConsensusError or be rejected |
| 3070 | + with pytest.raises(Exception) as excinfo: |
| 3071 | + await full_node_1.full_node.add_unfinished_block(unf_self_ref, None) |
| 3072 | + assert excinfo.value.args[0] == Err.GENERATOR_REF_HAS_NO_GENERATOR |
| 3073 | + |
| 3074 | + # unsorted Generator refs |
| 3075 | + unf_unsorted = unf.replace(transactions_generator_ref_list=[blocks[-2].height, blocks[-1].height]) |
| 3076 | + with pytest.raises(Exception) as excinfo: |
| 3077 | + await full_node_1.full_node.add_unfinished_block(unf_unsorted, None) |
| 3078 | + assert excinfo.value.args[0] == Err.GENERATOR_REF_HAS_NO_GENERATOR |
| 3079 | + |
| 3080 | + # valid unfinished block with refs |
| 3081 | + await full_node_1.full_node.add_unfinished_block(unf, None) |
| 3082 | + assert full_node_1.full_node.full_node_store.get_unfinished_block(unf.partial_hash) is not None |
| 3083 | + assert full_node_1.full_node.full_node_store.seen_unfinished_block(unf.get_hash()) |
| 3084 | + |
| 3085 | + # Test disconnected block |
| 3086 | + fork_blocks = blocks[:-3] |
| 3087 | + for i in range(3): |
| 3088 | + # Add a block with a transaction |
| 3089 | + fork_blocks = bt.get_consecutive_blocks( |
| 3090 | + 1, |
| 3091 | + block_list_input=fork_blocks, |
| 3092 | + guarantee_transaction_block=True, |
| 3093 | + transaction_data=wallet.generate_signed_transaction( |
| 3094 | + uint64(1000), |
| 3095 | + wallet_receiver.get_new_puzzlehash(), |
| 3096 | + fork_blocks[-3].get_included_reward_coins()[0], |
| 3097 | + ), |
| 3098 | + min_signage_point=blocks[-1].reward_chain_block.signage_point_index + 1, |
| 3099 | + seed=b"random_seed", |
| 3100 | + block_refs=[fork_blocks[-2].height], |
| 3101 | + ) |
| 3102 | + |
| 3103 | + disconnected_unf = unfinished_from_full_block(fork_blocks[-1]) |
| 3104 | + # Should not raise, but should not add the block either |
| 3105 | + await full_node_1.full_node.add_unfinished_block(disconnected_unf, None) |
| 3106 | + assert disconnected_unf.get_hash() not in full_node_1.full_node.full_node_store.seen_unfinished_blocks |
| 3107 | + |
| 3108 | + |
2998 | 3109 | def unfinished_from_full_block(block: FullBlock) -> UnfinishedBlock:
|
2999 | 3110 | unfinished_block_expected = UnfinishedBlock(
|
3000 | 3111 | block.finished_sub_slots,
|
|
0 commit comments