Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions chia/_tests/core/mempool/test_mempool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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,
)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()

Expand All @@ -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()

Expand All @@ -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()

Expand Down
28 changes: 3 additions & 25 deletions chia/_tests/core/mempool/test_singleton_fast_forward.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 4 additions & 5 deletions chia/full_node/eligible_coin_spends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion chia/full_node/mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 7 additions & 11 deletions chia/full_node/mempool_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -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,
Expand All @@ -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] = {}
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions chia/types/mempool_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down
Loading