{
 "number": 35492,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/35492",
 "title": "wallet: fail dump on incomplete writes",
 "author": "OSINTv96",
 "author_association": "FIRST_TIME_CONTRIBUTOR",
 "created_at": "2026-06-09T06:32:16Z",
 "updated_at": "2026-09-09T20:17:57Z",
 "age_days": 100,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "385a001f66ad23b3963d5c0acf960b94359c4e21",
 "head_ref": "codex/wallet-dump-write-failure",
 "head_repo": "OSINTv96/bitcoin",
 "head_history": [
  {
   "t": "2026-06-09T07:27:18Z",
   "sha": "6f9c782eead053019b1f196e6408d675e6286545"
  },
  {
   "t": "2026-06-10T09:16:40Z",
   "sha": "bb2a207d08782b93f77c003287d9b6bf4ea17fa1"
  }
 ],
 "additions": 73,
 "deletions": 43,
 "changed_files": 2,
 "commit_count": 2,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "pablomartin4btc",
      "url": "https://github.com/bitcoin/bitcoin/pull/35492#pullrequestreview-5149616832"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {
  "pablomartin4btc": {
   "kind": "ack",
   "hash": "385a001f66ad23b3963d5c0acf960b94359c4e21",
   "t": "2026-09-09T04:07:14Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 0,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 0,
  "changes_requested": 0,
  "distinct_reviewers": [
   "maflcko",
   "pablomartin4btc",
   "sedited"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-07-24T10:20:03Z",
  "last_reviewer_activity": "2026-09-09T20:17:57Z",
  "last_reviewer": "pablomartin4btc",
  "author_silent_days": 55,
  "waiting_on_author_days": 7,
  "days_since_update": 7
 },
 "refs": {
  "mentioned": [
   36210
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36210,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "streams: Include the OS error when AutoFile I/O fails"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/dump.cpp",
  "test/functional/tool_wallet.py"
 ],
 "body": "This makes `bitcoin-wallet dump` fail if writing the dump file does not complete successfully.\n\nPreviously, `DumpWallet()` only checked whether the dump file could be opened. If later writes or close failed, the tool could still return success and print the private-key warning, leaving a truncated dump file behind.\n\nThe change adds write/close checks while dumping wallet records. If writing fails, the partial dump file is removed and `bitcoin-wallet` returns an error.\n\nThe functional test now covers a forced incomplete dump on POSIX systems by limiting the dump process file size. It checks that:\n\n- stdout stays empty;\n- the command exits with failure;\n- the write error is reported;\n- the partial dump file is removed.\n\nTested with:\n\n```bash\ncmake -S /tmp/bitcoin-core-wallet-dump-pr -B /tmp/bitcoin-core-wallet-dump-pr-build -DBUILD_TESTS=OFF -DBUILD_BENCH=OFF -DBUILD_GUI=OFF -DENABLE_WALLET=ON -DBUILD_WALLET_TOOL=ON -DENABLE_IPC=OFF -DWITH_ZMQ=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo\ncmake --build /tmp/bitcoin-core-wallet-dump-pr-build --target bitcoind bitcoin-wallet -j\"$(sysctl -n hw.ncpu)\"\npython3 test/functional/tool_wallet.py --configfile=/tmp/bitcoin-core-wallet-dump-pr-build/test/config.ini\n```",
 "commits": [
  {
   "sha": "bb2a207d08782b93f77c003287d9b6bf4ea17fa1",
   "date": "2026-06-10T09:16:38Z",
   "message": "wallet: fail dump on incomplete writes"
  },
  {
   "sha": "385a001f66ad23b3963d5c0acf960b94359c4e21",
   "date": "2026-07-24T10:10:56Z",
   "message": "wallet: commit dump output before success\n\nUse AutoFile for dump writes, require Commit() and fclose() to succeed, and sync the parent directory before reporting a successful dump."
  }
 ],
 "timeline": [
  {
   "t": "2026-06-09T07:01:05Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "test/functional/tool_wallet.py",
   "commit": "385a001f66ad23b3963d5c0acf960b94359c4e21",
   "in_reply_to": null,
   "text": "TIL. Maybe we can use this in other tests \ud83d\udc40"
  },
  {
   "t": "2026-06-09T07:13:11Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/dump.cpp",
   "commit": "6787fdc4e09184f05b38898e6f9439c219c5678d",
   "in_reply_to": null,
   "text": "nit: I find this a bit hard to read `if (ret) ... else ... if !ret ...`. I wonder if the code was a bit cleaner if the pattern was a try-catch inside this function where each error was a `throw`?"
  },
  {
   "t": "2026-06-09T07:27:18Z",
   "kind": "force_push",
   "who": "OSINTv96",
   "commit": "6f9c782eead053019b1f196e6408d675e6286545"
  },
  {
   "t": "2026-06-09T07:27:34Z",
   "kind": "review_comment",
   "who": "OSINTv96",
   "assoc": "NONE",
   "path": "src/wallet/dump.cpp",
   "commit": "6787fdc4e09184f05b38898e6f9439c219c5678d",
   "in_reply_to": 3378601498,
   "text": "Thanks, agreed. I simplified the control flow here so the success/failure paths are easier to read."
  },
  {
   "t": "2026-06-09T19:49:45Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "6f9c782eead053019b1f196e6408d675e6286545",
   "text": "Approach ACK.\n\nThis PR seems like an improvement, fixing file write/ close failure handling and adding coverage for a file write failure scenario that wasn't previously tested. I think `RLIMIT_FSIZE` is a nice way to exercise a real mid-write failure here. Alternatives like `/dev/full`, permission changes, or mocked writers would either test a less realistic path or require more plumbing."
  },
  {
   "t": "2026-06-10T09:16:40Z",
   "kind": "force_push",
   "who": "OSINTv96",
   "commit": "bb2a207d08782b93f77c003287d9b6bf4ea17fa1"
  },
  {
   "t": "2026-07-24T09:14:01Z",
   "kind": "comment",
   "who": "sedited",
   "assoc": "MEMBER",
   "text": "I think this patch would be better if it used AutoFile instead of ofstream, and called FileCommit and DirectoryCommit on success."
  },
  {
   "t": "2026-07-24T10:20:03Z",
   "kind": "comment",
   "who": "OSINTv96",
   "assoc": "NONE",
   "text": "Thanks, that makes sense; the patch now uses `AutoFile` with checked `Commit()` and `fclose()`, followed by `DirectoryCommit()` on the parent directory."
  },
  {
   "t": "2026-09-09T04:06:29Z",
   "kind": "review_comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "path": "src/wallet/dump.cpp",
   "commit": "385a001f66ad23b3963d5c0acf960b94359c4e21",
   "in_reply_to": null,
   "text": "Aside, not for this PR: `AutoFile`'s I/O-failing throw sites (`read`/`ignore`/`seek`/`write`/`write_buffer` in `streams.cpp`) and `BufferedFile::Fill()` all throw fixed strings with no `errno` detail, so callers lose the actual OS error reason on failure. `dump.cpp`'s own catch here also discards it in favor of a generic message. I'll follow up with a small PR that folds `SysErrorString(errno)` into those five throw sites and wires `dump.cpp` to surface the detail \u2014 a couple of existing catchers (e.g. `rpc/blockchain.cpp`'s `loadtxoutset`) already forward `e.what()` to their callers, so they'd pick up the improvement for free."
  },
  {
   "t": "2026-09-09T04:07:14Z",
   "kind": "review",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "385a001f66ad23b3963d5c0acf960b94359c4e21",
   "text": "ACK 385a001f66ad23b3963d5c0acf960b94359c4e21\n\nSwitching to `AutoFile` is a meaningfully stronger guarantee than what the write/close-failbit checks alone gave us \u2014 `std::ofstream::close()` only flushes into the OS page cache, it never calls `fsync` . So even the version I'd already approach-ACK'd could report a \"successful\" dump that wasn't actually durable on disk [if the machine crashed right after](https://github.com/bitcoin/bitcoin/blob/4c9d55a6125ad2c1dd7215e1790ef268f4fe735c/src/streams.h#L388-L393).\n\n`AutoFile::Commit()` (\u2192 `FileCommit()` \u2014 cross-platform fsync/FlushFileBuffers helper \u2014 actually forces the file's data to disk) plus `DirectoryCommit()` on the parent directory close that gap \u2014 the latter matters because a crash can lose the new file's directory entry even when the file's own data already hit disk (POSIX requires syncing the directory separately from the file)."
  },
  {
   "t": "2026-09-09T20:17:57Z",
   "kind": "comment",
   "who": "pablomartin4btc",
   "assoc": "MEMBER",
   "text": "Follow-up on the aside [above](https://github.com/bitcoin/bitcoin/pull/35492#discussion_r3964535482): opened #36210 for the `AutoFile`/`BufferedFile` errno detail. It only covers `streams.cpp`/`streams.h` for now \u2014 this PR's `dump.cpp` doesn't have the AutoFile-based code on master yet, so wiring `DumpWallet` to actually surface that detail will follow as a small separate commit once whichever of the two PRs merges first."
  }
 ],
 "labels_log": [
  {
   "t": "2026-06-09T06:32:20Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4446,
 "text_tokens_estimate": 1111,
 "changed_paths": [
  "src/wallet/dump.cpp",
  "test/functional/tool_wallet.py"
 ],
 "files": [
  {
   "path": "src/wallet/dump.cpp",
   "add": 54,
   "del": 41
  },
  {
   "path": "test/functional/tool_wallet.py",
   "add": 19,
   "del": 2
  }
 ],
 "test_lines": 21,
 "git": {
  "head": "385a001f66ad23b3963d5c0acf960b94359c4e21",
  "head_matches_backup": true,
  "base": "577999c2ce9cd838fda301c3a76519e0aaea476e",
  "commits": [
   {
    "sha": "bb2a207d08",
    "subject": "wallet: fail dump on incomplete writes",
    "files": 2,
    "add": 65,
    "del": 38
   },
   {
    "sha": "385a001f66",
    "subject": "wallet: commit dump output before success",
    "files": 1,
    "add": 11,
    "del": 8
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "cc6b1a4e46f75280",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}