Skip to content

Commit ae2243e

Browse files
authored
checkpoint: into main from release/2.5.5 @ 5200310 (#19925)
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.
2 parents a803dc0 + 0f737e4 commit ae2243e

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
@@ -3201,3 +3201,61 @@ async def test_new_peak_deferred_ff_items() -> None:
32013201
latest_singleton_lineage2 = mi2.bundle_coin_spends[singleton2_id].latest_singleton_lineage
32023202
assert latest_singleton_lineage2 is not None
32033203
assert latest_singleton_lineage2.coin_id == singleton2_new_latest.name()
3204+
3205+
3206+
@pytest.mark.anyio
3207+
async def test_different_ff_versions() -> None:
3208+
"""
3209+
Covers the case where we send an item with an older ff singleton version
3210+
while the mempool is aware of a newer lineage.
3211+
"""
3212+
launcher_id = bytes32([1] * 32)
3213+
singleton_spend1 = make_singleton_spend(launcher_id, bytes32([2] * 32))
3214+
version1_id = singleton_spend1.coin.name()
3215+
singleton_spend2 = make_singleton_spend(launcher_id, bytes32([3] * 32))
3216+
version2_id = singleton_spend2.coin.name()
3217+
singleton_ph = singleton_spend2.coin.puzzle_hash
3218+
coins = TestCoins(
3219+
[singleton_spend1.coin, singleton_spend2.coin, TEST_COIN, TEST_COIN2], {singleton_ph: singleton_spend2.coin}
3220+
)
3221+
mempool_manager = await setup_mempool(coins)
3222+
mempool_items: list[MempoolItem] = []
3223+
for singleton_spend, regular_coin in [(singleton_spend1, TEST_COIN), (singleton_spend2, TEST_COIN2)]:
3224+
sb = SpendBundle([singleton_spend, mk_coin_spend(regular_coin)], G2Element())
3225+
sb_name = sb.name()
3226+
await mempool_manager.add_spend_bundle(
3227+
sb,
3228+
make_test_conds(spend_ids=[(singleton_spend.coin, ELIGIBLE_FOR_FF), (regular_coin, 0)], cost=1337),
3229+
sb_name,
3230+
uint32(1),
3231+
)
3232+
mi = mempool_manager.get_mempool_item(sb_name)
3233+
assert mi is not None
3234+
mempool_items.append(mi)
3235+
[mi1, mi2] = mempool_items
3236+
latest_lineage_id = version2_id
3237+
assert latest_lineage_id != version1_id
3238+
# Bundle coin spends key points to version 1 but the lineage is latest (v2)
3239+
latest_singleton_lineage1 = mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
3240+
assert latest_singleton_lineage1 is not None
3241+
assert latest_singleton_lineage1.coin_id == latest_lineage_id
3242+
# Both the bundle coin spends key and the lineage point to latest (v2)
3243+
latest_singleton_lineage2 = mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
3244+
assert latest_singleton_lineage2 is not None
3245+
assert latest_singleton_lineage2.coin_id == latest_lineage_id
3246+
# Let's update the lineage with a new version of the singleton
3247+
new_latest_lineage = Coin(version2_id, singleton_ph, singleton_spend2.coin.amount)
3248+
new_latest_lineage_id = new_latest_lineage.name()
3249+
coins.update_lineage(singleton_ph, new_latest_lineage)
3250+
await advance_mempool(mempool_manager, [version1_id, version2_id], use_optimization=True)
3251+
# Both items should get updated with the latest lineage
3252+
new_mi1 = mempool_manager.get_mempool_item(mi1.spend_bundle_name)
3253+
assert new_mi1 is not None
3254+
latest_singleton_lineage1 = new_mi1.bundle_coin_spends[version1_id].latest_singleton_lineage
3255+
assert latest_singleton_lineage1 is not None
3256+
assert latest_singleton_lineage1.coin_id == new_latest_lineage_id
3257+
new_mi2 = mempool_manager.get_mempool_item(mi2.spend_bundle_name)
3258+
assert new_mi2 is not None
3259+
latest_singleton_lineage2 = new_mi2.bundle_coin_spends[version2_id].latest_singleton_lineage
3260+
assert latest_singleton_lineage2 is not None
3261+
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
@@ -257,7 +257,22 @@ def check_removals(
257257
for item in conflicting_items:
258258
if item in conflicts:
259259
continue
260-
conflict_bcs = item.bundle_coin_spends[coin_id]
260+
conflict_bcs = item.bundle_coin_spends.get(coin_id)
261+
if conflict_bcs is None:
262+
# Check if this is an item that spends an older ff singleton
263+
# version with a latest version that matches our coin ID.
264+
conflict_bcs = next(
265+
(
266+
bcs
267+
for bcs in item.bundle_coin_spends.values()
268+
if bcs.latest_singleton_lineage is not None and bcs.latest_singleton_lineage.coin_id == coin_id
269+
),
270+
None,
271+
)
272+
# We're not expected to get here but let's handle it gracefully
273+
if conflict_bcs is None:
274+
log.warning(f"Coin ID {coin_id} expected but not found in mempool item {item.name}")
275+
return Err.INVALID_SPEND_BUNDLE, []
261276
# if the spend we're adding to the mempool is not DEDUP nor FF, it's
262277
# just a regular conflict
263278
if not coin_bcs.eligible_for_fast_forward and not coin_bcs.eligible_for_dedup:

0 commit comments

Comments
 (0)