refactor: Move calculation logic out from `CheckSequenceLocksAtTip()` #23897

pull hebasto wants to merge 2 commits into bitcoin:master from hebasto:211229-lockpoints changing 3 files +129 −88
  1. hebasto commented at 5:58 PM on December 29, 2021: member

    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 #22677 (review).

    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)
  2. DrahtBot added the label Mempool on Dec 29, 2021
  3. DrahtBot added the label Refactoring on Dec 29, 2021
  4. DrahtBot added the label Validation on Dec 29, 2021
  5. hebasto force-pushed on Dec 29, 2021
  6. hebasto marked this as ready for review on Dec 29, 2021
  7. hebasto force-pushed on Jan 3, 2022
  8. hebasto commented at 2:47 PM on January 3, 2022: member

    Rebased 154307c10b0d3c0e7dfbe463de7d040b2eb97c74 -> c4476a5ea787e3722d8c1e5fed5f0c6e2a554327 (pr23897.01 -> pr23897.02) on top of the merged #23683.

  9. DrahtBot commented at 3:03 PM on January 3, 2022: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK stickies-v, achow101
    Stale ACK glozow, aureleoules

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    No conflicts as of last run.

  10. DrahtBot added the label Needs rebase on Jan 19, 2022
  11. hebasto force-pushed on Jan 19, 2022
  12. hebasto commented at 9:53 PM on January 19, 2022: member

    Rebased c4476a5ea787e3722d8c1e5fed5f0c6e2a554327 -> 220475bdbca0598c6f5158e870b89ff9503a7258 (pr23897.02 -> pr23897.03) on top of the merged #23976.

  13. DrahtBot removed the label Needs rebase on Jan 19, 2022
  14. in src/validation.cpp:219 in 220475bdbc outdated
     210 | @@ -211,14 +211,75 @@ bool CheckFinalTx(const CBlockIndex* active_chain_tip, const CTransaction &tx, i
     211 |      return IsFinalTx(tx, nBlockHeight, nBlockTime);
     212 |  }
     213 |  
     214 | +bool CalculateLockPoints(
     215 | +    CBlockIndex* tip,
     216 | +    const CCoinsView& coins_view,
     217 | +    const CTransaction& tx,
     218 | +    int flags,
     219 | +    LockPoints* lp)
    


    glozow commented at 12:08 PM on January 26, 2022:

    It would be nice to remove the raw pointers while we're changing the interface anyway. I think a better interface is to have CalculateLockPoints() return a std::optional<LockPoints> which encodes the same information as the bool.


    hebasto commented at 4:06 PM on April 19, 2022:

    Thanks! Updated.

  15. in src/validation.cpp:240 in 220475bdbc outdated
     235 | +    prevheights.resize(tx.vin.size());
     236 | +    for (size_t txinIndex = 0; txinIndex < tx.vin.size(); ++txinIndex) {
     237 | +        const CTxIn& txin = tx.vin[txinIndex];
     238 | +        Coin coin;
     239 | +        if (!coins_view.GetCoin(txin.prevout, coin)) {
     240 | +            return error("%s: Missing input", __func__);
    


    glozow commented at 12:09 PM on January 26, 2022:

    Can be replaced with a LogPrintf and return std::nullopt. Also seems like this log could be improved by adding the transaction id and/or prevout that is missing.


    hebasto commented at 4:07 PM on April 19, 2022:

    Thanks! Updated.

  16. in src/validation.cpp:279 in 220475bdbc outdated
     279 | -                        const CCoinsView& coins_view,
     280 | -                        const CTransaction& tx,
     281 | -                        int flags,
     282 | -                        LockPoints* lp,
     283 | -                        bool useExistingLockPoints)
     284 | +                        const LockPoints* lp)
    


    glozow commented at 12:10 PM on January 26, 2022:

    Same here, would be nice to remove the raw pointer and make this parameter a const LockPoints& lp. It doesn't make sense to allow a nullptr lp to be passed in anymore, since we can't check sequence locks on empty lockpoints anyway.


    hebasto commented at 4:07 PM on April 19, 2022:

    Thanks! Updated.

  17. in src/validation.cpp:380 in 220475bdbc outdated
     385 | -            // If CheckSequenceLocks fails, remove the tx and don't depend on the LockPoints.
     386 | -            return true;
     387 | -        } else if (!validLP) {
     388 | -            // If CheckSequenceLocks succeeded, it also updated the LockPoints.
     389 | -            // Now update the mempool entry lockpoints as well.
     390 | +        if (!validLP) {
    


    glozow commented at 12:15 PM on January 26, 2022:

    Note that we do have a bit of a behavior change here: we now update the mempool entry LockPoints even if we're going to remove the entry later. I'm not sure if this is desirable.

    I suggest adding documentation as well, e.g. "If the cached LockPoints are now invalid, recalculate them and update the mempool entry before calling CheckSequenceLocks().


    hebasto commented at 4:09 PM on April 19, 2022:

    Note that we do have a bit of a behavior change here...

    Thank you!

    The current behavior has been preserved in the recent update.

  18. glozow commented at 12:20 PM on January 26, 2022: member

    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.

  19. DrahtBot added the label Needs rebase on Mar 14, 2022
  20. hebasto force-pushed on Apr 19, 2022
  21. hebasto renamed this:
    refactor: Separate lockpoint calculate logic from CheckSequenceLocks function
    refactor: Move calculation logic out from `CheckSequenceLocksAtTip()`
    on Apr 19, 2022
  22. hebasto commented at 4:06 PM on April 19, 2022: member

    Updated 220475bdbca0598c6f5158e870b89ff9503a7258 -> aa8f68b2352dd37cae59867276b38127d7041685 (pr23897.03 -> pr23897.04):

    • rebased
    • addressed @glozow's comments

    Also the PR description has been updated.

  23. DrahtBot removed the label Needs rebase on Apr 19, 2022
  24. DrahtBot added the label Needs rebase on May 6, 2022
  25. hebasto force-pushed on May 28, 2022
  26. hebasto commented at 12:35 PM on May 28, 2022: member

    Rebased aa8f68b2352dd37cae59867276b38127d7041685 -> 5a6ce8cca31b3105cb8e96cd3e5e6c0da0fb3479 (pr23897.04 -> pr23897.05) due to conflict with #24804.

  27. DrahtBot removed the label Needs rebase on May 28, 2022
  28. hebasto requested review from glozow on Jul 20, 2022
  29. in src/validation.h:304 in fc31aa12b9 outdated
     299 | + *                              coins, or a CCoinsViewMemPool that is connected to the
     300 | + *                              mempool and chainstate UTXO set. In the latter case, the caller is
     301 | + *                              responsible for holding the appropriate locks to ensure that
     302 | + *                              calls to GetCoin() return correct coins.
     303 | + *
     304 | + * @returns The resulting height and time calculated and the hash of the block needed for calculation.
    


    glozow commented at 9:18 AM on July 21, 2022:

    in fc31aa12b9599f3a23c22fd65b3eaa73dbb05d81:

    Should document that this returns std::nullopt if there is an error, i.e., if inputs cannot be found in the coins_view


    hebasto commented at 10:21 AM on July 21, 2022:

    Thanks! Updated.

  30. in src/validation.cpp:214 in 5a6ce8cca3 outdated
     184 | @@ -185,11 +185,67 @@ bool CheckFinalTxAtTip(const CBlockIndex* active_chain_tip, const CTransaction&
     185 |      return IsFinalTx(tx, nBlockHeight, nBlockTime);
     186 |  }
     187 |  
     188 | +std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
     189 | +                                                   const CCoinsView& coins_view,
     190 | +                                                   const CTransaction& tx)
     191 | +{
     192 | +    assert(tip);
    


    glozow commented at 9:48 AM on July 21, 2022:

    To me, this suggests tip should be a reference instead of a pointer?


    hebasto commented at 10:15 AM on July 21, 2022:

    CChain::Tip() is been passed as an argument, and it could be a nullptr.


    glozow commented at 10:29 AM on July 21, 2022:

    Sorry I should have said the parameter tip, as in, if a function expects a non-nullptr tip, then perhaps it should take a reference instead of a pointer. But I think I get what you mean, that CChain::Tip() returns a pointer and that's passed around pretty frequently in the codebase.

  31. in src/validation.h:314 in 5a6ce8cca3 outdated
     309 | +
     310 | +/**
     311 | + * Check if transaction will be BIP68 final in the next block to be created on top of tip.
     312 | + * @param[in]   tip             Chain tip to check tx sequence locks against. For example,
     313 | + *                              the tip of the current active chain.
     314 | + * @param[in]   lp              Relative locktime constraints (BIP68).
    


    glozow commented at 9:54 AM on July 21, 2022:

    in 5a6ce8cca31b3105cb8e96cd3e5e6c0da0fb3479

     * [@param](/bitcoin-bitcoin/contributor/param/)[in]   lp              LockPoints containing the height and time at which this transaction is final.
    

    hebasto commented at 10:21 AM on July 21, 2022:

    Thanks! Updated.

  32. glozow commented at 9:56 AM on July 21, 2022: member

    code review ACK 5a6ce8cca31b3105cb8e96cd3e5e6c0da0fb3479

    This looks correct to me, a few suggestions to make things clearer. Probably at least 1 other person should take a look as well.

  33. glozow requested review from maflcko on Jul 21, 2022
  34. hebasto force-pushed on Jul 21, 2022
  35. hebasto commented at 10:20 AM on July 21, 2022: member

    Updated 5a6ce8cca31b3105cb8e96cd3e5e6c0da0fb3479 -> 803869954e0e3bc32279dac71cf40a0614b79566 (pr23897.05 -> pr23897.06, diff):

  36. maflcko commented at 10:28 AM on July 21, 2022: member

    Not sure.

    • The functions are only ever called together, so it seems odd to pull them apart? No objection if you want to move-only some stuff out of a function and then call the newly created function in place.
    • I think the previous changes/docs made it clear that lp may be modified? Otherwise, an inline comment seems fine as well?
  37. glozow commented at 10:32 AM on July 21, 2022: member

    reACK 803869954e0e3bc32279dac71cf40a0614b79566

    Only changes are doc updates, for #23897 (review) and #23897 (review)

  38. glozow commented at 10:52 AM on July 21, 2022: member

    No objection if you want to move-only some stuff out of a function and then call the newly created function in place.

    fwiw this is >50% move-only.

  39. DrahtBot added the label Needs rebase on Aug 12, 2022
  40. hebasto force-pushed on Aug 16, 2022
  41. hebasto commented at 12:21 PM on August 16, 2022: member

    Rebased 803869954e0e3bc32279dac71cf40a0614b79566 -> 32e3fc173410641569bc4df805d5431cf0013d74 (pr23897.06 -> pr23897.07) due to the conflict with #25677.

  42. DrahtBot removed the label Needs rebase on Aug 16, 2022
  43. DrahtBot added the label Needs rebase on Oct 10, 2022
  44. hebasto force-pushed on Oct 10, 2022
  45. hebasto commented at 12:40 PM on October 10, 2022: member

    Rebased 32e3fc173410641569bc4df805d5431cf0013d74 -> bebaddb0767b9c52507e8d50a18510b592c14778 (pr23897.07 -> pr23897.08) due to the conflict with #25073.

  46. DrahtBot removed the label Needs rebase on Oct 10, 2022
  47. in src/validation.cpp:210 in bebaddb076 outdated
     205 | +        } else {
     206 | +            prevheights[txinIndex] = coin.nHeight;
     207 | +        }
     208 | +    }
     209 | +
     210 | +    const std::pair<int, int64_t> lock_pair{CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index)};
    


    aureleoules commented at 2:24 PM on October 11, 2022:

    nit in ed9db580c764cf26fb534be54684c46e79300a38

        const auto [minHeight, minTime] = CalculateSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, prevheights, index);
    

    hebasto commented at 12:06 PM on October 14, 2022:

    Thanks!.Updated.

  48. in src/validation.cpp:204 in ed9db580c7 outdated
     199 | +            LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, txinIndex, tx.GetHash().GetHex());
     200 | +            return std::nullopt;
     201 | +        }
     202 | +        if (coin.nHeight == MEMPOOL_HEIGHT) {
     203 | +            // Assume all mempool transaction confirm in the next block
     204 | +            prevheights[txinIndex] = tip->nHeight + 1;
    


    aureleoules commented at 3:17 PM on October 11, 2022:

    nit in ed9db580c764cf26fb534be54684c46e79300a38: Avoids dereferencing multiple times:

    diff --git a/src/validation.cpp b/src/validation.cpp
    index a25e817a5..0fa8dc30c 100644
    --- a/src/validation.cpp
    +++ b/src/validation.cpp
    @@ -201,7 +201,7 @@ std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
             }
             if (coin.nHeight == MEMPOOL_HEIGHT) {
                 // Assume all mempool transaction confirm in the next block
    -            prevheights[txinIndex] = tip->nHeight + 1;
    +            prevheights[txinIndex] = index.nHeight;
             } else {
                 prevheights[txinIndex] = coin.nHeight;
             }
    @@ -222,7 +222,7 @@ std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
         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 != tip->nHeight + 1) {
    +        if (height != index.nHeight) {
                 max_input_height = std::max(max_input_height, height);
             }
         }
    

    hebasto commented at 12:06 PM on October 14, 2022:

    Thanks!.Updated.

  49. aureleoules approved
  50. aureleoules commented at 4:20 PM on October 11, 2022: member

    ACK bebaddb0767b9c52507e8d50a18510b592c14778 The code is now much clearer and easier to understand and the behavior doesn't seem to have changed.

  51. hebasto force-pushed on Oct 14, 2022
  52. hebasto commented at 12:06 PM on October 14, 2022: member

    Updated bebaddb0767b9c52507e8d50a18510b592c14778 -> ed2d714bd1130291a2f781e644f787d421cdf26e (pr23897.08 -> pr23897.09, diff):

  53. aureleoules approved
  54. aureleoules commented at 12:59 PM on October 14, 2022: member

    reACK ed2d714bd1130291a2f781e644f787d421cdf26e

  55. in src/validation.cpp:194 in ed2d714bd1 outdated
     189 | +    // Thus if we want to know if a transaction can be part of the
     190 | +    // *next* block, we need to use one more than active_chainstate.m_chain.Height()
     191 | +    index.nHeight = tip->nHeight + 1;
     192 | +
     193 | +    std::vector<int> prevheights;
     194 | +    prevheights.resize(tx.vin.size());
    


    stickies-v commented at 2:00 PM on October 24, 2022:

    I'd avoid using resize() since there's no need to default-initialize everything. reserve() seems more appropriate:

        std::vector<int> prevheights;
        prevheights.reserve(tx.vin.size());
    

    hebasto commented at 9:48 AM on November 30, 2022:

    Going to leave it as this because the suggested diff won't work.

  56. in src/validation.cpp:185 in ed2d714bd1 outdated
     180 | +                                                   const CCoinsView& coins_view,
     181 | +                                                   const CTransaction& tx)
     182 | +{
     183 | +    assert(tip);
     184 | +
     185 | +    CBlockIndex index;
    


    stickies-v commented at 6:22 PM on November 3, 2022:

    nit: would this be a more helpful name?

        CBlockIndex next_tip;
    

    hebasto commented at 9:46 AM on November 30, 2022:

    Thanks! Updated.

  57. in src/validation.cpp:214 in ed2d714bd1 outdated
     175 | @@ -176,11 +176,67 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
     176 |      return IsFinalTx(tx, nBlockHeight, nBlockTime);
     177 |  }
     178 |  
     179 | +std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
     180 | +                                                   const CCoinsView& coins_view,
     181 | +                                                   const CTransaction& tx)
     182 | +{
     183 | +    assert(tip);
    


    stickies-v commented at 10:08 AM on November 4, 2022:

    Would it make sense to pass a CBlockIndex& instead and avoid checking for nullptr here? If this fn requires a non-nullptr CBlockIndex, I think the fn signature should reflect that and the callsite to take responsibility.


    hebasto commented at 9:46 AM on November 30, 2022:

    Going to leave it as this to keep this PR in more reviewable state.

  58. in src/validation.h:281 in ed2d714bd1 outdated
     275 | + * @returns The resulting height and time calculated and the hash of the block needed for
     276 | + *          calculation, or std::nullopt if there is an error.
     277 | + */
     278 | +std::optional<LockPoints> CalculateLockPointsAtTip(CBlockIndex* tip,
     279 | +                                                   const CCoinsView& coins_view,
     280 | +                                                   const CTransaction& tx);
    


    stickies-v commented at 10:12 AM on November 4, 2022:

    nit: missing doxygen for tx param


    hebasto commented at 9:45 AM on November 30, 2022:

    Thanks! Updated.

  59. stickies-v commented at 10:14 AM on November 4, 2022: contributor

    Concept ACK. Hooray for clearer function intents and effects!

  60. in src/validation.cpp:222 in ed2d714bd1 outdated
     183 | +    assert(tip);
     184 | +
     185 | +    CBlockIndex index;
     186 | +    index.pprev = tip;
     187 | +    // When SequenceLocks() is called within ConnectBlock(), the height
     188 | +    // of the block *being* evaluated is what is used.
    


    stickies-v commented at 3:29 PM on November 4, 2022:

    This documentation seems out of date now


    stickies-v commented at 4:48 PM on November 29, 2022:

    @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.


    hebasto commented at 1:30 PM on January 31, 2023:

    I couldn't find any connection between SequenceLocks() and ConnectBlock() and this function - but maybe I just missed it?

    https://github.com/bitcoin/bitcoin/blob/357d750cab5221695d718e6d3e8ce63fa2b5ab3a/src/validation.cpp#L2257

  61. in src/validation.cpp:203 in ed2d714bd1 outdated
     202 | +        if (coin.nHeight == MEMPOOL_HEIGHT) {
     203 | +            // Assume all mempool transaction confirm in the next block
     204 | +            prevheights[txinIndex] = index.nHeight;
     205 | +        } else {
     206 | +            prevheights[txinIndex] = coin.nHeight;
     207 | +        }
    


    stickies-v commented at 4:42 PM on November 4, 2022:

    For readability, I think this chunk of code could be separated out into a CalculatePrevHeights function?


    hebasto commented at 5:02 PM on November 29, 2022:

    Thanks! Updated.

  62. in src/validation.cpp:239 in ed2d714bd1 outdated
     238 |  bool CheckSequenceLocksAtTip(CBlockIndex* tip,
     239 | -                        const CCoinsView& coins_view,
     240 | -                        const CTransaction& tx,
     241 | -                        LockPoints* lp,
     242 | -                        bool useExistingLockPoints)
     243 | +                             const LockPoints& lp)
    


    stickies-v commented at 4:47 PM on November 4, 2022:

    nit: I think overly abbreviated variable names should be avoided for readability

                                 const LockPoints& lock_points)
    

    hebasto commented at 5:02 PM on November 29, 2022:

    Thanks! Updated.

  63. in src/validation.cpp:199 in ed2d714bd1 outdated
     194 | +    prevheights.resize(tx.vin.size());
     195 | +    for (size_t txinIndex = 0; txinIndex < tx.vin.size(); ++txinIndex) {
     196 | +        const CTxIn& txin = tx.vin[txinIndex];
     197 | +        Coin coin;
     198 | +        if (!coins_view.GetCoin(txin.prevout, coin)) {
     199 | +            LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, txinIndex, tx.GetHash().GetHex());
    


    stickies-v commented at 11:55 AM on November 10, 2022:

    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?


    hebasto commented at 5:01 PM on November 29, 2022:

    This change could be considered as a behavior change, so leaving it for now.

  64. in src/validation.cpp:353 in ed2d714bd1 outdated
     335 | @@ -334,20 +336,23 @@ void Chainstate::MaybeUpdateMempoolForReorg(
     336 |  
     337 |          // The transaction must be final.
    


    stickies-v commented at 12:44 PM on November 10, 2022:

    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.

    <details> <summary>git diff</summary>

    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);
    

    </details>


    hebasto commented at 5:00 PM on November 29, 2022:

    Going to leave it as this to keep this PR in more reviewable state.

  65. in src/validation.cpp:835 in ed2d714bd1 outdated
     831 | @@ -827,7 +832,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
     832 |      }
     833 |  
     834 |      entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(),
     835 | -            fSpendsCoinbase, nSigOpsCost, lp));
     836 | +            fSpendsCoinbase, nSigOpsCost, *lock_points));
    


    stickies-v commented at 3:50 PM on November 10, 2022:

    Since we're doing the .has_value() check at quite a distance from here, I think it's safer to use .value() for robustness with future refactoring.

                fSpendsCoinbase, nSigOpsCost, lock_points.value()));
    

    hebasto commented at 4:58 PM on November 29, 2022:

    Thanks! Updated.

  66. in src/validation.h:277 in ed2d714bd1 outdated
     287 | + *                              transaction is final.
     288 |   * Simulates calling SequenceLocks() with data from the tip passed in.
     289 |   * Optionally stores in LockPoints the resulting height and time calculated and the hash
     290 |   * of the block needed for calculation or skips the calculation and uses the LockPoints
     291 |   * passed in for evaluation.
     292 |   * The LockPoints should not be considered valid if CheckSequenceLocksAtTip returns false.
    


    stickies-v commented at 4:01 PM on November 10, 2022:

    This documentation now seems out of date


    stickies-v commented at 4:34 PM on November 29, 2022:

    @hebasto I think we don't modify LockPoints anymore?

     * Optionally stores in LockPoints the resulting height and time calculated and the hash
     * of the block needed for calculation or skips the calculation and uses the LockPoints
     * passed in for evaluation.
    

    hebasto commented at 4:57 PM on November 29, 2022:

    Thanks! Updated.

  67. in src/validation.cpp:244 in ed2d714bd1 outdated
     223 | +    for (const int height : prevheights) {
     224 | +        // Can ignore mempool inputs since we'll fail if they had non-zero locks
     225 | +        if (height != index.nHeight) {
     226 | +            max_input_height = std::max(max_input_height, height);
     227 | +        }
     228 | +    }
    


    stickies-v commented at 4:51 PM on November 10, 2022:

    Alternative approach using replace_if and max_element

        // 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())};
    

    hebasto commented at 4:57 PM on November 29, 2022:

    Going to leave it as this to keep this PR in more reviewable state.

  68. stickies-v commented at 5:03 PM on November 10, 2022: contributor

    Approach ACK ed2d714bd

    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.

  69. hebasto force-pushed on Nov 29, 2022
  70. hebasto commented at 4:55 PM on November 29, 2022: member

    Updated ed2d714bd1130291a2f781e644f787d421cdf26e -> 1636bc465fd96b9561de3dd21bed7f989f409de1 (pr23897.09 -> pr23897.10, diff):

  71. in src/validation.cpp:184 in 1636bc465f outdated
     175 | @@ -176,11 +176,79 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
     176 |      return IsFinalTx(tx, nBlockHeight, nBlockTime);
     177 |  }
     178 |  
     179 | +namespace {
     180 | +std::optional<std::vector<int>> CalculatePrevHeights(
    


    stickies-v commented at 12:46 PM on December 2, 2022:

    I think this function would benefit from a docstring


    hebasto commented at 4:17 PM on December 3, 2022:

    Thanks! Updated.

  72. in src/validation.cpp:181 in 1636bc465f outdated
     175 | @@ -176,11 +176,79 @@ bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction&
     176 |      return IsFinalTx(tx, nBlockHeight, nBlockTime);
     177 |  }
     178 |  
     179 | +namespace {
     180 | +std::optional<std::vector<int>> CalculatePrevHeights(
     181 | +    const CBlockIndex next_tip,
    


    stickies-v commented at 12:49 PM on December 2, 2022:

    any reason not to pass a reference here?

        const CBlockIndex& next_tip,
    

    hebasto commented at 4:17 PM on December 3, 2022:

    Thanks! Updated.

  73. in src/validation.cpp:224 in 1636bc465f outdated
     214 | +    // of the block *being* evaluated is what is used.
     215 | +    // Thus if we want to know if a transaction can be part of the
     216 | +    // *next* block, we need to use one more than active_chainstate.m_chain.Height()
     217 | +    next_tip.nHeight = tip->nHeight + 1;
     218 | +
     219 | +    auto prev_heights = CalculatePrevHeights(next_tip, coins_view, tx);
    


    stickies-v commented at 12:51 PM on December 2, 2022:

    nit

        auto prev_heights{CalculatePrevHeights(next_tip, coins_view, tx)};
    

    hebasto commented at 2:52 PM on December 3, 2022:

    https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-auto:

    Exception Avoid auto for initializer lists and in cases where you know exactly which type you want and where an initializer might require conversion.


    stickies-v commented at 4:16 PM on January 17, 2023:

    Correct me if I'm wrong, but I don't think this applies here. The recommendation you reference is if the type is not specified, e.g. auto i{1}. In this case, CalculatePrevHeights() has a well defined return type, so list initialization seems appropriate.


    hebasto commented at 1:36 PM on January 31, 2023:

    Thanks! Updated.

  74. in src/validation.cpp:196 in 1636bc465f outdated
     187 | +    for (size_t i = 0; i < tx.vin.size(); ++i) {
     188 | +        const CTxIn& txin = tx.vin[i];
     189 | +        Coin coin;
     190 | +        if (!coins.GetCoin(txin.prevout, coin)) {
     191 | +            LogPrintf("ERROR: %s: Missing input %d in transaction \'%s\'\n", __func__, i, tx.GetHash().GetHex());
     192 | +            return std::nullopt;
    


    stickies-v commented at 12:57 PM on December 2, 2022:

    util::Result would be a good candidate here too, so you can propagate the error message back


    hebasto commented at 3:32 PM on December 3, 2022:

    Why? The error message is being logged, and it is not exposed to a user in any other way.

    https://github.com/bitcoin/bitcoin/blob/cac29f5cd66f005ce65edd697990d495a904a1f1/src/util/result.h#L22-L26

    If I missed your point, what are benefits of propagating the error message back?


    stickies-v commented at 4:22 PM on January 17, 2023:

    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.

  75. hebasto force-pushed on Dec 3, 2022
  76. hebasto commented at 4:16 PM on December 3, 2022: member

    Updated 1636bc465fd96b9561de3dd21bed7f989f409de1 -> 63d6b9a9f067b81fed6029099a4643996e8d274b (pr23897.10 -> pr23897.11):

  77. hebasto marked this as a draft on Dec 3, 2022
  78. hebasto force-pushed on Dec 3, 2022
  79. hebasto force-pushed on Dec 3, 2022
  80. hebasto marked this as ready for review on Dec 3, 2022
  81. in src/validation.h:251 in 68c0f16fa2 outdated
     244 | @@ -245,7 +245,9 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
     245 |  bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
     246 |  
     247 |  /**
     248 | - * Check if transaction will be BIP68 final in the next block to be created on top of tip.
     249 | + * Calculate LockPoints required to check if transaction will be BIP68 final in the next block
     250 | + * to be created on top of tip.
     251 | + *
     252 |   * @param[in]   tip             Chain tip to check tx sequence locks against. For example,
    


    stickies-v commented at 4:44 PM on January 17, 2023:

    nit: "check tx sequence locks" I think should be updated to reflect that this is a pure calculation function, not a checking function?


    stickies-v commented at 11:42 AM on January 30, 2023:

    E.g. "Chain tip to check tx sequence locks against." -> "Chain tip for which tx sequence locks are calculated."


    hebasto commented at 1:36 PM on January 31, 2023:

    Thanks! Updated.

  82. stickies-v approved
  83. stickies-v commented at 6:22 PM on January 18, 2023: contributor

    ACK 68c0f16fa2a41d6a25361f0bfa0b74ac5c8c43e2

    A couple of improvements for follow-up that I think would further benefit the readability of this code, but I agree with @hebasto's assessment that they may reduce the reviewability of this PR and they are not blocking imo (so comments can be marked resolved, keeping track here):

  84. refactor: Add `CalculateLockPointsAtTip()` function 3bc434f459
  85. refactor: Move calculation logic out from `CheckSequenceLocksAtTip()` 75db62ba4c
  86. hebasto commented at 1:35 PM on January 31, 2023: member

    Updated 68c0f16fa2a41d6a25361f0bfa0b74ac5c8c43e2 -> 75db62ba4cae048e742ca02dc6a52b3b3d6727de (pr23897.12 -> pr23897.13, diff).

    Addressed some of @stickies-v's comments:

  87. hebasto force-pushed on Jan 31, 2023
  88. stickies-v approved
  89. stickies-v commented at 3:09 PM on January 31, 2023: contributor

    re-ACK 75db62b

  90. achow101 commented at 3:35 PM on February 28, 2023: member

    ACK 75db62ba4cae048e742ca02dc6a52b3b3d6727de

  91. achow101 requested review from glozow on Feb 28, 2023
  92. DrahtBot requested review from aureleoules on Feb 28, 2023
  93. glozow commented at 4:48 PM on February 28, 2023: member

    code LGTM

  94. DrahtBot requested review from glozow on Feb 28, 2023
  95. glozow merged this on Feb 28, 2023
  96. glozow closed this on Feb 28, 2023

  97. hebasto deleted the branch on Feb 28, 2023
  98. sidhujag referenced this in commit 8978946b4c on Mar 1, 2023
  99. bitcoin locked this on Feb 28, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-02 12:14 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me