{
 "number": 35187,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35187",
 "title": "kernel: Block validation without a complete UTXO set",
 "author": "sedited",
 "author_association": "MEMBER",
 "created_at": "2026-04-30T21:16:12Z",
 "updated_at": "2026-09-15T13:40:16Z",
 "age_days": 139,
 "draft": false,
 "labels": [
  "Validation",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "5c65fc94a3997356374568783045e465f3cae706",
 "head_ref": "external_coins_block_validation",
 "head_repo": "sedited/bitcoin",
 "head_history": [
  {
   "t": "2026-07-13T15:25:43Z",
   "sha": "a44e63b45c5b906b14ec53be0a98b67d6aa59b72"
  },
  {
   "t": "2026-07-13T19:19:04Z",
   "sha": "5eed028ed2a56f9ed5a0bcf29826c31d50d9a4ee"
  },
  {
   "t": "2026-07-13T19:53:30Z",
   "sha": "46b929c9c54ac796bb6072c1f8bc01805e374078"
  },
  {
   "t": "2026-07-14T08:12:03Z",
   "sha": "2c9e5da35c9f3360f4e1da3e0c20b87b17e329fb"
  },
  {
   "t": "2026-07-14T08:19:04Z",
   "sha": "a89b3553291b5824cf6ca9f21a39896d00669f25"
  },
  {
   "t": "2026-07-14T09:26:22Z",
   "sha": "7bb468db884c885db5af5ab7abc6580b416f650e"
  },
  {
   "t": "2026-07-14T20:22:46Z",
   "sha": "a77602ed7d4b4dda7cb4ef67148e004102a93be1"
  },
  {
   "t": "2026-08-06T11:48:59Z",
   "sha": "5c65fc94a3997356374568783045e465f3cae706"
  }
 ],
 "additions": 361,
 "deletions": 0,
 "changed_files": 6,
 "commit_count": 5,
 "size_bucket": "M",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "yuvicc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-5174181123"
     },
     {
      "login": "nervana21",
      "url": "https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-5414418228"
     }
    ],
    "approach_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-4510685199"
     }
    ],
    "stale_ack": [
     {
      "login": "ismaelsadeeq",
      "url": "https://github.com/bitcoin/bitcoin/pull/35187#pullrequestreview-4735196386"
     },
     {
      "login": "ajtowns",
      "url": "https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-5204427745"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36207,
     "title": "kernel: add wtxid accessor",
     "author": "KY-U"
    },
    {
     "number": 35557,
     "title": "kernel, validation: Add btck_chainstate_manager_set_clock_time",
     "author": "ryanofsky"
    },
    {
     "number": 34374,
     "title": "kernel: use struct-based logging and simplify logging interface",
     "author": "stickies-v"
    },
    {
     "number": 29700,
     "title": "kernel, refactor: return error status on all fatal errors",
     "author": "ryanofsky"
    },
    {
     "number": 26022,
     "title": "Add util::ResultPtr class",
     "author": "ryanofsky"
    },
    {
     "number": 25665,
     "title": "refactor: Add util::Result failure types and ability to merge result values",
     "author": "ryanofsky"
    }
   ]
  }
 },
 "acks_parsed": {
  "ismaelsadeeq": {
   "kind": "ack",
   "hash": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "t": "2026-07-20T15:11:27Z",
   "stale": true
  },
  "w0xlt": {
   "kind": "approach_ack",
   "hash": null,
   "t": "2026-05-21T17:09:51Z",
   "stale": false
  },
  "yuvicc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-04T03:14:07Z",
   "stale": false
  },
  "ajtowns": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-06T12:07:19Z",
   "stale": false
  },
  "nervana21": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-25T17:48:29Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 1,
  "concept_ack": 3,
  "approach_ack": 1,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "17031996",
   "ajtowns",
   "alexanderwiederin",
   "ismaelsadeeq",
   "nervana21",
   "optout21",
   "purpleKarrot",
   "w0xlt",
   "yuvicc"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-08-28T21:09:13Z",
  "last_reviewer_activity": "2026-09-09T15:59:10Z",
  "last_reviewer": "nervana21",
  "author_silent_days": 19,
  "waiting_on_author_days": 8,
  "days_since_update": 2
 },
 "refs": {
  "mentioned": [
   32317,
   36066
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 32317,
    "type": "pull",
    "state": "closed",
    "merged": false,
    "merged_at": null,
    "title": "kernel: Separate UTXO set access from validation functions"
   },
   {
    "number": 36066,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "validation: Separate check-only version of ConnectBlock"
   }
  ],
  "conflicts": [
   36207,
   35557,
   34374,
   29700,
   26022,
   25665
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/kernel/bitcoinkernel.cpp",
  "src/kernel/bitcoinkernel.h",
  "src/test/kernel/test_kernel.cpp",
  "src/validation.cpp",
  "src/validation.h"
 ],
 "body": "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.\n\nThe `CoinsViewBlock` class is introduced to validate user-provided coins passed through a callback. 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.\n\nThis also includes some more API endpoints to populate the coins and OutPoints and retrieve relevant data.",
 "commits": [
  {
   "sha": "644f73541f2123017ab4a5d24d53d4f2869654f5",
   "date": "2026-07-14T20:22:28Z",
   "message": "kernel: Add transaction is coinbase to C header"
  },
  {
   "sha": "d54e309d00dc7506da3c37d4695ec69bcf744b84",
   "date": "2026-07-22T14:36:23Z",
   "message": "kernel: Add coin creation to C header"
  },
  {
   "sha": "e65476d050fdc4b9c937e82145e008b383ba83c8",
   "date": "2026-07-22T14:37:53Z",
   "message": "kernel: Add outpoint creation to C header"
  },
  {
   "sha": "b369e88ad400b26bb51066786f2649cba9256617",
   "date": "2026-08-06T08:27:55Z",
   "message": "kernel: Add outpoint equals operator"
  },
  {
   "sha": "5c65fc94a3997356374568783045e465f3cae706",
   "date": "2026-08-06T08:46:31Z",
   "message": "kernel: Add sans utxo set block validation\n\nThis adds an API endpoint for validating a block without having the full\nutxo set present. To validate a block in such a fashion, its block\nheader needs to be processed first. The spent coins are passed to the\nfunction with a callback, which is invoked for each output spent by the\nblock (excluding outputs previously created by the block). The\nintroduced block validation function then validates the block through\n`CheckBlock`, `ContextualCheckBlock`, and `ConnectBlock`."
  }
 ],
 "timeline": [
  {
   "t": "2026-05-01T05:47:44Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "The goal of this endeavor is to retire `btck_block_spent_outputs_read` from the kernel?"
  },
  {
   "t": "2026-05-01T05:49:01Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.h",
   "commit": "b5eabfa6fd22b116f41c4691dc6851bc3bec6b40",
   "in_reply_to": null,
   "text": "```suggestion\n/**\n * Callback type for getting a single coin to construct a block spent\n * outputs. tx_index index indicates the position of a transaction within a\n * block, coin_index indicates the position of an input within that transaction.\n */\ntypedef const btck_Coin* (*btck_coin_getter)(void* context, size_t transaction_index, size_t coin_index);\n```"
  },
  {
   "t": "2026-05-01T06:07:35Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nNo, I think that is unrelated. Read deserializes data, while the creation function introduced here takes an existing shape of coins."
  },
  {
   "t": "2026-05-01T10:33:00Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "Approach ACK\nIf 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?\n\nNot 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.\n\nWhat 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?\nI 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.\n\nAfter #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."
  },
  {
   "t": "2026-05-01T13:44:34Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n...\n[quoted text omitted]\n\nI 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?"
  },
  {
   "t": "2026-05-05T22:40:55Z",
   "kind": "comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "text": "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:\n```rust\nlet state = chainman.validate_block(\n    &block,\n    |outpoint: TxOutPointRef<'_>| -> Option<Coin> {\n        my_db.get(&outpoint)\n    },\n)?;\n```\nWhich would translate to a signature of:\n```c\ntypedef const btck_Coin* (*btck_coin_getter)(\n    void* context,\n    const btck_TransactionOutPoint* outpoint\n);\n\nBITCOINKERNEL_API int btck_chainstate_manager_validate_block(\n    btck_ChainstateManager* chainstate_manager,\n    const btck_Block* block,\n    btck_coin_getter coin_getter,\n    void* context,\n    btck_BlockValidationState* block_validation_state\n);\n```"
  },
  {
   "t": "2026-05-21T17:09:51Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Approach ACK"
  },
  {
   "t": "2026-07-13T15:25:43Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "a44e63b45c5b906b14ec53be0a98b67d6aa59b72"
  },
  {
   "t": "2026-07-13T19:19:04Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "5eed028ed2a56f9ed5a0bcf29826c31d50d9a4ee"
  },
  {
   "t": "2026-07-13T19:53:30Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "46b929c9c54ac796bb6072c1f8bc01805e374078"
  },
  {
   "t": "2026-07-14T08:12:03Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "2c9e5da35c9f3360f4e1da3e0c20b87b17e329fb"
  },
  {
   "t": "2026-07-14T08:19:04Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "a89b3553291b5824cf6ca9f21a39896d00669f25"
  },
  {
   "t": "2026-07-14T08:28:58Z",
   "kind": "review",
   "who": "17031996",
   "assoc": "NONE",
   "state": "COMMENTED",
   "commit": "a89b3553291b5824cf6ca9f21a39896d00669f25",
   "text": "[``](url)"
  },
  {
   "t": "2026-07-14T08:44:46Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Opening this up for review again. Basically went with @ismaelsadeeq's suggestion of passing in two arrays encoding pairs of outpoints and coins."
  },
  {
   "t": "2026-07-14T09:01:43Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThis looks much simpler now. I will review the changes thoroughly \ud83d\udc4d\ud83c\udffe ."
  },
  {
   "t": "2026-07-14T09:26:22Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "7bb468db884c885db5af5ab7abc6580b416f650e"
  },
  {
   "t": "2026-07-14T20:22:46Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1"
  },
  {
   "t": "2026-07-20T13:09:49Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.h",
   "commit": "5c9189d48ec3194aed6c9d4a7b52f6e381cf2ba6",
   "in_reply_to": null,
   "text": "In 5c9189d48ec3194aed6c9d4a7b52f6e381cf2ba6  \"kernel: Add coin creation to C header\"\n\nnit: be specific `s/height/confirmation_height` or block height? same in the comments as well."
  },
  {
   "t": "2026-07-20T13:29:56Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.h",
   "commit": "5c9189d48ec3194aed6c9d4a7b52f6e381cf2ba6",
   "in_reply_to": null,
   "text": "In 5c9189d48ec3194aed6c9d4a7b52f6e381cf2ba6 \"kernel: Add coin creation to C header\"\n\n  `btck_coin_create()` accepts a `uint32_t height`, but the value is stored in internal `Coin::nHeight`, which is only 31 bits\n\nThis test fails ( Creating a custom type for this seems like an overkill, so atleast document it explicitly ?)\n```diff\ndiff --git a/src/test/kernel/test_kernel.cpp b/src/test/kernel/test_kernel.cpp\nindex 662553e2d1..7acc725936 100644\n--- a/src/test/kernel/test_kernel.cpp\n+++ b/src/test/kernel/test_kernel.cpp\n@@ -16,6 +16,7 @@\n #include <cstdint>\n #include <cstdlib>\n #include <iostream>\n+#include <limits>\n #include <memory>\n #include <optional>\n #include <random>\n@@ -502,12 +503,15 @@ BOOST_AUTO_TEST_CASE(btck_coin)\n     TransactionOutput output{script, 1};\n     Coin coin{output, 0, false};\n     Coin coin2{output, 1, true};\n+    Coin high_coin{output, std::numeric_limits<uint32_t>::max(), false};\n     CheckHandle(coin, coin2);\n\n     BOOST_CHECK(!coin.IsCoinbase());\n     BOOST_CHECK_EQUAL(coin.GetConfirmationHeight(), 0);\n     BOOST_CHECK(coin2.IsCoinbase());\n     BOOST_CHECK_EQUAL(coin2.GetConfirmationHeight(), 1);\n+    BOOST_CHECK(!high_coin.IsCoinbase());\n+    BOOST_CHECK_EQUAL(high_coin.GetConfirmationHeight(), std::numeric_limits<uint32_t>::max());\n }\n\n```"
  },
  {
   "t": "2026-07-20T13:39:07Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/test/kernel/test_kernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "In d48fe4a4a7ef8993e3d2c504cea91e26096f7b2d  \"kernel: Add outpoint creation to C header\"\n```suggestion\n+    OutPoint max_index_point{point_0.Txid(), std::numeric_limits<uint32_t>::max()};\n+    BOOST_CHECK_EQUAL(max_index_point.index(), std::numeric_limits<uint32_t>::max());\n\n```"
  },
  {
   "t": "2026-07-20T14:30:48Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  \"kernel: Add sans utxo set block validation\"\n\nThe omitting seems like an overkill and limiting, just remove it and document that we can't do full bip30 checks, it is done relative to the coins passed.\n\nWhen the user indeed has all the UTXOs, this prevents that (hypothetical but still being flexible is better?)."
  },
  {
   "t": "2026-07-20T14:31:59Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  \"kernel: Add sans utxo set block validation\"\n\n```suggestion\n```\n\nRemoving this does not trigger any failure fwiw. If we are going to enforce, maybe add a test."
  },
  {
   "t": "2026-07-20T14:32:42Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  \"kernel: Add sans utxo set block validation\"\n\nThis is important to prevent child spend before parent consensus bug, so perhaps document."
  },
  {
   "t": "2026-07-20T14:58:12Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.h",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  \"kernel: Add sans utxo set block validation\"\n\nI think we can relax the requirement a bit, such that the header of the block that we are validating does not need to be processed before calling this, we can process the header internally here, which will reduce the round trip."
  },
  {
   "t": "2026-07-20T15:02:28Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/validation.h",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  kernel: Add sans utxo set block validation\n\nnit: add a brief description?"
  },
  {
   "t": "2026-07-20T15:06:10Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/validation.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "In a77602ed7d4b4dda7cb4ef67148e004102a93be1  kernel: Add sans utxo set block validation\n\nThis is confusing we log that header is not processed but then returned cached invalid result and duplicate-invalid?"
  },
  {
   "t": "2026-07-20T15:11:27Z",
   "kind": "review",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "text": "Code review ACK a77602ed7d4b4dda7cb4ef67148e004102a93be1\n\nI did not find any issue,  these are just minor comments.\n\nI rebased this on https://github.com/bitcoin/bitcoin/pull/35000 and tested this path in the unit tests, I did not get any failure https://github.com/ismaelsadeeq/bitcoin/tree/pr-35187."
  },
  {
   "t": "2026-07-21T10:55:52Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "The [Validation](https://thecharlatan.ch/Validation/) blog post explains that there are essentially three different \"levels\" to validate a block with more or less consensus evidence:\n\n1. Intrinsic validation\n2. Validation against the ancestry of block headers\n3. Validation against the ancestry plus the set of spendable coins (aka utxo-set)\n\nThe titles of the PR and the commit (\"non-utxo set block validation\", \"sans utxo set block validation\") seem to imply that this is adding a validation function for either level one or two. But looking at the signature of the added function, I get the impression that this is about level three instead: The `block_tree_entry` argument provides the ancestry while  `spent_out_points`, `spent_coins`, and `spent_outputs_len` together provides access to the coins.\n\nThe way the spent coins are provided to the function (two arrays) requires the whole utxo-set to be converted twice:\n\n1. Clients flatten their own dictionary into the two arrays.\n2. The library then constructs an ephemeral `std::map` from the two arrays.\n\nWhile this approach may serve as a proof-of-concept, it will not scale to a production application.\nAs @alexanderwiederin [pointed out](https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-4383686798), a better approach would be to abstract the lookup function. This way, the library is given direct, zero-copy lookup access to the clients dictionary structure."
  },
  {
   "t": "2026-07-21T11:22:59Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\nAs @alexanderwiederin https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-4383686798, a better approach would be to abstract the lookup function. This way, the library is given direct, zero-copy lookup access to the clients dictionary structure.\n\nYeah, I agree that, depending on the script size, the coin copy may not be cheap. Your suggestion allows the lookup function to be passed to the `CoinsViewBlock` class, and the lookup will be direct and zero-copy, as u mention. But note the current approach does have the advantage of preventing footguns like child spend before parent, which is a consensus bug.  A naive client might shoot themselves in the foot easily by allowing access to the child utxo in the db before it is created."
  },
  {
   "t": "2026-07-22T15:45:09Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "in_reply_to": 3615051298,
   "text": "Currently this just filters out all intra-block spends. The current implementation is wasteful however: It does a fairly large allocation for something that isn't actually required if the user doesn't include such coins. This is just really annoying to deal with. Do you think it were preferable if we'd just say that the passed in array of coins must not contain intra-block spends?"
  },
  {
   "t": "2026-07-26T08:24:14Z",
   "kind": "review_comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "in_reply_to": 3615051298,
   "text": "It's tricky, I think it can be important to prevent users from shooting themselves in the foot, as I laid out here https://github.com/bitcoin/bitcoin/pull/35187#issuecomment-5033330747\n\nBut definitely, removing this will simplify the flow."
  },
  {
   "t": "2026-07-26T11:34:41Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nIt is not about the copy of a coin. It is about *two* copies of the complete utxo set, no? One copy to flatten it into two arrays and another copy to convert the two arrays into a map.\n\n[quoted text omitted]\nHow so? Inserting the block's utxos into the utxo set before the block is validated would indeed not be very smart. But how does this design prevent it?"
  },
  {
   "t": "2026-07-26T13:40:38Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nBecause we have the block and the user provided utxo's the `CoinsViewBlock` checks during insertion into the map that it is not a block utxo."
  },
  {
   "t": "2026-07-26T16:47:14Z",
   "kind": "comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nCan you elaborate? I want to make sure I follow. If you mean forward references in intra-block spends: `FetchCoinFromBase` already guards those with `m_block_txids.contains(outpoint.hash)` on every base fetch, before it consults the coin source. It could protect a callback source the same way it protects the two arrays."
  },
  {
   "t": "2026-07-26T19:58:26Z",
   "kind": "comment",
   "who": "ismaelsadeeq",
   "assoc": "MEMBER",
   "text": "@alexanderwiederin yeah sorry, my comment wasn't elaborate enough. Currently, we prevent forward references in intra block spends twice, both keyed on `m_block_txids.contains(outpoint.hash)`.\n\n1. In the `CoinsViewBlock` constructor, we don't add a supplied coin to the map if its outpoint belongs to a transaction created in this block.\n\n```c++\n        m_block_txids.reserve(block.vtx.size());\n        for (const auto& tx : block.vtx) m_block_txids.insert(tx->GetHash());\n\n        for (size_t i{0}; i < len; ++i) {\n            const COutPoint& outpoint{btck_TransactionOutPoint::get(out_points[i])};\n            const Coin& coin{btck_Coin::get(coins[i])};\n            if (m_block_txids.contains(outpoint.hash)) continue;\n            m_coins.try_emplace(outpoint, coin);\n        }\n    }\n```\n\n2. In `FetchCoinFromBase`, we return `nullopt` for any in-block outpoint before even consulting the map:\n\n```c++\n    std::optional<Coin> FetchCoinFromBase(const COutPoint& outpoint) const override\n    {\n        if (m_block_txids.contains(outpoint.hash)) return std::nullopt;\n        if (auto it{m_coins.find(outpoint)}; it != m_coins.end()) return it->second;\n        return std::nullopt;\n    }\n```\n\nThese two guards enforce the same invariant, so either one alone is sufficient. With only the constructor filter, `m_coins` can never hold an outpoint whose hash is an in block txid.\n\nThis holds for the `BIP30` edge case too?  But with only the `FetchCoinFromBase` guard, an in-block coin could sit in `m_coins`  but would never be served.\n\nSo the IMO `FetchCoinFromBase` check is the redundant one, and it's on the hot path evaluated on every `FetchCoinFromBase` call, whereas the constructor check runs once per supplied coin at setup.\n\nThat's why I suggested removing it here https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3615051298, and why keeping approach 2 instead is the less efficient choice, as I noted here https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3652089515, but if you go with the approach you suggested, then we may need approach 2.\n\nPlease correct me if I am wrong here."
  },
  {
   "t": "2026-08-04T03:14:07Z",
   "kind": "comment",
   "who": "yuvicc",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-08-06T08:45:20Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.h",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": 3615250492,
   "text": "Are you suggesting here to allow a null `block_tree_entry` and then process the header separately if it is null?"
  },
  {
   "t": "2026-08-06T11:38:50Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "a77602ed7d4b4dda7cb4ef67148e004102a93be1",
   "in_reply_to": 3615051298,
   "text": "Kept it for now."
  },
  {
   "t": "2026-08-06T11:48:59Z",
   "kind": "force_push",
   "who": "sedited",
   "commit": "5c65fc94a3997356374568783045e465f3cae706"
  },
  {
   "t": "2026-08-06T11:49:13Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "Updated a77602ed7d4b4dda7cb4ef67148e004102a93be1 -> 5c65fc94a3997356374568783045e465f3cae706 ([external_coins_block_validation_0](https://github.com/sedited/bitcoin/tree/external_coins_block_validation_0) -> [external_coins_block_validation_1](https://github.com/sedited/bitcoin/tree/external_coins_block_validation_1), [compare](https://github.com/sedited/bitcoin/compare/external_coins_block_validation_0..external_coins_block_validation_1))\n\n* Switched to callback approach for fetching the coins\n* Added a commit for checking outpoint equality\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3614500382), added comment explaining block height.\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3614635254), explaining coin height value range.\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3614694468), added max range change for outpoint.\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3615060656), removed superfluous block txid check.\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3615280384), briefly described ValidateBlock(...).\n* Addressed @ismaelsadeeq's [comment](https://github.com/bitcoin/bitcoin/pull/35187#discussion_r3615307869), improved log message to no longer mention previously unprocessed headers, since that is no longer possible."
  },
  {
   "t": "2026-08-06T12:07:19Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "ConceptACK, approach = nearly, but not quite? (err, this was looking at a77602ed7d4b4dda7cb4ef67148e004102a93be1 but still mostly applies after the update, I think)\n\n[quoted text omitted]\nI think it should be possible to design this in a way that doesn't require copying the utxo set (from the caller's representation into two vectors and from two vectors into `CoinsViewBlock::m_coins`).\n\nRelatedly, I don't think `ValidateBlock` has quite the right API -- returning a `BlockValidationState` is fine, but that throws away the \"here's how the utxo set ends up\" info that the `CoinsView` should be able to provide.\n\nI think a better approach would be:\n * in addition to `btck_Coin` and `btck_TxOutPoint`, provide a `btck_CoinsDb` type, which would be an in-memory container for utxos, essentially `map<OutPoint,Coin>`\n * have `CoinsViewBlock` (`btck_CoinsViewCoinsDb` ?) be able to be no-copy constructed from `btck_CoinsDb`\n * pass that as a parameter to `ValidateBlock` without needing an additional copy\n * expose `btck_CoinsViewCoinsDb` 's delta to kernel users so you can easily query what changed\n\nHaving `btck_CoinsDb` be constructable directly from deserializing a block's rev.dat data should be plausible, I think, and would make the \"I have the blk.dat and rev.dat info for this block, I want to validate it in isolation\" work sensibly.\n\nI think we should control the container ourselves, rather than using a `lookup(outpoint) -> coin` callback that the caller provides so that we can be sure the lookups are consistent across calls. I think we should probably only expose two sorts of views: in-memory and full leveldb-based-utxo-set, to avoid adding more code paths versus what we use in the node. `btck_CoinsDb` might just be `CCoinsViewCache{CoinsViewEmpty::Get()}` ?\n\nProbably don't need to provide too many accessors/modifiers (`btck_coinsdb_lookup`, `_add`, `_remove`, `_iterate(callback)`, `_update(coinsviewcoinsbd)`, and `btck_coinsview_iterate(added_cb, removed_cb)`) ?"
  },
  {
   "t": "2026-08-06T14:33:57Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nAllowing two concrete types means having two separate code paths, where one code path is the one that is used in production and the other one is basically dead. An abstraction, on the other hand, means having a single code path all while being able to use any compatible type for testing. Please elaborate what you mean with consistency here."
  },
  {
   "t": "2026-08-11T03:28:50Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nThe leveldb approach is precisely what we use in production; the in memory approach could probably just be a `CCoinsViewCache` on top of `CoinsViewEmpty::Get()`, which we already use in tests, and just re-exercises mempool code.\n\n[quoted text omitted]\nA `lookup(outpoint)` function can return `(randbool() ? coin1 : coin2)` to break our logic, or do something equivalent by accident."
  },
  {
   "t": "2026-08-12T12:37:54Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nIs that a \"yes\"? If one code path is used exclusively for tests and never exercised in production, then this code path is dead. Plus the risk that the code path that is exercised in production may be not tested properly.\n\n[quoted text omitted]\nThat does not answer my question about consistency. Is your concern that the client may provide a non-deterministic lookup function? What about making it a requirement that the lookup function must be deterministic?"
  },
  {
   "t": "2026-08-24T16:46:20Z",
   "kind": "review_comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "path": "src/test/kernel/test_kernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "Nit: would an `unordered_map` be better? It would need a hasher over txid + index. Understand that it's just test code, but it would be valuable for demonstration purposes."
  },
  {
   "t": "2026-08-25T17:48:29Z",
   "kind": "comment",
   "who": "nervana21",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2026-08-26T10:28:28Z",
   "kind": "comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "text": "I wrote a test against these bindings that exercises the current API the way I would expect a caller to: sync all headers, then walk the blocks, validating each against a UTXO set the test maintains itself. No blocks are ever connected, so every coin comes from the callback.\n\nTest is [here](https://github.com/alexanderwiederin/rust-bitcoinkernel/blob/callback-utxo-free/tests/validate_block.rs) if useful.\n\nThe design holds up well in my opinion.\n\n*Edit: opened a [draft PR on rust-bitcoinkernel](https://github.com/sedited/rust-bitcoinkernel/pull/218) for feedback*"
  },
  {
   "t": "2026-08-28T21:09:13Z",
   "kind": "review_comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "path": "src/test/kernel/test_kernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": 3845560893,
   "text": "I specifically implemented it this way, which admittedly reads a bit bizarre, to avoid having to implement a hasher for the tests here."
  },
  {
   "t": "2026-09-04T09:22:25Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/kernel/bitcoinkernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nNit: class documentation would be nice here."
  },
  {
   "t": "2026-09-04T09:31:36Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nA note-only (orthogonal to this PR): With #36066, there would be no longer need for a mutable `CBlockIndex` (`TestConnectBlock`)."
  },
  {
   "t": "2026-09-04T09:33:53Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.h",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nTo be more precise, `TestBlockValidity`/`ConnectBlock(.../*fJustCheck=*/true)` does some state updates through side effects."
  },
  {
   "t": "2026-09-04T09:39:58Z",
   "kind": "review_comment",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/kernel/test_kernel.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nPassing exactly those coins that are spent by this block looks like a neat trick here. However, it assumes the undo data for the block is already available. This would probably be not the case for a scenario where a fresh block is being checked.\nMaybe a test where the whole UTXO set is supplied (via the coin fetcher) would be more realistic for such a use case."
  },
  {
   "t": "2026-09-04T09:45:55Z",
   "kind": "review",
   "who": "optout21",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "text": "Reviewed. I understand that this is an important need to be solved. The solution looks correct. However, I have no strong opinion on whether this is the right approach, and I have limited kernel experience, so I give no formal opinion for or against.\nI have no strong opinion on whether the `ValidateBlock` solution (in `validation.cpp`) is the right generic approach in the validation layer.\nI think there should be a direct (non-kernel) test for the new `ValidateBlock` (in `validation.cpp`).\nLeft some minor comments."
  },
  {
   "t": "2026-09-09T15:59:10Z",
   "kind": "review_comment",
   "who": "nervana21",
   "assoc": "CONTRIBUTOR",
   "path": "src/validation.cpp",
   "commit": "5c65fc94a3997356374568783045e465f3cae706",
   "in_reply_to": null,
   "text": "5c65fc94a3997356374568783045e465f3cae706 kernel: Add sans utxo set block validation:\n\nWhen we call `ConnectBlock` on a main chain block that is an ancestor of the `assumevalid` block (and satisfies some other straightforward criteria), we inherit `assumevalid` logic and omit script checks.\n\nShould there instead be some logic that forces a `script_check_reason` when `ValidateBlock` calls `ConnectBlock` or is the current logic the desired behavior?"
  }
 ],
 "labels_log": [
  {
   "t": "2026-04-30T21:16:15Z",
   "action": "labeled",
   "label": "Validation",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-01T03:18:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-01T08:27:21Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-04T22:32:04Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-13T15:44:37Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-13T16:30:41Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T09:23:29Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T14:13:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T21:19:39Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T13:40:15Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-07-14T08:44:49Z",
   "kind": "ready_for_review",
   "who": "sedited"
  },
  {
   "t": "2026-08-06T11:48:07Z",
   "kind": "renamed",
   "who": "sedited",
   "from": "kernel: Add non-utxo set block validation to API",
   "to": "kernel: Block validation without a complete UTXO set"
  }
 ],
 "text_chars": 21891,
 "text_tokens_estimate": 5472,
 "changed_paths": [
  "src/kernel/bitcoinkernel.cpp",
  "src/kernel/bitcoinkernel.h",
  "src/kernel/bitcoinkernel_wrapper.h",
  "src/test/kernel/test_kernel.cpp",
  "src/validation.cpp",
  "src/validation.h"
 ],
 "files": [
  {
   "path": "src/kernel/bitcoinkernel.cpp",
   "add": 76,
   "del": 0
  },
  {
   "path": "src/kernel/bitcoinkernel.h",
   "add": 75,
   "del": 0
  },
  {
   "path": "src/kernel/bitcoinkernel_wrapper.h",
   "add": 42,
   "del": 0
  },
  {
   "path": "src/test/kernel/test_kernel.cpp",
   "add": 119,
   "del": 0
  },
  {
   "path": "src/validation.cpp",
   "add": 34,
   "del": 0
  },
  {
   "path": "src/validation.h",
   "add": 15,
   "del": 0
  }
 ],
 "test_lines": 119,
 "git": {
  "head": "5c65fc94a3997356374568783045e465f3cae706",
  "head_matches_backup": true,
  "base": "8d9e4f8dbd80260e52b29336b58eecb7c0d63cd6",
  "commits": [
   {
    "sha": "644f73541f",
    "subject": "kernel: Add transaction is coinbase to C header",
    "files": 4,
    "add": 23,
    "del": 0
   },
   {
    "sha": "d54e309d00",
    "subject": "kernel: Add coin creation to C header",
    "files": 4,
    "add": 37,
    "del": 0
   },
   {
    "sha": "e65476d050",
    "subject": "kernel: Add outpoint creation to C header",
    "files": 4,
    "add": 25,
    "del": 0
   },
   {
    "sha": "b369e88ad4",
    "subject": "kernel: Add outpoint equals operator",
    "files": 4,
    "add": 24,
    "del": 0
   },
   {
    "sha": "5c65fc94a3",
    "subject": "kernel: Add sans utxo set block validation",
    "files": 6,
    "add": 253,
    "del": 1
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "6882d6f4b46439c0",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}