diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index aa658a714206..1dc53e27f2aa 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -3156,3 +3156,53 @@ def test_get_items_by_coin_ids(coin_ids: list[bytes32]) -> list[MempoolItem]: assert err == expected_err assert len(conflicts) == len(expected_conflicts) assert set(conflicts) == set(expected_conflicts) + + +@pytest.mark.anyio +async def test_new_peak_deferred_ff_items() -> None: + """ + Covers the case where we update lineage info for multiple fast forward + singletons at new peak. + """ + singleton_spend1 = make_singleton_spend(bytes32([1] * 32)) + singleton1_id = singleton_spend1.coin.name() + singleton_spend2 = make_singleton_spend(bytes32([2] * 32)) + singleton2_id = singleton_spend2.coin.name() + coins = TestCoins( + [singleton_spend1.coin, singleton_spend2.coin, TEST_COIN, TEST_COIN2], + { + singleton_spend1.coin.puzzle_hash: singleton_spend1.coin, + singleton_spend2.coin.puzzle_hash: singleton_spend2.coin, + }, + ) + mempool_manager = await setup_mempool(coins) + # Let's submit the two singletons transactions to the mempool + sb_names = [] + for singleton_spend, regular_coin in [(singleton_spend1, TEST_COIN), (singleton_spend2, TEST_COIN2)]: + sb = SpendBundle([singleton_spend, mk_coin_spend(regular_coin)], G2Element()) + sb_name = sb.name() + await mempool_manager.add_spend_bundle( + sb, + make_test_conds(spend_ids=[(singleton_spend.coin, ELIGIBLE_FOR_FF), (regular_coin, 0)], cost=1337), + sb_name, + uint32(1), + ) + assert mempool_manager.get_mempool_item(sb_name) is not None + sb_names.append(sb_name) + # Let's advance the mempool by spending these singletons into new lineages + singleton1_new_latest = Coin(singleton1_id, singleton_spend1.coin.puzzle_hash, singleton_spend1.coin.amount) + coins.update_lineage(singleton_spend1.coin.puzzle_hash, singleton1_new_latest) + singleton2_new_latest = Coin(singleton2_id, singleton_spend2.coin.puzzle_hash, singleton_spend2.coin.amount) + coins.update_lineage(singleton_spend2.coin.puzzle_hash, singleton2_new_latest) + await advance_mempool(mempool_manager, [singleton1_id, singleton2_id], use_optimization=True) + # Both items should get updated with their related latest lineages + mi1 = mempool_manager.get_mempool_item(sb_names[0]) + assert mi1 is not None + latest_singleton_lineage1 = mi1.bundle_coin_spends[singleton1_id].latest_singleton_lineage + assert latest_singleton_lineage1 is not None + assert latest_singleton_lineage1.coin_id == singleton1_new_latest.name() + mi2 = mempool_manager.get_mempool_item(sb_names[1]) + assert mi2 is not None + latest_singleton_lineage2 = mi2.bundle_coin_spends[singleton2_id].latest_singleton_lineage + assert latest_singleton_lineage2 is not None + assert latest_singleton_lineage2.coin_id == singleton2_new_latest.name() diff --git a/chia/full_node/mempool_manager.py b/chia/full_node/mempool_manager.py index c621cd37d8a0..b66b1c45641f 100644 --- a/chia/full_node/mempool_manager.py +++ b/chia/full_node/mempool_manager.py @@ -833,7 +833,7 @@ async def new_peak( # rebasing a fast forward spend is more expensive than to just # evict the item. So, any FF spend we may need to rebase, defer # them until after we've gone through all spends - deferred_ff_items: set[tuple[bytes32, bytes32]] = set() + deferred_ff_items: set[tuple[bytes32, MempoolItem]] = set() for spend in spent_coins: items = self.mempool.get_items_by_coin_id(spend) @@ -856,7 +856,7 @@ async def new_peak( spendbundle_ids_to_remove.add(item_name) continue - deferred_ff_items.add((spend, item_name)) + deferred_ff_items.add((spend, item)) # fast forward spends are indexed under the latest singleton coin ID # if it's spent, we need to update the index in the mempool. This @@ -864,7 +864,8 @@ async def new_peak( # new_coin_id, current_coin_id, mempool item name spends_to_update: list[tuple[bytes32, bytes32, bytes32]] = [] - for spend, item_name in deferred_ff_items: + for spend, item in deferred_ff_items: + item_name = item.spend_bundle_name if item_name in spendbundle_ids_to_remove: continue # there may be multiple matching spends in the mempool