diff --git a/src/validation.cpp b/src/validation.cpp index 1cf6fc0675..21c91fbdda 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -170,6 +170,87 @@ 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> CalculatePrevHeights( + const CBlockIndex& tip, + const CCoinsView& coins, + const CTransaction& tx) +{ + std::vector 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 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, diff --git a/src/validation.h b/src/validation.h index b8151dc1fc..36c59b41df 100644 --- a/src/validation.h +++ b/src/validation.h @@ -244,6 +244,29 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM */ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); +/** + * Calculate LockPoints required to check if transaction will be BIP68 final in the next block + * to be created on top of tip. + * + * @param[in] tip Chain tip for which tx sequence locks are calculated. For + * example, the tip of the current active chain. + * @param[in] coins_view Any CCoinsView that provides access to the relevant coins for + * checking sequence locks. For example, it can be a CCoinsViewCache + * that isn't connected to anything but contains all the relevant + * coins, or a CCoinsViewMemPool that is connected to the + * mempool and chainstate UTXO set. In the latter case, the caller + * is responsible for holding the appropriate locks to ensure that + * calls to GetCoin() return correct coins. + * @param[in] tx The transaction being evaluated. + * + * @returns The resulting height and time calculated and the hash of the block needed for + * calculation, or std::nullopt if there is an error. + */ +std::optional CalculateLockPointsAtTip( + CBlockIndex* tip, + const CCoinsView& coins_view, + const CTransaction& tx); + /** * Check if transaction will be BIP68 final in the next block to be created on top of tip. * @param[in] tip Chain tip to check tx sequence locks against. For example,