Skip to content

Commit cfeeae1

Browse files
authored
CHIA-3737 Simplify BundleCoinSpend's tracking of fast forward spends (#20069)
Simplify BundleCoinSpend's tracking of fast forward spends.
1 parent 3652e60 commit cfeeae1

File tree

6 files changed

+35
-56
lines changed

6 files changed

+35
-56
lines changed

chia/_tests/core/mempool/test_mempool_manager.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -579,7 +579,6 @@ def make_bundle_spends_map_and_fee(
579579
bundle_coin_spends[coin_id] = BundleCoinSpend(
580580
coin_spend=coin_spend,
581581
eligible_for_dedup=bool(spend_conds.flags & ELIGIBLE_FOR_DEDUP),
582-
eligible_for_fast_forward=bool(spend_conds.flags & ELIGIBLE_FOR_FF),
583582
additions=additions,
584583
cost=uint64(spend_conds.condition_cost + spend_conds.execution_cost),
585584
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:
890889
return BundleCoinSpend(
891890
coin_spend=coin_spend,
892891
eligible_for_dedup=bool(flags & ELIGIBLE_FOR_DEDUP),
893-
eligible_for_fast_forward=bool(flags & ELIGIBLE_FOR_FF),
894892
additions=[],
895893
cost=uint64(0),
894+
latest_singleton_lineage=UnspentLineageInfo(
895+
coin_spend.coin.name(), coin_spend.coin.parent_coin_info, bytes32([0] * 32)
896+
)
897+
if flags & ELIGIBLE_FOR_FF
898+
else None,
896899
)
897900

898901

@@ -1802,16 +1805,16 @@ async def test_bundle_coin_spends() -> None:
18021805
assert mi123e.bundle_coin_spends[coins[i].name()] == BundleCoinSpend(
18031806
coin_spend=sb123.coin_spends[i],
18041807
eligible_for_dedup=False,
1805-
eligible_for_fast_forward=False,
18061808
additions=[Coin(coins[i].name(), IDENTITY_PUZZLE_HASH, coins[i].amount)],
18071809
cost=uint64(ConditionCost.CREATE_COIN.value + ConditionCost.AGG_SIG.value + execution_cost),
1810+
latest_singleton_lineage=None,
18081811
)
18091812
assert mi123e.bundle_coin_spends[coins[3].name()] == BundleCoinSpend(
18101813
coin_spend=eligible_sb.coin_spends[0],
18111814
eligible_for_dedup=True,
1812-
eligible_for_fast_forward=False,
18131815
additions=[Coin(coins[3].name(), IDENTITY_PUZZLE_HASH, coins[3].amount)],
18141816
cost=uint64(ConditionCost.CREATE_COIN.value + execution_cost),
1817+
latest_singleton_lineage=None,
18151818
)
18161819

18171820

@@ -2463,7 +2466,7 @@ async def test_new_peak_ff_eviction(
24632466
item = mempool_manager.get_mempool_item(bundle.name())
24642467
assert item is not None
24652468
singleton_name = singleton_spend.coin.name()
2466-
assert item.bundle_coin_spends[singleton_name].eligible_for_fast_forward
2469+
assert item.bundle_coin_spends[singleton_name].supports_fast_forward
24672470
latest_singleton_lineage = item.bundle_coin_spends[singleton_name].latest_singleton_lineage
24682471
assert latest_singleton_lineage is not None
24692472
assert latest_singleton_lineage.coin_id == singleton_name
@@ -2495,7 +2498,7 @@ async def test_new_peak_ff_eviction(
24952498
else:
24962499
item = mempool_manager.get_mempool_item(bundle.name())
24972500
assert item is not None
2498-
assert item.bundle_coin_spends[singleton_spend.coin.name()].eligible_for_fast_forward
2501+
assert item.bundle_coin_spends[singleton_spend.coin.name()].supports_fast_forward
24992502
latest_singleton_lineage = item.bundle_coin_spends[singleton_spend.coin.name()].latest_singleton_lineage
25002503
assert latest_singleton_lineage is not None
25012504
assert latest_singleton_lineage.coin_id == singleton_spend.coin.name()
@@ -2552,9 +2555,9 @@ async def test_multiple_ff(use_optimization: bool) -> None:
25522555

25532556
item = mempool_manager.get_mempool_item(bundle.name())
25542557
assert item is not None
2555-
assert item.bundle_coin_spends[singleton_spend1.coin.name()].eligible_for_fast_forward
2556-
assert item.bundle_coin_spends[singleton_spend2.coin.name()].eligible_for_fast_forward
2557-
assert not item.bundle_coin_spends[coin_spend.coin.name()].eligible_for_fast_forward
2558+
assert item.bundle_coin_spends[singleton_spend1.coin.name()].supports_fast_forward
2559+
assert item.bundle_coin_spends[singleton_spend2.coin.name()].supports_fast_forward
2560+
assert not item.bundle_coin_spends[coin_spend.coin.name()].supports_fast_forward
25582561

25592562
# spend the singleton coin2 and make coin3 the latest version
25602563
coins.update_lineage(singleton_ph, singleton_spend3.coin)
@@ -2616,7 +2619,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
26162619
item = mempool_manager.get_mempool_item(bundle.name())
26172620
assert item is not None
26182621
spend = item.bundle_coin_spends[spend_a.coin.name()]
2619-
assert spend.eligible_for_fast_forward
2622+
assert spend.supports_fast_forward
26202623
assert spend.latest_singleton_lineage is not None
26212624
assert spend.latest_singleton_lineage.coin_id == spend_a.coin.name()
26222625

@@ -2628,7 +2631,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
26282631
item = mempool_manager.get_mempool_item(bundle.name())
26292632
assert item is not None
26302633
spend = item.bundle_coin_spends[spend_a.coin.name()]
2631-
assert spend.eligible_for_fast_forward
2634+
assert spend.supports_fast_forward
26322635
assert spend.latest_singleton_lineage is not None
26332636
assert spend.latest_singleton_lineage.coin_id == spend_b.coin.name()
26342637

@@ -2640,7 +2643,7 @@ async def test_advancing_ff(use_optimization: bool) -> None:
26402643
item = mempool_manager.get_mempool_item(bundle.name())
26412644
assert item is not None
26422645
spend = item.bundle_coin_spends[spend_a.coin.name()]
2643-
assert spend.eligible_for_fast_forward
2646+
assert spend.supports_fast_forward
26442647
assert spend.latest_singleton_lineage is not None
26452648
assert spend.latest_singleton_lineage.coin_id == spend_c.coin.name()
26462649

chia/_tests/core/mempool/test_singleton_fast_forward.py

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_process_fast_forward_spends_nothing_to_do() -> None:
5252
sb = spend_bundle_from_conditions(conditions, TEST_COIN, sig)
5353
item = mempool_item_from_spendbundle(sb)
5454
# This coin is not eligible for fast forward
55-
assert item.bundle_coin_spends[TEST_COIN_ID].eligible_for_fast_forward is False
55+
assert not item.bundle_coin_spends[TEST_COIN_ID].supports_fast_forward
5656
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
5757
original_version = dataclasses.replace(internal_mempool_item)
5858
singleton_ff = SingletonFastForward()
@@ -63,28 +63,6 @@ def test_process_fast_forward_spends_nothing_to_do() -> None:
6363
assert bundle_coin_spends == original_version.bundle_coin_spends
6464

6565

66-
def test_process_fast_forward_spends_unknown_ff() -> None:
67-
"""
68-
This tests the case when we process for the first time but we are unable
69-
to lookup the latest version from the item's latest singleton lineage
70-
"""
71-
test_coin = Coin(TEST_COIN_ID, IDENTITY_PUZZLE_HASH, uint64(1))
72-
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, 1]]
73-
sb = spend_bundle_from_conditions(conditions, test_coin)
74-
item = mempool_item_from_spendbundle(sb)
75-
# The coin is eligible for fast forward
76-
assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True
77-
item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = None
78-
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
79-
singleton_ff = SingletonFastForward()
80-
# We have no fast forward records yet, so we'll process this coin for the
81-
# first time here, but the item's latest singleton lineage returns None
82-
with pytest.raises(ValueError, match="Cannot proceed with singleton spend fast forward"):
83-
singleton_ff.process_fast_forward_spends(
84-
mempool_item=internal_mempool_item, height=TEST_HEIGHT, constants=DEFAULT_CONSTANTS
85-
)
86-
87-
8866
def test_process_fast_forward_spends_latest_unspent() -> None:
8967
"""
9068
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:
10381
conditions = [[ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, test_amount]]
10482
sb = spend_bundle_from_conditions(conditions, test_coin)
10583
item = mempool_item_from_spendbundle(sb)
106-
assert item.bundle_coin_spends[test_coin.name()].eligible_for_fast_forward is True
84+
assert item.bundle_coin_spends[test_coin.name()].supports_fast_forward
10785
item.bundle_coin_spends[test_coin.name()].latest_singleton_lineage = test_unspent_lineage_info
10886
internal_mempool_item = InternalMempoolItem(sb, item.conds, item.height_added_to_mempool, item.bundle_coin_spends)
10987
original_version = dataclasses.replace(internal_mempool_item)
@@ -168,12 +146,12 @@ def test_perform_the_fast_forward() -> None:
168146
"517b0dadb0c310ded24dd86dff8205398080ff808080"
169147
)
170148
test_coin_spend = CoinSpend(test_coin, test_puzzle_reveal, test_solution)
171-
test_spend_data = BundleCoinSpend(test_coin_spend, False, True, [test_child_coin], uint64(0))
172149
test_unspent_lineage_info = UnspentLineageInfo(
173150
coin_id=latest_unspent_coin.name(),
174151
parent_id=latest_unspent_coin.parent_coin_info,
175152
parent_parent_id=test_child_coin.parent_coin_info,
176153
)
154+
test_spend_data = BundleCoinSpend(test_coin_spend, False, [test_child_coin], uint64(0), test_unspent_lineage_info)
177155
# Start from a fresh state of fast forward spends
178156
fast_forward_spends: dict[bytes32, UnspentLineageInfo] = {}
179157
# Perform the fast forward on the test coin (the grandparent)

chia/full_node/eligible_coin_spends.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def process_fast_forward_spends(
196196
new_bundle_coin_spends = {}
197197
fast_forwarded_spends = 0
198198
for coin_id, spend_data in mempool_item.bundle_coin_spends.items():
199-
if not spend_data.eligible_for_fast_forward:
199+
if not spend_data.supports_fast_forward:
200200
# Nothing to do for this spend, moving on
201201
new_coin_spends.append(spend_data.coin_spend)
202202
new_bundle_coin_spends[coin_id] = spend_data
@@ -210,8 +210,7 @@ def process_fast_forward_spends(
210210
unspent_lineage_info = self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash)
211211
if unspent_lineage_info is None:
212212
# We didn't, so let's check the item's latest lineage info
213-
if spend_data.latest_singleton_lineage is None:
214-
raise ValueError("Cannot proceed with singleton spend fast forward.")
213+
assert spend_data.latest_singleton_lineage is not None
215214
unspent_lineage_info = spend_data.latest_singleton_lineage
216215
# See if we're the most recent version
217216
if unspent_lineage_info.coin_id == coin_id:
@@ -235,9 +234,9 @@ def process_fast_forward_spends(
235234
new_bundle_coin_spends[new_coin_spend.coin.name()] = BundleCoinSpend(
236235
coin_spend=new_coin_spend,
237236
eligible_for_dedup=spend_data.eligible_for_dedup,
238-
eligible_for_fast_forward=spend_data.eligible_for_fast_forward,
239237
additions=patched_additions,
240238
cost=spend_data.cost,
239+
latest_singleton_lineage=self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash),
241240
)
242241
# Update the list of coins spends that will make the new fast
243242
# forward spend bundle
@@ -258,9 +257,9 @@ def process_fast_forward_spends(
258257
new_bundle_coin_spends[new_coin_spend.coin.name()] = BundleCoinSpend(
259258
coin_spend=new_coin_spend,
260259
eligible_for_dedup=spend_data.eligible_for_dedup,
261-
eligible_for_fast_forward=spend_data.eligible_for_fast_forward,
262260
additions=patched_additions,
263261
cost=spend_data.cost,
262+
latest_singleton_lineage=self.fast_forward_spends.get(spend_data.coin_spend.coin.puzzle_hash),
264263
)
265264
# Update the list of coins spends that make the new fast forward bundle
266265
new_coin_spends.append(new_coin_spend)

chia/full_node/mempool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ def create_bundle_from_mempool_items(
611611
# might fit, but we also want to avoid spending too much
612612
# time on potentially expensive ones, hence this shortcut.
613613
if any(
614-
sd.eligible_for_dedup or sd.eligible_for_fast_forward for sd in item.bundle_coin_spends.values()
614+
sd.eligible_for_dedup or sd.supports_fast_forward for sd in item.bundle_coin_spends.values()
615615
):
616616
log.info(f"Skipping transaction with dedup or FF spends {name}")
617617
continue

chia/full_node/mempool_manager.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def check_removals(
243243
conflicts = set()
244244
for coin_id, coin_bcs in bundle_coin_spends.items():
245245
# 1. Checks if it's been spent already
246-
if removals[coin_id].spent and not coin_bcs.eligible_for_fast_forward:
246+
if removals[coin_id].spent and not coin_bcs.supports_fast_forward:
247247
return Err.DOUBLE_SPEND, []
248248

249249
# 2. Checks if there's a mempool conflict
@@ -269,12 +269,12 @@ def check_removals(
269269
return Err.INVALID_SPEND_BUNDLE, []
270270
# if the spend we're adding to the mempool is not DEDUP nor FF, it's
271271
# just a regular conflict
272-
if not coin_bcs.eligible_for_fast_forward and not coin_bcs.eligible_for_dedup:
272+
if not coin_bcs.supports_fast_forward and not coin_bcs.eligible_for_dedup:
273273
conflicts.add(item)
274274

275275
# if the spend we're adding is FF, but there's a conflicting spend
276276
# that isn't FF, they can't be chained, so that's a conflict
277-
elif coin_bcs.eligible_for_fast_forward and not conflict_bcs.eligible_for_fast_forward:
277+
elif coin_bcs.supports_fast_forward and not conflict_bcs.supports_fast_forward:
278278
conflicts.add(item)
279279

280280
# if the spend we're adding is DEDUP, but there's a conflicting spend
@@ -611,8 +611,7 @@ async def validate_spend_bundle(
611611
return Err.INVALID_COIN_SOLUTION, None, []
612612

613613
lineage_info = None
614-
eligible_for_ff = bool(spend_conds.flags & ELIGIBLE_FOR_FF) and supports_fast_forward(coin_spend)
615-
if eligible_for_ff:
614+
if bool(spend_conds.flags & ELIGIBLE_FOR_FF) and supports_fast_forward(coin_spend):
616615
# Make sure the fast forward spend still has a version that is
617616
# still unspent, because if the singleton has been spent in a
618617
# non-FF spend, this fast forward spend will never become valid.
@@ -622,8 +621,6 @@ async def validate_spend_bundle(
622621
# spent_index will also fail this test, and such spends will
623622
# fall back to be treated as non-FF spends.
624623
lineage_info = await get_unspent_lineage_info_for_puzzle_hash(spend_conds.puzzle_hash)
625-
if lineage_info is None:
626-
eligible_for_ff = False
627624

628625
spend_additions = []
629626
for puzzle_hash, amount, _ in spend_conds.create_coin:
@@ -635,7 +632,6 @@ async def validate_spend_bundle(
635632
bundle_coin_spends[coin_id] = BundleCoinSpend(
636633
coin_spend=coin_spend,
637634
eligible_for_dedup=bool(spend_conds.flags & ELIGIBLE_FOR_DEDUP),
638-
eligible_for_fast_forward=eligible_for_ff,
639635
additions=spend_additions,
640636
cost=uint64(spend_conds.condition_cost + spend_conds.execution_cost),
641637
latest_singleton_lineage=lineage_info,
@@ -644,7 +640,7 @@ async def validate_spend_bundle(
644640
# fast forward spends are only allowed when bundled with other, non-FF, spends
645641
# in order to evict an FF spend, it must be associated with a normal
646642
# spend that can be included in a block or invalidated some other way
647-
if all([s.eligible_for_fast_forward for s in bundle_coin_spends.values()]):
643+
if all([s.supports_fast_forward for s in bundle_coin_spends.values()]):
648644
return Err.INVALID_SPEND_BUNDLE, None, []
649645

650646
removal_record_dict: dict[bytes32, CoinRecord] = {}
@@ -1062,7 +1058,7 @@ def can_replace(
10621058
if coin_id not in removal_names:
10631059
log.debug("Rejecting conflicting tx as it does not spend conflicting coin %s", coin_id)
10641060
return False
1065-
if bcs.eligible_for_fast_forward:
1061+
if bcs.supports_fast_forward:
10661062
existing_ff_spends.add(bytes32(coin_id))
10671063
if bcs.eligible_for_dedup:
10681064
existing_dedup_spends.add(bytes32(coin_id))
@@ -1113,7 +1109,7 @@ def can_replace(
11131109

11141110
if len(existing_ff_spends) > 0 or len(existing_dedup_spends) > 0:
11151111
for coin_id, bcs in new_item.bundle_coin_spends.items():
1116-
if not bcs.eligible_for_fast_forward and coin_id in existing_ff_spends:
1112+
if not bcs.supports_fast_forward and coin_id in existing_ff_spends:
11171113
log.debug("Rejecting conflicting tx due to changing ELIGIBLE_FOR_FF of coin spend %s", coin_id)
11181114
return False
11191115

chia/types/mempool_item.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class UnspentLineageInfo:
2222
class BundleCoinSpend:
2323
coin_spend: CoinSpend
2424
eligible_for_dedup: bool
25-
eligible_for_fast_forward: bool
2625
additions: list[Coin]
2726
# cost on the specific solution in this item. The cost includes execution
2827
# cost and conditions cost, not byte-cost.
@@ -32,7 +31,11 @@ class BundleCoinSpend:
3231
# current unspent lineage belonging to this singleton, that we would rebase
3332
# this spend on top of if we were to make a block now
3433
# When finding MempoolItems by coin ID, we use Coin ID from it if it's set
35-
latest_singleton_lineage: Optional[UnspentLineageInfo] = None
34+
latest_singleton_lineage: Optional[UnspentLineageInfo]
35+
36+
@property
37+
def supports_fast_forward(self) -> bool:
38+
return self.latest_singleton_lineage is not None
3639

3740

3841
@dataclass(frozen=True)

0 commit comments

Comments
 (0)