diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index 24d92fe0efcb..7f94280be643 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -579,7 +579,6 @@ def make_bundle_spends_map_and_fee( bundle_coin_spends[coin_id] = BundleCoinSpend( coin_spend=coin_spend, eligible_for_dedup=bool(spend_conds.flags & ELIGIBLE_FOR_DEDUP), - eligible_for_fast_forward=bool(spend_conds.flags & ELIGIBLE_FOR_FF), additions=additions, cost=uint64(spend_conds.condition_cost + spend_conds.execution_cost), latest_singleton_lineage=UnspentLineageInfo(coin_id, coin_spend.coin.parent_coin_info, bytes32([0] * 32)) @@ -890,9 +889,13 @@ def mk_bcs(coin_spend: CoinSpend, flags: int = 0) -> BundleCoinSpend: return BundleCoinSpend( coin_spend=coin_spend, eligible_for_dedup=bool(flags & ELIGIBLE_FOR_DEDUP), - eligible_for_fast_forward=bool(flags & ELIGIBLE_FOR_FF), additions=[], cost=uint64(0), + latest_singleton_lineage=UnspentLineageInfo( + coin_spend.coin.name(), coin_spend.coin.parent_coin_info, bytes32([0] * 32) + ) + if flags & ELIGIBLE_FOR_FF + else None, ) @@ -1802,16 +1805,16 @@ async def test_bundle_coin_spends() -> None: assert mi123e.bundle_coin_spends[coins[i].name()] == BundleCoinSpend( coin_spend=sb123.coin_spends[i], eligible_for_dedup=False, - eligible_for_fast_forward=False, additions=[Coin(coins[i].name(), IDENTITY_PUZZLE_HASH, coins[i].amount)], cost=uint64(ConditionCost.CREATE_COIN.value + ConditionCost.AGG_SIG.value + execution_cost), + latest_singleton_lineage=None, ) assert mi123e.bundle_coin_spends[coins[3].name()] == BundleCoinSpend( coin_spend=eligible_sb.coin_spends[0], eligible_for_dedup=True, - eligible_for_fast_forward=False, additions=[Coin(coins[3].name(), IDENTITY_PUZZLE_HASH, coins[3].amount)], cost=uint64(ConditionCost.CREATE_COIN.value + execution_cost), + latest_singleton_lineage=None, ) @@ -2463,7 +2466,7 @@ async def test_new_peak_ff_eviction( item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None singleton_name = singleton_spend.coin.name() - assert item.bundle_coin_spends[singleton_name].eligible_for_fast_forward + assert item.bundle_coin_spends[singleton_name].supports_fast_forward latest_singleton_lineage = item.bundle_coin_spends[singleton_name].latest_singleton_lineage assert latest_singleton_lineage is not None assert latest_singleton_lineage.coin_id == singleton_name @@ -2495,7 +2498,7 @@ async def test_new_peak_ff_eviction( else: item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None - assert item.bundle_coin_spends[singleton_spend.coin.name()].eligible_for_fast_forward + assert item.bundle_coin_spends[singleton_spend.coin.name()].supports_fast_forward latest_singleton_lineage = item.bundle_coin_spends[singleton_spend.coin.name()].latest_singleton_lineage assert latest_singleton_lineage is not None assert latest_singleton_lineage.coin_id == singleton_spend.coin.name() @@ -2552,9 +2555,9 @@ async def test_multiple_ff(use_optimization: bool) -> None: item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None - assert item.bundle_coin_spends[singleton_spend1.coin.name()].eligible_for_fast_forward - assert item.bundle_coin_spends[singleton_spend2.coin.name()].eligible_for_fast_forward - assert not item.bundle_coin_spends[coin_spend.coin.name()].eligible_for_fast_forward + assert item.bundle_coin_spends[singleton_spend1.coin.name()].supports_fast_forward + assert item.bundle_coin_spends[singleton_spend2.coin.name()].supports_fast_forward + assert not item.bundle_coin_spends[coin_spend.coin.name()].supports_fast_forward # spend the singleton coin2 and make coin3 the latest version coins.update_lineage(singleton_ph, singleton_spend3.coin) @@ -2616,7 +2619,7 @@ async def test_advancing_ff(use_optimization: bool) -> None: item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None spend = item.bundle_coin_spends[spend_a.coin.name()] - assert spend.eligible_for_fast_forward + assert spend.supports_fast_forward assert spend.latest_singleton_lineage is not None assert spend.latest_singleton_lineage.coin_id == spend_a.coin.name() @@ -2628,7 +2631,7 @@ async def test_advancing_ff(use_optimization: bool) -> None: item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None spend = item.bundle_coin_spends[spend_a.coin.name()] - assert spend.eligible_for_fast_forward + assert spend.supports_fast_forward assert spend.latest_singleton_lineage is not None assert spend.latest_singleton_lineage.coin_id == spend_b.coin.name() @@ -2640,7 +2643,7 @@ async def test_advancing_ff(use_optimization: bool) -> None: item = mempool_manager.get_mempool_item(bundle.name()) assert item is not None spend = item.bundle_coin_spends[spend_a.coin.name()] - assert spend.eligible_for_fast_forward + assert spend.supports_fast_forward assert spend.latest_singleton_lineage is not None assert spend.latest_singleton_lineage.coin_id == spend_c.coin.name() diff --git a/chia/_tests/core/mempool/test_singleton_fast_forward.py b/chia/_tests/core/mempool/test_singleton_fast_forward.py index 3d6a01a38b6a..183f1b214422 100644 --- a/chia/_tests/core/mempool/test_singleton_fast_forward.py +++ b/chia/_tests/core/mempool/test_singleton_fast_forward.py @@ -52,7 +52,7 @@ def test_process_fast_forward_spends_nothing_to_do() -> None: sb = spend_bundle_from_conditions(conditions, TEST_COIN, sig) item = mempool_item_from_spendbundle(sb) # This coin is not eligible for fast forward - assert item.bundle_coin_spends[TEST_COIN_ID].eligible_for_fast_forward is False + assert not item.bundle_coin_spends[TEST_COIN_ID].supports_fast_forward internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends) original_version = dataclasses.replace(internal_mempool_item) singleton_ff = SingletonFastForward() @@ -63,28 +63,6 @@ def test_process_fast_forward_spends_nothing_to_do() -> None: assert bundle_coin_spends == original_version.bundle_coin_spends -def test_process_fast_forward_spends_unknown_ff() -> None: - """ - This tests the case when we process for the first time but we are unable - to lookup the latest version from the item's latest singleton lineage - """ - test_coin = Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, uint64(1)) - conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, 1]] - sb = spend_bundle_from_conditions(conditions, test_coin) - item = mempool_item_from_spendbundle(sb) - # The coin is eligible for fast forward - assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True - item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = None - internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends) - singleton_ff = SingletonFastForward() - # We have no fast forward records yet, so we'll process this coin for the - # first time here, but the item's latest singleton lineage returns None - with pytest.raises(ValueError, match="Cannot proceed with singleton spend fast forward"): - singleton_ff.process_fast_forward_spends( - mempool_item=internal_mempool_item, height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS - ) - - def test_process_fast_forward_spends_latest_unspent() -> None: """ This tests the case when we are the latest singleton version already, so @@ -103,7 +81,7 @@ def test_process_fast_forward_spends_latest_unspent() -> None: conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, test_amount]] sb = spend_bundle_from_conditions(conditions, test_coin) item = mempool_item_from_spendbundle(sb) - assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True + assert item.bundle_coin_spends[test_coin.name()].supports_fast_forward item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = test_unspent_lineage_info internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends) original_version = dataclasses.replace(internal_mempool_item) @@ -168,12 +146,12 @@ def test_perform_the_fast_forward() -> None: "517b0dadb0c310ded24dd86dff8205398080ff808080" ) test_coin_spend = CoinSpend(test_coin, test_puzzle_reveal, test_solution) - test_spend_data = BundleCoinSpend(test_coin_spend, False, True, [test_child_coin], uint64(0)) test_unspent_lineage_info = UnspentLineageInfo( coin_id=latest_unspent_coin.name(), parent_id=latest_unspent_coin.parent_coin_info, parent_parent_id=test_child_coin.parent_coin_info, ) + test_spend_data = BundleCoinSpend(test_coin_spend, False, [test_child_coin], uint64(0), test_unspent_lineage_info) # Start from a fresh state of fast forward spends fast_forward_spends: dict[bytes32, UnspentLineageInfo] = {} # Perform the fast forward on the test coin (the grandparent) diff --git a/chia/full_node/eligible_coin_spends.py b/chia/full_node/eligible_coin_spends.py index 559b682a2514..e67705dcc5f6 100644 --- a/chia/full_node/eligible_coin_spends.py +++ b/chia/full_node/eligible_coin_spends.py @@ -196,7 +196,7 @@ def process_fast_forward_spends( new_bundle_coin_spends = {} fast_forwarded_spends = 0 for coin_id, spend_data in mempool_item.bundle_coin_spends.items(): - if not spend_data.eligible_for_fast_forward: + if not spend_data.supports_fast_forward: # Nothing to do for this spend, moving on new_coin_spends.append(spend_data.coin_spend) new_bundle_coin_spends[coin_id] = spend_data @@ -210,8 +210,7 @@ def process_fast_forward_spends( unspent_lineage_info = self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash) if unspent_lineage_info is None: # We didn't, so let's check the item's latest lineage info - if spend_data.latest_singleton_lineage is None: - raise ValueError("Cannot proceed with singleton spend fast forward.") + assert spend_data.latest_singleton_lineage is not None unspent_lineage_info = spend_data.latest_singleton_lineage # See if we're the most recent version if unspent_lineage_info.coin_id == coin_id: @@ -235,9 +234,9 @@ def process_fast_forward_spends( new_bundle_coin_spends[new_coin_spend.coin.name()] = BundleCoinSpend( coin_spend=new_coin_spend, eligible_for_dedup=spend_data.eligible_for_dedup, - eligible_for_fast_forward=spend_data.eligible_for_fast_forward, additions=patched_additions, cost=spend_data.cost, + latest_singleton_lineage=self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash), ) # Update the list of coins spends that will make the new fast # forward spend bundle @@ -258,9 +257,9 @@ def process_fast_forward_spends( new_bundle_coin_spends[new_coin_spend.coin.name()] = BundleCoinSpend( coin_spend=new_coin_spend, eligible_for_dedup=spend_data.eligible_for_dedup, - eligible_for_fast_forward=spend_data.eligible_for_fast_forward, additions=patched_additions, cost=spend_data.cost, + latest_singleton_lineage=self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash), ) # Update the list of coins spends that make the new fast forward bundle new_coin_spends.append(new_coin_spend) diff --git a/chia/full_node/mempool.py b/chia/full_node/mempool.py index ade1ee17ca49..f175c3c3c106 100644 --- a/chia/full_node/mempool.py +++ b/chia/full_node/mempool.py @@ -611,7 +611,7 @@ def create_bundle_from_mempool_items( # might fit, but we also want to avoid spending too much # time on potentially expensive ones, hence this shortcut. if any( - sd.eligible_for_dedup or sd.eligible_for_fast_forward for sd in item.bundle_coin_spends.values() + sd.eligible_for_dedup or sd.supports_fast_forward for sd in item.bundle_coin_spends.values() ): log.info(f"Skipping transaction with dedup or FF spends {name}") continue diff --git a/chia/full_node/mempool_manager.py b/chia/full_node/mempool_manager.py index 034afe4b0a13..273a8909e237 100644 --- a/chia/full_node/mempool_manager.py +++ b/chia/full_node/mempool_manager.py @@ -243,7 +243,7 @@ def check_removals( conflicts = set() for coin_id, coin_bcs in bundle_coin_spends.items(): # 1. Checks if it's been spent already - if removals[coin_id].spent and not coin_bcs.eligible_for_fast_forward: + if removals[coin_id].spent and not coin_bcs.supports_fast_forward: return Err.DOUBLE_SPEND, [] # 2. Checks if there's a mempool conflict @@ -269,12 +269,12 @@ def check_removals( return Err.INVALID_SPEND_BUNDLE, [] # if the spend we're adding to the mempool is not DEDUP nor FF, it's # just a regular conflict - if not coin_bcs.eligible_for_fast_forward and not coin_bcs.eligible_for_dedup: + if not coin_bcs.supports_fast_forward and not coin_bcs.eligible_for_dedup: conflicts.add(item) # if the spend we're adding is FF, but there's a conflicting spend # that isn't FF, they can't be chained, so that's a conflict - elif coin_bcs.eligible_for_fast_forward and not conflict_bcs.eligible_for_fast_forward: + elif coin_bcs.supports_fast_forward and not conflict_bcs.supports_fast_forward: conflicts.add(item) # if the spend we're adding is DEDUP, but there's a conflicting spend @@ -611,8 +611,7 @@ async def validate_spend_bundle( return Err.INVALID_COIN_SOLUTION, None, [] lineage_info = None - eligible_for_ff = bool(spend_conds.flags & ELIGIBLE_FOR_FF) and supports_fast_forward(coin_spend) - if eligible_for_ff: + if bool(spend_conds.flags & ELIGIBLE_FOR_FF) and supports_fast_forward(coin_spend): # Make sure the fast forward spend still has a version that is # still unspent, because if the singleton has been spent in a # non-FF spend, this fast forward spend will never become valid. @@ -622,8 +621,6 @@ async def validate_spend_bundle( # spent_index will also fail this test, and such spends will # fall back to be treated as non-FF spends. lineage_info = await get_unspent_lineage_info_for_puzzle_hash(spend_conds.puzzle_hash) - if lineage_info is None: - eligible_for_ff = False spend_additions = [] for puzzle_hash, amount, _ in spend_conds.create_coin: @@ -635,7 +632,6 @@ async def validate_spend_bundle( bundle_coin_spends[coin_id] = BundleCoinSpend( coin_spend=coin_spend, eligible_for_dedup=bool(spend_conds.flags & ELIGIBLE_FOR_DEDUP), - eligible_for_fast_forward=eligible_for_ff, additions=spend_additions, cost=uint64(spend_conds.condition_cost + spend_conds.execution_cost), latest_singleton_lineage=lineage_info, @@ -644,7 +640,7 @@ async def validate_spend_bundle( # fast forward spends are only allowed when bundled with other, non-FF, spends # in order to evict an FF spend, it must be associated with a normal # spend that can be included in a block or invalidated some other way - if all([s.eligible_for_fast_forward for s in bundle_coin_spends.values()]): + if all([s.supports_fast_forward for s in bundle_coin_spends.values()]): return Err.INVALID_SPEND_BUNDLE, None, [] removal_record_dict: dict[bytes32, CoinRecord] = {} @@ -1062,7 +1058,7 @@ def can_replace( if coin_id not in removal_names: log.debug("Rejecting conflicting tx as it does not spend conflicting coin %s", coin_id) return False - if bcs.eligible_for_fast_forward: + if bcs.supports_fast_forward: existing_ff_spends.add(bytes32(coin_id)) if bcs.eligible_for_dedup: existing_dedup_spends.add(bytes32(coin_id)) @@ -1113,7 +1109,7 @@ def can_replace( if len(existing_ff_spends) > 0 or len(existing_dedup_spends) > 0: for coin_id, bcs in new_item.bundle_coin_spends.items(): - if not bcs.eligible_for_fast_forward and coin_id in existing_ff_spends: + if not bcs.supports_fast_forward and coin_id in existing_ff_spends: log.debug("Rejecting conflicting tx due to changing ELIGIBLE_FOR_FF of coin spend %s", coin_id) return False diff --git a/chia/types/mempool_item.py b/chia/types/mempool_item.py index 76f233c592c5..1a796d9aaaea 100644 --- a/chia/types/mempool_item.py +++ b/chia/types/mempool_item.py @@ -22,7 +22,6 @@ class UnspentLineageInfo: class BundleCoinSpend: coin_spend: CoinSpend eligible_for_dedup: bool - eligible_for_fast_forward: bool additions: list[Coin] # cost on the specific solution in this item. The cost includes execution # cost and conditions cost, not byte-cost. @@ -32,7 +31,11 @@ class BundleCoinSpend: # current unspent lineage belonging to this singleton, that we would rebase # this spend on top of if we were to make a block now # When finding MempoolItems by coin ID, we use Coin ID from it if it's set - latest_singleton_lineage: Optional[UnspentLineageInfo] = None + latest_singleton_lineage: Optional[UnspentLineageInfo] + + @property + def supports_fast_forward(self) -> bool: + return self.latest_singleton_lineage is not None @dataclass(frozen=True)