wallet: Store transactions in a separate sqlite table #33034

pull achow101 wants to merge 36 commits into bitcoin:master from achow101:wallet-sql-txs-table changing 24 files +1176 −388
  1. achow101 commented at 10:52 PM on July 21, 2025: member

    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.

    Notably, 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.

    This 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.

    The 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.

    This 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.

    Depends on #32763, #32895, #33033

  2. DrahtBot added the label Wallet on Jul 21, 2025
  3. DrahtBot commented at 10:52 PM on July 21, 2025: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33034.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    Concept ACK w0xlt, rkrux

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #bitcoin-core/gui/872 (Menu action to export a watchonly wallet by achow101)
    • #35381 (wallet, test: optinrbf deprecation followups by rkrux)
    • #35302 (Silent Payments: Sending (take 2) by Eunovo)
    • #34909 (wallet, refactor: modularise wallet by extracting out legacy wallet migration by rkrux)
    • #34502 (wallet: remove most asserts of WALLET_FLAG_DESCRIPTORS flag by rkrux)
    • #33033 (wallet, sqlite: Encapsulate SQLite statements in a RAII class by achow101)
    • #32895 (wallet: Prepare for future upgrades by recording versions of last client to open and decrypt by achow101)
    • #32763 (wallet: Replace CWalletTx::mapValue and vOrderForm with explicit class members by achow101)
    • #32489 (wallet: Add exportwatchonlywallet RPC to export a watchonly version of a wallet by achow101)
    • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
    • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

    LLM Linter (✨ experimental)

    Possible typos and grammar issues:

    • // InMempool needs to be written as InActive(abandoned=false) -> Inactive [“InActive” appears to be a misspelling of the transaction state name.]

    <sup>2026-05-29 19:15:13</sup>

  4. DrahtBot added the label CI failed on Jul 22, 2025
  5. DrahtBot commented at 12:40 AM on July 22, 2025: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/runs/46429203587</sub> <sub>LLM reason (✨ experimental): The CI failure is caused by Python lint errors due to unresolved 'self' references in test files.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  6. achow101 force-pushed on Jul 22, 2025
  7. achow101 force-pushed on Jul 22, 2025
  8. DrahtBot removed the label CI failed on Jul 22, 2025
  9. achow101 force-pushed on Jul 23, 2025
  10. w0xlt commented at 9:15 PM on July 23, 2025: contributor

    Concept ACK

  11. achow101 force-pushed on Jul 24, 2025
  12. DrahtBot added the label Needs rebase on Jul 29, 2025
  13. achow101 force-pushed on Jul 29, 2025
  14. DrahtBot removed the label Needs rebase on Jul 29, 2025
  15. DrahtBot added the label Needs rebase on Aug 8, 2025
  16. achow101 force-pushed on Aug 8, 2025
  17. DrahtBot removed the label Needs rebase on Aug 8, 2025
  18. DrahtBot added the label Needs rebase on Aug 13, 2025
  19. achow101 force-pushed on Aug 14, 2025
  20. DrahtBot removed the label Needs rebase on Aug 14, 2025
  21. DrahtBot added the label Needs rebase on Aug 16, 2025
  22. achow101 force-pushed on Aug 16, 2025
  23. achow101 force-pushed on Aug 19, 2025
  24. DrahtBot removed the label Needs rebase on Aug 19, 2025
  25. DrahtBot added the label Needs rebase on Sep 23, 2025
  26. achow101 force-pushed on Sep 23, 2025
  27. DrahtBot removed the label Needs rebase on Sep 24, 2025
  28. rkrux commented at 8:14 AM on October 9, 2025: contributor

    Concept ACK a25557a

    I 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. I 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.

  29. DrahtBot added the label Needs rebase on Dec 2, 2025
  30. achow101 force-pushed on Dec 10, 2025
  31. DrahtBot removed the label Needs rebase on Dec 10, 2025
  32. DrahtBot added the label CI failed on Dec 10, 2025
  33. DrahtBot commented at 8:15 PM on December 10, 2025: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task test max 6 ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/20110042688/job/57704599161</sub> <sub>LLM reason (✨ experimental): CMake build failed with exit code 2 during project compilation (gmake reported an error).</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  34. achow101 force-pushed on Dec 10, 2025
  35. DrahtBot removed the label CI failed on Dec 11, 2025
  36. DrahtBot added the label Needs rebase on Dec 17, 2025
  37. achow101 force-pushed on Dec 22, 2025
  38. DrahtBot removed the label Needs rebase on Dec 22, 2025
  39. DrahtBot added the label CI failed on Dec 23, 2025
  40. achow101 force-pushed on Jan 3, 2026
  41. achow101 force-pushed on Jan 3, 2026
  42. DrahtBot removed the label CI failed on Jan 3, 2026
  43. DrahtBot added the label Needs rebase on Jan 19, 2026
  44. achow101 force-pushed on Jan 19, 2026
  45. DrahtBot added the label CI failed on Jan 19, 2026
  46. DrahtBot commented at 7:49 PM on January 19, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task test max 6 ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/21148471093/job/60819287525</sub> <sub>LLM reason (✨ experimental): Build failed: the CI cmake/make run exited with non-zero status (gmake: all target) after linking bitcoinkernel, indicating a generic build error.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  47. DrahtBot removed the label Needs rebase on Jan 19, 2026
  48. achow101 force-pushed on Jan 19, 2026
  49. achow101 force-pushed on Jan 26, 2026
  50. DrahtBot added the label Needs rebase on Feb 4, 2026
  51. achow101 force-pushed on Feb 4, 2026
  52. DrahtBot removed the label Needs rebase on Feb 4, 2026
  53. DrahtBot added the label Needs rebase on Mar 2, 2026
  54. achow101 force-pushed on Mar 5, 2026
  55. DrahtBot removed the label Needs rebase on Mar 5, 2026
  56. DrahtBot added the label Needs rebase on Mar 11, 2026
  57. achow101 force-pushed on Mar 14, 2026
  58. DrahtBot removed the label Needs rebase on Mar 14, 2026
  59. achow101 force-pushed on Apr 1, 2026
  60. achow101 force-pushed on Apr 2, 2026
  61. achow101 force-pushed on Apr 2, 2026
  62. achow101 force-pushed on Apr 2, 2026
  63. achow101 force-pushed on Apr 2, 2026
  64. DrahtBot added the label Needs rebase on Apr 19, 2026
  65. achow101 force-pushed on Apr 20, 2026
  66. DrahtBot removed the label Needs rebase on Apr 20, 2026
  67. DrahtBot removed the label CI failed on Apr 20, 2026
  68. sedited referenced this in commit db98e357d3 on May 2, 2026
  69. DrahtBot added the label Needs rebase on May 2, 2026
  70. achow101 force-pushed on May 19, 2026
  71. DrahtBot removed the label Needs rebase on May 19, 2026
  72. DrahtBot added the label Needs rebase on May 26, 2026
  73. achow101 force-pushed on May 26, 2026
  74. DrahtBot removed the label Needs rebase on May 26, 2026
  75. DrahtBot added the label CI failed on May 26, 2026
  76. DrahtBot removed the label CI failed on May 27, 2026
  77. DrahtBot added the label Needs rebase on May 28, 2026
  78. DrahtBot commented at 10:44 PM on May 28, 2026: contributor

    <!--cf906140f33d8803c4a75a2196329ecb-->

    🐙 This pull request conflicts with the target branch and needs rebase.

  79. wallet: Pass replaces_txid to CommitTransaction outside of mapValue
    Instead of updating mapValue with the "replaces_txid" value and passing
    the updated mapValue to CommitTransaction, pass the replaces_txid
    directly to CommitTransaction which updates mapValue as necessary.
    
    This is prepration for removing mapValue.
    b3dce17d02
  80. wallet: Pass comment and comment_to to CommitTransaction
    Instead of passing these by setting them in mapValue, pass them directly
    to CommitTransaction.
    
    This is preparation for removing mapValue.
    bc43765841
  81. wallet: Drop mapValue from CommitTransaction
    The values previously passed in mapValue are now parameters to
    CommitTransaction so there is no need for mapValue to be passed.
    77ea833c50
  82. wallet: Make CWalletTx "from" and "message" member variables
    Instead of storing "from" and "message" inside of mapValue, store these
    explicitly as members of CWalletTx.
    bd2873722c
  83. wallet: Make CWalletTx "comment" and "to" member variables
    Instead of storing "comment" and "to" inside of mapValue, store these
    expliclty as members of CWalletTx.
    7367002f72
  84. wallet: Make CWalletTx "replaces_txid" and "replaced_by_txid" member variables
    Instead of storing "replaces_txid" and "replaced_by_txid" as strings inside of
    mapValue, store these expliclty as members of CWalletTx.
    e4b8adc5e8
  85. wallet: Drop mapValue from CWalletTx
    It doesn't make sense to be storing relevant metadata variables inside
    of a string map in CWalletTx. All of the fields have been pulled out
    into separate members, so there is no need for mapValue to stick around.
    2aa356a62a
  86. wallet: Drop vOrderForm from CommitTransaction
    This parameter is only used to pass in the "Messages" from the GUI.
    Instead of making it opaque by putting those into vOrderForm, use a
    specific dedicated parameter for providing the messages.
    5f91350ee3
  87. wallet: Replace CWalletTx's vOrderForm with specific fields
    vOrderForm contained 2 kinds of strings: BIP 21 messages, and BIP 70
    Payment Requests. Instead of having both inside of a single vOrderForm
    field that is opaque, split them into separate std::vector<std::string>
    to contain this metadata.
    de2cb8c2db
  88. walletdb: Decouple last client record from CLIENT_VERSION
    Instead of tying the last client record with the node version, introduce
    a separate wallet client version that will be increased as necessary
    when new automatic upgrades are introduced.
    dde8b25c22
  89. wallet: Introduce LastClientFeatures flags and LAST_OPENED_FEATURES record
    LastClientFeatures are feature flags indicating what automatic upgrade
    features supported by the last client that opened the wallet.
    
    The LAST_OPENED_FEATURES record stores these flags and must be
    set to match the exact features supported by the client that opens a
    wallet.
    c7f66e0d77
  90. wallet: Record the supported features of the last client to decrypt a wallet 42377223cc
  91. wallet: Set last opened and decrypted features during migration
    Set these flags to avoid the automatic upgrade after migrating.
    1b3812707b
  92. sqlite: Add SQLiteStatement RAII class
    This class will be used to encapsulate a sqlite3_stmt
    86b180a73e
  93. sqlite: Make Column template function 291070dad4
  94. sqlite: Refactor ReadPragmaInteger to use SQLiteStatement 8efdfd7ab8
  95. sqlite: Use SQLiteStatement in PRAGMA integrity_check dae205dc1c
  96. sqlite: Use SQLiteStatement in check_main_stmt 6a1ae3c708
  97. sqlite: Have SQLiteCursor store SQLiteStatement fb70de7311
  98. sqlite: Replace remaining sqlite3_stmt usage with SQLiteStatement 8835b8de3a
  99. sqlite: Refactor common writing code from WriteKey and ExecStatement
    WriteKey and ExecStatement use the same code for the actual execution of
    the statement. This is refactored into a separate function, also called
    ExecStatement, and the original ExecStatement renamed to
    ExecEraseStatement as it is only used by the erase functions.
    7836fa7107
  100. sqlite: Inline BindBlobToStatement and SpanFromBlob a87b95c6d9
  101. sqlite: Construct SQLiteStatements when needed
    Instead of constructing all SQLiteStatements when SQLiteBatch is
    constructed, construct them once when they are needed before each read,
    write, or erase operation. Once constructed, the statement will persist
    for the lifetime of the SQLiteBatch to be reused across multiple
    statements.
    9183e10e2b
  102. util: Add span constructor to (W)Txid 06e1eb94ea
  103. sqlite: Make SQLiteStatement::Bind a template function
    We will need to bind data types other than blob
    1d70ab83fa
  104. sqlite: Make SQLiteDatabase::Column an std::optional
    To handle columns containing NULL values, Column needs to return some
    value representing NULL, so make it a std::optional.
    92d5b0611b
  105. sqlite: Add additional blob types to SQLiteStatement::Column
    Not all blob data types fit in ColumnBlob, so we need additional
    template type requirements to match those.
    f45af415a9
  106. wallet: Add Un/Serialize to TxState structs 45f333890d
  107. wallet: Add new serialization format functions for TxState 0ee6abb579
  108. sqlite: Create a 'transactions' table if it does not exist 7a9ef4e84e
  109. sqlite: Add functions and statements for transactions table b55269a394
  110. sqlite: Iterate the transactions table with SQLiteCursor 70230035c0
  111. walletdb: Add functions to modify transactions table ee889415a4
  112. wallet: Also write to the new transactions table 7751a225bb
  113. wallet: Perform automatic upgrade to using the transactions table 39b8b16e3e
  114. walletdb: Load from transactions table and use original tx for upgrade
    When loading a wallet, always load from the transactions table, except
    when loading a legacy wallet for migration.
    
    The original 'tx' records are only used to upgrade to the transactions
    table if an upgraded is necessary.
    aa00219a57
  115. achow101 force-pushed on May 29, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-29 20:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me