{
 "number": 35998,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35998",
 "title": "wallet: Handle or explicitly ignore `WalletBatch` write failures",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2026-08-17T21:52:38Z",
 "updated_at": "2026-09-17T02:29:37Z",
 "age_days": 30,
 "draft": true,
 "labels": [
  "Wallet",
  "Needs rebase"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
 "head_ref": "walletdb-nodiscard",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2026-08-25T22:53:25Z",
   "sha": "f8b9da8007ad6468618fc8e4235508a5b7038386"
  },
  {
   "t": "2026-08-26T20:47:12Z",
   "sha": "c794ae0e64a15e0230ca31611bb95694cc05c7e5"
  },
  {
   "t": "2026-09-14T22:07:32Z",
   "sha": "8cf010745031e91ce97c9bbc2ff252ceb134bb4b"
  },
  {
   "t": "2026-09-14T23:42:07Z",
   "sha": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e"
  }
 ],
 "additions": 542,
 "deletions": 230,
 "changed_files": 19,
 "commit_count": 25,
 "size_bucket": "L",
 "mergeable_state": "dirty",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35998#issuecomment-5320742444"
     },
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/35998#issuecomment-5423370516"
     },
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/35998#pullrequestreview-5111792081"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36167,
     "title": "[RFC] Enable `-Wunused`",
     "author": "fanquake"
    },
    {
     "number": 36133,
     "title": "wallet: store multipath descriptor",
     "author": "Sjors"
    },
    {
     "number": 36126,
     "title": "wallet, rpc: Implements set key label functionality",
     "author": "polespinasa"
    },
    {
     "number": 36070,
     "title": "wallet: Add deriveHDKey interface",
     "author": "PraneethGunas"
    },
    {
     "number": 36031,
     "title": "wallet: Remove mapMasterKeys and enforce that only one encryption key can exist",
     "author": "achow101"
    },
    {
     "number": 35786,
     "title": "wallet: drop spent parents redundant cache invalidation and notification",
     "author": "furszy"
    },
    {
     "number": 35752,
     "title": "wallet: make encryption state updates atomic",
     "author": "l0rinc"
    },
    {
     "number": 35716,
     "title": "wallet: Replace mapWallet and wtxOrdered with a boost::multi_index",
     "author": "achow101"
    },
    {
     "number": 35444,
     "title": "wallet: make descriptor SPKM mutex non-recursive",
     "author": "w0xlt"
    },
    {
     "number": 35377,
     "title": "wallet: Allow importing of descriptors without private keys when the wallet has the private keys",
     "author": "achow101"
    },
    {
     "number": 34909,
     "title": "wallet, refactor: modularise wallet by extracting out legacy wallet migration",
     "author": "rkrux"
    },
    {
     "number": 34861,
     "title": "wallet: Add importdescriptors interface",
     "author": "polespinasa"
    },
    {
     "number": 33034,
     "title": "wallet: Store transactions in a separate sqlite table",
     "author": "achow101"
    },
    {
     "number": 32895,
     "title": "wallet: Prepare for future upgrades by recording versions of last client to open and decrypt",
     "author": "achow101"
    },
    {
     "number": 29278,
     "title": "Wallet:  Add `maxfeerate` wallet startup option",
     "author": "ismaelsadeeq"
    },
    {
     "number": 27865,
     "title": "wallet: Track no-longer-spendable TXOs separately",
     "author": "achow101"
    }
   ]
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-08-17T21:58:12Z",
   "stale": false
  },
  "rkrux": {
   "kind": "concept_ack",
   "hash": "f8b9da8007ad6468618fc8e4235508a5b7038386",
   "t": "2026-08-26T09:33:33Z",
   "stale": false
  },
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-04T10:24:21Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 3,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "jeanpablojp",
   "l0rinc",
   "rkrux"
  ]
 },
 "signals": {
  "needs_rebase": true,
  "ci_failed": false,
  "mergeable_state": "dirty",
  "last_author_activity": "2026-09-14T23:42:07Z",
  "last_reviewer_activity": "2026-09-04T10:24:21Z",
  "last_reviewer": "jeanpablojp",
  "author_silent_days": 2,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   35752
  ],
  "depends_on": [
   35752
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35752,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet: make encryption state updates atomic"
   }
  ],
  "conflicts": [
   36167,
   36133,
   36126,
   36070,
   36031,
   35786,
   35752,
   35716,
   35444,
   35377,
   34909,
   34861,
   33034,
   32895,
   29278,
   27865
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/test/util.h",
  "src/wallet/wallet.cpp",
  "src/wallet/walletdb.cpp"
 ],
 "body": "A common theme in the wallet is that many database write failures are ignored, when they should probably be handled, or at least documented that a failure is being ignored. This PR marks all database functions in `WalletBatch` as `[[nodiscard]]` so that a compiler will tell us if a return value is implicitly ignored. All of the calls to those functions are updated to either deal with failure, or explicitly ignore it with a rationale.\n\nBased on #35752 which handles failures for the encryption functions.",
 "commits": [
  {
   "sha": "e4639ca02d99270aff968c67014d779fcabc9088",
   "date": "2026-09-14T20:44:32Z",
   "message": "refactor/test: add wallet failure injection\n\nAdd a reusable SQLite-backed wallet database that can reject selected record writes, erases, or transaction commits and inspect stored records.\n\nCo-authored-by: Ava Chow <github@achow101.com>"
  },
  {
   "sha": "f4c63bfe2fc066915cddf922f1bb1540b4cae6ed",
   "date": "2026-09-14T20:45:10Z",
   "message": "test: characterize encryption transaction failures\n\nWallet encryption currently reports success after a failed master-key write.\nA failed transaction commit aborts after publishing master and descriptor encryption state, which prevents retry in the same process.\n\nRecord both outcomes before returning transaction failures as errors and publishing live state only after commit.\n\nCo-authored-by: Ava Chow <github@achow101.com>"
  },
  {
   "sha": "55e217119cd69d13b6ccc786c48620e824a22e5f",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: abort failed encryption transactions\n\nWallet encryption publishes master and descriptor key state before the database transaction commits.\nIt also ignores a failed master-key write and aborts the process after a failed commit.\n\nUse `RunWithinTxn()` for the master key and existing descriptor keys.\nStage descriptor keys in a commit listener and publish the master key after commit, so failed master-key writes and commits leave live state unchanged and encryption can be retried."
  },
  {
   "sha": "f76530bff6f089f32dcbc7783168e93647e6a874",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: restore lock state before re-encryption\n\n`ChangeWalletPassphrase()` temporarily unlocks a locked wallet after validating the old passphrase.\nIf `EncryptMasterKey()` then fails, the wallet returns without restoring its locked state.\n\nRestore the original lock state immediately after validating the old passphrase, before re-encrypting or writing the master key."
  },
  {
   "sha": "14dcad0ff2fe14a45e6faba9df118b0196eb065a",
   "date": "2026-09-14T20:45:15Z",
   "message": "test: characterize passphrase write failure\n\nWallet passphrase changes currently report success after the master-key write fails.\nThe new passphrase works only in memory while the old passphrase remains on disk.\n\nRecord that behavior before encrypting a copy of the master key and publishing it only after the database write succeeds.\n\nCo-authored-by: Ava Chow <github@achow101.com>"
  },
  {
   "sha": "d9b9637e057cbebadb08db9f585085f10b0820c0",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: reject failed passphrase changes\n\n`ChangeWalletPassphrase()` updates the in-memory master key before writing it to the database.\nIf `WriteMasterKey()` fails, the new passphrase works only in memory while the old passphrase remains on disk.\n\nEncrypt a copy of the master key, persist it, and replace the live value only after the write succeeds.\nA failed write now leaves the old passphrase active and allows the change to be retried."
  },
  {
   "sha": "9723788a02b8e619aeacc5814e88ba8c19138133",
   "date": "2026-09-14T20:45:15Z",
   "message": "test: characterize descriptor key failures\n\nDescriptor encryption currently reports success after an encrypted-key record write or plaintext-key record erase fails.\nBoth failures publish wallet and descriptor encryption state and prevent retry.\nAn ignored erase also commits both records.\n\nRecord the existing write and erase failure behavior before propagating either error and staging descriptor memory."
  },
  {
   "sha": "e95e2c24bca9f30b945e616005d07745f578a8fc",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: abort failed descriptor key writes\n\n`DescriptorScriptPubKeyMan::Encrypt()` ignores failed encrypted-key writes, allowing the transaction to publish keys whose records were not persisted.\n\nCheck each write before staging its encrypted value.\n`RunWithinTxn()` can then abort without publishing memory, leaving encryption retryable."
  },
  {
   "sha": "06678c34263e422a03bdb42723eca004f0af8832",
   "date": "2026-09-14T20:45:15Z",
   "message": "test: cover encrypted descriptor key insertion\n\nThe descriptor-key write helper also inserts encrypted keys when no plaintext record exists.\nPin this path before propagating replacement erase failures, so insertion remains successful without attempting a plaintext erase."
  },
  {
   "sha": "bd8ec910aadd56af36dcaae6e979846421d865a3",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: abort failed descriptor key erases\n\n`WriteCryptedDescriptorKey()` replaces a plaintext key during wallet encryption.\nThe same helper can insert an encrypted key when no plaintext record exists.\n\nAfter writing the encrypted key, erase its plaintext record if it exists and propagate any erase failure.\nKeeping both operations in the helper prevents replacement callers from omitting the erase while allowing new encrypted-key insertion.\n\nCo-authored-by: w0xlt <94266259+w0xlt@users.noreply.github.com>\nCo-authored-by: Ava Chow <github@achow101.com>"
  },
  {
   "sha": "a2326cf0cbf787946f5d5a9611c26d5e91c25c68",
   "date": "2026-09-14T20:45:15Z",
   "message": "test: characterize descriptor insertion failure\n\nDescriptor key insertion currently updates the live key map before writing the key record.\nIf the write fails, a retry sees the live-only key and skips persistence while reporting success.\n\nRecord the plaintext and encrypted cases before making live-map publication conditional on successful database writes."
  },
  {
   "sha": "166d7385247012d53e585bb7e2e59b7881212c3c",
   "date": "2026-09-14T20:45:15Z",
   "message": "wallet: publish descriptor keys after writes\n\n`AddDescriptorKeyWithDB()` publishes plaintext and encrypted keys before writing their database records.\nA failed write leaves a live-only key, and the duplicate check prevents a retry from persisting it.\n\nWrite each key record first and update the corresponding live key map only after the write succeeds.\nA failed insertion then leaves no key behind and can be retried."
  },
  {
   "sha": "adae0baecf581b3f6ffd5edc05ccfdf55946f41e",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: return passphrase errors with Expected\n\nChange wallet unlock and passphrase methods from `bool` to `util::Expected<void, WalletError>` so they can return specific error codes and messages.\nUpdate the wallet interfaces while callers continue to use the success state."
  },
  {
   "sha": "3eed7f994c0b0be799ca64d6759526648cbe3ebd",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: deduplicate wallet unlock and passphrase change errors\n\nUnlocking and changing the passphrase of a wallet may result in errors.\nThese functions should produce specific errors with codes and messages\nso that callers can handle the errors appropriately.\n\nAdditionally, doing so allows us to deduplicate some passphrase error\nmessages."
  },
  {
   "sha": "d4c5ecfd5781cf221fc2ba2bcdcab83573effeec",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Handle db write failures in ExportWatchOnlyWallet"
  },
  {
   "sha": "830e5663405fda119b000284d7009a7491ada932",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Explicitly mark ignored write failures\n\nReturn values for some writes can be ignored, if they fail, no harm\noccurred."
  },
  {
   "sha": "d30d9c2edb20418a29ba074502571fecf0e493cd",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Handle descriptor update write failures"
  },
  {
   "sha": "53986e37347184988b4f45e68cf1efbe1d5b1768",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Handle Write failure during loading"
  },
  {
   "sha": "45f583dbe8dc81bb1f04709f8cb6aa027358e776",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Ignore TxnAbort failure in RunWithinTxn\n\nRunWithinTxn calls TxnAbort, but if this fails, the WalletBatch will go\nout of scope anyways, which will call destructors that reach\nSQLiteBatch::Close, which will also call TxnAbort, and has more handling\nof abort failures."
  },
  {
   "sha": "09dcf742b31b727c93e647f31f68baa36d089a28",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Handle db write failure in IncOrderPosNext"
  },
  {
   "sha": "64e9ec4ec90e9431aa95d0434d5ca273f4bd7b61",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Handle transaction write failures\n\nHandle write failures for WriteOrderPosNext and WriteTx"
  },
  {
   "sha": "706b8f31c3de260cfb3c1d1b72ceb0709a7bf53a",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet, migration: Handle address book data write failures"
  },
  {
   "sha": "3abfc9b9bdf04410ccba3cc09d5f41bff4eccd59",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet, bench: Ignore db write failures"
  },
  {
   "sha": "8783d5c293cbd4424ae336636e8bcb41031c3f65",
   "date": "2026-09-14T23:42:01Z",
   "message": "wallet: Mark WriteBestBlock [[nodiscard]] and handle write errors\n\nMark WriteBestBlock as [[nodiscard]] and explicitly ignore errors, or\nhandle them."
  },
  {
   "sha": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "date": "2026-09-14T23:42:01Z",
   "message": "walletdb: Mark all WalletBatch operations [[nodisard]]\n\nAll WalletBatch operations that interact with the database (read, write,\nerase, transactions) have bool returns values that callers need to check\nas database operations may fail."
  }
 ],
 "timeline": [
  {
   "t": "2026-08-17T21:58:12Z",
   "kind": "comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "text": "Concept ACK, thanks for taking over.\nPlease see the latest push in https://github.com/bitcoin/bitcoin/pull/35752#issuecomment-5320733236, feel free to adjust it any way you like."
  },
  {
   "t": "2026-08-25T22:53:25Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "f8b9da8007ad6468618fc8e4235508a5b7038386"
  },
  {
   "t": "2026-08-26T09:33:33Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "MEMBER",
   "text": "Concept ACK f8b9da8007ad6468618fc8e4235508a5b7038386 because it enforces the handling of database errors, thereby ensuring consistency between in-memory behaviour and databases behaviour."
  },
  {
   "t": "2026-08-26T20:47:12Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c794ae0e64a15e0230ca31611bb95694cc05c7e5"
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "c794ae0e64a15e0230ca31611bb95694cc05c7e5",
   "text": "Concept ACK\n\nLeft some comments."
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": null,
   "text": "A stale locator only costs a rescan on the next load, but here it cancels the backup before `Backup()` is reached. With the best block write failing, `BackupWallet` returns false and no copy is taken; before this change, `Backup()` still runs. This is the copy you most want when the database starts refusing writes. Would logging and carrying on make sense here?"
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/wallet.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": null,
   "text": "The `emplace()` into `mapWallet` happens before `IncOrderPosNext()`, so this return leaves the entry behind with no `nOrderPos`, no `wtxOrdered` slot and no `AddToSpends`, and every later call with the same txid then takes the `!fInsertedNew` path and reports success without repairing it. With `CommitTransaction`'s `update_wtx`, the `tx` record still reaches disk while `wtxOrdered` stays empty. Before this change, the first call returns the wtx and writes the `tx` record.\n\n```suggestion\n        std::optional<int64_t> pos = IncOrderPosNext(batch);\n        if (!pos) {\n            mapWallet.erase(hash);\n            return nullptr;\n        }\n```"
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/scriptpubkeyman.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": null,
   "text": "The throw propagates out of `TopUp` before `TxnCommit`, so the cache items roll back while `m_max_cached_index` and the new range end stay in memory. The retry then derives nothing and `WriteDescriptor` succeeds, persisting a range wider than the cache behind it. Reopening that wallet stops with `Unable to expand wallet descriptor from cache` and returns `Error loading <file>: Wallet corrupted`; before this change, it reopens fine.\n\nIt depends on the descriptor. I hit it with `wpkh(<xprv>/0h/*h)`, which needs a cached xpub per index; a plain `wpkh(<xprv>/0h/0/*)` reopens with no error.\n\nWorth rolling the in-memory side back when the write fails?"
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/util.h",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": null,
   "text": "All three of these go unnoticed by the suite as it stands, and `FaultInjectingDatabase` covers each of them with a single injected failure. The `TopUpWithDB` one also needs a file-backed variant, since the in-memory database dies with the wallet and the effect only shows on reload."
  },
  {
   "t": "2026-09-04T10:24:21Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/walletdb.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": null,
   "text": "On a genuinely full disk, with just enough room left for the database to open, this reports `Error loading <file>: Wallet corrupted` while nothing is corrupt.\n\n```suggestion\n            return DBErrors::LOAD_FAIL;\n```\n\nWith that swap, loading still fails, but it is reported only as `Error loading <file>`. I see `EraseMasterKey` below already returns `CORRUPT`, so this may well be deliberate."
  },
  {
   "t": "2026-09-08T20:24:15Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": 3933080173,
   "text": "No. If a write fails, it is possible that the database is already corrupted, and we do not want to be making a backup that may not be usable."
  },
  {
   "t": "2026-09-08T20:27:13Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/wallet.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": 3933080179,
   "text": "This is ok, a failure here results in an exception higher up in the call stack which will result in a crash. Database write failures are supposed to be catastrophic, they are not expected to happen."
  },
  {
   "t": "2026-09-08T20:30:33Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/scriptpubkeyman.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": 3933080195,
   "text": "[quoted text omitted]\n\nWhat retry?\n\nWrite failures are supposed to be catastrophic and an indicator that corruption has likely happened."
  },
  {
   "t": "2026-09-08T20:31:02Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/test/util.h",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": 3933080201,
   "text": "it is not necessary to leave review comments describing what is happening in the code."
  },
  {
   "t": "2026-09-08T20:32:44Z",
   "kind": "review_comment",
   "who": "achow101",
   "assoc": "MEMBER",
   "path": "src/wallet/walletdb.cpp",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
   "in_reply_to": 3933080205,
   "text": "Failure to write is always corruption, regardless of how that may come about. We don't know why the write failed, it could be for reasons other than a disk being full."
  },
  {
   "t": "2026-09-14T22:07:32Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "8cf010745031e91ce97c9bbc2ff252ceb134bb4b"
  },
  {
   "t": "2026-09-14T23:42:07Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e"
  }
 ],
 "labels_log": [
  {
   "t": "2026-08-17T21:52:41Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-18T23:20:45Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-26T00:22:05Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-26T00:36:47Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-26T22:22:55Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-12T12:01:10Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T22:39:19Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-14T23:37:11Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T01:12:40Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-17T02:29:36Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 10548,
 "text_tokens_estimate": 2637,
 "changed_paths": [
  "src/bench/wallet_migration.cpp",
  "src/interfaces/wallet.h",
  "src/qt/askpassphrasedialog.cpp",
  "src/qt/walletmodel.cpp",
  "src/qt/walletmodel.h",
  "src/wallet/export.cpp",
  "src/wallet/interfaces.cpp",
  "src/wallet/rpc/encrypt.cpp",
  "src/wallet/scan.cpp",
  "src/wallet/scriptpubkeyman.cpp",
  "src/wallet/scriptpubkeyman.h",
  "src/wallet/test/util.h",
  "src/wallet/test/wallet_tests.cpp",
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletdb.h",
  "test/functional/wallet_encryption.py",
  "test/functional/wallet_migration.py"
 ],
 "files": [
  {
   "path": "src/bench/wallet_migration.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/interfaces/wallet.h",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/qt/askpassphrasedialog.cpp",
   "add": 9,
   "del": 33
  },
  {
   "path": "src/qt/walletmodel.cpp",
   "add": 4,
   "del": 13
  },
  {
   "path": "src/qt/walletmodel.h",
   "add": 2,
   "del": 3
  },
  {
   "path": "src/wallet/export.cpp",
   "add": 24,
   "del": 6
  },
  {
   "path": "src/wallet/interfaces.cpp",
   "add": 3,
   "del": 3
  },
  {
   "path": "src/wallet/rpc/encrypt.cpp",
   "add": 17,
   "del": 23
  },
  {
   "path": "src/wallet/scan.cpp",
   "add": 5,
   "del": 2
  },
  {
   "path": "src/wallet/scriptpubkeyman.cpp",
   "add": 33,
   "del": 10
  },
  {
   "path": "src/wallet/scriptpubkeyman.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/test/util.h",
   "add": 86,
   "del": 0
  },
  {
   "path": "src/wallet/test/wallet_tests.cpp",
   "add": 172,
   "del": 4
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 117,
   "del": 73
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 4,
   "del": 4
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 17,
   "del": 8
  },
  {
   "path": "src/wallet/walletdb.h",
   "add": 38,
   "del": 38
  },
  {
   "path": "test/functional/wallet_encryption.py",
   "add": 1,
   "del": 1
  },
  {
   "path": "test/functional/wallet_migration.py",
   "add": 3,
   "del": 3
  }
 ],
 "test_lines": 276,
 "git": {
  "head": "d9a9c927a8708e7c1cc0d232a6d767c20a30175e",
  "head_matches_backup": true,
  "base": "cff00c547e7460fa1ec4aa1e38217793593d4263",
  "commits": [
   {
    "sha": "e4639ca02d",
    "subject": "refactor/test: add wallet failure injection",
    "files": 1,
    "add": 86,
    "del": 0
   },
   {
    "sha": "f4c63bfe2f",
    "subject": "test: characterize encryption transaction failures",
    "files": 2,
    "add": 66,
    "del": 2
   },
   {
    "sha": "55e217119c",
    "subject": "wallet: abort failed encryption transactions",
    "files": 4,
    "add": 39,
    "del": 42
   },
   {
    "sha": "f76530bff6",
    "subject": "wallet: restore lock state before re-encryption",
    "files": 1,
    "add": 2,
    "del": 2
   },
   {
    "sha": "14dcad0ff2",
    "subject": "test: characterize passphrase write failure",
    "files": 1,
    "add": 20,
    "del": 0
   },
   {
    "sha": "d9b9637e05",
    "subject": "wallet: reject failed passphrase changes",
    "files": 2,
    "add": 12,
    "del": 9
   },
   {
    "sha": "9723788a02",
    "subject": "test: characterize descriptor key failures",
    "files": 1,
    "add": 29,
    "del": 0
   },
   {
    "sha": "e95e2c24bc",
    "subject": "wallet: abort failed descriptor key writes",
    "files": 2,
    "add": 9,
    "del": 7
   },
   {
    "sha": "06678c3426",
    "subject": "test: cover encrypted descriptor key insertion",
    "files": 1,
    "add": 17,
    "del": 0
   },
   {
    "sha": "bd8ec910aa",
    "subject": "wallet: abort failed descriptor key erases",
    "files": 2,
    "add": 11,
    "del": 10
   },
   {
    "sha": "a2326cf0cb",
    "subject": "test: characterize descriptor insertion failure",
    "files": 1,
    "add": 42,
    "del": 4
   },
   {
    "sha": "166d738524",
    "subject": "wallet: publish descriptor keys after writes",
    "files": 2,
    "add": 10,
    "del": 5
   },
   {
    "sha": "adae0baecf",
    "subject": "wallet: return passphrase errors with Expected",
    "files": 7,
    "add": 45,
    "del": 36
   },
   {
    "sha": "3eed7f994c",
    "subject": "wallet: deduplicate wallet unlock and passphrase change errors",
    "files": 5,
    "add": 32,
    "del": 67
   },
   {
    "sha": "d4c5ecfd57",
    "subject": "wallet: Handle db write failures in ExportWatchOnlyWallet",
    "files": 1,
    "add": 24,
    "del": 6
   },
   {
    "sha": "830e566340",
    "subject": "wallet: Explicitly mark ignored write failures",
    "files": 1,
    "add": 3,
    "del": 2
   },
   {
    "sha": "d30d9c2edb",
    "subject": "wallet: Handle descriptor update write failures",
    "files": 1,
    "add": 7,
    "del": 2
   },
   {
    "sha": "53986e3734",
    "subject": "wallet: Handle Write failure during loading",
    "files": 1,
    "add": 9,
    "del": 2
   },
   {
    "sha": "45f583dbe8",
    "subject": "wallet: Ignore TxnAbort failure in RunWithinTxn",
    "files": 1,
    "add": 2,
    "del": 1
   },
   {
    "sha": "09dcf742b3",
    "subject": "wallet: Handle db write failure in IncOrderPosNext",
    "files": 2,
    "add": 9,
    "del": 7
   },
   {
    "sha": "64e9ec4ec9",
    "subject": "wallet: Handle transaction write failures",
    "files": 1,
    "add": 20,
    "del": 6
   },
   {
    "sha": "706b8f31c3",
    "subject": "wallet, migration: Handle address book data write failures",
    "files": 1,
    "add": 22,
    "del": 5
   },
   {
    "sha": "3abfc9b9bd",
    "subject": "wallet, bench: Ignore db write failures",
    "files": 1,
    "add": 3,
    "del": 3
   },
   {
    "sha": "8783d5c293",
    "subject": "wallet: Mark WriteBestBlock [[nodiscard]] and handle write errors",
    "files": 3,
    "add": 20,
    "del": 9
   },
   {
    "sha": "d9a9c927a8",
    "subject": "walletdb: Mark all WalletBatch operations [[nodisard]]",
    "files": 1,
    "add": 38,
    "del": 38
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "c40a5e3ea7ff88da",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}