Skip to content

Commit 5200310

Browse files
authored
CHIA-3484 Handle items that spend an older ff version when newer lineage exists (#19865)
Handle items that spend an older ff version when newer lineage exists.
1 parent 673728c commit 5200310

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

chia/_tests/core/mempool/test_mempool_manager.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3211,3 +3211,61 @@ async def test_new_peak_deferred_ff_items() -> None:
32113211
latest_singleton_lineage2 = mi2.bundle_coin_spends[singleton2_id].latest_singleton_lineage
32123212
assert latest_singleton_lineage2 is not None
32133213
assert latest_singleton_lineage2.coin_id == singleton2_new_latest.name()
3214+
3215+
3216+
@pytest.mark.anyio
3217+
async def test_different_ff_versions() -> None:
3218+
"""
3219+
Covers the case where we send an item with an older ff singleton version
3220+
while the mempool is aware of a newer lineage.
3221+
"""
3222+
launcher_id = bytes32([1] * 32)
3223+
singleton_spend1 = make_singleton_spend(launcher_id, bytes32([2] * 32))
3224+
version1_id = singleton_spend1.coin.name()
3225+
singleton_spend2 = make_singleton_spend(launcher_id, bytes32([3] * 32))
3226+
version2_id = singleton_spend2.coin.name()
3227+
singleton_ph = singleton_spend2.coin.puzzle_hash
3228+
coins = TestCoins(
3229+
[singleton_spend1.coin, singleton_spend2.coin, TEST_COIN, TEST_COIN2], {singleton_ph: singleton_spend2.coin}
3230+
)
3231+
mempool_manager = await setup_mempool(coins)
3232+
mempool_items: list[MempoolItem] = []
3233+
for singleton_spend, regular_coin in [(singleton_spend1, TEST_COIN), (singleton_spend2, TEST_COIN2)]:
3234+
sb = SpendBundle([singleton_spend, mk_coin_spend(regular_coin)], G2Element())
3235+
sb_name = sb.name()
3236+
await mempool_manager.add_spend_bundle(
3237+
sb,
3238+
make_test_conds(spend_ids=[(singleton_spend.coin, ELIGIBLE_FOR_FF), (regular_coin, 0)], cost=1337),
3239+
sb_name,
3240+
uint32(1),
3241+
)
3242+
mi = mempool_manager.get_mempool_item(sb_name)
3243+
assert mi is not None
3244+
mempool_items.append(mi)
3245+
[mi1, mi2] = mempool_items
3246+
latest_lineage_id = version2_id
3247+
assert latest_lineage_id != version1_id
3248+
# Bundle coin spends key points to version 1 but the lineage is latest (v2)
3249+
latest_singleton_lineage1 = mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
3250+
assert latest_singleton_lineage1 is not None
3251+
assert latest_singleton_lineage1.coin_id == latest_lineage_id
3252+
# Both the bundle coin spends key and the lineage point to latest (v2)
3253+
latest_singleton_lineage2 = mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
3254+
assert latest_singleton_lineage2 is not None
3255+
assert latest_singleton_lineage2.coin_id == latest_lineage_id
3256+
# Let's update the lineage with a new version of the singleton
3257+
new_latest_lineage = Coin(version2_id, singleton_ph, singleton_spend2.coin.amount)
3258+
new_latest_lineage_id = new_latest_lineage.name()
3259+
coins.update_lineage(singleton_ph, new_latest_lineage)
3260+
await advance_mempool(mempool_manager, [version1_id, version2_id], use_optimization=True)
3261+
# Both items should get updated with the latest lineage
3262+
new_mi1 = mempool_manager.get_mempool_item(mi1.spend_bundle_name)
3263+
assert new_mi1 is not None
3264+
latest_singleton_lineage1 = new_mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
3265+
assert latest_singleton_lineage1 is not None
3266+
assert latest_singleton_lineage1.coin_id == new_latest_lineage_id
3267+
new_mi2 = mempool_manager.get_mempool_item(mi2.spend_bundle_name)
3268+
assert new_mi2 is not None
3269+
latest_singleton_lineage2 = new_mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
3270+
assert latest_singleton_lineage2 is not None
3271+
assert latest_singleton_lineage2.coin_id == new_latest_lineage_id

chia/full_node/mempool_manager.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,22 @@ def check_removals(
258258
for item in conflicting_items:
259259
if item in conflicts:
260260
continue
261-
conflict_bcs = item.bundle_coin_spends[coin_id]
261+
conflict_bcs = item.bundle_coin_spends.get(coin_id)
262+
if conflict_bcs is None:
263+
# Check if this is an item that spends an older ff singleton
264+
# version with a latest version that matches our coin ID.
265+
conflict_bcs = next(
266+
(
267+
bcs
268+
for bcs in item.bundle_coin_spends.values()
269+
if bcs.latest_singleton_lineage is not None and bcs.latest_singleton_lineage.coin_id == coin_id
270+
),
271+
None,
272+
)
273+
# We're not expected to get here but let's handle it gracefully
274+
if conflict_bcs is None:
275+
log.warning(f"Coin ID {coin_id} expected but not found in mempool item {item.name}")
276+
return Err.INVALID_SPEND_BUNDLE, []
262277
# if the spend we're adding to the mempool is not DEDUP nor FF, it's
263278
# just a regular conflict
264279
if not coin_bcs.eligible_for_fast_forward and not coin_bcs.eligible_for_dedup:

0 commit comments

Comments
 (0)