fix(tx_pool): dedupe key-image conflict list; make remove_tx idempotent#199
Open
raw391 wants to merge 1 commit into
Open
fix(tx_pool): dedupe key-image conflict list; make remove_tx idempotent#199raw391 wants to merge 1 commit into
raw391 wants to merge 1 commit into
Conversation
have_tx_keyimges_as_spent walks tx.vin and appends every conflicting txid per spent key image. If one mempool tx shares 2+ key images with the incoming tx, its txid is appended multiple times. remove_flash_conflicts iterates that vector and calls remove_tx per entry. The first call succeeds; the second on the same txid fails at the sorted-container check. remove_flash_conflicts treats failure as fatal and returns early, so the LockedTXN destructor aborts the DB batch. The LMDB row is restored, but in-memory mutations from the first call (m_txpool_weight decrement, remove_transaction_keyimages, sorted-container erase) are not rolled back. m_spent_key_images then no longer contains entries for that txs inputs, so subsequent txs spending the same outputs are not rejected by have_tx_keyimges_as_spent until the orphaned row is aged out or the daemon restarts. Triggered by a single signer producing two txs sharing 2+ inputs. Dedupes the conflict list at source in have_tx_keyimges_as_spent via unordered_set. Makes remove_tx idempotent when called without stc_it: a missing sorted-container entry is treated as already-removed with an MWARNING log so the path stays observable.
75a6616 to
1769d5a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tx_memory_pool::have_tx_keyimges_as_spentatsrc/cryptonote_core/tx_pool.cpp:1447-1465appends every conflicting txid into theconflictingvector per spent key image, with no dedupe. If one mempool tx shares two or more key images with the incoming tx, its txid is appended multiple times.The approved-flash path in
tx_pool::add_tx(tx_pool.cpp:361-365) callsremove_flash_conflicts(which starts attx_pool.cpp:674).remove_flash_conflictsiterates that vector and callsremove_txper entry. The first call succeeds; the second call on the same txid fails at the sorted-container check at line 760.remove_flash_conflictstreats the failure as a hard error and returns early, so theLockedTXNdestructor aborts the DB batch. The LMDB row is restored, but the in-memory mutations from the first call (m_txpool_weightdecrement at line 787,remove_transaction_keyimagesat line 788, sorted-container erase at line 789) are not rolled back.m_spent_key_imagesthen no longer contains entries for that txs inputs, so a subsequent tx spending the same outputs is not rejected byhave_tx_keyimges_as_spentuntil the orphaned row is aged out or the daemon restarts.This is reachable when a single signer produces two transactions sharing 2+ key images (a normal mempool tx and an approved flash tx for the same inputs).
Patch dedupes the conflict list at source via
unordered_set, and makesremove_txidempotent for ad-hoc callers that passstc_it = nullptr(with an MWARNING log so the path stays observable):bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, std::vector<crypto::hash> *conflicting) const { auto locks = tools::unique_locks(m_transactions_lock, m_blockchain); bool ret = false; + std::unordered_set<crypto::hash> seen; for(const auto& in: tx.vin) { CHECKED_GET_SPECIFIC_VARIANT(in, txin_to_key, tokey_in, true); auto it = m_spent_key_images.find(tokey_in.k_image); if (it != m_spent_key_images.end()) { if (!conflicting) return true; ret = true; - conflicting->insert(conflicting->end(), it->second.begin(), it->second.end()); + for (const auto &h : it->second) + if (seen.insert(h).second) + conflicting->push_back(h); } } return ret; }const auto it = stc_it ? *stc_it : find_tx_in_sorted_container(txid); if (it == m_txs_by_fee_and_receive_time.end()) { + if (!stc_it) + { + MWARNING("remove_tx: tx " << txid << " not in sorted container, treating as already-removed"); + return true; + } MERROR("Failed to find tx in txpool sorted list"); return false; }The bool-only fast path of
have_tx_keyimges_as_spent(conflicting == nullptr) is unchanged. The pruner at line 794 passes a known-goodstc_itand keeps the loud-fail semantics.