Skip to content

Commit a8080c0

Browse files
committed
Merge bitcoin/bitcoin#23897: refactor: Move calculation logic out from CheckSequenceLocksAtTip()
75db62b refactor: Move calculation logic out from `CheckSequenceLocksAtTip()` (Hennadii Stepanov) 3bc434f refactor: Add `CalculateLockPointsAtTip()` function (Hennadii Stepanov) Pull request description: This PR is follow up for bitcoin/bitcoin#22677 and bitcoin/bitcoin#23683. On master (013daed) it is not obvious that `CheckSequenceLocksAtTip()` function can modify its `LockPoints* lp` parameter which leads to bitcoin/bitcoin#22677 (comment). 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 75db62b stickies-v: re-ACK 75db62b Tree-SHA512: 072c3fd9cd1e1b0e0bfc8960a67b01c80a9f16d6778f374b6944ade03a020415ce8b8ab2593b0f5e787059c8cf90af798290b4c826785d41955092f6e12e7486
2 parents 8303f11 + 75db62b commit a8080c0

File tree

3 files changed

+129
-88
lines changed

3 files changed

+129
-88
lines changed

src/test/miner_tests.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ struct MinerTestingSetup : public TestingSetup {
3737
bool TestSequenceLocks(const CTransaction& tx, CTxMemPool& tx_mempool) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
3838
{
3939
CCoinsViewMemPool view_mempool{&m_node.chainman->ActiveChainstate().CoinsTip(), tx_mempool};
40-
return CheckSequenceLocksAtTip(m_node.chainman->ActiveChain().Tip(), view_mempool, tx);
40+
CBlockIndex* tip{m_node.chainman->ActiveChain().Tip()};
41+
const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(tip, view_mempool, tx)};
42+
return lock_points.has_value() && CheckSequenceLocksAtTip(tip, *lock_points);
4143
}
4244
CTxMemPool& MakeMempool()
4345
{

src/validation.cpp

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,89 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
157157
return IsFinalTx(tx, nBlockHeight, nBlockTime);
158158
}
159159

160+
namespace {
161+
/**
162+
* A helper which calculates heights of inputs of a given transaction.
163+
*
164+
* @param[in] tip The current chain tip. If an input belongs to a mempool
165+
* transaction, we assume it will be confirmed in the next block.
166+
* @param[in] coins Any CCoinsView that provides access to the relevant coins.
167+
* @param[in] tx The transaction being evaluated.
168+
*
169+
* @returns A vector of input heights or nullopt, in case of an error.
170+
*/
171+
std::optional<std::vector<int>> CalculatePrevHeights(
172+
const CBlockIndex& tip,
173+
const CCoinsView& coins,
174+
const CTransaction& tx)
175+
{
176+
std::vector<int> prev_heights;
177+
prev_heights.resize(tx.vin.size());
178+
for (size_t i = 0; i < tx.vin.size(); ++i) {
179+
const CTxIn& txin = tx.vin[i];
180+
Coin coin;
181+
if (!coins.GetCoin(txin.prevout, coin)) {
182+
LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex());
183+
return std::nullopt;
184+
}
185+
if (coin.nHeight == MEMPOOL_HEIGHT) {
186+
// Assume all mempool transaction confirm in the next block.
187+
prev_heights[i] = tip.nHeight + 1;
188+
} else {
189+
prev_heights[i] = coin.nHeight;
190+
}
191+
}
192+
return prev_heights;
193+
}
194+
} // namespace
195+
196+
std::optional<LockPoints> CalculateLockPointsAtTip(
197+
CBlockIndex* tip,
198+
const CCoinsView& coins_view,
199+
const CTransaction& tx)
200+
{
201+
assert(tip);
202+
203+
auto prev_heights{CalculatePrevHeights(*tip, coins_view, tx)};
204+
if (!prev_heights.has_value()) return std::nullopt;
205+
206+
CBlockIndex next_tip;
207+
next_tip.pprev = tip;
208+
// When SequenceLocks() is called within ConnectBlock(), the height
209+
// of the block *being* evaluated is what is used.
210+
// Thus if we want to know if a transaction can be part of the
211+
// *next* block, we need to use one more than active_chainstate.m_chain.Height()
212+
next_tip.nHeight = tip->nHeight + 1;
213+
const auto [min_height, min_time] = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prev_heights.value(), next_tip);
214+
215+
// Also store the hash of the block with the highest height of
216+
// all the blocks which have sequence locked prevouts.
217+
// This hash needs to still be on the chain
218+
// for these LockPoint calculations to be valid
219+
// Note: It is impossible to correctly calculate a maxInputBlock
220+
// if any of the sequence locked inputs depend on unconfirmed txs,
221+
// except in the special case where the relative lock time/height
222+
// is 0, which is equivalent to no sequence lock. Since we assume
223+
// input height of tip+1 for mempool txs and test the resulting
224+
// min_height and min_time from CalculateSequenceLocks against tip+1.
225+
int max_input_height{0};
226+
for (const int height : prev_heights.value()) {
227+
// Can ignore mempool inputs since we'll fail if they had non-zero locks
228+
if (height != next_tip.nHeight) {
229+
max_input_height = std::max(max_input_height, height);
230+
}
231+
}
232+
233+
// tip->GetAncestor(max_input_height) should never return a nullptr
234+
// because max_input_height is always less than the tip height.
235+
// It would, however, be a bad bug to continue execution, since a
236+
// LockPoints object with the maxInputBlock member set to nullptr
237+
// signifies no relative lock time.
238+
return LockPoints{min_height, min_time, Assert(tip->GetAncestor(max_input_height))};
239+
}
240+
160241
bool CheckSequenceLocksAtTip(CBlockIndex* tip,
161-
const CCoinsView& coins_view,
162-
const CTransaction& tx,
163-
LockPoints* lp,
164-
bool useExistingLockPoints)
242+
const LockPoints& lock_points)
165243
{
166244
assert(tip != nullptr);
167245

@@ -175,61 +253,7 @@ bool CheckSequenceLocksAtTip(CBlockIndex* tip,
175253
// *next* block, we need to use one more than active_chainstate.m_chain.Height()
176254
index.nHeight = tip->nHeight + 1;
177255

178-
std::pair<int, int64_t> lockPair;
179-
if (useExistingLockPoints) {
180-
assert(lp);
181-
lockPair.first = lp->height;
182-
lockPair.second = lp->time;
183-
}
184-
else {
185-
std::vector<int> prevheights;
186-
prevheights.resize(tx.vin.size());
187-
for (size_t txinIndex = 0; txinIndex < tx.vin.size(); txinIndex++) {
188-
const CTxIn& txin = tx.vin[txinIndex];
189-
Coin coin;
190-
if (!coins_view.GetCoin(txin.prevout, coin)) {
191-
return error("%s: Missing input", __func__);
192-
}
193-
if (coin.nHeight == MEMPOOL_HEIGHT) {
194-
// Assume all mempool transaction confirm in the next block
195-
prevheights[txinIndex] = tip->nHeight + 1;
196-
} else {
197-
prevheights[txinIndex] = coin.nHeight;
198-
}
199-
}
200-
lockPair = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index);
201-
if (lp) {
202-
lp->height = lockPair.first;
203-
lp->time = lockPair.second;
204-
// Also store the hash of the block with the highest height of
205-
// all the blocks which have sequence locked prevouts.
206-
// This hash needs to still be on the chain
207-
// for these LockPoint calculations to be valid
208-
// Note: It is impossible to correctly calculate a maxInputBlock
209-
// if any of the sequence locked inputs depend on unconfirmed txs,
210-
// except in the special case where the relative lock time/height
211-
// is 0, which is equivalent to no sequence lock. Since we assume
212-
// input height of tip+1 for mempool txs and test the resulting
213-
// lockPair from CalculateSequenceLocks against tip+1. We know
214-
// EvaluateSequenceLocks will fail if there was a non-zero sequence
215-
// lock on a mempool input, so we can use the return value of
216-
// CheckSequenceLocksAtTip to indicate the LockPoints validity
217-
int maxInputHeight = 0;
218-
for (const int height : prevheights) {
219-
// Can ignore mempool inputs since we'll fail if they had non-zero locks
220-
if (height != tip->nHeight+1) {
221-
maxInputHeight = std::max(maxInputHeight, height);
222-
}
223-
}
224-
// tip->GetAncestor(maxInputHeight) should never return a nullptr
225-
// because maxInputHeight is always less than the tip height.
226-
// It would, however, be a bad bug to continue execution, since a
227-
// LockPoints object with the maxInputBlock member set to nullptr
228-
// signifies no relative lock time.
229-
lp->maxInputBlock = Assert(tip->GetAncestor(maxInputHeight));
230-
}
231-
}
232-
return EvaluateSequenceLocks(index, lockPair);
256+
return EvaluateSequenceLocks(index, {lock_points.height, lock_points.time});
233257
}
234258

235259
// Returns the script flags which should be checked for a given block
@@ -315,20 +339,23 @@ void Chainstate::MaybeUpdateMempoolForReorg(
315339

316340
// The transaction must be final.
317341
if (!CheckFinalTxAtTip(*Assert(m_chain.Tip()), tx)) return true;
318-
LockPoints lp = it->GetLockPoints();
319-
const bool validLP{TestLockPointValidity(m_chain, lp)};
320-
CCoinsViewMemPool view_mempool(&CoinsTip(), *m_mempool);
342+
343+
const LockPoints& lp = it->GetLockPoints();
321344
// CheckSequenceLocksAtTip checks if the transaction will be final in the next block to be
322-
// created on top of the new chain. We use useExistingLockPoints=false so that, instead of
323-
// using the information in lp (which might now refer to a block that no longer exists in
324-
// the chain), it will update lp to contain LockPoints relevant to the new chain.
325-
if (!CheckSequenceLocksAtTip(m_chain.Tip(), view_mempool, tx, &lp, validLP)) {
326-
// If CheckSequenceLocksAtTip fails, remove the tx and don't depend on the LockPoints.
327-
return true;
328-
} else if (!validLP) {
329-
// If CheckSequenceLocksAtTip succeeded, it also updated the LockPoints.
330-
// Now update the mempool entry lockpoints as well.
331-
m_mempool->mapTx.modify(it, [&lp](CTxMemPoolEntry& e) { e.UpdateLockPoints(lp); });
345+
// created on top of the new chain.
346+
if (TestLockPointValidity(m_chain, lp)) {
347+
if (!CheckSequenceLocksAtTip(m_chain.Tip(), lp)) {
348+
return true;
349+
}
350+
} else {
351+
const CCoinsViewMemPool view_mempool{&CoinsTip(), *m_mempool};
352+
const std::optional<LockPoints> new_lock_points{CalculateLockPointsAtTip(m_chain.Tip(), view_mempool, tx)};
353+
if (new_lock_points.has_value() && CheckSequenceLocksAtTip(m_chain.Tip(), *new_lock_points)) {
354+
// Now update the mempool entry lockpoints as well.
355+
m_mempool->mapTx.modify(it, [&new_lock_points](CTxMemPoolEntry& e) { e.UpdateLockPoints(*new_lock_points); });
356+
} else {
357+
return true;
358+
}
332359
}
333360

334361
// If the transaction spends any coinbase outputs, it must be mature.
@@ -732,7 +759,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
732759
}
733760
}
734761

735-
LockPoints lp;
736762
m_view.SetBackend(m_viewmempool);
737763

738764
const CCoinsViewCache& coins_cache = m_active_chainstate.CoinsTip();
@@ -774,7 +800,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
774800
// be mined yet.
775801
// Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's
776802
// backend was removed, it no longer pulls coins from the mempool.
777-
if (!CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx, &lp)) {
803+
const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)};
804+
if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) {
778805
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
779806
}
780807

@@ -810,7 +837,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
810837
}
811838

812839
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
813-
fSpendsCoinbase, nSigOpsCost, lp));
840+
fSpendsCoinbase, nSigOpsCost, lock_points.value()));
814841
ws.m_vsize = entry->GetTxSize();
815842

816843
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)

src/validation.h

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,27 +267,39 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
267267
bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
268268

269269
/**
270-
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
271-
* @param[in] tip Chain tip to check tx sequence locks against. For example,
272-
* the tip of the current active chain.
270+
* Calculate LockPoints required to check if transaction will be BIP68 final in the next block
271+
* to be created on top of tip.
272+
*
273+
* @param[in] tip Chain tip for which tx sequence locks are calculated. For
274+
* example, the tip of the current active chain.
273275
* @param[in] coins_view Any CCoinsView that provides access to the relevant coins for
274276
* checking sequence locks. For example, it can be a CCoinsViewCache
275277
* that isn't connected to anything but contains all the relevant
276278
* coins, or a CCoinsViewMemPool that is connected to the
277-
* mempool and chainstate UTXO set. In the latter case, the caller is
278-
* responsible for holding the appropriate locks to ensure that
279+
* mempool and chainstate UTXO set. In the latter case, the caller
280+
* is responsible for holding the appropriate locks to ensure that
279281
* calls to GetCoin() return correct coins.
282+
* @param[in] tx The transaction being evaluated.
283+
*
284+
* @returns The resulting height and time calculated and the hash of the block needed for
285+
* calculation, or std::nullopt if there is an error.
286+
*/
287+
std::optional<LockPoints> CalculateLockPointsAtTip(
288+
CBlockIndex* tip,
289+
const CCoinsView& coins_view,
290+
const CTransaction& tx);
291+
292+
/**
293+
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
294+
* @param[in] tip Chain tip to check tx sequence locks against. For example,
295+
* the tip of the current active chain.
296+
* @param[in] lock_points LockPoints containing the height and time at which this
297+
* transaction is final.
280298
* Simulates calling SequenceLocks() with data from the tip passed in.
281-
* Optionally stores in LockPoints the resulting height and time calculated and the hash
282-
* of the block needed for calculation or skips the calculation and uses the LockPoints
283-
* passed in for evaluation.
284299
* The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false.
285300
*/
286301
bool CheckSequenceLocksAtTip(CBlockIndex* tip,
287-
const CCoinsView& coins_view,
288-
const CTransaction& tx,
289-
LockPoints* lp = nullptr,
290-
bool useExistingLockPoints = false);
302+
const LockPoints& lock_points);
291303

292304
/**
293305
* Closure representing one script verification

0 commit comments

Comments
 (0)