kernel: Separate UTXO set access from validation functions #32317

pull sedited wants to merge 8 commits into bitcoin:master from sedited:spendblock changing 16 files +425 −206
  1. sedited commented at 1:15 pm on April 21, 2025: contributor

    The current code makes it hard to call functions for checking the consensus-correctness of a block without having a full UTXO set at hand. This makes implementing features such as Utreexo and non-assumevalid swiftsync, where maintaining a full UTXO set defeats their purpose, through the kernel API difficult. Checks like the coinbase subsidy, or block sigops are only available through ConnectBlock, which requires a complete coins cache.

    Solve this by moving accessing coins and the responsibility of spending them out of ConnectBlock and into a new separate function called SpendBlock. Pass a block’s spent coins through the existing CBlockUndo data structure to ConnectBlock.

    While the change is largely a refactor, some smaller behaviour changes were unavoidable. These should be highlighted in the commit messages. The last commit contains the most important part of the coins logic move, the commits before attempt to prepare it piecemeal.

    This pull request was benchmarked on benchcoin, which showed no performance regression. While discussing this pull request it was noted that pre-fetching and eliminating the need for some additional map lookups might improve performance a bit, but this does not make a measurable difference in the grand scheme of things and was not the motivation for this pull request.

    In future changes, ConnectBlock could be made even more self contained, accessing the coins from the internal map could be further optimized by consolidating existence checking and spending, and the function eventually exposed in an external kernel API (as done here)

  2. DrahtBot commented at 1:15 pm on April 21, 2025: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32317.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ismaelsadeeq
    Concept ACK w0xlt, stickies-v, stringintech, enirox001

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34165 (coins: don’t mutate main cache when connecting block by andrewtoth)
    • #34164 (validation: add reusable coins view for ConnectBlock by andrewtoth)
    • #34004 (Implementation of SwiftSync by rustaceanrob)
    • #32840 (test: Enhance GetTxSigOpCost tests for coinbase transactions by average-gary)
    • #32729 (test,refactor: extract script template helpers & widen sigop count coverage by l0rinc)
    • #32575 (refactor: Split multithreaded case out of CheckInputScripts by fjahr)
    • #32468 (rpc: generateblock to allow multiple outputs by polespinasa)
    • #32427 ((RFC) kernel: Replace leveldb-based BlockTreeDB with flat-file based store by sedited)
    • #31974 (Drop testnet3 by Sjors)
    • #31132 (validation: fetch block inputs on parallel threads 3x faster IBD by andrewtoth)
    • #29843 (policy: Allow non-standard scripts with -acceptnonstdtxn=1 (test nets only) by ajtowns)
    • #29060 (Policy: Report debug message why inputs are non standard by ismaelsadeeq)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • “…(amounts and maturity) This does not modify the UTXO set.” -> “…(amounts and maturity). This does not modify the UTXO set.” [Missing sentence separator (period) after the parenthesis makes the sentence run-on and harms readability.]

    • “Return a vector of unspent outputs of coins in the cache that are spent by the provided transaction.” -> “Return a vector of unspent outputs (CTxOut) from the cache that are being spent by the provided transaction.” [The original phrasing is internally contradictory/unclear (“unspent outputs … that are spent”); rewording clarifies that these are the outputs in the cache which the transaction spends.]

    • “@param[in] blockundo Has to contain all coins spent by block. Written to disk on successful validation” -> “@param[in] blockundo Must contain all coins spent by the block and is written to disk on successful validation.” [Sentence fragment and missing auxiliary verb; the original is grammatically fragmented and slightly ambiguous.]

    2025-12-03

  3. DrahtBot added the label Validation on Apr 21, 2025
  4. sedited renamed this:
    kernel: Seperate UTXO set access from validation functions
    kernel: Separate UTXO set access from validation functions
    on Apr 21, 2025
  5. in src/validation.h:737 in 2be3c59151 outdated
    708@@ -709,6 +709,9 @@ class Chainstate
    709     bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
    710                       CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    711 
    712+    bool SpendBlock(const CBlock& block, const CBlockIndex* pindex, const uint256& block_hash,
    


    Sjors commented at 1:44 pm on April 21, 2025:

    In 2be3c59151bcb840eb8cbd045a3dada6775ebc9e “validation: Add SpendBlock function”: this or the final commit would be a good time to document both SpendBlock and ConnectBlock.

    IIUC SpendBlock:

    1. Checks that all spent coins existed i) including coins created and spent within the block, in that order
    2. Optimistically updates the UTXO set with newly created coins i) checks BIP30 (not in this order, but conceptually)
    3. Populates Undo data

    And then ConnectBlock does all the other consensus checks (like verifying the input scripts). It uses the Undo data populated by SpendBlock and does not need the UTXO set.


    sedited commented at 4:15 pm on April 21, 2025:
    Yes, that is the gist of it. Will add a commit at the end to add some documentation in the header.

    sedited commented at 7:33 am on April 22, 2025:
    Added a commit.
  6. DrahtBot added the label CI failed on Apr 21, 2025
  7. DrahtBot commented at 2:43 pm on April 21, 2025: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/40876609750

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  8. sedited force-pushed on Apr 21, 2025
  9. in src/validation.h:710 in c1b36c4554 outdated
    705@@ -706,8 +706,11 @@ class Chainstate
    706     // Block (dis)connection on a given view:
    707     DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
    708         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
    709-    bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
    710-                      CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    711+    bool ConnectBlock(const CBlock& block, const uint256& block_hash, BlockValidationState& state, CBlockIndex* pindex,
    712+                      CBlockUndo& blockundo, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    


    darosior commented at 5:14 pm on April 21, 2025:
    I think it would be clearer to take a vector of spent Coins directly? Or even a span?

    sedited commented at 7:02 pm on April 21, 2025:
    I might be missing something, but it would have to be a span of CTxUndo, and you’d have to move WriteBlockUndo to ConnectTip too, no? I wasn’t quite sure about doing that change here though. The implications of separately writing the undo data and raising the BlockIndex validity level are not quite clear to me yet. There is always a question where to draw the scope line with these refactors. While it might not be useful to write undo data if we ever get to the alternative kernel API client use cases, there might be other ways to handle that.

    darosior commented at 7:37 pm on April 21, 2025:
    Oh, i was just reading the diff and overlooked WriteBlockUndo! Yes that makes sense.
  10. sedited force-pushed on Apr 21, 2025
  11. DrahtBot removed the label CI failed on Apr 21, 2025
  12. ?
    project_v2_item_status_changed sedited
  13. ?
    added_to_project_v2 sedited
  14. w0xlt commented at 6:32 pm on April 22, 2025: contributor

    Concept ACK, as this PR increases the decoupling of components.

    nit: The name SpendBlock may not be clear (unless this concept is already known and I’m missing the point). Maybe BIP30Validation, something like that, would be clearer.

  15. in src/validation.h:733 in 76a8f22c5c outdated
    730+     * populates the CBlockUndo data structure.
    731+     *
    732+     * @param[in] block      The block to be spent
    733+     * @param[in] pindex     Points to the block map entry associated with block
    734+     * @param[in] block_hash Hash of block
    735+     * @param[out] view      Its coins are spent and used to populate CBlockUndo during its execution
    


    stickies-v commented at 9:54 am on April 29, 2025:
    nit: I think this is an @param[in,out]?
  16. in src/validation.h:715 in 76a8f22c5c outdated
    712+    /**
    713+     * Validates the block against the coins in the blockundo data, writes the
    714+     * undo data, and raises the block validity in the block index.
    715+     *
    716+     * @param[in] block       The block to be validated
    717+     * @param[in] block_hash  Hash of block
    


    stickies-v commented at 4:47 pm on April 29, 2025:

    I presume adding block_hash is done to avoid calculating CBlockHeader::GetHash() multiple times? Did you find that to have a measurable impact somewhere?

    I think the most elegant approach would be to implement caching inside CBlockHeader::GetHash() (if meaningful), but that would involve a pretty big LoC change making the CBlockHeader members private and adding getters.

    If we stick with this approach, I think at least it should be documented that block_hash is duplicated for performance reasons.


    sedited commented at 9:15 am on May 14, 2025:
    Yes, that is it. I don’t think this will actually matter in the grand scheme. I think I’ll drop it again.

    sedited commented at 1:37 pm on May 15, 2025:
    Dropped it again, but I also changed things slightly to get the block hash from the block index now.
  17. in src/consensus/tx_verify.cpp:28 in 76a8f22c5c outdated
    21+        return item.get();
    22+    } else {
    23+        return item;
    24+    }
    25+}
    26+
    


    stickies-v commented at 4:11 pm on May 6, 2025:

    Since std::reference_wrapper<T> implicitly converts to T&, I don’t think we actually need this helper? At least it compiles fine on my machine. If you’ve found any compiler issues, perhaps documenting them would be good so we can remove it in the future?

     0diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp
     1index 97f8241089..f2a0259b23 100644
     2--- a/src/consensus/tx_verify.cpp
     3+++ b/src/consensus/tx_verify.cpp
     4@@ -14,16 +14,6 @@
     5 #include <util/check.h>
     6 #include <util/moneystr.h>
     7 
     8-template <typename T>
     9-static constexpr const Coin& GetCoin(const T& item)
    10-{
    11-    if constexpr (std::is_same_v<T, std::reference_wrapper<const Coin>>) {
    12-        return item.get();
    13-    } else {
    14-        return item;
    15-    }
    16-}
    17-
    18 bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
    19 {
    20     if (tx.nLockTime == 0)
    21@@ -143,7 +133,7 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins)
    22     Assert(coins.size() == tx.vin.size());
    23     auto input_it = tx.vin.begin();
    24     for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
    25-        const Coin& coin{GetCoin(*it)};
    26+        const Coin& coin{*it};
    27         assert(!coin.IsSpent());
    28         const CTxOut &prevout = coin.out;
    29         if (prevout.scriptPubKey.IsPayToScriptHash())
    30@@ -173,7 +163,7 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t
    31     Assert(coins.size() == tx.vin.size());
    32     auto input_it = tx.vin.begin();
    33     for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
    34-        const Coin& coin{GetCoin(*it)};
    35+        const Coin& coin{*it};
    36         assert(!coin.IsSpent());
    37         const CTxOut &prevout = coin.out;
    38         nSigOps += CountWitnessSigOps(input_it->scriptSig, prevout.scriptPubKey, &input_it->scriptWitness, flags);
    39@@ -193,7 +183,7 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
    40     Assert(coins.size() == tx.vin.size());
    41     auto input_it = tx.vin.begin();
    42     for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
    43-        const Coin& coin{GetCoin(*it)};
    44+        const Coin& coin{*it};
    45         assert(!coin.IsSpent());
    46 
    47         // If prev is coinbase, check that it's matured
    

    stringintech commented at 4:55 pm on May 13, 2025:
    It compiles for me as well.

    sedited commented at 9:23 am on May 14, 2025:

    stickies-v commented at 11:20 am on May 14, 2025:

    It seems like list initializing coin is triggering MSVC to copy-initialize a Coin temporary, causing a second user-defined conversion and subsequent error. I don’t really understand why direct initialization rules are different in this regard, but using it does seem to make MSVC happy: https://github.com/stickies-v/bitcoin/actions/runs/15018875180/job/42203094843

    Fix: https://github.com/stickies-v/bitcoin/commit/6f1bc11c336db6dcea2954a03520b7064f0f0e6d


    sedited commented at 12:41 pm on May 14, 2025:
    Sweet, will apply this!
  18. in src/consensus/tx_verify.cpp:162 in 76a8f22c5c outdated
    163+
    164+template unsigned int GetP2SHSigOpCount<std::span<std::reference_wrapper<const Coin>>>(
    165+    const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>>);
    166+
    167+template <typename T>
    168+int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t flags)
    


    stickies-v commented at 4:49 pm on May 6, 2025:

    nit: for non-trivial functions, I think it helps to narrow down T instead of having to deduct it from its callsites and usage.

    What do you think about adding an IsCoinRef concept, and adding std::span to the function signature?

     0diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp
     1index 97f8241089..ab27387cc4 100644
     2--- a/src/consensus/tx_verify.cpp
     3+++ b/src/consensus/tx_verify.cpp
     4@@ -14,6 +14,8 @@
     5 #include <util/check.h>
     6 #include <util/moneystr.h>
     7 
     8+#include <span>
     9+
    10 template <typename T>
    11 static constexpr const Coin& GetCoin(const T& item)
    12 {
    13@@ -158,8 +160,8 @@ template unsigned int GetP2SHSigOpCount<std::span<const Coin>>(
    14 template unsigned int GetP2SHSigOpCount<std::span<std::reference_wrapper<const Coin>>>(
    15     const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>>);
    16 
    17-template <typename T>
    18-int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t flags)
    19+template<IsCoinRef CoinRef>
    20+int64_t GetTransactionSigOpCost(const CTransaction& tx, std::span<CoinRef> coins, uint32_t flags)
    21 {
    22     int64_t nSigOps = GetLegacySigOpCount(tx) * WITNESS_SCALE_FACTOR;
    23 
    24@@ -172,18 +174,18 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t
    25 
    26     Assert(coins.size() == tx.vin.size());
    27     auto input_it = tx.vin.begin();
    28-    for (auto it = coins.begin(); it != coins.end(); ++it, ++input_it) {
    29-        const Coin& coin{GetCoin(*it)};
    30+    for (const Coin& coin : coins) {
    31         assert(!coin.IsSpent());
    32         const CTxOut &prevout = coin.out;
    33         nSigOps += CountWitnessSigOps(input_it->scriptSig, prevout.scriptPubKey, &input_it->scriptWitness, flags);
    34+        ++input_it;
    35     }
    36     return nSigOps;
    37 }
    38-template int64_t GetTransactionSigOpCost<std::span<const Coin>>(
    39+template int64_t GetTransactionSigOpCost<const Coin>(
    40     const CTransaction& tx, std::span<const Coin> coins, uint32_t flags);
    41 
    42-template int64_t GetTransactionSigOpCost<std::span<std::reference_wrapper<const Coin>>>(
    43+template int64_t GetTransactionSigOpCost<std::reference_wrapper<const Coin>>(
    44     const CTransaction& tx, const std::span<std::reference_wrapper<const Coin>> coins, uint32_t flags);
    45 
    46 template <typename T>
    47diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h
    48index 88b2d6164b..b8ab114306 100644
    49--- a/src/consensus/tx_verify.h
    50+++ b/src/consensus/tx_verify.h
    51@@ -48,6 +48,8 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx);
    52 template <typename T>
    53 unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins);
    54 
    55+template<typename T>
    56+concept IsCoinRef = std::convertible_to<T, const Coin&>;
    57 /**
    58  * Compute total signature operation cost of a transaction.
    59  * [@param](/bitcoin-bitcoin/contributor/param/)[in] tx    Transaction for which we are computing the cost
    60@@ -55,8 +57,8 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins);
    61  * [@param](/bitcoin-bitcoin/contributor/param/)[in] flags Script verification flags
    62  * [@return](/bitcoin-bitcoin/contributor/return/) Total signature operation cost of tx
    63  */
    64-template <typename T>
    65-int64_t GetTransactionSigOpCost(const CTransaction& tx, const T coins, uint32_t flags);
    66+template<IsCoinRef CoinRef>
    67+int64_t GetTransactionSigOpCost(const CTransaction& tx, std::span<CoinRef> coins, uint32_t flags);
    68 
    69 /**
    70  * Check if transaction is final and can be included in a block with the
    

    (the same applies for GetLegacySigOpCount and GetP2SHSigOpCount)

    edit: I see this is already (partially) suggested and addressed in #32317 (review), I think adding the IsCoinRef concept could still be nice but less important than specifying the std::span


    sedited commented at 9:26 am on May 14, 2025:

    edit: I see this is already (partially) suggested and addressed in #32317 (review), I think adding the IsCoinRef concept could still be nice but less important than specifying the std::span

    I did implement a concept initially, but did not find it really that helpful, since the two usable template variants are concretely defined anyway.


    stickies-v commented at 10:07 am on May 14, 2025:

    Imo, there are 2 main advantages:

    • discoverability: grepping for callsites, template instantiations etc is a lot more cumbersome (and easy to miss) than just inspecting the canonical template definition
    • type safety: explicitly narrowing down the actual types that T can be makes reviewing the function easier, because you have to think about edge cases less, e.g. will the function still be safe when a new callsite is added

    This is more a general style preference, and not particularly important for this PR, so feel free to ignore / can mark as resolved.


    sedited commented at 1:36 pm on May 15, 2025:
    Thanks, added the the concept.
  19. sedited commented at 10:30 am on May 7, 2025: contributor

    Re #32317#pullrequestreview-2785006540

    nit: The name SpendBlock may not be clear (unless this concept is already known and I’m missing the point). Maybe BIP30Validation, something like that, would be clearer.

    It is doing more than BIP-30 validation, so I don’t think that would accurate either. I thought the name was pretty clear for what it does, but I’d be open to other suggestions.

  20. in src/coins.cpp:185 in 76a8f22c5c outdated
    177+    for (const auto& txin : tx.vin) {
    178+        const COutPoint& prevout = txin.prevout;
    179+        const Coin& coin = AccessCoin(prevout);
    180+        assert(!coin.IsSpent());
    181+        spent_outputs.emplace_back(coin.out);
    182+    }
    


    stickies-v commented at 3:17 pm on May 7, 2025:

    I think you can use AccessCoins here?

    0    for (const Coin& coin : AccessCoins(tx)) {
    1        assert(!coin.IsSpent());
    2        spent_outputs.emplace_back(coin.out);
    3    }
    

    stickies-v commented at 3:19 pm on May 7, 2025:
    nit: inconsistent naming between GetUnspentOutputs and spent_outputs.

    sedited commented at 1:36 pm on May 15, 2025:
    We could also use AccessCoin, but I think accessing it directly is a bit more efficient, because we don’t allocate another vector. Do you have a better suggestion for the naming there? It is all a bit confusing.

    stickies-v commented at 12:35 pm on May 29, 2025:

    I hadn’t considered the extra vector allocation, yeah I’m okay leaving this as-is then.

    I did nerd-snipe myself into looking into having AccessCoins return a view would be quite elegant in some ways, allowing lazy evaluation, but seems like it would require quite a bit of overhaul and might have other drawbacks I’ve not considered yet.

    0    auto AccessCoins(const CTransaction& tx) const
    1    {
    2        return tx.vin | std::views::transform([this](const CTxIn& input) -> const Coin& {
    3            return this->AccessCoin(input.prevout);
    4        });
    5    }
    

    alexanderwiederin commented at 9:19 am on October 24, 2025:

    Do you have a better suggestion for the naming there? It is all a bit confusing.

    I agree the naming is confusing. I’d suggest GetUnspentOutputs -> GetPreviousOutputs and spent_outputs -> previous_outputs to avoid the spent/unspent terminology. What do you think?

  21. in src/consensus/tx_verify.h:48 in a7e4132623 outdated
    45  * @return maximum number of sigops required to validate this transaction's inputs
    46  * @see CTransaction::FetchInputs
    47  */
    48-unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs);
    49+template <typename T>
    50+unsigned int GetP2SHSigOpCount(const CTransaction& tx, const T coins);
    


    stringintech commented at 8:15 pm on May 7, 2025:

    Small nit: I was thinking maybe we could make the template parameter more specific like:

    0template <typename T>
    1unsigned int GetP2SHSigOpCount(const CTransaction& tx, const std::span<T> coins)
    

    This might better align with the “Sorted span” mentioned in the docs, where T would then be either const Coin or std::reference_wrapper<const Coin>. It might also make the implementation slightly more readable when calling iterator methods on the coins parameter.


    sedited commented at 12:16 pm on May 10, 2025:
    Yeah, that seems better. I had another version of this that tried to enforce everything through concepts, but thought that didn’t really help with keeping things clear, so just dropped all the fancy type hints altogether.
  22. in src/validation.h:712 in 76a8f22c5c outdated
    705@@ -706,8 +706,36 @@ class Chainstate
    706     // Block (dis)connection on a given view:
    707     DisconnectResult DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
    708         EXCLUSIVE_LOCKS_REQUIRED(::cs_main);
    709-    bool ConnectBlock(const CBlock& block, BlockValidationState& state, CBlockIndex* pindex,
    710-                      CCoinsViewCache& view, bool fJustCheck = false) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
    711+
    712+    /**
    713+     * Validates the block against the coins in the blockundo data, writes the
    714+     * undo data, and raises the block validity in the block index.
    


    stickies-v commented at 5:30 pm on May 8, 2025:
    0     * undo data to disk, and raises the block validity in the block index.
    
  23. in src/validation.cpp:2089 in 76a8f22c5c outdated
    2171@@ -2161,7 +2172,7 @@ ValidationCache::ValidationCache(const size_t script_execution_cache_bytes, cons
    2172  * Non-static (and redeclared) in src/test/txvalidationcache_tests.cpp
    2173  */
    2174 bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
    2175-                       const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
    2176+                       std::vector<CTxOut>&& spent_outputs, unsigned int flags, bool cacheSigStore,
    2177                        bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
    


    stringintech commented at 10:51 am on May 10, 2025:
    Is there a reason we keep passing the spent_outputs instead of removing it (to simplify the API) and requiring callers to initialize txdata before calling CheckInputScripts?

    sedited commented at 12:38 pm on May 10, 2025:
    My read is that the call to txdata.Init is delayed to after checking the script execution cache for performance reasons. Calling Init does some hashing, that would not be required if it is already in the cache. Moving the spent_outputs vector construction is already a potential performance downside for potential cache hits, though I think that is more acceptable in the grand scheme of things.

    stringintech commented at 8:34 pm on May 11, 2025:
    Oh, it makes sense. Thanks for the insights.
  24. sedited force-pushed on May 10, 2025
  25. sedited commented at 4:21 pm on May 10, 2025: contributor

    Updated 76a8f22c5c5cf48a9c36cc40db9224f0454917d0 -> 16a695fbff4a92eba3eb72ec6aa766e71c52f773 (spendblock_0 -> spendblock_1, compare)

  26. in src/validation.cpp:2449 in 16a695fbff outdated
    2557-
    2558         nInputs += tx.vin.size();
    2559 
    2560         if (!tx.IsCoinBase())
    2561         {
    2562+            auto spent_coins = std::span<const Coin>{blockundo.vtxundo[i - 1].vprevout};
    


    stickies-v commented at 2:29 pm on May 13, 2025:

    This is UB if e.g. blockundo is not populated, would add a size assertion at the beginning:

     0diff --git a/src/validation.cpp b/src/validation.cpp
     1index adec151e14..20e1dc6482 100644
     2--- a/src/validation.cpp
     3+++ b/src/validation.cpp
     4@@ -2446,6 +2446,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, const uint256& block_hash, co
     5     assert(pindex);
     6     Assume(block.GetHash() == block_hash);
     7     assert(*pindex->phashBlock == block_hash);
     8+    assert(block.vtx.size() == blockundo.vtxundo.size() + 1);  // blockundo does not contain a CTxUndo for the coinbase
     9     const bool parallel_script_checks{m_chainman.GetCheckQueue().HasThreads()};
    10 
    11     const auto time_start{SteadyClock::now()};
    
  27. in src/validation.cpp:2440 in 16a695fbff outdated
    2438@@ -2437,13 +2439,12 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex& block_index, const Ch
    2439 /** Apply the effects of this block (with given index) on the UTXO set represented by coins.
    2440  *  Validity checks that depend on the UTXO set are also done; ConnectBlock()
    


    stickies-v commented at 2:31 pm on May 13, 2025:

    Validity checks that depend on the UTXO set are also done

    nit: I don’t think that’s correct anymore?

  28. in src/validation.h:717 in 16a695fbff outdated
    714+     * undo data, and raises the block validity in the block index.
    715+     *
    716+     * @param[in] block       The block to be validated
    717+     * @param[in] block_hash  Hash of block
    718+     * @param[in] blockundo   Has to contain all coins spent by block. Written to disk on successful validation
    719+     * @param[out] state      This may be set to an Error state if any error occurred validating block
    


    stickies-v commented at 2:33 pm on May 13, 2025:
    0     * [@param](/bitcoin-bitcoin/contributor/param/)[out] state      This is set to an Error state if any error occurred while validating block
    
  29. in src/validation.h:718 in 16a695fbff outdated
    715+     *
    716+     * @param[in] block       The block to be validated
    717+     * @param[in] block_hash  Hash of block
    718+     * @param[in] blockundo   Has to contain all coins spent by block. Written to disk on successful validation
    719+     * @param[out] state      This may be set to an Error state if any error occurred validating block
    720+     * @param[out] pindex     Points to the block map entry associated with block. On successful validation, raises the block validity
    


    stickies-v commented at 2:34 pm on May 13, 2025:

    nit: grammar change to highlight that pindex can be mutated here

    0     * [@param](/bitcoin-bitcoin/contributor/param/)[out] pindex     Points to the block map entry associated with block. On successful validation, its nStatus block validity is raised
    
  30. in src/test/txvalidationcache_tests.cpp:128 in 16a695fbff outdated
    123@@ -124,6 +124,9 @@ static void ValidateCheckInputsForAllFlags(const CTransaction &tx, uint32_t fail
    124 {
    125     PrecomputedTransactionData txdata;
    126 
    127+    std::vector<CTxOut> spent_outputs{active_coins_tip.GetUnspentOutputs(tx)};
    128+    txdata.Init(tx, std::move(spent_outputs));
    


    stickies-v commented at 3:39 pm on May 13, 2025:
    In a way, it’s a bit silly to add a parameter to the CheckInputScripts signature to then just move it into another parameter (txdata). An alternative approach could be to let the caller handle this, and assert txdata.m_spent_outputs_ready, but that could raise runtime assertion errors instead of catching them at compile time.

    stringintech commented at 4:53 pm on May 13, 2025:
    As @TheCharlatan noted here, delaying txdata.Init could be on purpose for the potential cache hit here. I wonder if there is a cleaner version for achieving the same goal though.
  31. stickies-v commented at 3:57 pm on May 13, 2025: contributor

    Concept ACK

    Reducing the coupling between validation functions and the UTXO set makes the validation functions more usable, and more testable. The approach taken here seems sensible, but I’m going to think about alternatives a bit more.

  32. in src/validation.cpp:2601 in 16a695fbff outdated
    2593@@ -2687,7 +2594,12 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
    2594         // * legacy (always)
    2595         // * p2sh (when P2SH enabled in flags and excludes coinbase)
    2596         // * witness (when witness enabled in flags and excludes coinbase)
    2597-        nSigOpsCost += GetTransactionSigOpCost(tx, view, flags);
    2598+        if (!tx.IsCoinBase()) {
    2599+            nSigOpsCost += GetTransactionSigOpCost(tx, std::span<const Coin>{blockundo.vtxundo[i - 1].vprevout}, flags);
    2600+        } else {
    2601+            std::vector<Coin> coin;
    2602+            nSigOpsCost += GetTransactionSigOpCost(tx, std::span<const Coin>{coin}, flags);
    


    stringintech commented at 8:01 pm on May 13, 2025:

    Looks like we have an early return in GetTransactionSigOpCost for coinbase tx and no use for coins span; so maybe we could just pass an empty span here?

    0            nSigOpsCost += GetTransactionSigOpCost<const Coin>(tx, {}, flags);
    
  33. in src/consensus/tx_verify.cpp:170 in 16a695fbff outdated
    200-{
    201-    // are the actual inputs available?
    202-    if (!inputs.HaveInputs(tx)) {
    203-        return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
    204-                         strprintf("%s: inputs missing/spent", __func__));
    205-    }
    


    stringintech commented at 8:25 pm on May 13, 2025:
    Do we need to update the documentation for CheckTxInputs now that the input existence check is removed from it?
  34. in src/validation.cpp:2918 in 16a695fbff outdated
    3027+                    return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-txns-BIP30",
    3028+                                  "tried to overwrite transaction");
    3029+                }
    3030+            }
    3031+        }
    3032+    }
    


    stringintech commented at 8:47 pm on May 13, 2025:

    In commit 5908680, the description mentions: “By returning early in case of a BIP30 validation failure, an additional failure check for an invalid block reward in case of its failure is left out.”

    To validate my understanding: there are also other checks in ConnectBlock before the !state.IsValid() check that we may have skipped because of the early return when SpendBlock fails - including transaction input validation, script verification, fee range checks, …; Is that correct?

    It makes sense to me not to proceed with additional validation if SpendBlock already detects a consensus failure, but I’m curious why the previous code continued checking for other consensus violations even after detecting an early BIP30 failure?


    sedited commented at 10:27 am on May 14, 2025:

    There are also other checks in ConnectBlock before the !state.IsValid() check that we may have skipped because of the early return when SpendBlock fails - including transaction input validation, script verification, fee range checks, …; Is that correct?

    Good catch! Actually I think the commit message description is misleading, if not wrong. The checks you mention are skipped in any case, because we check if (!state.IsValid()) break;, but similarly the coinbase check won’t override the validation result if it is already invalid. The check is still executed though before returning.

  35. stringintech commented at 8:59 pm on May 13, 2025: contributor
    Concept ACK. I had a few more comments (sorry for the scattered feedback; didn’t have the chance to review everything earlier).
  36. stickies-v commented at 0:04 am on May 14, 2025: contributor
    PSA: Bitcoin Core PR Review Club will cover this PR in its next meeting at 2025-05-14 at 17:00 UTC. See https://bitcoincore.reviews/32317 for notes, questions, and instructions on how to join.
  37. sedited force-pushed on May 15, 2025
  38. sedited commented at 1:34 pm on May 15, 2025: contributor

    Thank you for the reviews @stringintech and @stickies-v!

    16a695fbff4a92eba3eb72ec6aa766e71c52f773 -> e8ac6d29273c48328f1f77886c3c3f653e6cc58e (spendblock_1 -> spendblock_2, compare)

    • Addressed @stickies-v’s comments 1, 2, 3, 4, 5 improving docstrings for ConnectBlock and SpendBlock.
    • Addressedd @stickies-v’s comment, removed block_hash argument from ConnectBlock and SpendBlock methods.
    • Addressed @stickies-v’s comment, added CoinRef concept.
    • Addressed @stickies-v’s comment, removed unneeded helper, use direct initialization to solve msvc compilation error.
    • Addressed @stickies-v’s comment, add assert for blockundo.vtxundo.size() to rule out UB. *Addressed @stringintech’s comment, pass in empty span to GetTransactionSigOpCost.
    • Addressed @stringintech’s comment, update documentation for the removal of the inputs availability check in CheckTxInputs.
    • Addressed @stringintech’s comment, removed line in commit message mentioning execution of the coinbase amount check even when another check has failed previously.
  39. DrahtBot added the label Needs rebase on May 22, 2025
  40. sedited force-pushed on May 23, 2025
  41. sedited commented at 10:09 am on May 23, 2025: contributor

    Rebased e8ac6d29273c48328f1f77886c3c3f653e6cc58e -> e604a57dccac1c4e6802a3ecc7f4cb4c3229c8ed (spendblock_2 -> spendblock_3, compare)

  42. DrahtBot removed the label Needs rebase on May 23, 2025
  43. enirox001 commented at 8:45 pm on May 27, 2025: contributor
    Concept ACK. I’ll be conducting a detailed commit-by-commit code review shortly.
  44. DrahtBot added the label CI failed on May 30, 2025
  45. sedited force-pushed on Jun 2, 2025
  46. sedited commented at 2:19 pm on June 2, 2025: contributor

    Rebased e604a57dccac1c4e6802a3ecc7f4cb4c3229c8ed -> 15ff9cb1789b7fe69c0ad24d737712aa6955e9cb (spendblock_3 -> spendblock_4, compare)

    • Fixed silent merge conflict with #32304
  47. DrahtBot removed the label CI failed on Jun 2, 2025
  48. DrahtBot added the label Needs rebase on Jun 3, 2025
  49. sedited force-pushed on Jun 3, 2025
  50. sedited commented at 11:16 am on June 3, 2025: contributor

    Rebased 15ff9cb1789b7fe69c0ad24d737712aa6955e9cb -> df0eb65bd98f300618cccd667720f1ccc6145865 (spendblock_4 -> spendblock_5, compare)

  51. DrahtBot removed the label Needs rebase on Jun 3, 2025
  52. DrahtBot added the label Needs rebase on Jun 11, 2025
  53. sedited commented at 10:22 am on June 12, 2025: contributor

    Rebased df0eb65bd98f300618cccd667720f1ccc6145865 -> 969799291d34da706b43209ba1209038ec349424 (spendblock_5 -> spendblock_6, compare)

  54. sedited force-pushed on Jun 12, 2025
  55. DrahtBot removed the label Needs rebase on Jun 12, 2025
  56. DrahtBot added the label Needs rebase on Jun 19, 2025
  57. sedited force-pushed on Jun 19, 2025
  58. sedited commented at 2:08 pm on June 19, 2025: contributor

    Rebased 969799291d34da706b43209ba1209038ec349424 -> 6e90a0eb605b5477b4e91128e08ed1cc5501a939 (spendblock_6 -> spendblock_7, compare)

  59. DrahtBot removed the label Needs rebase on Jun 19, 2025
  60. DrahtBot added the label CI failed on Jul 6, 2025
  61. DrahtBot commented at 10:46 pm on July 6, 2025: contributor

    🚧 At least one of the CI tasks failed. Task tidy: https://github.com/bitcoin/bitcoin/runs/44423644781 LLM reason (✨ experimental): The failure is caused by a compilation error in script_p2sh_tests.cpp due to an incompatible function call to GetP2SHSigOpCount.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  62. sedited force-pushed on Jul 9, 2025
  63. sedited commented at 1:37 pm on July 9, 2025: contributor

    Rebased 6e90a0eb605b5477b4e91128e08ed1cc5501a939 -> 1f96d1cefc721b579b7aa3b05c2e65df62c0c5cf (spendblock_7 -> spendblock_8, compare)

    • Fixed silent merge conflict with #32850
  64. DrahtBot removed the label CI failed on Jul 9, 2025
  65. DrahtBot added the label CI failed on Jul 22, 2025
  66. DrahtBot commented at 11:29 am on July 22, 2025: contributor

    🚧 At least one of the CI tasks failed. Task previous releases, depends DEBUG: https://github.com/bitcoin/bitcoin/runs/45643912981 LLM reason (✨ experimental): The CI failure is caused by compilation errors in transaction_tests.cpp due to mismatched function calls to GetP2SHSigOpCount, indicating a code or API mismatch.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  67. sedited force-pushed on Jul 24, 2025
  68. sedited commented at 10:32 am on July 24, 2025: contributor

    Updated 1f96d1cefc721b579b7aa3b05c2e65df62c0c5cf -> 10c3a0689a3b73eb2b291ee81dd85066fa32497d (spendblock_8 -> spendblock_9, compare)

    • Fixed silent merge conflict with #32521
  69. DrahtBot removed the label CI failed on Jul 24, 2025
  70. DrahtBot added the label Needs rebase on Aug 8, 2025
  71. sedited force-pushed on Aug 19, 2025
  72. sedited commented at 11:45 am on August 19, 2025: contributor

    Rebased 10c3a0689a3b73eb2b291ee81dd85066fa32497d -> 3843a15f5b5b50d47c2524f607ba0b01c90d42b1 (spendblock_9 -> spendblock_10, compare)

    • Fixed merge conflict with #33105
  73. DrahtBot removed the label Needs rebase on Aug 19, 2025
  74. DrahtBot added the label Needs rebase on Aug 20, 2025
  75. sedited force-pushed on Aug 21, 2025
  76. sedited commented at 2:49 pm on August 21, 2025: contributor

    Rebased 3843a15f5b5b50d47c2524f607ba0b01c90d42b1 -> 5537ac00983a9abd24689411b5d76058d7a02f1b (spendblock_10 -> spendblock_11, compare)

  77. DrahtBot removed the label Needs rebase on Aug 21, 2025
  78. sedited force-pushed on Sep 4, 2025
  79. sedited commented at 1:31 pm on September 4, 2025: contributor
    Rebased 5537ac00983a9abd24689411b5d76058d7a02f1b -> b27be6cb18dde53863d83d2f2f61d4c92916257b (spendblock_11 -> spendblock_12, compare)
  80. ajtowns commented at 9:27 am on September 9, 2025: contributor

    Pass a block’s spent coins through the existing CBlockUndo data structure to ConnectBlock

    I don’t think this is really a good direction. We already have a generic interface for supplying info about unspent coins: CCoinsView; I think we should be using that, rather than requiring users to convert their utxo data store into our undo format?

    The thing that gets in the way of that seems to be the AccessCoin function returning a Coin by reference rather than value, but that doesn’t seem particularly more efficient (it’s an 8 byte copy rather than a 56 byte one unless the coin being spent is very non-standard, at least since #32279), and also seems a bit risky (in that the return value’s lifetime is dependent on when the cache gets invalidated which is somewhat unpredictable).

    But that seems to be fixable: https://github.com/ajtowns/bitcoin/commits/202509-ccoinsview/

    (When AccessCoin was introduced in #1677, CScript always involved an additional heap allocation as it was just a vector<char>, so the reference would have saved an allocation. That changed a few years later when prevector was introduced in #6914 which allowed small scripts to not require an allocation. However, I haven’t actually checked to see if the 8-vs-56-byte issue is actually as trivial as I think it should be)

  81. sedited commented at 8:44 am on September 10, 2025: contributor

    My hesitation was mostly about re-using the coins cache as is. In my opinion it is the wrong data structure to use in this case. Potential clients of the kernel are often capable of supplying the coins in order as consumed by a transaction’s inputs. Requiring these to be stored in a map seems wrong. I.e. if a utreexo client has all the leaf data to go with a transaction, it should not have to use a less efficient data structure that consumes more memory to pass in the coins. But maybe we have to build out some of the usages here in order to see what the actual drawbacks are? My current thinking is though that just passing in the required coins contiguously, i.e. use the undo data structure, is the more pragmatic solution for most external users that do not wish to implement a utxo set.

    However this is also not exactly what I think you are suggesting here, since the CCoinsView could eventually provide a view over contiguous data too. As implemented at the moment, that seems more confusing to use though compared to a nested vector of coins.

    Another alternative solution that was suggested by cfields is introducing a new transaction type that holds the coins alongside the prevouts. I think that would be an interesting alternative too.

  82. ajtowns commented at 2:19 am on September 11, 2025: contributor

    My hesitation was mostly about re-using the coins cache as is. In my opinion it is the wrong data structure to use in this case. Potential clients of the kernel are often capable of supplying the coins in order as consumed by a transaction’s inputs.

    I think that’s a false optimisation for two reasons: first, is that the primary client of the bitcoin core kernel is the bitcoin core node software, we should be designing for that case, not for other hypothetical clients; second, looking up a map isn’t very expensive in comparison to the other parts of block validation, particular signature validation. So if we already know the exact utxos spent in a block, converting just those utxos into a CCoinsView and using that seems likely to be within the margin of error for any benchmarks on realistic blocks. If we were designing this from scratch for a utreexo-based system, then yeah, the undo-centric approach would make sense (and including the prevouts inside the transaction makes a lot of sense then), but that’s not the primary use case here (at least not yet).

    I think constructing a CCoinsView from a block and undo would look something like this (on top of the previous changes I suggested above, untested):

     0class BlockUndoCoinsView : public CCoinsView
     1{
     2private:
     3    struct CoinCmp
     4    {
     5        bool operator()(const COutPoint* l, const COutPoint* r) const {
     6            if (l && r) return *l < *r;
     7            return r != nullptr;
     8        }
     9    };
    10    std::map<const COutPoint*, const Coin*, CoinCmp> m_coins;
    11
    12public:
    13    explicit BlockUndoCoinsView(const CBlock& block, const CBlockUndo& undo)
    14    {
    15        size_t i = 0;
    16        for (const auto& txundo : undo.vtxundo) {
    17            ++i; // skip the coinbase tx
    18            if (i >= block.vtx.size()) break;
    19            size_t j = 0;
    20            const auto& tx = block.vtx[i];
    21            for (const auto& prevout : txundo.vprevout) {
    22                if (j >= tx->vin.size()) break;
    23                const auto& outpoint = tx->vin[j].prevout;
    24                // XXX skip coins whose txid was from a previous tx in this block? 
    25                m_coins.try_emplace(&outpoint, &prevout);
    26                ++j;
    27            }
    28        }
    29    }
    30
    31    std::optional<Coin> GetCoin(const COutPoint& outpoint) const override
    32    {
    33        auto coin = m_coins.find(&outpoint);
    34        if (coin == m_coins.end()) return std::nullopt;
    35        return *coin->second;
    36    }
    37
    38    bool HaveCoin(const COutPoint &outpoint) const override
    39    {
    40        return m_coins.find(&outpoint) != m_coins.end();
    41    }
    42};
    

    I guess what I mean is I think we can achieve the stated goal of this PR (being able to validate blocks/txs without the full utxo set) with a smaller change.

    If returning a const Coin& rather than a copy is needed for performance, it might be better to do that through an RAII cursor object that guarantees the reference remains valid until the cursor is destroyed.

  83. sedited commented at 2:12 pm on September 11, 2025: contributor

    My goal with this PR was making the bulk of ConnectBlock logic work without using a CCoinsViewCache by moving the call to UpdateCoins out of the function. The side effect of that is that the CBlockUndo is populated outside of ConnectBlock too and has to be passed in. Since it already holds all the required coins, I rather took those coins vectors, than additionally requiring a view providing the same data (next to the reasons I provided in my comment above).

    The change was motivated by wanting to provide a single method for validating a block with external coins, such that others can use it verbatim in the near future. The alternative of just changing some of the functions that ConnectBlock calls to be able to accept external coins (through either a view or contiguous data), and then splitting the rest of the ConnectBlock logic, such as amount, locktime, assumevalid, and validity semantics into separate functions seemed inferior to me. If an external user would want to use these functions to achieve the same outcome, a single function not only seems friendlier, but also less likely to result in bugs.

    I could change this patch set to pass in a CCoinsView and then either handle the undo data outside of ConnectBlock, or populate it through the view within ConnectBlock.

  84. DrahtBot added the label Needs rebase on Oct 8, 2025
  85. sedited force-pushed on Oct 13, 2025
  86. sedited commented at 9:36 am on October 13, 2025: contributor
    Rebased b27be6cb18dde53863d83d2f2f61d4c92916257b -> 9ce01e051bae5fb1d48d6d297eb011b13d20ec76 (spendblock_12 -> spendblock_13, compare)
  87. DrahtBot removed the label Needs rebase on Oct 13, 2025
  88. sedited force-pushed on Nov 5, 2025
  89. sedited commented at 8:13 pm on November 5, 2025: contributor

    Rebased 9ce01e051bae5fb1d48d6d297eb011b13d20ec76 -> 5988c7172406e3caca32fc78c011d878a1abdf1e (spendblock_13 -> spendblock_14, compare)

    • Fixed a silent merge conflict
  90. purpleKarrot commented at 9:50 am on November 6, 2025: contributor
    A function that takes a const std::span<T> coins argument cannot modify coins, but it has mutable access to coins[x].
  91. DrahtBot added the label Needs rebase on Nov 7, 2025
  92. sedited force-pushed on Nov 12, 2025
  93. sedited commented at 2:28 pm on November 12, 2025: contributor
    Rebased 5988c7172406e3caca32fc78c011d878a1abdf1e -> 6493e47fd8d32c030d553f791caa48fcdb848c0b (spendblock_14 -> spendblock_15, compare)
  94. DrahtBot removed the label Needs rebase on Nov 12, 2025
  95. DrahtBot added the label Needs rebase on Dec 2, 2025
  96. consensus: Use Coin span in GetP2SHSigOpCount
    Move the responsibility of retrieving coins from GetP2SHSigOpCount to
    its caller.
    
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    
    Define two explicit template specializations for both a span of
    references to coins and a span of coins. This allows using it for both
    Coin entries referenced from the CCoinsViewCache, and from contiguous
    memory, like the vector in CBlockUndo.
    5709d073e0
  97. consensus: Use Coin span in GetTransactionSigOpCost
    Move the responsibility of retrieving coins from GetTransactionSigOpCost
    to its caller.
    
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    
    Define two explicit template specializations for both a span of
    references to coins and a span of coins. This allows using it for both
    Coin entries referenced from the CCoinsViewCache, and from contiguous
    memory, like the vector in CBlockUndo.
    3bdb4a89a9
  98. consensus: Use Coin span in CheckTxInputs
    Move the responsibility of retrieving coins from CheckTxInputs
    to its caller. The missing inputs check will be moved in an upcoming
    commit.
    
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    
    Define two explicit template specializations for both a span of
    references to coins and a span of coins. This allows using it for both
    Coin entries referenced from the CCoinsViewCache, and from contiguous
    memory, like the vector in CBlockUndo.
    162f2668be
  99. validation: Use vector of outputs instead of CCoinsViewCache in CheckInputScripts
    Move the responsibility of retrieving coins from the CCoinsViewCache
    in CheckInputScripts to its caller.
    
    Add a helper method in CCoinsViewCache to collect all Coin's outputs
    spent by a transaction's inputs.
    
    Callers of CCoinsViewCache are updated to either pre-fetch the spent
    outputs, or pass in an empty vector if the precomputed transaction data
    has already been initialized with the required outputs.
    
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    f8186cfaf6
  100. validation: Add SpendBlock function
    Move the BIP30 checks from ConnectBlock to a new SpendBlock method.
    This is a move-only change, more content to SpendBlock is added in the
    next commits. The goal is to move logic requiring access to the
    CCoinsViewCache out of ConnectBlock and to the new SpendBlock method.
    SpendBlock will in future handle all UTXO set interactions that
    previously took place in ConnectBlock.
    
    Callers of ConnectBlock now also need to call SpendBlock before. This is
    enforced in a future commit by adding a CBlockUndo argument that needs
    to be passed to both.
    
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    c937b12745
  101. validation: Move SetBestBlock out of ConnectBlock
    This is a part of a series of commits for removing access to the
    CCoinsViewCache in consensus verification functions. The goal is to
    allow calling verification functions with pre-fetched, or a user-defined
    set of coins.
    d9f1fc7ab3
  102. validation: Move coin existence and spend check to SpendBlock
    Move the remaining UTXO-related operations from ConnectBlock to
    SpendBlock. This includes moving the existence check, the UpdateCoins
    call, and CBlockUndo generation.
    
    ConnectBlock now takes a pre-populated CBlockUndo as an argument and no
    longer accesses or manipulates the UTXO set.
    c2a5497e99
  103. doc: Add docstrings for ConnectBlock and SpendBlock a0e9578ec1
  104. sedited force-pushed on Dec 3, 2025
  105. sedited commented at 10:29 am on December 3, 2025: contributor

    Rebased 6493e47fd8d32c030d553f791caa48fcdb848c0b -> a0e9578ec1ff1d8ec57f76a37e389aeeb554a639 (spendblock_15 -> spendblock_16, compare)

  106. DrahtBot removed the label Needs rebase on Dec 3, 2025
  107. ?
    project_v2_item_status_changed sedited
  108. in src/test/script_p2sh_tests.cpp:384 in 5709d073e0
    380@@ -380,7 +381,8 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
    381     txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
    382 
    383     BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins));
    384-    BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U);
    385+    auto coinsTxToNonStd1{coins.AccessCoins(CTransaction(txToNonStd1))};
    


    ismaelsadeeq commented at 1:23 pm on December 17, 2025:

    In “consensus: Use Coin span in GetP2SHSigOpCount” 5709d073e06280c915e7d74e8f49e4904598c348

    nit: here and other places, these new variables should be snake case?

  109. in src/consensus/tx_verify.h:60 in 3bdb4a89a9
    55@@ -56,12 +56,13 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const std::span<T> coins)
    56 
    57 /**
    58  * Compute total signature operation cost of a transaction.
    59- * @param[in] tx     Transaction for which we are computing the cost
    60- * @param[in] inputs Map of previous transactions that have outputs we're spending
    61+ * @param[in] tx    Transaction for which we are computing the cost
    62+ * @param[in] coins Sorted span of Coins containing previous transaction outputs we're spending
    


    ismaelsadeeq commented at 1:59 pm on December 17, 2025:

    In “consensus: Use Coin span in GetP2SHSigOpCount” 5709d073e06280c915e7d74e8f49e4904598c348

    Here and other commits. Sorted in what order?

  110. in src/validation.h:764 in a0e9578ec1
    759+     * @param[in] pindex     Points to the block map entry associated with block
    760+     * @param[in, out] view  Its coins are spent and used to populate CBlockUndo during its execution
    761+     * @param[out] state     This may be set to an Error state if any error occurred processing them
    762+     * @param[out] blockundo Coins consumed by the block are added to it.
    763+     */
    764     bool SpendBlock(const CBlock& block, const CBlockIndex* pindex,
    


    ismaelsadeeq commented at 4:52 pm on December 17, 2025:

    Since we are doing this, I think this should also be separated into two functions.

    1. EnforceBIP30 — a method that performs BIP30 validation.
    2. SpendBlock — for each transaction in the block, checks that the outputs it is spending exist, spends them, and populates the CBlockUndo data structure.

    It would also make testing easier, and removing BIP30 after a consensus cleanup soft fork would be straightforward.

    What I will want to see is we have just SpendBlock and non-contextual block validation using the block data and undo data in a series of consensus rules validation functions.

  111. in src/validation.cpp:2832 in c937b12745
    2827+        return true;
    2828+    }
    2829+
    2830+    // verify that the view's current state corresponds to the previous block
    2831+    uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash();
    2832+    assert(hashPrevBlock == view.GetBestBlock());
    


    ismaelsadeeq commented at 4:56 pm on December 17, 2025:
    In “validation: Add SpendBlock function” c937b12745971435111eab948e505580754d56bd why duplicate this, I think we should just add a comment warning that we expect they are already executed before?

    sedited commented at 10:26 pm on December 17, 2025:
    I’m not sure what you mean here. Afaict this is moved over from ConnectBlock.

    sedited commented at 2:10 pm on December 19, 2025:
    I think I’d rather keep these duplicated for now. These are short-circuits to our logic, similar to how the coinbase checks work in various places. Based on that I’d rather keep them in place. This could also easily be improved in a follow-up.
  112. in src/validation.cpp:2812 in c937b12745
    2808@@ -2899,6 +2809,127 @@ static void UpdateTipLog(
    2809                    !warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
    2810 }
    2811 
    2812+bool Chainstate::SpendBlock(
    


    ismaelsadeeq commented at 5:02 pm on December 17, 2025:

    In “validation: Add SpendBlock function” c937b12745971435111eab948e505580754d56bd

    The goal is to move logic requiring access to the CCoinsViewCache out of ConnectBlock and to the new SpendBlock method. SpendBlock will in future handle all UTXO set interactions that previously took place in ConnectBlock.

    This is counter to the objective of the PR; we are still accessing the UTXO set in this block validation function. My hope was that we would have two UTXO-accessing validation functions that 1 populate the block undo data and 2. BIP30 separately, while the remaining validation functions would simply get passed the block undo data and operate as stateless validation functions.


    sedited commented at 10:59 am on December 18, 2025:
    It is important that the BIP30 validation is done before we spend the coins. The current split makes sense to me for this reason. Can you elaborate a bit more what you think might not be optimal here?

    sedited commented at 2:09 pm on December 19, 2025:
    I’m still not sure if this is a good thing. I don’t see why you would ever spend the coins in the utxo set without also doing the bip30 checks beforehand. To this end, I did implement it, but added a tag type that gets passed from BIP30Validate to SpendBlock: https://github.com/sedited/bitcoin/tree/spendblock_17 . Let me know what you think.

    ismaelsadeeq commented at 11:54 am on December 22, 2025:

    I’ve reviewed the branch, and it looks good overall. My only concern is the last commit that adds the tag. Initially, I thought it should be the caller’s responsibility to decide whether bip30validate is called, rather than mandating it here.

    My motivation for separating SpendBlock from BIP30Validate is discussed in: https://github.com/stratum-mining/sv2-apps/issues/120

    The branch without the last commit would make it easier to implement a variant of the idea described in https://github.com/stratum-mining/sv2-apps/issues/120, where block validation for this JDS is as follows call SpendBlock and then ConnectBlock skip BIP30. However, the idea of skipping consensus checks is not acceptable.

    Given that, I’m inclined to keep this as-is. For https://github.com/stratum-mining/sv2-apps/issues/120, we can instead expose IPC methods: SpendBlock, which takes a block and returns the coins, and ValidateBlock, which performs the full block validation checks. Since blocks from miners tend to share many transactions, they can still benefit from this approach, because the JDS needs to only call SpendBlock to validate newly seen transactions.

    For Utreexo client, they provide their own undo data and cannot perform BIP30 validation anyway, so they benefit from this approach as well.

    Overall, this is still an improvement over the current status quo.

  113. ismaelsadeeq commented at 5:05 pm on December 17, 2025: member

    Concept ACK

    ACK first four commits, I think the remaining commits can even be made better. comments are inline

  114. ismaelsadeeq approved
  115. ismaelsadeeq commented at 11:55 am on December 22, 2025: member
    Code review ACK a0e9578ec1ff1d8ec57f76a37e389aeeb554a639
  116. DrahtBot requested review from stringintech on Dec 22, 2025
  117. DrahtBot requested review from stickies-v on Dec 22, 2025
  118. DrahtBot requested review from w0xlt on Dec 22, 2025

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-01-07 00:13 UTC

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