@@ -129,6 +129,7 @@ class MempoolManager:
129129 constants : ConsensusConstants
130130 seen_bundle_hashes : dict [bytes32 , bytes32 ]
131131 get_coin_records : Callable [[Collection [bytes32 ]], Awaitable [list [CoinRecord ]]]
132+ get_unspent_lineage_info_for_puzzle_hash : Callable [[bytes32 ], Awaitable [Optional [UnspentLineageInfo ]]]
132133 nonzero_fee_minimum_fpc : int
133134 mempool_max_total_cost : int
134135 # a cache of MempoolItems that conflict with existing items in the pool
@@ -145,6 +146,7 @@ class MempoolManager:
145146 def __init__ (
146147 self ,
147148 get_coin_records : Callable [[Collection [bytes32 ]], Awaitable [list [CoinRecord ]]],
149+ get_unspent_lineage_info_for_puzzle_hash : Callable [[bytes32 ], Awaitable [Optional [UnspentLineageInfo ]]],
148150 consensus_constants : ConsensusConstants ,
149151 * ,
150152 single_threaded : bool = False ,
@@ -156,6 +158,7 @@ def __init__(
156158 self .seen_bundle_hashes : dict [bytes32 , bytes32 ] = {}
157159
158160 self .get_coin_records = get_coin_records
161+ self .get_unspent_lineage_info_for_puzzle_hash = get_unspent_lineage_info_for_puzzle_hash
159162
160163 # The fee per cost must be above this amount to consider the fee "nonzero", and thus able to kick out other
161164 # transactions. This prevents spam. This is equivalent to 0.055 XCH per block, or about 0.00005 XCH for two
@@ -349,6 +352,7 @@ async def add_spend_bundle(
349352 spend_name ,
350353 first_added_height ,
351354 get_coin_records ,
355+ self .get_unspent_lineage_info_for_puzzle_hash ,
352356 )
353357 if err is None :
354358 # No error, immediately add to mempool, after removing conflicting TXs.
@@ -379,6 +383,7 @@ async def validate_spend_bundle(
379383 spend_name : bytes32 ,
380384 first_added_height : uint32 ,
381385 get_coin_records : Callable [[Collection [bytes32 ]], Awaitable [list [CoinRecord ]]],
386+ get_unspent_lineage_info_for_puzzle_hash : Callable [[bytes32 ], Awaitable [Optional [UnspentLineageInfo ]]],
382387 ) -> tuple [Optional [Err ], Optional [MempoolItem ], list [bytes32 ]]:
383388 """
384389 Validates new_spend with the given NPCResult, and spend_name, and the current mempool. The mempool should
@@ -423,7 +428,7 @@ async def validate_spend_bundle(
423428 eligibility_and_additions [coin_id ] = EligibilityAndAdditions (
424429 is_eligible_for_dedup = is_eligible_for_dedup ,
425430 spend_additions = spend_additions ,
426- is_eligible_for_ff = is_eligible_for_ff ,
431+ ff_puzzle_hash = bytes32 ( spend . puzzle_hash ) if is_eligible_for_ff else None ,
427432 )
428433 removal_names_from_coin_spends : set [bytes32 ] = set ()
429434 fast_forward_coin_ids : set [bytes32 ] = set ()
@@ -433,14 +438,20 @@ async def validate_spend_bundle(
433438 removal_names_from_coin_spends .add (coin_id )
434439 eligibility_info = eligibility_and_additions .get (
435440 coin_id ,
436- EligibilityAndAdditions (is_eligible_for_dedup = False , spend_additions = [], is_eligible_for_ff = False ),
441+ EligibilityAndAdditions (is_eligible_for_dedup = False , spend_additions = [], ff_puzzle_hash = None ),
437442 )
438- mark_as_fast_forward = eligibility_info .is_eligible_for_ff and supports_fast_forward (coin_spend )
443+ mark_as_fast_forward = eligibility_info .ff_puzzle_hash is not None and supports_fast_forward (coin_spend )
444+ if mark_as_fast_forward :
445+ # Make sure the fast forward spend still has a version that is
446+ # still unspent, because if the singleton has been melted, the
447+ # fast forward spend will never become valid.
448+ assert eligibility_info .ff_puzzle_hash is not None
449+ if await get_unspent_lineage_info_for_puzzle_hash (eligibility_info .ff_puzzle_hash ) is None :
450+ return Err .DOUBLE_SPEND , None , []
451+ fast_forward_coin_ids .add (coin_id )
439452 # We are now able to check eligibility of both dedup and fast forward
440453 if not (eligibility_info .is_eligible_for_dedup or mark_as_fast_forward ):
441454 non_eligible_coin_ids .append (coin_id )
442- if mark_as_fast_forward :
443- fast_forward_coin_ids .add (coin_id )
444455 bundle_coin_spends [coin_id ] = BundleCoinSpend (
445456 coin_spend = coin_spend ,
446457 eligible_for_dedup = eligibility_info .is_eligible_for_dedup ,
0 commit comments