Skip to content

Commit 3bc434f

Browse files
committed
refactor: Add CalculateLockPointsAtTip() function
1 parent 78aee0f commit 3bc434f

File tree

2 files changed

+104
-0
lines changed

2 files changed

+104
-0
lines changed

src/validation.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,87 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
170170
return IsFinalTx(tx, nBlockHeight, nBlockTime);
171171
}
172172

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

src/validation.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,29 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
244244
*/
245245
bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
246246

247+
/**
248+
* Calculate LockPoints required to check if transaction will be BIP68 final in the next block
249+
* to be created on top of tip.
250+
*
251+
* @param[in] tip Chain tip for which tx sequence locks are calculated. For
252+
* example, the tip of the current active chain.
253+
* @param[in] coins_view Any CCoinsView that provides access to the relevant coins for
254+
* checking sequence locks. For example, it can be a CCoinsViewCache
255+
* that isn't connected to anything but contains all the relevant
256+
* coins, or a CCoinsViewMemPool that is connected to the
257+
* mempool and chainstate UTXO set. In the latter case, the caller
258+
* is responsible for holding the appropriate locks to ensure that
259+
* calls to GetCoin() return correct coins.
260+
* @param[in] tx The transaction being evaluated.
261+
*
262+
* @returns The resulting height and time calculated and the hash of the block needed for
263+
* calculation, or std::nullopt if there is an error.
264+
*/
265+
std::optional<LockPoints> CalculateLockPointsAtTip(
266+
CBlockIndex* tip,
267+
const CCoinsView& coins_view,
268+
const CTransaction& tx);
269+
247270
/**
248271
* Check if transaction will be BIP68 final in the next block to be created on top of tip.
249272
* @param[in] tip Chain tip to check tx sequence locks against. For example,

0 commit comments

Comments
 (0)