{
 "number": 32895,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/32895",
 "title": "wallet: Prepare for future upgrades by recording versions of last client to open and decrypt",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2025-07-07T20:13:08Z",
 "updated_at": "2026-09-17T10:46:46Z",
 "age_days": 436,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
 "head_ref": "wallet-lastclient",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2025-07-08T20:39:42Z",
   "sha": "f747394b622ada3d89c9d6072bd92f64c69c4fb5"
  },
  {
   "t": "2025-07-14T21:14:51Z",
   "sha": "5d367db7c1d2e49d38cb76685a8658fbd2407ff7"
  },
  {
   "t": "2025-07-21T20:06:48Z",
   "sha": "3662960470d74850008b7b48b6820d02002f7a5d"
  },
  {
   "t": "2025-08-08T18:55:03Z",
   "sha": "93d4e890f3b32d95eabdd65bb2f78cb1ce48fc0e"
  },
  {
   "t": "2025-08-16T03:02:41Z",
   "sha": "bd59a25e85679c8ce867f35b1b70b9096ef9266f"
  },
  {
   "t": "2026-01-19T18:49:46Z",
   "sha": "4e0618034254e1cf4d8d6cc59fbeb6614d5735b0"
  },
  {
   "t": "2026-01-19T19:00:46Z",
   "sha": "562172b35008688ab7dec19fea80c72a5e2e856f"
  },
  {
   "t": "2026-02-04T19:49:29Z",
   "sha": "69d0a6b80b7920d4f4c34839c92212b7fc309f0c"
  },
  {
   "t": "2026-02-11T22:18:03Z",
   "sha": "d069e251920d1466b9a062f602ec6fd350912116"
  },
  {
   "t": "2026-03-05T20:29:53Z",
   "sha": "67417e946e1dcf3e5bd8b22be06ad9bba3859efe"
  },
  {
   "t": "2026-04-27T20:10:53Z",
   "sha": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b"
  },
  {
   "t": "2026-04-28T18:24:29Z",
   "sha": "3e4490241f26a6932d1d15312d9cb10b35027d48"
  },
  {
   "t": "2026-08-19T20:03:11Z",
   "sha": "7369187be35e6962c8c1ef123d77e0582a0ea1b2"
  },
  {
   "t": "2026-09-16T22:18:59Z",
   "sha": "1f5c103fe079d7e42003b8b29c156d3fc9204053"
  }
 ],
 "additions": 123,
 "deletions": 18,
 "changed_files": 6,
 "commit_count": 4,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/32895#pullrequestreview-5229324703"
     }
    ],
    "concept_ack": [
     {
      "login": "ryanofsky",
      "url": "https://github.com/bitcoin/bitcoin/pull/32895#issuecomment-3049244656"
     }
    ],
    "stale_ack": [
     {
      "login": "ajtowns",
      "url": "https://github.com/bitcoin/bitcoin/pull/32895#issuecomment-4962686259"
     },
     {
      "login": "Eunovo",
      "url": "https://github.com/bitcoin/bitcoin/pull/32895#pullrequestreview-4992707719"
     },
     {
      "login": "Bortlesboat",
      "url": "https://github.com/bitcoin/bitcoin/pull/32895#pullrequestreview-5009644272"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36126,
     "title": "wallet, rpc: Implements set key label functionality",
     "author": "polespinasa"
    },
    {
     "number": 36031,
     "title": "wallet: Remove mapMasterKeys and enforce that only one encryption key can exist",
     "author": "achow101"
    },
    {
     "number": 35998,
     "title": "wallet: Handle or explicitly ignore `WalletBatch` write failures",
     "author": "achow101"
    },
    {
     "number": 35752,
     "title": "wallet: make encryption state updates atomic",
     "author": "l0rinc"
    },
    {
     "number": 34909,
     "title": "wallet, refactor: modularise wallet by extracting out legacy wallet migration",
     "author": "rkrux"
    },
    {
     "number": 33034,
     "title": "wallet: Store transactions in a separate sqlite table",
     "author": "achow101"
    },
    {
     "number": 27865,
     "title": "wallet: Track no-longer-spendable TXOs separately",
     "author": "achow101"
    }
   ]
  }
 },
 "acks_parsed": {
  "ryanofsky": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-07-08T14:42:25Z",
   "stale": false
  },
  "w0xlt": {
   "kind": "ack",
   "hash": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "t": "2026-09-16T23:20:27Z",
   "stale": false
  },
  "Bortlesboat": {
   "kind": "ack",
   "hash": "7369187be35e6962c8c1ef123d77e0582a0ea1b2",
   "t": "2026-08-24T15:30:14Z",
   "stale": true
  },
  "ajtowns": {
   "kind": "ack",
   "hash": "3e4490241f26a6932d1d15312d9cb10b35027d48",
   "t": "2026-07-13T21:06:32Z",
   "stale": true
  },
  "Eunovo": {
   "kind": "ack",
   "hash": null,
   "t": "2026-08-21T11:20:27Z",
   "stale": true
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 3,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "Bortlesboat",
   "Eunovo",
   "ajtowns",
   "maflcko",
   "ryanofsky",
   "sedited",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-16T22:19:07Z",
  "last_reviewer_activity": "2026-09-16T23:20:27Z",
  "last_reviewer": "w0xlt",
  "author_silent_days": 0,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   36126,
   36031,
   35998,
   35752,
   34909,
   33034,
   27865
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletutil.h"
 ],
 "body": "When a wallet is automatically upgraded, we always do so in a way that allows the user to load their wallet in an older version. When the user loads their wallet into an upgraded version again, the wallet may not perform the automatic upgrade and end up with upgraded and non-upgraded material in the wallet.\n\nIf we write some information about the last client to open the wallet, we would be able to detect these upgrade-downgrade-upgrade situations and perform another upgrade if so. However, in order for this to work, we need to have implemented writing such information into versions prior to the ones which introduce new automatic upgrades.\n\nWe are already writing the CLIENT_VERSION into all wallets in a \"version\" record, although this record is not updated in the same way across all previous versions. Since v24.0, we are always updating the record when the client version differs, but prior to that, the record would only be updated when the client version is newer. Even so, we can use this record to store the last client version.\n\nThe first commit decouples the written client version from the node version. This aids in development of new features. The version is entirely decoupled from previous versions by requiring all versions to have bit 19 set, and the initial version being `0 | (1 << 19)`. The versions just need to be at least 299900, and forcing bit 19 to be set was an easy way to achieve this.\n\nHowever, using version numbers is not as flexible as feature flags, so the second commit introduces the concept of Wallet Client Features. These flags are distinct from the existing Wallet Feature Flags since they are conceptually different. These are written in a separate record, and the wallet client version is incremented to `(1 << 19) + 1`.\n\nLastly, since some upgrades may need private keys and can only occur after the wallet is decrypted, the third commit also adds a last decrypted features flags record to record the features of the last client to decrypt the wallet.\n\nThe result is that we will now be able to detect a downgrade down to v24.0 and be able to implement in parallel multiple features that require automatic upgrades.",
 "commits": [
  {
   "sha": "728922515087bbc09119d1ba9c8375c866ce9986",
   "date": "2026-08-19T18:36:58Z",
   "message": "walletdb: Decouple last client record from CLIENT_VERSION\n\nInstead of tying the last client record with the node version, introduce\na separate wallet client version that will be increased as necessary\nwhen new automatic upgrades are introduced."
  },
  {
   "sha": "74b247511c18fa09346894b60a8a2da230ed3fc3",
   "date": "2026-08-19T18:36:59Z",
   "message": "wallet: Introduce LastClientFeatures flags and LAST_OPENED_FEATURES record\n\nLastClientFeatures are feature flags indicating what automatic upgrade\nfeatures supported by the last client that opened the wallet.\n\nThe LAST_OPENED_FEATURES record stores these flags and must be\nset to match the exact features supported by the client that opens a\nwallet."
  },
  {
   "sha": "cded0c6ef82bf8febe17b911ee46e5653815b574",
   "date": "2026-09-16T22:18:49Z",
   "message": "wallet: Record the supported features of the last client to decrypt a wallet"
  },
  {
   "sha": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "date": "2026-09-16T22:18:49Z",
   "message": "wallet: Set last opened and decrypted features during migration\n\nSet these flags to avoid the automatic upgrade after migrating."
  }
 ],
 "timeline": [
  {
   "t": "2025-07-08T09:24:38Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "I wonder if this can be uncoupled from `CLIENT_VERSION`, because the coupling is confusing and makes testing harder:\n\n* It is confusing, because switching between versions that kept the wallet code the same will pretend that there was a relevant upgrade/downgrade, even though the wallet behavior is exactly identical.\n* `CLIENT_VERSION` on the development branch is changed every 6 months, not when a new wallet feature was added that needs to deal with un-upgraded material. I know that running from the master branch is not supported and devs will have to deal with un-upgraded material themselves, but it seems easy to fix (and cleaner) to just introduce a new variable in the wallet that is equal to the current `CLIENT_VERSION` and then handle it separately and explicitly from now on."
  },
  {
   "t": "2025-07-08T14:42:25Z",
   "kind": "comment",
   "who": "ryanofsky",
   "assoc": "MEMBER",
   "text": "Concept ACK. It shouldn't hurt to add more metadata to help detect when there might be a mix of old data in a newer format and new data in an older format in the same wallet.\n\nConceptually, I think we could always detect these cases by just scanning the entire wallet for data that needs to be upgraded on loading. But having this extra metadata is nice because it can avoid slowing down wallet loading by avoiding unnecessary upgrade rescans.\n\nOn the approach in e94ef51837daa5ec9552eb14e8ccc38d1b82b65b, I think it is ok, but would suggest some ways it might be improved:\n\n- I agree with Marco's [comment](https://github.com/bitcoin/bitcoin/pull/32895#issuecomment-3048078577) that it would be great to move away from CLIENT_VERSION and use something independent of the node version.\n  - A downside of this suggestion is that the PR might not be able to reuse the existing VERSION field and might need to introduce 2 new fields instead of 1. I think the tradeoff could be worth it though.\n- It would be nice to avoid version numbers altogether and use feature flags instead, defining new feature flags when new data fields are introduced, and writing supported feature flags to LAST_OPENED_FEATURES & LAST_DECRYPTED_FEATURES fields on wallet loading.\n  - This would not require introducing a new data type since we already have a [WalletFlags](https://github.com/bitcoin/bitcoin/blob/927055e42afbfd998b5d85108e9c5fe7c6ffb7aa/src/wallet/walletutil.h#L36-L78) enum. New internal and external features could just be new (nonmandatory) `WalletFlags` values.\n  - IMO, flags are better than version numbers because they decouple code that implements new features from the merge and release process. They also make code more readable and maintainable because checks like `if (last_opened_supported_specific_feature_x)` are more obvious than `if (last_opened_version > some_version_number_with_collection_of_features)`\n- I assume we don't care about detecting upgrades & downgrades to versions before v24.0 which wouldn't overwrite the `VERSION` field on opening. But if we did, it'd be possible to detect this by writing last_opened and last_decrypted metadata to a file alongside the wallet database instead of inside the wallet database, and touching the file whenever the database is flushed. This way, if the database file modification time was greater than metadata file modification time, you could infer that the database had been loaded by an older version in the meantime and needs to be upgraded. Wouldn't recommend this approach because it is more complicated, but wanted to mention it as an alternative to introducing new database fields."
  },
  {
   "t": "2025-07-08T17:52:15Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nDefinitely, however I'd like to be able to reuse the existing \"version\" record which mostly has the desired behavior since v24.0. The detection only works because of older versions overwriting the field with their CLIENT_VERSION. Introducing a new field now means that we cannot detect when an upgraded wallet was opened in any version older than v30.0 (assuming this is merged for the next release). The timeline would be too soon for any newly introduced features in say v31.0 or v32.0 (and I've got a few things in mind already that would utilize this). I'm already not particularly thrilled by the fact that the current implementation can only detect down to 24.0 as I expect that users doing upgrade-downgrade-upgrade with even older versions is commonplace.\n\n[quoted text omitted]\nI don't think that is actually viable as it would largely end up performing the upgrade on all loads regardless of whether the upgrade has already been done."
  },
  {
   "t": "2025-07-08T18:28:50Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "Maybe I am missing something obvious, but my suggestion was not to introduce a new field/record. It was to re-use the existing field (and value) and manually handle the value going forward only when needed.\n\nThat is, old wallets will (obviously) continue to write their `CLIENT_VERSION` to the `version` record. Also, any new wallet will continue to write a value of the current `CLIENT_VERSION` to it, just like on the current development branch (this is unchanged as well). The only change is that for the next release, the wallet will *continue* to write the same value as today, unless a feature was added that requires bumping the version, in which case the version is bumped in the pull request introducing the feature."
  },
  {
   "t": "2025-07-08T18:34:04Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nSorry, I was responding to @ryanofsky's comment at the same time since they were very similar.\n\nI think that may be workable."
  },
  {
   "t": "2025-07-08T20:39:42Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "f747394b622ada3d89c9d6072bd92f64c69c4fb5"
  },
  {
   "t": "2025-07-08T20:53:32Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "I've updated the PR to do a combination of @maflcko's and @ryanofsky's suggestions.\n\nThe new approach decouples `version` from CLIENT_VERSION. It also writes out a set of client specific feature flags into two new records: `lastopenedfeatures` and `lastdecryptedfeatures`. However, these feature flags are separate from the existing wallet feature flags.\n\nI believe this preserves the behavior of detecting downgrades down to v24.0, while also giving us the flexibility of a new field and feature flags."
  },
  {
   "t": "2025-07-09T06:28:50Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f",
   "in_reply_to": null,
   "text": "nit in the first commit:\n\n* \u201cwhen a upgrade-downgrade-upgrade was performed\u201d -> \u201cwhen an upgrade-downgrade-upgrade was performed\u201d [incorrect indefinite article before a vowel sound]"
  },
  {
   "t": "2025-07-14T21:14:51Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "5d367db7c1d2e49d38cb76685a8658fbd2407ff7"
  },
  {
   "t": "2025-07-21T20:06:48Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3662960470d74850008b7b48b6820d02002f7a5d"
  },
  {
   "t": "2025-07-22T08:24:05Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f",
   "in_reply_to": null,
   "text": "LastClientFEatures -> LastClientFeatures [typo in capitalization of \u201cFeatures\u201d]"
  },
  {
   "t": "2025-07-23T21:27:30Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "b0470f573735a04ebc87ca5d3e00a73e3f10fee1",
   "text": "ACK https://github.com/bitcoin/bitcoin/pull/32895/commits/b0470f573735a04ebc87ca5d3e00a73e3f10fee1\n\nnit: Commit https://github.com/bitcoin/bitcoin/pull/32895/commits/3662960470d74850008b7b48b6820d02002f7a5d appears to have CI failures."
  },
  {
   "t": "2025-07-23T21:30:03Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "[quoted text omitted]\n\nI believe that's only because the tasks were canceled by a new push."
  },
  {
   "t": "2025-08-08T18:55:03Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "93d4e890f3b32d95eabdd65bb2f78cb1ce48fc0e"
  },
  {
   "t": "2025-08-16T03:02:41Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f"
  },
  {
   "t": "2025-10-27T13:35:41Z",
   "kind": "review",
   "who": "maflcko",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f",
   "text": "(just the llm nits)"
  },
  {
   "t": "2026-01-19T18:49:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "4e0618034254e1cf4d8d6cc59fbeb6614d5735b0"
  },
  {
   "t": "2026-01-19T18:49:50Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f",
   "in_reply_to": 2194162406,
   "text": "Done"
  },
  {
   "t": "2026-01-19T18:49:54Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "bd59a25e85679c8ce867f35b1b70b9096ef9266f",
   "in_reply_to": 2221675720,
   "text": "Done"
  },
  {
   "t": "2026-01-19T19:00:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "562172b35008688ab7dec19fea80c72a5e2e856f"
  },
  {
   "t": "2026-02-04T19:49:29Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "69d0a6b80b7920d4f4c34839c92212b7fc309f0c"
  },
  {
   "t": "2026-02-11T22:18:03Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "d069e251920d1466b9a062f602ec6fd350912116"
  },
  {
   "t": "2026-03-05T20:29:53Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "67417e946e1dcf3e5bd8b22be06ad9bba3859efe"
  },
  {
   "t": "2026-03-16T15:26:59Z",
   "kind": "review",
   "who": "Bortlesboat",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "67417e946e1dcf3e5bd8b22be06ad9bba3859efe",
   "text": "Concept ACK 67417e946e1dcf3e5bd8b22be06ad9bba3859efe"
  },
  {
   "t": "2026-03-16T15:27:00Z",
   "kind": "review_comment",
   "who": "Bortlesboat",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": null,
   "text": "If `WriteLastDecryptedFeatures()` fails, this still updates the in-memory guard, so the write won't be retried on subsequent unlocks. Should the return value be checked here, and `SetLastDecryptedFeatures` skipped on failure? That way the next unlock would retry the write."
  },
  {
   "t": "2026-03-23T21:56:47Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": 2941109291,
   "text": "It doesn't matter. The automatic upgrades are no-ops if they've already been done. If the write fails, the next time the wallet is loaded and unlocked, the upgrade gets done again, but that will do nothing. Really this is an optimization to avoid doing the work multiple times, but it doesn't functionally matter if it does happen multiple times."
  },
  {
   "t": "2026-04-27T05:53:59Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "698552ecabd1cad980f8772b99de62c17cf55cdb",
   "in_reply_to": null,
   "text": "`VERSION_LATEST = (1L << 19) + 0` would be sufficient here; seems something of a shame to waste 3/4ths of the available space (half due to negative values, quarter by going straight to the highest remaining bit). Using `1ULL` for a value going into an `int32_t` seems odd."
  },
  {
   "t": "2026-04-27T06:06:51Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": null,
   "text": "What happens when you want to implement a 65th wallet feature?\n\nDoes `VERSION_LATEST` get bumped every time there's a new feature defined, or is that unnecessary? It would seem possible to only bump `VERSION_LATEST` when there's an incompatible change to features (some features change their meanings, the field size is increased etc). If so, it might be good for there to be some compatibility overlap, so that you can cleanly switch between software with wallet client version `x` and `x-1`, even if switching between `x` and `x-2` involves full rescans of wallet features. (With 64 feature bits, bumping from `x` to `x+1` is probably at least five or six years of releases before bits need to be reused)"
  },
  {
   "t": "2026-04-27T06:31:47Z",
   "kind": "review",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "67417e946e1dcf3e5bd8b22be06ad9bba3859efe",
   "text": "Concept ACK 67417e946e1dcf3e5bd8b22be06ad9bba3859efe\n\nExcept for lack of clarity on what happens after 64 features, this looks good to me. Should it be added the the 32.0 milestone? Not really clear to me what the priority PRs are for wallet."
  },
  {
   "t": "2026-04-27T20:10:10Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": 3145159898,
   "text": "[quoted text omitted]\n\nI think what would happen is that for the 64th feature, there are actually 2 flags, one flag to indicate that there are more feature flags, and then we extend the record with another `uint64_t` to contain the next 63 feature flags. This should be fine to do, as the flags aren't indicating features of the wallet file itself, but rather capabilities of the last client that opened the wallet. When `x-1` is missing the flags extension flag, version `x` can see that and knows that the capabilities stored in the extension are missing and can therefore do the automatic upgrade.\n\n[quoted text omitted]\nNo, it only needs to be bumped when there is a major incompatible change that cannot be done in the normal wallet feature flags. But for the most part, I don't expect version to ever need to be bumped."
  },
  {
   "t": "2026-04-27T20:10:53Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b"
  },
  {
   "t": "2026-04-27T20:11:40Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "698552ecabd1cad980f8772b99de62c17cf55cdb",
   "in_reply_to": 3145118367,
   "text": "Changed `VERSION_MASK` to `MIN_VERSION = (1L << 19)`, and for the subsequent version number to increment on that."
  },
  {
   "t": "2026-04-28T06:01:39Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": null,
   "text": "Comment needs bumping"
  },
  {
   "t": "2026-04-28T06:05:32Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.h",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": null,
   "text": "`GUARDED_BY(cs_wallet)` ? if so, `SetLastDecryptedFeatrues()` needs `EXCLUSIVE_LOCKS_REQUIRED`"
  },
  {
   "t": "2026-04-28T06:08:30Z",
   "kind": "review_comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": null,
   "text": "Should this also initalize the features?"
  },
  {
   "t": "2026-04-28T18:24:29Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3e4490241f26a6932d1d15312d9cb10b35027d48"
  },
  {
   "t": "2026-04-28T18:24:37Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": 3151882545,
   "text": "Ended up dropping and updating the larger comment."
  },
  {
   "t": "2026-04-28T18:24:44Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.h",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": 3151899583,
   "text": "Sure, done."
  },
  {
   "t": "2026-04-28T18:24:51Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "9c6cd7daf1c3ea71c7234dd0e847dd101012572b",
   "in_reply_to": 3151916221,
   "text": "Indeed, done"
  },
  {
   "t": "2026-06-23T09:28:15Z",
   "kind": "review_comment",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/32895/commits/e40b36da5939052ed9e00bde7ee351e1b6193712:\n\nIt seems the PR description erroneously mentions setting bit 30."
  },
  {
   "t": "2026-07-03T14:07:40Z",
   "kind": "review_comment",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "path": "src/wallet/walletdb.cpp",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/32895/commits/e40b36da5939052ed9e00bde7ee351e1b6193712:\n\nnit: double blank lines; can fix if retouching"
  },
  {
   "t": "2026-07-03T14:11:23Z",
   "kind": "review_comment",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": null,
   "text": "https://github.com/bitcoin/bitcoin/pull/32895/commits/e40b36da5939052ed9e00bde7ee351e1b6193712:\n\n`MIN_VERSION` is not an actual writable version, so it's a bit misleading for it to be part of the enum; consider making it a separate `const`."
  },
  {
   "t": "2026-07-03T14:19:00Z",
   "kind": "review",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "3e4490241f26a6932d1d15312d9cb10b35027d48",
   "text": "Concept ACK https://github.com/bitcoin/bitcoin/pull/32895/commits/3e4490241f26a6932d1d15312d9cb10b35027d48"
  },
  {
   "t": "2026-07-13T20:06:42Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": 3458564083,
   "text": "Updated"
  },
  {
   "t": "2026-07-13T20:15:04Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletutil.h",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "in_reply_to": 3520382191,
   "text": "It is written in the commit that introduces it. I don't think it makes sense to have it be separate, it's defining the lower bound of this enum, just as `VERSION_LATEST` defines the upper bound. Even with future versions, we would never write earlier version numbers, but those versions should be in the enum."
  },
  {
   "t": "2026-07-13T21:06:32Z",
   "kind": "comment",
   "who": "ajtowns",
   "assoc": "MEMBER",
   "text": "ACK 3e4490241f26a6932d1d15312d9cb10b35027d48 ; didn't find any problems"
  },
  {
   "t": "2026-07-14T01:01:24Z",
   "kind": "review",
   "who": "Bortlesboat",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "3e4490241f26a6932d1d15312d9cb10b35027d48",
   "text": "ACK 3e4490241f26a6932d1d15312d9cb10b35027d48\n\nReviewed the range-diff from 67417e946e1dcf3e5bd8b22be06ad9bba3859efe. The revised wallet-client version namespace, creation-time metadata initialization, and cs_wallet annotations address the intervening review feedback; I did not find additional issues. CI is green."
  },
  {
   "t": "2026-07-20T20:36:53Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "@w0xlt can you take another look here?"
  },
  {
   "t": "2026-08-19T20:03:11Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7369187be35e6962c8c1ef123d77e0582a0ea1b2"
  },
  {
   "t": "2026-08-21T11:20:27Z",
   "kind": "review",
   "who": "Eunovo",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "7369187be35e6962c8c1ef123d77e0582a0ea1b2",
   "text": "ACK https://github.com/bitcoin/bitcoin/pull/32895/commits/7369187be35e6962c8c1ef123d77e0582a0ea1b2"
  },
  {
   "t": "2026-08-24T15:30:14Z",
   "kind": "review",
   "who": "Bortlesboat",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "7369187be35e6962c8c1ef123d77e0582a0ea1b2",
   "text": "ACK 7369187be35e6962c8c1ef123d77e0582a0ea1b2\n\nI reviewed the range-diff from 3e4490241f26a6932d1d15312d9cb10b35027d48; it is just the rebase preserving the upstream `WriteVersion(int)` documentation/argument handling and best-block-locator comment, with no change to the feature-record logic. Built in WSL2 Ubuntu 24.04 and ran `wallet_tests`, `walletdb_tests`, `walletload_tests`, `wallet_crypto_tests`, `init_tests`, plus `wallet_createwallet.py` (RPC and `--usecli`), `wallet_encryption.py`, and `wallet_migration.py` against v28.2; all passed."
  },
  {
   "t": "2026-09-16T21:24:01Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Should we delete `LAST_DECRYPTED_FEATURES` when loading a wallet that was last opened by a client that doesn\u2019t support this record?\n\nIf the record is absent, the client will recreate it after the next successful unlock and any required upgrades.\n\nEx:\n```diff\ndiff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp\nindex 90f04c3c53..1672656c24 100644\n--- a/src/wallet/walletdb.cpp\n+++ b/src/wallet/walletdb.cpp\n@@ -1235,6 +1235,15 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)\n     }\n\n+    // Discard stale decryption features before updating the version, so that a locked\n+    // reload cannot restore the features recorded before a downgrade.\n+    if (last_client < VERSION_LAST_CLIENT_FEATURES && m_batch->Exists(DBKeys::LAST_DECRYPTED_FEATURES)) {\n+        if (!EraseIC(DBKeys::LAST_DECRYPTED_FEATURES)) {\n+            pwallet->WalletLogPrintf(\"Error: Unable to erase last decrypted client features.\\n\");\n+            return DBErrors::LOAD_FAIL;\n+        }\n+    }\n+\n     // Record the current client version as the last version to successfully open this wallet file\n     // This must always be done after all automatic upgrades so that those upgrades can be performed\n     // in an upgrade-downgrade-upgrade scenario.\n```"
  },
  {
   "t": "2026-09-16T22:18:59Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053"
  },
  {
   "t": "2026-09-16T22:19:07Z",
   "kind": "comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "text": "Took the suggestioin"
  },
  {
   "t": "2026-09-16T23:20:27Z",
   "kind": "review",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
   "text": "ACK 1f5c103fe079d7e42003b8b29c156d3fc9204053"
  }
 ],
 "labels_log": [
  {
   "t": "2025-07-07T20:13:11Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-22T01:36:47Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-22T09:45:55Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-08T17:41:59Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-08T19:51:34Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-16T00:38:42Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-16T05:05:50Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T12:11:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T19:01:17Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T20:01:03Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T20:25:01Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-04T21:02:55Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-11T23:43:22Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-02T21:30:09Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-05T22:01:05Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T23:26:12Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-19T21:38:51Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 15900,
 "text_tokens_estimate": 3975,
 "changed_paths": [
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletdb.h",
  "src/wallet/walletutil.h",
  "test/functional/wallet_createwallet.py"
 ],
 "files": [
  {
   "path": "src/wallet/wallet.cpp",
   "add": 32,
   "del": 3
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 6,
   "del": 0
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 52,
   "del": 5
  },
  {
   "path": "src/wallet/walletdb.h",
   "add": 9,
   "del": 8
  },
  {
   "path": "src/wallet/walletutil.h",
   "add": 22,
   "del": 0
  },
  {
   "path": "test/functional/wallet_createwallet.py",
   "add": 2,
   "del": 2
  }
 ],
 "test_lines": 4,
 "git": {
  "head": "1f5c103fe079d7e42003b8b29c156d3fc9204053",
  "head_matches_backup": true,
  "base": "367b2202a496c866c485d940864171ca97c514f2",
  "commits": [
   {
    "sha": "7289225150",
    "subject": "walletdb: Decouple last client record from CLIENT_VERSION",
    "files": 5,
    "add": 31,
    "del": 16
   },
   {
    "sha": "74b247511c",
    "subject": "wallet: Introduce LastClientFeatures flags and LAST_OPENED_FEATURES record",
    "files": 5,
    "add": 45,
    "del": 5
   },
   {
    "sha": "cded0c6ef8",
    "subject": "wallet: Record the supported features of the last client to decrypt a wallet",
    "files": 4,
    "add": 43,
    "del": 0
   },
   {
    "sha": "1f5c103fe0",
    "subject": "wallet: Set last opened and decrypted features during migration",
    "files": 1,
    "add": 7,
    "del": 0
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "77108ef85bba2b24",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}