{
 "number": 35271,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35271",
 "title": "Update CoinsView::NeedsUpgrade to add additional checks",
 "author": "tomt1664",
 "author_association": "NONE",
 "created_at": "2026-05-12T13:53:22Z",
 "updated_at": "2026-05-21T14:02:00Z",
 "age_days": 128,
 "draft": true,
 "labels": [
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "697ffa196e6852520d0011e55334f86f0e81ac0c",
 "head_ref": "fix_coinsview_upgrade",
 "head_repo": "tomt1664/bitcoin",
 "head_history": [],
 "additions": 2,
 "deletions": 1,
 "changed_files": 1,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "blocked",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "l0rinc",
   "maflcko",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": true,
  "mergeable_state": "blocked",
  "last_author_activity": "2026-05-12T15:37:32Z",
  "last_reviewer_activity": "2026-05-21T14:02:00Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 128,
  "waiting_on_author_days": 119,
  "days_since_update": 119
 },
 "refs": {
  "mentioned": [
   24236
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 24236,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2022-04-05",
    "title": "Remove utxo db upgrade code"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "Additional two checks after cursor->Valid():\n\nSeek() in LevelDB finds the first key \u2265 the target. If no DB_COINS entry exists but there happens to be another key whose byte value is greater than DB_COINS, cursor->Valid() returns true \u2014 even though no legacy coins are present.\nThe original code would incorrectly signal an upgrade is needed. The new code explicitly verifies the found key's prefix is DB_COINS.",
 "commits": [
  {
   "sha": "697ffa196e6852520d0011e55334f86f0e81ac0c",
   "date": "2026-05-12T13:29:54Z",
   "message": "update CoinsView::NeedsUpgrade check for correctness"
  }
 ],
 "timeline": [
  {
   "t": "2026-05-12T15:11:23Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "Thank you for your contribution, but I don't think this needs fixing. This is logic that has been there for a long time and has served its purpose as such. If a change to the db format is ever repeated, it can be re-investigated then.\n\nEDIT: If there is a motivation for this in the form of an actual bug report or down stream issue, I'll be happy to re-open this."
  },
  {
   "t": "2026-05-12T15:11:42Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "This check disappeared in https://github.com/bitcoin/bitcoin/pull/24236/changes#diff-cafbe1353eff6084b73fd3b6c3dee603e0827348fdd2fe12dfad1e01003a84edR52-R60\n\nCan you please explain why `there happens to be another key whose byte value is greater than DB_COINS` can happen for a Bitcoin Core-produced chainstate? Did you encounter this on an actual datadir, or is this based on reasoning from LevelDB's `Seek()` semantics? Could you please check historically whether any Bitcoin Core version ever wrote another chainstate key that sorts after `DB_COINS`?\n\n@maflcko, do you remember if #24236 intentionally simplified this because `cursor->Valid()` implied the only possible key at that position was a legacy `DB_COINS` key?\n\nIf we do decide this defensive change makes sense for corrupt/foreign/past/future keys, I think it would be useful to add a [first commit documenting the current behavior](https://github.com/bitcoin/bitcoin/pull/35260):\n```patch\ndiff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp\n--- a/src/test/coins_tests.cpp\t(revision 10ca73c02cbff59f2134c0c7da3b8d0a7e727475)\n+++ b/src/test/coins_tests.cpp\t(revision 786432696f5e702529bbc8becd3483c8b595c929)\n@@ -5,6 +5,7 @@\n#include <addresstype.h>\n#include <clientversion.h>\n#include <coins.h>\n+#include <dbwrapper.h>\n#include <streams.h>\n#include <test/util/common.h>\n#include <test/util/poolresourcetester.h>\n@@ -16,8 +17,10 @@\n#include <util/byte_units.h>\n#include <util/strencodings.h>\n\n+#include <cstdint>\n#include <map>\n#include <string>\n+#include <utility>\n#include <variant>\n#include <vector>\n\n@@ -305,6 +308,25 @@\n\nBOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)\n\n+BOOST_AUTO_TEST_CASE(coins_view_db_needs_upgrade)\n+{\n+    static constexpr uint8_t LEGACY_COINS_KEY{'c'};\n+    static constexpr uint8_t INVALID_KEY{'x'};\n+\n+    auto needs_upgrade_with_first_key{[&](const char* name, uint8_t key) {\n+        const fs::path path{m_args.GetDataDirBase() / name};\n+        {\n+            CDBWrapper db{{.path = path, .cache_bytes = 1_MiB, .wipe_data = true, .obfuscate = false}};\n+            db.Write(std::make_pair(key, uint256{}), uint256{});\n+        }\n+        CCoinsViewDB coins_view{{.path = path, .cache_bytes = 1_MiB, .obfuscate = false}, {}};\n+        return coins_view.NeedsUpgrade();\n+    }};\n+\n+    BOOST_CHECK(needs_upgrade_with_first_key(\"coins_view_db_needs_upgrade_legacy\", LEGACY_COINS_KEY));\n+    BOOST_CHECK(needs_upgrade_with_first_key(\"coins_view_db_needs_upgrade_invalid\", INVALID_KEY));\n+}\n+\nstruct UpdateTest : BasicTestingSetup {\n// Store of all necessary tx and undo data for next test\ntypedef std::map<COutPoint, std::tuple<CTransaction,CTxUndo,Coin>> UtxoData;\n```\nwhich could be flipped on the second commit with the fix:\n```C++\n  BOOST_CHECK(!needs_upgrade_with_first_key(\"coins_view_db_needs_upgrade_invalid\", INVALID_KEY));\n```\nAlso, if the intended check is only the key prefix, `GetKey()` does not need to deserialize the whole `(prefix, uint256)` pair:\n```C++\n  uint8_t key;\n  return cursor->Valid() && cursor->GetKey(key) && key == DB_COINS;\n```\n(But if the goal is instead to recognize only well-formed legacy coins keys, then the current pair decode is more precise.)"
  },
  {
   "t": "2026-05-12T15:37:32Z",
   "kind": "comment",
   "who": "tomt1664",
   "assoc": "NONE",
   "text": "[quoted text omitted]\n\nMotivation is that it is needed downstream in Elements (https://github.com/tomt1664/elements/commit/697f03a17e714ebca15f732bae83b0f2e4f0c05a) and thought it may be relevant in other contexts. But fine if it is not appropriate to address here."
  },
  {
   "t": "2026-05-12T15:39:27Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "CONTRIBUTOR",
   "text": "Thanks for the reply and motivation @tomt1664. I'll re-open this then, given that it is a rather small change."
  },
  {
   "t": "2026-05-12T15:52:06Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "CONTRIBUTOR",
   "text": "Was there a corresponding value in Core for https://github.com/tomt1664/elements/blob/60cf64e07053b6a1bac89d9d3fa25f354ad82933/src/txdb.cpp#L36? Regardless, if we add it here the test key could be adjusted to `static constexpr uint8_t INVALID_KEY{'w'};` adding a comment to the corresponding repo.\n\nnit: the linter fails for the trailing spaces in `return cursor->Valid() && cursor->GetKey(key) && key.first == DB_COINS;  `"
  },
  {
   "t": "2026-05-21T14:02:00Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "are you still working on this, or can it be closed?"
  }
 ],
 "labels_log": [
  {
   "t": "2026-05-12T16:04:48Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-05-12T15:11:23Z",
   "kind": "closed",
   "who": "sedited"
  },
  {
   "t": "2026-05-12T15:39:27Z",
   "kind": "reopened",
   "who": "sedited"
  },
  {
   "t": "2026-05-15T08:28:50Z",
   "kind": "convert_to_draft",
   "who": "DrahtBot"
  }
 ],
 "text_chars": 4933,
 "text_tokens_estimate": 1233,
 "changed_paths": [
  "src/txdb.cpp"
 ],
 "files": [
  {
   "path": "src/txdb.cpp",
   "add": 2,
   "del": 1
  }
 ],
 "test_lines": 0,
 "git": {
  "head": "697ffa196e6852520d0011e55334f86f0e81ac0c",
  "head_matches_backup": true,
  "base": "10ca73c02cbff59f2134c0c7da3b8d0a7e727475",
  "commits": [
   {
    "sha": "697ffa196e",
    "subject": "update CoinsView::NeedsUpgrade check for correctness",
    "files": 1,
    "add": 2,
    "del": 1
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "eea22e23b9746260",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}