-
Notifications
You must be signed in to change notification settings - Fork 35.6k
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
refactor: Move calculation logic out from CheckSequenceLocksAtTip()
#23897
Conversation
81841f0
to
154307c
Compare
154307c
to
c4476a5
Compare
Rebased 154307c -> c4476a5 (pr23897.01 -> pr23897.02) on top of the merged #23683. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsNo conflicts as of last run. |
c4476a5
to
220475b
Compare
Rebased c4476a5 -> 220475b (pr23897.02 -> pr23897.03) on top of the merged #23976. |
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.
Concept ACK, I agree that calculating lockpoints and checking them against the current chain should be separate methods and it was definitely unexpected that CheckSequenceLocks()
was mutating the lockpoints passed in. There is a slight behavior change to MaybeUpdateMempoolForReorg()
here.
220475b
to
aa8f68b
Compare
CheckSequenceLocksAtTip()
Updated 220475b -> aa8f68b (pr23897.03 -> pr23897.04):
Also the PR description has been updated. |
aa8f68b
to
5a6ce8c
Compare
Rebased aa8f68b -> 5a6ce8c (pr23897.04 -> pr23897.05) due to conflict with #24804. |
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.
ACK bebaddb
The code is now much clearer and easier to understand and the behavior doesn't seem to have changed.
bebaddb
to
ed2d714
Compare
Updated bebaddb -> ed2d714 (pr23897.08 -> pr23897.09, diff):
|
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.
reACK ed2d714
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.
Concept ACK. Hooray for clearer function intents and effects!
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.
Approach ACK ed2d714
I think this is already a great step in the right direction, but imo there are a bunch more refactoring maintainability improvements in the touched functions that we can make. Quite a few of my comments are suggestions in that direction - I realize some may be out of scope. Even though I'd like to see them incorporated, I'm not very fussed about necessarily doing them in this PR - I'm happy working on a follow-up too.
// When SequenceLocks() is called within ConnectBlock(), the height | ||
// of the block *being* evaluated is what is used. |
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.
This documentation seems out of date now
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.
@hebasto I believe my comment was aimed at that I couldn't find any connection between SequenceLocks()
and ConnectBlock()
and this function - but maybe I just missed it? I also don't really understand what the documentation means, but maybe that's just my lack of context.
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.
I couldn't find any connection between
SequenceLocks()
andConnectBlock()
and this function - but maybe I just missed it?
Line 2257 in 357d750
if (!SequenceLocks(tx, nLockTimeFlags, prevheights, *pindex)) { |
src/validation.cpp
Outdated
const CTxIn& txin = tx.vin[txinIndex]; | ||
Coin coin; | ||
if (!coins_view.GetCoin(txin.prevout, coin)) { | ||
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, txinIndex, tx.GetHash().GetHex()); |
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.
Perhaps you could Assume(false)
here to ensure this is visible in debug builds? I don't think we ever expect this path to be reached?
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.
This change could be considered as a behavior change, so leaving it for now.
@@ -334,20 +336,23 @@ void Chainstate::MaybeUpdateMempoolForReorg( | |||
|
|||
// The transaction must be final. |
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.
While refactoring this function, would it be appropriate to eliminate this behemoth filter_final_and_mature
lambda and turn it into a member function? It's a very straightforward carve-out and I think that massively helps with readability. Example very quick diff below (some lock warnings but compiles otherwise). In addition to carve-out, it also flips the true/false behaviour for an (imo) more intuitive behaviour and naming and thus readability.
git diff
diff --git a/src/validation.cpp b/src/validation.cpp
index 0986147b7..1657323bd 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -323,60 +323,60 @@ void Chainstate::MaybeUpdateMempoolForReorg(
// the disconnectpool that were added back and cleans up the mempool state.
m_mempool->UpdateTransactionsFromBlock(vHashUpdate);
- // Predicate to use for filtering transactions in removeForReorg.
- // Checks whether the transaction is still final and, if it spends a coinbase output, mature.
- // Also updates valid entries' cached LockPoints if needed.
- // If false, the tx is still valid and its lockpoints are updated.
- // If true, the tx would be invalid in the next block; remove this entry and all of its descendants.
- const auto filter_final_and_mature = [this](CTxMemPool::txiter it)
- EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main) {
- AssertLockHeld(m_mempool->cs);
- AssertLockHeld(::cs_main);
- const CTransaction& tx = it->GetTx();
+ // We also need to remove any now-immature transactions
+ m_mempool->removeForReorg(m_chain, [this](CTxMemPool::txiter it){ return !this->IsTxFinalAndMature(it); });
+ // Re-limit mempool size, in case we added any transactions
+ LimitMempoolSize(*m_mempool, this->CoinsTip());
+}
- // The transaction must be final.
- if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true;
+// Predicate to use for filtering transactions in removeForReorg.
+// Checks whether the transaction is still final and, if it spends a coinbase output, mature.
+// Also updates valid entries' cached LockPoints if needed.
+// If true, the tx is still valid and its lockpoints are updated.
+// If false, the tx would be invalid in the next block; remove this entry and all of its descendants.
+bool Chainstate::IsTxFinalAndMature(CTxMemPool::txiter it) EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main)
+{
+ AssertLockHeld(m_mempool->cs);
+ AssertLockHeld(::cs_main);
+ const CTransaction& tx = it->GetTx();
- const LockPoints& lp = it->GetLockPoints();
- // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be
- // created on top of the new chain.
- if (TestLockPointValidity(m_chain, lp)) {
- if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) {
- return true;
- }
+ // The transaction must be final.
+ if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true;
+
+ const LockPoints& lp = it->GetLockPoints();
+ // CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be
+ // created on top of the new chain.
+ if (TestLockPointValidity(m_chain, lp)) {
+ if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) {
+ return false;
+ }
+ } else {
+ const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool};
+ const std::optional<LockPoints> new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)};
+ if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) {
+ // Now update the mempool entry lockpoints as well.
+ m_mempool->mapTx.modify(it, [&new_lock_points](CTxMemPoolEntry& e) { e.UpdateLockPoints(*new_lock_points); });
} else {
- const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool};
- const std::optional<LockPoints> new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)};
- if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) {
- // Now update the mempool entry lockpoints as well.
- m_mempool->mapTx.modify(it, [&new_lock_points](CTxMemPoolEntry& e) { e.UpdateLockPoints(*new_lock_points); });
- } else {
- return true;
- }
+ return false;
}
+ }
- // If the transaction spends any coinbase outputs, it must be mature.
- if (it->GetSpendsCoinbase()) {
- for (const CTxIn& txin : tx.vin) {
- auto it2 = m_mempool->mapTx.find(txin.prevout.hash);
- if (it2 != m_mempool->mapTx.end())
- continue;
- const Coin& coin{CoinsTip().AccessCoin(txin.prevout)};
- assert(!coin.IsSpent());
- const auto mempool_spend_height{m_chain.Tip()->nHeight + 1};
- if (coin.IsCoinBase() && mempool_spend_height - coin.nHeight < COINBASE_MATURITY) {
- return true;
- }
+ // If the transaction spends any coinbase outputs, it must be mature.
+ if (it->GetSpendsCoinbase()) {
+ for (const CTxIn& txin : tx.vin) {
+ auto it2 = m_mempool->mapTx.find(txin.prevout.hash);
+ if (it2 != m_mempool->mapTx.end())
+ continue;
+ const Coin& coin{CoinsTip().AccessCoin(txin.prevout)};
+ assert(!coin.IsSpent());
+ const auto mempool_spend_height{m_chain.Tip()->nHeight + 1};
+ if (coin.IsCoinBase() && mempool_spend_height - coin.nHeight < COINBASE_MATURITY) {
+ return false;
}
}
- // Transaction is still valid and cached LockPoints are updated.
- return false;
- };
-
- // We also need to remove any now-immature transactions
- m_mempool->removeForReorg(m_chain, filter_final_and_mature);
- // Re-limit mempool size, in case we added any transactions
- LimitMempoolSize(*m_mempool, this->CoinsTip());
+ }
+ // Transaction is still valid and cached LockPoints are updated.
+ return true;
}
/**
diff --git a/src/validation.h b/src/validation.h
index bd6f710d1..52f1ee66f 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -291,6 +291,7 @@ std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
* passed in for evaluation.
* The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false.
*/
+
bool CheckSequenceLocksAtTip(CBlockIndex* tip,
const LockPoints& lp);
@@ -770,6 +771,8 @@ private:
DisconnectedBlockTransactions& disconnectpool,
bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
+ bool IsTxFinalAndMature(CTxMemPool::txiter it) EXCLUSIVE_LOCKS_REQUIRED(m_mempool->cs, ::cs_main);
+
/** Check warning conditions and do some notifications on new chain tip set. */
void UpdateTip(const CBlockIndex* pindexNew)
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
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.
Going to leave it as this to keep this PR in more reviewable state.
src/validation.cpp
Outdated
int max_input_height{0}; | ||
for (const int height : prevheights) { | ||
// Can ignore mempool inputs since we'll fail if they had non-zero locks | ||
if (height != index.nHeight) { | ||
max_input_height = std::max(max_input_height, height); | ||
} | ||
} |
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.
Alternative approach using replace_if
and max_element
int max_input_height{0}; | |
for (const int height : prevheights) { | |
// Can ignore mempool inputs since we'll fail if they had non-zero locks | |
if (height != index.nHeight) { | |
max_input_height = std::max(max_input_height, height); | |
} | |
} | |
// Set heights of mempool inputs to 0 since we'll fail if they had non-zero locks | |
std::replace_if(prevheights.begin(), prevheights.end(), [&index](const int height){ return height == index.nHeight; }, 0); | |
const auto max_input_height{*std::max_element(prevheights.begin(), prevheights.end())}; |
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.
Going to leave it as this to keep this PR in more reviewable state.
ed2d714
to
1636bc4
Compare
Updated ed2d714 -> 1636bc4 (pr23897.09 -> pr23897.10, diff):
|
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex()); | ||
return std::nullopt; |
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.
util::Result
would be a good candidate here too, so you can propagate the error message back
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.
Why? The error message is being logged, and it is not exposed to a user in any other way.
Lines 22 to 26 in cac29f5
//! It is intended for high-level functions that need to report error strings to | |
//! end users. Lower-level functions that don't need this error-reporting and | |
//! only need error-handling should avoid util::Result and instead use standard | |
//! classes like std::optional, std::variant, and std::tuple, or custom structs | |
//! and enum types to return function results. |
If I missed your point, what are benefits of propagating the error message back?
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.
Same reason we want to let the callsite decide how to handle what to do when the calculation failed: with util::Result
, the callsite can also decide if they want to log the error or not. In this case, there is only one callsite so it doesn't matter much. Can always refactor later on if necessary. Happy to mark as resolved, just a thought i had.
1636bc4
to
63d6b9a
Compare
Updated 1636bc4 -> 63d6b9a (pr23897.10 -> pr23897.11):
|
4e83b98
to
68c0f16
Compare
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.
Updated 68c0f16 -> 75db62b (pr23897.12 -> pr23897.13, diff). Addressed some of @stickies-v's comments: |
68c0f16
to
75db62b
Compare
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.
re-ACK 75db62b
ACK 75db62b |
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.
code LGTM
This PR is follow up for #22677 and #23683.
On master (013daed) it is not obvious that
CheckSequenceLocksAtTip()
function can modify itsLockPoints* lp
parameter which leads to #22677 (comment).This PR:
CheckSequenceLocksAtTip()
function into a newCalculateLockPointsAtTip()
oneCheckSequenceLocksAtTip()
function interface