Repository: Chia-Network/chia-blockchain
File: chia/full_node/mempool_manager.py (Python)

Code before:
    """
    This function checks for double spends, unknown spends and conflicting transactions in mempool.
    Returns Error (if any), the set of existing MempoolItems with conflicting spends (if any).
    Note that additions are not checked for duplicates, because having duplicate additions requires also
    having duplicate 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:
            return Err.DOUBLE_SPEND, []

        # 2. Checks if there's a mempool conflict
        conflicting_items = get_items_by_coin_ids([coin_id])
        for item in conflicting_items:
            if item in conflicts:
                continue
            conflict_bcs = item.bundle_coin_spends[coin_id]
            # 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:
                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:
                conflicts.add(item)

            # if the spend we're adding is DEDUP, but there's a conflicting spend
            # that isn't DEDUP, we cannot merge them, so that's a conflict
            elif coin_bcs.eligible_for_dedup and not conflict_bcs.eligible_for_dedup:
                conflicts.add(item)

            # if the spend we're adding is DEDUP but the existing spend has a
            # different solution, we cannot merge them, so that's a conflict
            elif coin_bcs.eligible_for_dedup and bytes(coin_bcs.coin_spend.solution) != bytes(
                conflict_bcs.coin_spend.solution
            ):
                conflicts.add(item)

    if len(conflicts) > 0:
        return Err.MEMPOOL_CONFLICT, list(conflicts)
    return None, []


class MempoolManager:
    pool: Executor
    constants: ConsensusConstants
    seen_bundle_hashes: dict[bytes32, bytes32]
    get_coin_records: Callable[[Collection[bytes32]], Awaitable[list[CoinRecord]]]
    get_unspent_lineage_info_for_puzzle_hash: Callable[[bytes32], Awaitable[Optional[UnspentLineageInfo]]]

Review comment (suggestion):
Am I understanding this correctly, that the underlying issue is that we change `latest_singleton_lineage.coin_id` without updating the corresponding dictionary key in `item.bundle_coin_spends`.

Code after:
    """
    This function checks for double spends, unknown spends and conflicting transactions in mempool.
    Returns Error (if any), the set of existing MempoolItems with conflicting spends (if any).
    Note that additions are not checked for duplicates, because having duplicate additions requires also
    having duplicate 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:
            return Err.DOUBLE_SPEND, []

        # 2. Checks if there's a mempool conflict
        conflicting_items = get_items_by_coin_ids([coin_id])
        for item in conflicting_items:
            if item in conflicts:
                continue
            conflict_bcs = item.bundle_coin_spends.get(coin_id)
            if conflict_bcs is None:
                # Check if this is an item that spends an older ff singleton
                # version with a latest version that matches our coin ID.
                conflict_bcs = next(
                    (
                        bcs
                        for bcs in item.bundle_coin_spends.values()
                        if bcs.latest_singleton_lineage is not None and bcs.latest_singleton_lineage.coin_id == coin_id
                    ),
                    None,
                )
                # We're not expected to get here but let's handle it gracefully
                if conflict_bcs is None:
                    log.warning(f"Coin ID {coin_id} expected but not found in mempool item {item.name}")
                    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:
                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:
                conflicts.add(item)

            # if the spend we're adding is DEDUP, but there's a conflicting spend
            # that isn't DEDUP, we cannot merge them, so that's a conflict
            elif coin_bcs.eligible_for_dedup and not conflict_bcs.eligible_for_dedup:
                conflicts.add(item)

            # if the spend we're adding is DEDUP but the existing spend has a
            # different solution, we cannot merge them, so that's a conflict
            elif coin_bcs.eligible_for_dedup and bytes(coin_bcs.coin_spend.solution) != bytes(