{
 "number": 33034,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/33034",
 "title": "wallet: Store transactions in a separate sqlite table",
 "author": "achow101",
 "author_association": "MEMBER",
 "created_at": "2025-07-21T22:52:54Z",
 "updated_at": "2026-09-17T10:09:29Z",
 "age_days": 422,
 "draft": true,
 "labels": [
  "Wallet",
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "7b514416425975d2b0dd19ab9f373061aebc4725",
 "head_ref": "wallet-sql-txs-table",
 "head_repo": "achow101/bitcoin",
 "head_history": [
  {
   "t": "2025-07-22T06:16:53Z",
   "sha": "3d61435013d277146817e7bdf09420727da9e5d4"
  },
  {
   "t": "2025-07-22T17:22:16Z",
   "sha": "80e1937a8b78aea66f5e9bc31e8cfc2690b1286b"
  },
  {
   "t": "2025-07-23T00:05:07Z",
   "sha": "18035d10c13fa5f7c7e2ef5b1df7c5d569ded0c7"
  },
  {
   "t": "2025-07-24T20:48:58Z",
   "sha": "678c2ea5f71ef960d558ae027871f91c4d9df826"
  },
  {
   "t": "2025-07-29T20:11:29Z",
   "sha": "3c31700205a1e29c18dfb91a9c5ef04e6fbf7a2e"
  },
  {
   "t": "2025-08-08T19:06:39Z",
   "sha": "2e43bd58999d050bfab7398b9dd29f21b520fa4a"
  },
  {
   "t": "2025-08-14T19:00:57Z",
   "sha": "acb388650193a34459c6dac17c1d98bf2ff56beb"
  },
  {
   "t": "2025-08-16T03:10:27Z",
   "sha": "c2efcd641d03e10e162e1c0e8337f2e2af871f87"
  },
  {
   "t": "2025-08-19T18:12:17Z",
   "sha": "b0e81e66e89778999af6d60c24b540b0c875307a"
  },
  {
   "t": "2025-09-23T23:19:10Z",
   "sha": "a25557a731b00a2d3fd9c0d8b01cf01799a7a006"
  },
  {
   "t": "2025-12-10T19:00:43Z",
   "sha": "de7f4b1339d1ea6d30a678e192941c9b5a144c88"
  },
  {
   "t": "2025-12-10T22:49:11Z",
   "sha": "90753fba4df7e88ee1b3c2564f63f29b1bfcb5c1"
  },
  {
   "t": "2025-12-22T22:06:03Z",
   "sha": "073cb10b16e190146012cda81af135f56adfe03d"
  },
  {
   "t": "2026-01-03T00:58:36Z",
   "sha": "e479564c7bddbf6bfa0b723c589bb472aa8d6adb"
  },
  {
   "t": "2026-01-03T01:24:27Z",
   "sha": "9e093b26ac3723a6143bc3aebd555c968e2dbf67"
  },
  {
   "t": "2026-01-19T18:51:23Z",
   "sha": "82470fc75bdf2ae91e82fbd1ab0d3c9aef17fc14"
  },
  {
   "t": "2026-01-19T20:38:11Z",
   "sha": "4923f6b927c227fdeeb67b93c3f937cbe88ee66a"
  },
  {
   "t": "2026-01-26T19:52:17Z",
   "sha": "57c1e3f5775c32660edff7d6ad5d40febea2f37d"
  },
  {
   "t": "2026-02-04T20:32:30Z",
   "sha": "e257bda19ff6c179b9c5cf68f48d9ed59918342b"
  },
  {
   "t": "2026-03-05T20:30:43Z",
   "sha": "2888c10b1a6627264c04fb8d1d83139eee34c15e"
  },
  {
   "t": "2026-03-14T01:34:55Z",
   "sha": "cecfc0c0ebda01f63d1983ce84a0be4a9f5224c2"
  },
  {
   "t": "2026-04-01T23:26:15Z",
   "sha": "44e3fe7e26e147044eb1a3278d511a686343a519"
  },
  {
   "t": "2026-04-02T18:40:31Z",
   "sha": "ff0cdc37803fa31092827ca35d2b21544ff10a85"
  },
  {
   "t": "2026-04-02T20:10:18Z",
   "sha": "95ba9f3dc7c8013ae5f13fd9350b9b1e24593c54"
  },
  {
   "t": "2026-04-02T20:45:33Z",
   "sha": "b400ccbffccda21001f3d1ef142ede87a65860a1"
  },
  {
   "t": "2026-04-02T20:55:36Z",
   "sha": "6e7777f0a8de3e3f39d677a384956f3ed489346d"
  },
  {
   "t": "2026-04-20T20:02:31Z",
   "sha": "67d182392b2497f0ed8e1fbf6adf6f886b81ef25"
  },
  {
   "t": "2026-05-19T21:37:05Z",
   "sha": "7d6de1124ecbcdad10c92c40e3b4b4ccef5058c5"
  },
  {
   "t": "2026-05-26T18:15:05Z",
   "sha": "bce6de84d46bcab0b7ff57dec9b052fb43522374"
  },
  {
   "t": "2026-05-29T19:14:47Z",
   "sha": "aa00219a570bb73533450fa03bf8ae8f26e9d8b4"
  },
  {
   "t": "2026-07-13T20:16:10Z",
   "sha": "6f6ffa19c5ef1e196c609414a0fe94976cb1137b"
  },
  {
   "t": "2026-07-14T00:25:32Z",
   "sha": "372e21d7faa06bfeeed0bb34f6647c01b6c49301"
  },
  {
   "t": "2026-09-14T23:40:46Z",
   "sha": "c23b14803cc3bdc4a48c7669fa93367888930e99"
  },
  {
   "t": "2026-09-15T02:00:43Z",
   "sha": "7b514416425975d2b0dd19ab9f373061aebc4725"
  }
 ],
 "additions": 1055,
 "deletions": 256,
 "changed_files": 15,
 "commit_count": 27,
 "size_bucket": "XL",
 "mergeable_state": "unstable",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "w0xlt",
      "url": "https://github.com/bitcoin/bitcoin/pull/33034#issuecomment-3110194893"
     },
     {
      "login": "rkrux",
      "url": "https://github.com/bitcoin/bitcoin/pull/33034#issuecomment-3384679811"
     }
    ]
   },
   "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": 35786,
     "title": "wallet: drop spent parents redundant cache invalidation and notification",
     "author": "furszy"
    },
    {
     "number": 35760,
     "title": "wallet: make corrupted transaction records fail wallet loading instead of forcing a rescan",
     "author": "achow101"
    },
    {
     "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": 34909,
     "title": "wallet, refactor: modularise wallet by extracting out legacy wallet migration",
     "author": "rkrux"
    },
    {
     "number": 33033,
     "title": "wallet, sqlite: Encapsulate SQLite statements in a RAII class",
     "author": "achow101"
    },
    {
     "number": 32895,
     "title": "wallet: Prepare for future upgrades by recording versions of last client to open and decrypt",
     "author": "achow101"
    },
    {
     "number": 27865,
     "title": "wallet: Track no-longer-spendable TXOs separately",
     "author": "achow101"
    }
   ]
  }
 },
 "acks_parsed": {
  "w0xlt": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2025-07-23T21:15:06Z",
   "stale": false
  },
  "rkrux": {
   "kind": "concept_ack",
   "hash": "a25557a",
   "t": "2025-10-09T08:14:48Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 0,
  "stale_ack": 0,
  "concept_ack": 2,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "rkrux",
   "w0xlt"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": true,
  "mergeable_state": "unstable",
  "last_author_activity": "2026-09-15T02:00:43Z",
  "last_reviewer_activity": "2025-10-09T08:14:48Z",
  "last_reviewer": "rkrux",
  "author_silent_days": 2,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [
   27865,
   32895,
   33033
  ],
  "depends_on": [
   32895
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 32895,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet: Prepare for future upgrades by recording versions of last client to open and decrypt"
   },
   {
    "number": 27865,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet: Track no-longer-spendable TXOs separately"
   },
   {
    "number": 33033,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "wallet, sqlite: Encapsulate SQLite statements in a RAII class"
   }
  ],
  "conflicts": [
   36126,
   36031,
   35998,
   35786,
   35760,
   35752,
   35716,
   34909,
   33033,
   32895,
   27865
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [],
 "body": "The wallet uses SQLite as a key-value store even though SQLite is a powerful relational database engine. This causes us numerous headaches due to the need to serialize multiple fields together, and since record read from the database during loading can come in any order.\n\nNotably, for transactions, if we were able to load them in the transaction order assigned by the wallet, PRs like #27865 would be a bit simpler and easier to reason about.\n\nThis PR makes it possible for us to do that by storing transactions in a separate table. This table has a column for each field in `CWalletTx`, which lets us use a SQL statement like `SELECT * FROM transactions ORDER BY pos` which guarantees us the order in which the transactions are loaded into the wallet.\n\nThe eventual goal is to use SQLite as a relational database with tables that store our keys and other metadata that can reference each other so that the wallet doesn't just use the database as a storage mechanism. But that is still a work in progress.\n\nThis PR makes use of the last client opened features introduced in #32895 to determine when the old record style needs to be upgraded to the new transactions table. This should give us sufficient upgrade-downgrade-upgrade handling to not lose any funds.\n\nDepends on #32895, #33033",
 "commits": [
  {
   "sha": "42592047377cd8c3efc58c787f82d9e194765165",
   "date": "2026-09-14T22:02:48Z",
   "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": "f8f6ee12bbfc02a6ae836cb440c006bc359667d8",
   "date": "2026-09-14T22:02:48Z",
   "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": "b097bfcc2b275b86188569427447f00aac59480d",
   "date": "2026-09-14T22:02:48Z",
   "message": "wallet: Record the supported features of the last client to decrypt a wallet"
  },
  {
   "sha": "7db4b68715d40e22135d59eccbd4326eb3401f4e",
   "date": "2026-09-14T22:02:48Z",
   "message": "wallet: Set last opened and decrypted features during migration\n\nSet these flags to avoid the automatic upgrade after migrating."
  },
  {
   "sha": "1373a7a45e221f53aff2f73082b7b601dfe3450c",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Add SQLiteStatement RAII class\n\nThis class will be used to encapsulate a sqlite3_stmt"
  },
  {
   "sha": "8ed2743d9a1713f9d683ac00b75ff965d2293451",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Make Column template function"
  },
  {
   "sha": "baf406cd80e77e00185eaad276e4063c56dc3acc",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Refactor ReadPragmaInteger to use SQLiteStatement"
  },
  {
   "sha": "4c3b8bc0e6c844e8030e8226ed33c9abf0813c68",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Use SQLiteStatement in PRAGMA integrity_check"
  },
  {
   "sha": "ca367aec6258fe79f80ee5d7740651b11cc355b6",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Use SQLiteStatement in check_main_stmt"
  },
  {
   "sha": "678a72ff0f8eb42ed0186f9cf4e2769145b3e7fd",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Have SQLiteCursor store SQLiteStatement"
  },
  {
   "sha": "81dc6d9ece16eed79aa578ebe7919ca4409882bd",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Replace remaining sqlite3_stmt usage with SQLiteStatement"
  },
  {
   "sha": "43fddc7e9cdb25934a8f07b3c88bd008df9fd3d5",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Refactor common writing code from WriteKey and ExecStatement\n\nWriteKey and ExecStatement use the same code for the actual execution of\nthe statement. This is refactored into a separate function, also called\nExecStatement, and the original ExecStatement renamed to\nExecEraseStatement as it is only used by the erase functions."
  },
  {
   "sha": "ed06764d32ae18c0a6483452409c5347886892da",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Inline BindBlobToStatement and SpanFromBlob"
  },
  {
   "sha": "d6dccfc61ce1e851fa3039b32d7c80b0ac31f0c1",
   "date": "2026-09-14T22:02:48Z",
   "message": "sqlite: Construct SQLiteStatements when needed\n\nInstead of constructing all SQLiteStatements when SQLiteBatch is\nconstructed, construct them once when they are needed before each read,\nwrite, or erase operation. Once constructed, the statement will persist\nfor the lifetime of the SQLiteBatch to be reused across multiple\nstatements."
  },
  {
   "sha": "453d5f4f3f14df46897eee5532b0449c4fdf3ea8",
   "date": "2026-09-14T22:02:48Z",
   "message": "util: Add span constructor to (W)Txid"
  },
  {
   "sha": "ffcabbb29b962cfac0af65fa535325ca527472ab",
   "date": "2026-09-14T22:14:03Z",
   "message": "sqlite: Make SQLiteStatement::Bind a template function\n\nWe will need to bind data types other than blob"
  },
  {
   "sha": "e79680694367c04fb3b0400de153362fa7ed2716",
   "date": "2026-09-14T22:20:00Z",
   "message": "sqlite: Make SQLiteDatabase::Column an std::optional\n\nTo handle columns containing NULL values, Column needs to return some\nvalue representing NULL, so make it a std::optional."
  },
  {
   "sha": "f3b2cf0619ef76058b9a96e7aec8d56243c1d01c",
   "date": "2026-09-14T22:20:01Z",
   "message": "sqlite: Add additional blob types to SQLiteStatement::Column\n\nNot all blob data types fit in ColumnBlob, so we need additional\ntemplate type requirements to match those."
  },
  {
   "sha": "7b7c7a5c6b774c31c69b06876b4cdd42371bd72c",
   "date": "2026-09-14T22:20:05Z",
   "message": "wallet: Add Un/Serialize to TxState structs"
  },
  {
   "sha": "dd873bc815e50447236ec622d9822b5fcab68575",
   "date": "2026-09-14T22:20:11Z",
   "message": "wallet: Add new serialization format functions for TxState"
  },
  {
   "sha": "4702b9c75aa75562296d3c5ebe866a9890696e98",
   "date": "2026-09-14T22:20:14Z",
   "message": "sqlite: Create a 'transactions' table if it does not exist"
  },
  {
   "sha": "ade4120d1d59b6b3fb91ef33c5ff0e1863d58430",
   "date": "2026-09-14T22:27:03Z",
   "message": "sqlite: Add functions and statements for transactions table"
  },
  {
   "sha": "a1b96118c801e2517b9dad94fe306f40230df4d0",
   "date": "2026-09-14T22:27:36Z",
   "message": "sqlite: Iterate the transactions table with SQLiteCursor"
  },
  {
   "sha": "c43aee09fd17446e07a7b9f772dec71723cd0bf5",
   "date": "2026-09-15T02:00:29Z",
   "message": "walletdb: Add functions to modify transactions table"
  },
  {
   "sha": "72ffe49003e8ebd300185799fe2d7452c0be19d6",
   "date": "2026-09-15T02:00:31Z",
   "message": "wallet: Also write to the new transactions table"
  },
  {
   "sha": "cc31e068db10f5f162ebad69d0d373c5376d8361",
   "date": "2026-09-15T02:00:31Z",
   "message": "wallet: Perform automatic upgrade to using the transactions table"
  },
  {
   "sha": "7b514416425975d2b0dd19ab9f373061aebc4725",
   "date": "2026-09-15T02:00:31Z",
   "message": "walletdb: Load from transactions table and use original tx for upgrade\n\nWhen loading a wallet, always load from the transactions table, except\nwhen loading a legacy wallet for migration.\n\nThe original 'tx' records are only used to upgrade to the transactions\ntable if an upgraded is necessary."
  }
 ],
 "timeline": [
  {
   "t": "2025-07-22T06:16:53Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3d61435013d277146817e7bdf09420727da9e5d4"
  },
  {
   "t": "2025-07-22T17:22:16Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "80e1937a8b78aea66f5e9bc31e8cfc2690b1286b"
  },
  {
   "t": "2025-07-23T00:05:07Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "18035d10c13fa5f7c7e2ef5b1df7c5d569ded0c7"
  },
  {
   "t": "2025-07-23T21:15:06Z",
   "kind": "comment",
   "who": "w0xlt",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK"
  },
  {
   "t": "2025-07-24T20:48:58Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "678c2ea5f71ef960d558ae027871f91c4d9df826"
  },
  {
   "t": "2025-07-29T20:11:29Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "3c31700205a1e29c18dfb91a9c5ef04e6fbf7a2e"
  },
  {
   "t": "2025-08-08T19:06:39Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "2e43bd58999d050bfab7398b9dd29f21b520fa4a"
  },
  {
   "t": "2025-08-14T19:00:57Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "acb388650193a34459c6dac17c1d98bf2ff56beb"
  },
  {
   "t": "2025-08-16T03:10:27Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c2efcd641d03e10e162e1c0e8337f2e2af871f87"
  },
  {
   "t": "2025-08-19T18:12:17Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b0e81e66e89778999af6d60c24b540b0c875307a"
  },
  {
   "t": "2025-09-23T23:19:10Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "a25557a731b00a2d3fd9c0d8b01cf01799a7a006"
  },
  {
   "t": "2025-10-09T08:14:48Z",
   "kind": "comment",
   "who": "rkrux",
   "assoc": "MEMBER",
   "text": "Concept ACK a25557a\n\nI have gone through the PR description and have skimmed over the commit messages as of now. At the outset, I find myself agreeing with the intent here. I am not aware of a strong enough reason to keep using SQLite as a KVS for all purposes, using its relational database properties for wallet transactions seem like a good start.\nI did notice some serialisation specifics of transaction properties in a couple previous PRs that I didn't fully understand why were required; it could be a lot cleaner and explicit in implementation if using SQLite as an RDBMS can avoid the need for doing those specific serialisations."
  },
  {
   "t": "2025-12-10T19:00:43Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "de7f4b1339d1ea6d30a678e192941c9b5a144c88"
  },
  {
   "t": "2025-12-10T22:49:11Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "90753fba4df7e88ee1b3c2564f63f29b1bfcb5c1"
  },
  {
   "t": "2025-12-22T22:06:03Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "073cb10b16e190146012cda81af135f56adfe03d"
  },
  {
   "t": "2026-01-03T00:58:36Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "e479564c7bddbf6bfa0b723c589bb472aa8d6adb"
  },
  {
   "t": "2026-01-03T01:24:27Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "9e093b26ac3723a6143bc3aebd555c968e2dbf67"
  },
  {
   "t": "2026-01-19T18:51:23Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "82470fc75bdf2ae91e82fbd1ab0d3c9aef17fc14"
  },
  {
   "t": "2026-01-19T20:38:11Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "4923f6b927c227fdeeb67b93c3f937cbe88ee66a"
  },
  {
   "t": "2026-01-26T19:52:17Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "57c1e3f5775c32660edff7d6ad5d40febea2f37d"
  },
  {
   "t": "2026-02-04T20:32:30Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "e257bda19ff6c179b9c5cf68f48d9ed59918342b"
  },
  {
   "t": "2026-03-05T20:30:43Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "2888c10b1a6627264c04fb8d1d83139eee34c15e"
  },
  {
   "t": "2026-03-14T01:34:55Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "cecfc0c0ebda01f63d1983ce84a0be4a9f5224c2"
  },
  {
   "t": "2026-04-01T23:26:15Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "44e3fe7e26e147044eb1a3278d511a686343a519"
  },
  {
   "t": "2026-04-02T18:40:31Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "ff0cdc37803fa31092827ca35d2b21544ff10a85"
  },
  {
   "t": "2026-04-02T20:10:18Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "95ba9f3dc7c8013ae5f13fd9350b9b1e24593c54"
  },
  {
   "t": "2026-04-02T20:45:33Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "b400ccbffccda21001f3d1ef142ede87a65860a1"
  },
  {
   "t": "2026-04-02T20:55:36Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "6e7777f0a8de3e3f39d677a384956f3ed489346d"
  },
  {
   "t": "2026-04-20T20:02:31Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "67d182392b2497f0ed8e1fbf6adf6f886b81ef25"
  },
  {
   "t": "2026-05-19T21:37:05Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7d6de1124ecbcdad10c92c40e3b4b4ccef5058c5"
  },
  {
   "t": "2026-05-26T18:15:05Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "bce6de84d46bcab0b7ff57dec9b052fb43522374"
  },
  {
   "t": "2026-05-29T19:14:47Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "aa00219a570bb73533450fa03bf8ae8f26e9d8b4"
  },
  {
   "t": "2026-07-13T20:16:10Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "6f6ffa19c5ef1e196c609414a0fe94976cb1137b"
  },
  {
   "t": "2026-07-14T00:25:32Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "372e21d7faa06bfeeed0bb34f6647c01b6c49301"
  },
  {
   "t": "2026-09-14T23:40:46Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "c23b14803cc3bdc4a48c7669fa93367888930e99"
  },
  {
   "t": "2026-09-15T02:00:43Z",
   "kind": "force_push",
   "who": "achow101",
   "commit": "7b514416425975d2b0dd19ab9f373061aebc4725"
  }
 ],
 "labels_log": [
  {
   "t": "2025-07-21T22:52:57Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-22T00:40:31Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-22T20:17:11Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-29T18:20:59Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-07-29T20:46:23Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-08T17:41:26Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-08T19:49:28Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-13T20:31:09Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-14T19:51:48Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-16T00:36:30Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-08-19T18:16:39Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-23T20:44:15Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-09-24T01:13:28Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-02T14:55:57Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-10T19:52:32Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-10T20:15:49Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-11T00:59:05Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-17T19:28:13Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-22T23:41:29Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2025-12-23T01:48:16Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-03T02:49:25Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T12:10:06Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T19:48:51Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-01-19T20:00:44Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-04T20:31:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-02-04T22:48:13Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-02T21:29:06Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-05T22:00:49Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-11T11:27:24Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-03-14T02:06:18Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-19T10:09:57Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-20T21:03:09Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-04-20T22:06:52Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-02T08:49:24Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-19T23:12:17Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T01:49:19Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T19:46:25Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-26T20:29:19Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-27T00:16:28Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-28T22:44:35Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-05-29T20:47:45Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-10T21:15:30Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-07-14T00:47:59Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-08-04T22:58:55Z",
   "action": "labeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T00:50:59Z",
   "action": "unlabeled",
   "label": "Needs rebase",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-15T01:18:01Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 5183,
 "text_tokens_estimate": 1295,
 "changed_paths": [
  "src/primitives/transaction_identifier.h",
  "src/wallet/db.h",
  "src/wallet/export.cpp",
  "src/wallet/migrate.h",
  "src/wallet/sqlite.cpp",
  "src/wallet/sqlite.h",
  "src/wallet/transaction.cpp",
  "src/wallet/transaction.h",
  "src/wallet/wallet.cpp",
  "src/wallet/wallet.h",
  "src/wallet/walletdb.cpp",
  "src/wallet/walletdb.h",
  "src/wallet/walletutil.h",
  "test/functional/wallet_backwards_compatibility.py",
  "test/functional/wallet_createwallet.py"
 ],
 "files": [
  {
   "path": "src/primitives/transaction_identifier.h",
   "add": 3,
   "del": 0
  },
  {
   "path": "src/wallet/db.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/export.cpp",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/migrate.h",
   "add": 1,
   "del": 0
  },
  {
   "path": "src/wallet/sqlite.cpp",
   "add": 476,
   "del": 195
  },
  {
   "path": "src/wallet/sqlite.h",
   "add": 86,
   "del": 15
  },
  {
   "path": "src/wallet/transaction.cpp",
   "add": 66,
   "del": 0
  },
  {
   "path": "src/wallet/transaction.h",
   "add": 87,
   "del": 0
  },
  {
   "path": "src/wallet/wallet.cpp",
   "add": 53,
   "del": 4
  },
  {
   "path": "src/wallet/wallet.h",
   "add": 6,
   "del": 0
  },
  {
   "path": "src/wallet/walletdb.cpp",
   "add": 224,
   "del": 30
  },
  {
   "path": "src/wallet/walletdb.h",
   "add": 18,
   "del": 8
  },
  {
   "path": "src/wallet/walletutil.h",
   "add": 23,
   "del": 0
  },
  {
   "path": "test/functional/wallet_backwards_compatibility.py",
   "add": 8,
   "del": 2
  },
  {
   "path": "test/functional/wallet_createwallet.py",
   "add": 2,
   "del": 2
  }
 ],
 "test_lines": 14,
 "git": {
  "head": "7b514416425975d2b0dd19ab9f373061aebc4725",
  "head_matches_backup": true,
  "base": "cff00c547e7460fa1ec4aa1e38217793593d4263",
  "commits": [
   {
    "sha": "4259204737",
    "subject": "walletdb: Decouple last client record from CLIENT_VERSION",
    "files": 5,
    "add": 31,
    "del": 16
   },
   {
    "sha": "f8f6ee12bb",
    "subject": "wallet: Introduce LastClientFeatures flags and LAST_OPENED_FEATURES record",
    "files": 5,
    "add": 45,
    "del": 5
   },
   {
    "sha": "b097bfcc2b",
    "subject": "wallet: Record the supported features of the last client to decrypt a wallet",
    "files": 4,
    "add": 35,
    "del": 0
   },
   {
    "sha": "7db4b68715",
    "subject": "wallet: Set last opened and decrypted features during migration",
    "files": 1,
    "add": 7,
    "del": 0
   },
   {
    "sha": "1373a7a45e",
    "subject": "sqlite: Add SQLiteStatement RAII class",
    "files": 1,
    "add": 49,
    "del": 0
   },
   {
    "sha": "8ed2743d9a",
    "subject": "sqlite: Make Column template function",
    "files": 1,
    "add": 19,
    "del": 2
   },
   {
    "sha": "baf406cd80",
    "subject": "sqlite: Refactor ReadPragmaInteger to use SQLiteStatement",
    "files": 1,
    "add": 13,
    "del": 17
   },
   {
    "sha": "4c3b8bc0e6",
    "subject": "sqlite: Use SQLiteStatement in PRAGMA integrity_check",
    "files": 1,
    "add": 29,
    "del": 29
   },
   {
    "sha": "ca367aec62",
    "subject": "sqlite: Use SQLiteStatement in check_main_stmt",
    "files": 1,
    "add": 3,
    "del": 10
   },
   {
    "sha": "678a72ff0f",
    "subject": "sqlite: Have SQLiteCursor store SQLiteStatement",
    "files": 2,
    "add": 26,
    "del": 41
   },
   {
    "sha": "81dc6d9ece",
    "subject": "sqlite: Replace remaining sqlite3_stmt usage with SQLiteStatement",
    "files": 2,
    "add": 43,
    "del": 67
   },
   {
    "sha": "43fddc7e9c",
    "subject": "sqlite: Refactor common writing code from WriteKey and ExecStatement",
    "files": 2,
    "add": 30,
    "del": 36
   },
   {
    "sha": "ed06764d32",
    "subject": "sqlite: Inline BindBlobToStatement and SpanFromBlob",
    "files": 1,
    "add": 17,
    "del": 29
   },
   {
    "sha": "d6dccfc61c",
    "subject": "sqlite: Construct SQLiteStatements when needed",
    "files": 2,
    "add": 34,
    "del": 14
   },
   {
    "sha": "453d5f4f3f",
    "subject": "util: Add span constructor to (W)Txid",
    "files": 1,
    "add": 3,
    "del": 0
   },
   {
    "sha": "ffcabbb29b",
    "subject": "sqlite: Make SQLiteStatement::Bind a template function",
    "files": 1,
    "add": 24,
    "del": 6
   },
   {
    "sha": "e796806943",
    "subject": "sqlite: Make SQLiteDatabase::Column an std::optional",
    "files": 1,
    "add": 10,
    "del": 5
   },
   {
    "sha": "f3b2cf0619",
    "subject": "sqlite: Add additional blob types to SQLiteStatement::Column",
    "files": 1,
    "add": 16,
    "del": 0
   },
   {
    "sha": "7b7c7a5c6b",
    "subject": "wallet: Add Un/Serialize to TxState structs",
    "files": 1,
    "add": 39,
    "del": 0
   },
   {
    "sha": "dd873bc815",
    "subject": "wallet: Add new serialization format functions for TxState",
    "files": 2,
    "add": 74,
    "del": 2
   },
   {
    "sha": "4702b9c75a",
    "subject": "sqlite: Create a 'transactions' table if it does not exist",
    "files": 4,
    "add": 55,
    "del": 0
   },
   {
    "sha": "ade4120d1d",
    "subject": "sqlite: Add functions and statements for transactions table",
    "files": 2,
    "add": 169,
    "del": 0
   },
   {
    "sha": "a1b96118c8",
    "subject": "sqlite: Iterate the transactions table with SQLiteCursor",
    "files": 4,
    "add": 78,
    "del": 2
   },
   {
    "sha": "c43aee09fd",
    "subject": "walletdb: Add functions to modify transactions table",
    "files": 4,
    "add": 76,
    "del": 0
   },
   {
    "sha": "72ffe49003",
    "subject": "wallet: Also write to the new transactions table",
    "files": 3,
    "add": 25,
    "del": 1
   },
   {
    "sha": "cc31e068db",
    "subject": "wallet: Perform automatic upgrade to using the transactions table",
    "files": 4,
    "add": 25,
    "del": 5
   },
   {
    "sha": "7b51441642",
    "subject": "walletdb: Load from transactions table and use original tx for upgrade",
    "files": 2,
    "add": 142,
    "del": 31
   }
  ],
  "patch_truncated": true
 },
 "input_hash": "c8916793e9bf5e2b",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}