Repository navigation
fix: purge cache of an overridden confirmed islock and bound the ConnectBlock conflict loop #7885
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,11 +80,18 @@ void CInstantSendDb::RemoveInstantSendLock(CDBBatch& batch, const uint256& hash, | |
| } | ||
|
|
||
| if (!keep_cache) { | ||
| islockCache.erase(hash); | ||
| txidCache.erase(islock.txid); | ||
| for (const auto& in : islock.inputs) { | ||
| outpointCache.erase(in); | ||
| } | ||
| EraseInstantSendLockCache(hash, islock); | ||
| } | ||
| } | ||
|
|
||
| void CInstantSendDb::EraseInstantSendLockCache(const uint256& hash, const InstantSendLock& islock) | ||
| { | ||
| AssertLockHeld(cs_db); | ||
|
|
||
| islockCache.erase(hash); | ||
| txidCache.erase(islock.txid); | ||
| for (const auto& in : islock.inputs) { | ||
| outpointCache.erase(in); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -345,15 +352,15 @@ std::vector<uint256> CInstantSendDb::GetInstantSendLocksByParent(const uint256& | |
| return result; | ||
| } | ||
|
|
||
| std::vector<uint256> CInstantSendDb::RemoveChainedInstantSendLocks(const uint256& islockHash, const uint256& txid, | ||
| int nHeight) | ||
| std::vector<uint256> CInstantSendDb::RemoveChainedInstantSendLocks(const uint256& islockHash, | ||
| const InstantSendLock& islock, int nHeight) | ||
| { | ||
| LOCK(cs_db); | ||
| std::vector<uint256> result; | ||
|
|
||
| std::vector<uint256> stack; | ||
| Uint256HashSet added; | ||
| stack.emplace_back(txid); | ||
| stack.emplace_back(islock.txid); | ||
|
|
||
| CDBBatch batch(*db); | ||
| while (!stack.empty()) { | ||
|
|
@@ -376,9 +383,11 @@ std::vector<uint256> CInstantSendDb::RemoveChainedInstantSendLocks(const uint256 | |
| } | ||
| } | ||
|
|
||
| if (auto islock = GetInstantSendLockByHashInternal(islockHash, /*use_cache=*/false)) { | ||
| RemoveInstantSendLock(batch, islockHash, *islock, false); | ||
| if (GetInstantSendLockByHashInternal(islockHash, /*use_cache=*/false)) { | ||
| RemoveInstantSendLock(batch, islockHash, islock); | ||
| } | ||
| // Also needed when the lock was already confirmed: that removed it from the db but kept it cached | ||
| EraseInstantSendLockCache(islockHash, islock); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an already-confirmed multi-input lock A has one cached input replaced by a newer conflicting lock B while another input still maps to A, overriding A through that other input reaches this unconditional purge and erases B's AGENTS.md reference: AGENTS.md:L242-L242 Useful? React with 👍 / 👎.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. I'm leaving this as is: the erased entry can't be one the code depends on.
An ownership check before each erase would add code to protect an entry the cache can drop at any moment. The existing 🤖 Posted autonomously by Claude on behalf of pasta. |
||
| WriteInstantSendLockArchived(batch, islockHash, nHeight); | ||
| result.emplace_back(islockHash); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the overridden lock has an IS-locked child that was mined at or below the same confirmation height (for example, parent and child are in one block),
RemoveConfirmedInstantSendLockshas already deleted the child'sDB_HASH_BY_OUTPOINTrow while retaining its cache entries. The descendant scan in this function therefore cannot find that child, and this new unconditional erase clears only the root, so after the ChainLocked replacementIsLocked(child_txid)remains true and the child outpoint still reports a conflict. Track or traverse confirmed descendants and extend the new scenario with a confirmed child so both caches are cleared.AGENTS.md reference: AGENTS.md:L139-L139
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accurate, but out of scope for this PR. Develop has the same behavior, and this PR doesn't make it worse.
A child lock that was confirmed together with its parent has already lost its
DB_HASH_BY_OUTPOINTrows. Its entries survive only in the LRU caches, which have no index by parent, soGetInstantSendLocksByParentcan't find it. Fixing that means changing whatRemoveConfirmedInstantSendLockskeeps cached (keep_cache), not this removal path.The impact is also limited. The child spends an output of the overridden transaction, which conflicts with the ChainLocked chain. So the child can never be mined there or re-enter the mempool, and its cache entries age out of the LRU. The
ConnectBlockbound in the second commit doesn't depend on it either: that pass looks only at the block transaction's own inputs.🤖 Posted autonomously by Claude on behalf of pasta.