aboutsummaryrefslogtreecommitdiff
path: root/src/validation.cpp
diff options
context:
space:
mode:
authorglozow <gloriajzhao@gmail.com>2023-02-28 16:47:15 +0000
committerglozow <gloriajzhao@gmail.com>2023-02-28 16:53:02 +0000
commita8080c0def0be59b98fe418d1e8efaf502a20062 (patch)
tree94f1828d3c5e7ca3f55db9a09910b04d58c454ba /src/validation.cpp
parent8303f11e106807ea82938b2f3878b33daedb3d3f (diff)
parent75db62ba4cae048e742ca02dc6a52b3b3d6727de (diff)
Merge bitcoin/bitcoin#23897: refactor: Move calculation logic out from `CheckSequenceLocksAtTip()`
75db62ba4cae048e742ca02dc6a52b3b3d6727de refactor: Move calculation logic out from `CheckSequenceLocksAtTip()` (Hennadii Stepanov) 3bc434f4590758db673e1bd4ebf1906ea632f593 refactor: Add `CalculateLockPointsAtTip()` function (Hennadii Stepanov) Pull request description: This PR is follow up for bitcoin/bitcoin#22677 and bitcoin/bitcoin#23683. On master (013daed9acca1b723f599d63ab36b9c2a5c60e5f) it is not obvious that `CheckSequenceLocksAtTip()` function can modify its `LockPoints* lp` parameter which leads to https://github.com/bitcoin/bitcoin/pull/22677#discussion_r762040101. This PR: - separates the lockpoint calculate logic from `CheckSequenceLocksAtTip()` function into a new `CalculateLockPointsAtTip()` one - cleans up the `CheckSequenceLocksAtTip()` function interface - makes code easier to reason about (hopefully) ACKs for top commit: achow101: ACK 75db62ba4cae048e742ca02dc6a52b3b3d6727de stickies-v: re-ACK 75db62b Tree-SHA512: 072c3fd9cd1e1b0e0bfc8960a67b01c80a9f16d6778f374b6944ade03a020415ce8b8ab2593b0f5e787059c8cf90af798290b4c826785d41955092f6e12e7486
Diffstat (limited to 'src/validation.cpp')
-rw-r--r--src/validation.cpp177
1 files changed, 102 insertions, 75 deletions
diff --git a/src/validation.cpp b/src/validation.cpp
index 64e4d148de..0674454883 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -157,11 +157,89 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
return IsFinalTx(tx, nBlockHeight, nBlockTime);
}
+namespace {
+/**
+ * A helper which calculates heights of inputs of a given transaction.
+ *
+ * @param[in] tip The current chain tip. If an input belongs to a mempool
+ * transaction, we assume it will be confirmed in the next block.
+ * @param[in] coins Any CCoinsView that provides access to the relevant coins.
+ * @param[in] tx The transaction being evaluated.
+ *
+ * @returns A vector of input heights or nullopt, in case of an error.
+ */
+std::optional<std::vector<int>> CalculatePrevHeights(
+ const CBlockIndex& tip,
+ const CCoinsView& coins,
+ const CTransaction& tx)
+{
+ std::vector<int> prev_heights;
+ prev_heights.resize(tx.vin.size());
+ for (size_t i = 0; i < tx.vin.size(); ++i) {
+ const CTxIn& txin = tx.vin[i];
+ Coin coin;
+ if (!coins.GetCoin(txin.prevout, coin)) {
+ LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex());
+ return std::nullopt;
+ }
+ if (coin.nHeight == MEMPOOL_HEIGHT) {
+ // Assume all mempool transaction confirm in the next block.
+ prev_heights[i] = tip.nHeight + 1;
+ } else {
+ prev_heights[i] = coin.nHeight;
+ }
+ }
+ return prev_heights;
+}
+} // namespace
+
+std::optional<LockPoints> CalculateLockPointsAtTip(
+ CBlockIndex* tip,
+ const CCoinsView& coins_view,
+ const CTransaction& tx)
+{
+ assert(tip);
+
+ auto prev_heights{CalculatePrevHeights(*tip, coins_view, tx)};
+ if (!prev_heights.has_value()) return std::nullopt;
+
+ CBlockIndex next_tip;
+ next_tip.pprev = tip;
+ // When SequenceLocks() is called within ConnectBlock(), the height
+ // of the block *being* evaluated is what is used.
+ // Thus if we want to know if a transaction can be part of the
+ // *next* block, we need to use one more than active_chainstate.m_chain.Height()
+ next_tip.nHeight = tip->nHeight + 1;
+ const auto [min_height, min_time] = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prev_heights.value(), next_tip);
+
+ // Also store the hash of the block with the highest height of
+ // all the blocks which have sequence locked prevouts.
+ // This hash needs to still be on the chain
+ // for these LockPoint calculations to be valid
+ // Note: It is impossible to correctly calculate a maxInputBlock
+ // if any of the sequence locked inputs depend on unconfirmed txs,
+ // except in the special case where the relative lock time/height
+ // is 0, which is equivalent to no sequence lock. Since we assume
+ // input height of tip+1 for mempool txs and test the resulting
+ // min_height and min_time from CalculateSequenceLocks against tip+1.
+ int max_input_height{0};
+ for (const int height : prev_heights.value()) {
+ // Can ignore mempool inputs since we'll fail if they had non-zero locks
+ if (height != next_tip.nHeight) {
+ max_input_height = std::max(max_input_height, height);
+ }
+ }
+
+ // tip->GetAncestor(max_input_height) should never return a nullptr
+ // because max_input_height is always less than the tip height.
+ // It would, however, be a bad bug to continue execution, since a
+ // LockPoints object with the maxInputBlock member set to nullptr
+ // signifies no relative lock time.
+ return LockPoints{min_height, min_time, Assert(tip->GetAncestor(max_input_height))};
+}
+
bool CheckSequenceLocksAtTip(CBlockIndex* tip,
- const CCoinsView& coins_view,
- const CTransaction& tx,
- LockPoints* lp,
- bool useExistingLockPoints)
+ const LockPoints& lock_points)
{
assert(tip != nullptr);
@@ -175,61 +253,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip,
// *next* block, we need to use one more than active_chainstate.m_chain.Height()
index.nHeight = tip->nHeight + 1;
- std::pair<int, int64_t> lockPair;
- if (useExistingLockPoints) {
- assert(lp);
- lockPair.first = lp->height;
- lockPair.second = lp->time;
- }
- else {
- std::vector<int> prevheights;
- prevheights.resize(tx.vin.size());
- for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
- const CTxIn& txin = tx.vin[txinIndex];
- Coin coin;
- if (!coins_view.GetCoin(txin.prevout, coin)) {
- return error("%s: Missing input", __func__);
- }
- if (coin.nHeight == MEMPOOL_HEIGHT) {
- // Assume all mempool transaction confirm in the next block
- prevheights[txinIndex] = tip->nHeight + 1;
- } else {
- prevheights[txinIndex] = coin.nHeight;
- }
- }
- lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index);
- if (lp) {
- lp->height = lockPair.first;
- lp->time = lockPair.second;
- // Also store the hash of the block with the highest height of
- // all the blocks which have sequence locked prevouts.
- // This hash needs to still be on the chain
- // for these LockPoint calculations to be valid
- // Note: It is impossible to correctly calculate a maxInputBlock
- // if any of the sequence locked inputs depend on unconfirmed txs,
- // except in the special case where the relative lock time/height
- // is 0, which is equivalent to no sequence lock. Since we assume
- // input height of tip+1 for mempool txs and test the resulting
- // lockPair from CalculateSequenceLocks against tip+1. We know
- // EvaluateSequenceLocks will fail if there was a non-zero sequence
- // lock on a mempool input, so we can use the return value of
- // CheckSequenceLocksAtTip to indicate the LockPoints validity
- int maxInputHeight = 0;
- for (const int height : prevheights) {
- // Can ignore mempool inputs since we'll fail if they had non-zero locks
- if (height != tip->nHeight+1) {
- maxInputHeight = std::max(maxInputHeight, height);
- }
- }
- // tip->GetAncestor(maxInputHeight) should never return a nullptr
- // because maxInputHeight is always less than the tip height.
- // It would, however, be a bad bug to continue execution, since a
- // LockPoints object with the maxInputBlock member set to nullptr
- // signifies no relative lock time.
- lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight));
- }
- }
- return EvaluateSequenceLocks(index, lockPair);
+ return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time});
}
// Returns the script flags which should be checked for a given block
@@ -315,20 +339,23 @@ void Chainstate::MaybeUpdateMempoolForReorg(
// The transaction must be final.
if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true;
- LockPoints lp = it->GetLockPoints();
- const bool validLP{TestLockPointValidity(m_chain, lp)};
- CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool);
+
+ 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. We use useExistingLockPoints=false so that, instead of
- // using the information in lp (which might now refer to a block that no longer exists in
- // the chain), it will update lp to contain LockPoints relevant to the new chain.
- if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) {
- // If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints.
- return true;
- } else if (!validLP) {
- // If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints.
- // Now update the mempool entry lockpoints as well.
- m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); });
+ // created on top of the new chain.
+ if (TestLockPointValidity(m_chain, lp)) {
+ if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) {
+ return true;
+ }
+ } 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;
+ }
}
// If the transaction spends any coinbase outputs, it must be mature.
@@ -732,7 +759,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
}
- LockPoints lp;
m_view.SetBackend(m_viewmempool);
const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
@@ -774,7 +800,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// be mined yet.
// Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's
// backend was removed, it no longer pulls coins from the mempool.
- if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) {
+ const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)};
+ if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
}
@@ -810,7 +837,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
- fSpendsCoinbase, nSigOpsCost, lp));
+ fSpendsCoinbase, nSigOpsCost, lock_points.value()));
ws.m_vsize = entry->GetTxSize();
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)