kernel: Add non-utxo set block validation to API #35187

pull sedited wants to merge 5 commits into bitcoin:master from sedited:external_coins_block_validation changing 6 files +296 −2
  1. sedited commented at 9:16 PM on April 30, 2026: contributor

    This adds a kernel C header API endpoint for validating a block without having access to the full UTXO set. The introduced block validation function is intended to be called after having instantiated a chainstate manager and processing the block's header. Following this contract, the block is internally validated by the CheckBlock, ContextualCheckBlockHeader, ContextualCheckBlock, and finally ConnectBlock functions.

    The CoinsViewBlock class is introduced to validate user-provided coins from a BlockSpentOutputs against the block spending them. It inherits from CCoinsViewCache and is eventually passed to ConnectBlock. This allows validating the block's scripts and spends against user-provided UTXOs instead of using the chainstate's own internal UTXO set.

    This also includes some more API endpoints to populate the coins and retrieve relevant data.

    Will keep this draft for now, until I've fuzzed this a bit through rust-bitcoinkernel, and have added a few more test cases.

  2. kernel: Silence warning unset logs in test ae26307c31
  3. kernel: Add transaction is coinbase to C header d06b4bc164
  4. kernel: Add coin creation to C header e58b511556
  5. kernel: Add block spent outputs creation function a60cc8987a
  6. kernel: Add sans utxo set block validation
    This adds an API endpoint for validating a block without having the full
    utxo set present. To validate a block in such a fashion, its block
    header needs to processed first. The spent coins are expected to be
    passed as BlockSpentOutputs. The introduced block validation function
    then validates the block through `CheckBlock`, `ContextualCheckBlock`,
    and `ConnectBlock`.
    b5eabfa6fd
  7. DrahtBot added the label Validation on Apr 30, 2026
  8. DrahtBot commented at 9:16 PM on April 30, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Approach ACK ismaelsadeeq

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34775 (kernel: make logging callback global by stickies-v)
    • #33796 (kernel: Expose CheckTransaction consensus validation function by w0xlt)

    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.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • tx_index index -> tx_index [duplicated word; the intended parameter name is clear]

    Possible places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • Coin{output, 0, false} in src/test/kernel/test_kernel.cpp
    • Coin{output, 1, true} in src/test/kernel/test_kernel.cpp
    • tx_spent_coins.emplace_back(tx->GetOutput(point.index()), 0, false) in src/test/kernel/test_kernel.cpp

    Possible places where comparison-specific test macros should replace generic comparisons:

    • [src/test/kernel/test_kernel.cpp] BOOST_CHECK_THROW(Transaction{invalid_data}, std::runtime_error); -> consider using BOOST_CHECK_EXCEPTION(..., std::runtime_error, HasReason(...)) to verify the specific failure message instead of only the exception type.

    <sup>2026-04-30 21:16:43</sup>

  9. ?
    added_to_project_v2 sedited
  10. ?
    project_v2_item_status_changed github-project-automation[bot]
  11. ?
    project_v2_item_status_changed sedited
  12. DrahtBot added the label CI failed on May 1, 2026
  13. purpleKarrot commented at 5:47 AM on May 1, 2026: contributor

    The goal of this endeavor is to retire btck_block_spent_outputs_read from the kernel?

  14. in src/kernel/bitcoinkernel.h:1507 in b5eabfa6fd
    1498 | @@ -1473,6 +1499,31 @@ BITCOINKERNEL_API btck_BlockSpentOutputs* BITCOINKERNEL_WARN_UNUSED_RESULT btck_
    1499 |      const btck_ChainstateManager* chainstate_manager,
    1500 |      const btck_BlockTreeEntry* block_tree_entry) BITCOINKERNEL_ARG_NONNULL(1, 2);
    1501 |  
    1502 | +/**
    1503 | + * Callback type for getting a single coin to construct a block spent
    1504 | + * outputs. tx_index index indicates the position of a transaction within a
    1505 | + * block, coin_index indicates the position of an input within a block.
    1506 | + */
    1507 | +typedef const btck_Coin* (*btck_coin_getter)(void* context, size_t transaction_index, size_t coin_index);
    


    purpleKarrot commented at 5:49 AM on May 1, 2026:
    /**
     * Callback type for getting a single coin to construct a block spent
     * outputs. tx_index index indicates the position of a transaction within a
     * block, coin_index indicates the position of an input within that transaction.
     */
    typedef const btck_Coin* (*btck_coin_getter)(void* context, size_t transaction_index, size_t coin_index);
    
  15. sedited commented at 6:07 AM on May 1, 2026: contributor

    The goal of this endeavor is to retire btck_block_spent_outputs_read from the kernel?

    No, I think that is unrelated. Read deserializes data, while the creation function introduced here takes an existing shape of coins.

  16. DrahtBot removed the label CI failed on May 1, 2026
  17. ismaelsadeeq commented at 10:33 AM on May 1, 2026: member

    Approach ACK If we are going through this route, won't it be more straightforward to just pass the spent coins and add them to the coins cache as demonstrated in https://github.com/ismaelsadeeq/bitcoin/commit/9b9db183535d063631b2e13f9d6fb1cd0b7dc98b?

    Not sure why it is necessary to have the caller pass the block undo? Is it because we expect the client to have block undo, if not then it's an unnecessary round trip. Clients will map spent coins to block undo, and then we map the block undo to spent coins.

    What format do we expect the block undo to be in? If we say they have to be well-constructed, i.e. be in the right index as the transaction that consumes them, etc., we have to verify that contract no? I don't think we do here. So, if we go ahead and do this and the proposed refactor in #32317 gets done, will passing an undo that violates that contract break things? I find it less footgun-y for this approach to just receive a list of spent coins instead of block undo, because of the edge cases of empty undo ordering, in the coinsview map of undo to coins you have to skip block undo entries whose coins are created in the same block etc.

    After #32317, we can skip the validation of the block undo ordering and contract by us mapping the spent coins and the block into the desired format, i.e., block undo vector.

  18. sedited commented at 1:44 PM on May 1, 2026: contributor

    If we are going through this route, won't it be more straightforward to just pass the spent coins and add them to the coins cache ... I find it less footgun-y for this approach to just receive a list of spent coins instead of block undo, because of the edge cases of empty undo ordering, in the coinsview map of undo to coins you have to skip block undo entries whose coins are created in the same block etc.

    I agree that the current approach here is not good and should be changed to not use the undo data structure. As you correctly lay out, it does rely on some internal coins cache behavior, and this was part of the motivation for opening #32317. I think passing the correctly shaped contiguous coins vector is a bit annoying to deal with for the calling developer. Maybe it's best to just rewire FetchCoinFromBase to take a callback that the developer passes to the validation function and then deals with it in their own space?

  19. DrahtBot commented at 10:32 PM on May 4, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    🐙 This pull request conflicts with the target branch and needs rebase.

  20. DrahtBot added the label Needs rebase on May 4, 2026
  21. alexanderwiederin commented at 10:40 PM on May 5, 2026: contributor

    Not sure I truly understand the implications, but the callback passed to the validate method makes sense to me. From a client perspective, I believe the following would be most intuitive:

    let state = chainman.validate_block(
        &block,
        |outpoint: TxOutPointRef<'_>| -> Option<Coin> {
            my_db.get(&outpoint)
        },
    )?;
    

    Which would translate to a signature of:

    typedef const btck_Coin* (*btck_coin_getter)(
        void* context,
        const btck_TransactionOutPoint* outpoint
    );
    
    BITCOINKERNEL_API int btck_chainstate_manager_validate_block(
        btck_ChainstateManager* chainstate_manager,
        const btck_Block* block,
        btck_coin_getter coin_getter,
        void* context,
        btck_BlockValidationState* block_validation_state
    );
    

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-09 15:12 UTC

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