{
 "number": 36342,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36342",
 "title": "wallet: reject recipient amounts above MAX_MONEY",
 "author": "Bruce039",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-26T09:53:11Z",
 "updated_at": "2026-11-01T02:53:35Z",
 "description_hash": "0f87c815ecd0ef05",
 "age_days": 36,
 "draft": false,
 "labels": [
  "Wallet"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "bab579e56aa8b80a6f2923285e55cbcb2a7730b8",
 "head_ref": "fix-recipients-sum-overflow",
 "head_repo": "Bruce039/bitcoin",
 "head_history": [
  {
   "t": "2026-09-26T10:01:40Z",
   "sha": "f2915243d616e9e579663cbea41e12f2ba1e93fa"
  },
  {
   "t": "2026-09-29T14:16:57Z",
   "sha": "bab579e56aa8b80a6f2923285e55cbcb2a7730b8"
  }
 ],
 "additions": 25,
 "deletions": 4,
 "changed_files": 2,
 "commit_count": 1,
 "size_bucket": "S",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "stale_ack": [
     {
      "login": "maflcko",
      "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5873589058"
     }
    ]
   },
   "conflicts": []
  }
 },
 "acks_parsed": {},
 "acks_tally": {
  "ack": 0,
  "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": [
   "furszy",
   "maflcko",
   "pinheadmz"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-29T14:17:25Z",
  "last_reviewer_activity": "2026-09-29T14:03:50Z",
  "last_reviewer": "furszy",
  "author_silent_days": 33,
  "waiting_on_author_days": 0,
  "days_since_update": 0
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/spend.cpp"
 ],
 "body": "`CreateTransaction` checks that recipient amounts aren't negative, but not that they're within `MAX_MONEY`. `CreateTransactionInternal` then sums them into `recipients_sum` (spend.cpp:1100) with no range check, and that sum can overflow.\n\nEasiest way to hit it is `fundrawtransaction` with a raw tx that has two outputs of 2^62 sat. It can also be reached with only valid amounts through `walletcreatefundedpsbt`/`send`/`sendmany` and a few thousand 21M BTC outputs. The \"Transaction too large\" check only runs after coin selection, so it doesn't stop this. On master the node aborts:\n\n```\nAssertion failed: (!m_selected_inputs.empty()), function RecalculateWaste, file coinselection.cpp, line 904.\n```\n\nUBSan reports the overflow at spend.cpp:1100 first. With other values the sum wraps to a small positive number instead, and `fundrawtransaction` returns success for a tx with outputs of 46116860184 BTC each.\n\nThis checks every amount and the running sum against `MoneyRange` in the existing recipients loop of `CreateTransactionInternal`, before the amount is added to `recipients_sum`, and returns \"Transaction output amount must be valid\" before coin selection starts. The same check rejects negative amounts, so the separate negative check in `CreateTransaction` is removed. The new `test_outputs_above_max_money` in `wallet_fundrawtransaction.py` covers a single output above MAX_MONEY, two outputs whose sum is above it, a sum that overflows int64, and a negative output. On master the first case returns \"Insufficient funds\" and the overflow case crashes the node; with the fix all of them get the new error. `wallet_fundrawtransaction.py`, `wallet_send.py`, `wallet_basic.py` and `rpc_psbt.py` pass.",
 "commits": [
  {
   "sha": "bab579e56aa8b80a6f2923285e55cbcb2a7730b8",
   "date": "2026-09-29T14:16:53Z",
   "message": "wallet: reject recipient amounts outside MoneyRange\n\nCreateTransactionInternal summed the recipient amounts without checking\nthem, so an output above MAX_MONEY, or outputs whose sum exceeds it or\noverflows CAmount, reached coin selection. Check each amount and the\nrunning sum against MoneyRange in the existing recipients loop. This also\ncovers negative amounts, so the separate check in CreateTransaction is\nremoved."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-26T10:01:40Z",
   "kind": "force_push",
   "who": "Bruce039",
   "commit": "f2915243d616e9e579663cbea41e12f2ba1e93fa",
   "id": "push:f2915243d616e9e579663cbea41e12f2ba1e93fa"
  },
  {
   "t": "2026-09-26T11:40:32Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "You just opened three PRs in ten minutes. Do all three of these conform to our [AI Policy](https://github.com/bitcoin/bitcoin/blob/master/doc/AI_POLICY.md)? Before we spend any precious human resources reading through your submissions can you please assure us that you understand all the choices you've made and that you are motivated by solving real problems real users have with the software?",
   "id": "c:5845964081",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5845964081",
   "hash": "b9915b54916a98d9",
   "edited": null
  },
  {
   "t": "2026-09-26T12:05:57Z",
   "kind": "comment",
   "who": "Bruce039",
   "assoc": "CONTRIBUTOR",
   "text": "Yes, I've read the AI policy and I accept it. And yes, I used AI, but only for the PR description and the tests. Finding the bug and the rest of the underlying work was done by me.\nAbout the 3 PRs: I've been preparing for PRs for the last few days, and it made more sense to me to collect all the bugs I found and send them at the same time.",
   "id": "c:5846124971",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5846124971",
   "hash": "4f3144d401ef93b1",
   "edited": null
  },
  {
   "t": "2026-09-28T14:49:51Z",
   "kind": "comment",
   "who": "pinheadmz",
   "assoc": "MEMBER",
   "text": "You now have 7 PRs open in two days, and you are a brand new contributor. If you really want to support the project I suggest closing 6 of them. Pick one that really means a lot to you, that you think is the most important for the most bitcoin users, and really commit to it. Show us that you the human cares about the project and is willing to put in the time and the hard work that the rest of us put in.\n\nEarn your reputation here by starting slow.\n\nWe do not need another \"AI operator\" pasting claude output, that will result in a ban.",
   "id": "c:5872415752",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5872415752",
   "hash": "2c338dee816194d8",
   "edited": null
  },
  {
   "t": "2026-09-28T15:50:20Z",
   "kind": "comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "text": "lgtm ACK f2915243d616e9e579663cbea41e12f2ba1e93fa\n\nSeems fine as a low-prio sanity check and for nicer sanitizer use",
   "id": "c:5873589058",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5873589058",
   "hash": "467c159650c3b14b",
   "edited": null
  },
  {
   "t": "2026-09-29T08:34:36Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/spend.cpp",
   "commit": "f2915243d616e9e579663cbea41e12f2ba1e93fa",
   "in_reply_to": null,
   "text": "nit: I think you can combine this check/loop  and have the error message just be \"Transaction output amount must be valid\". this avoids two loops and overly strict hand holding for errors that should never happen anyway.",
   "id": "rc:4131313711",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#discussion_r4131313711",
   "hash": "ce82095a9396cef9",
   "edited": null
  },
  {
   "t": "2026-09-29T13:46:47Z",
   "kind": "review_comment",
   "who": "Bruce039",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/spend.cpp",
   "commit": "f2915243d616e9e579663cbea41e12f2ba1e93fa",
   "in_reply_to": 4131313711,
   "text": "Done: the negative-amount check is folded into the MoneyRange loop, with the single \"Transaction output amount must be valid\" error. (Changes prepared with AI tools.)",
   "id": "rc:4134197408",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#discussion_r4134197408",
   "hash": "ec0634acf4e2e5c6",
   "edited": null
  },
  {
   "t": "2026-09-29T13:51:26Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/wallet/spend.cpp",
   "commit": "f2915243d616e9e579663cbea41e12f2ba1e93fa",
   "in_reply_to": 4131313711,
   "text": "Please squash your commits according to https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md#squashing-commits",
   "id": "rc:4134252001",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#discussion_r4134252001",
   "hash": "44b2f2a0331db5ad",
   "edited": null
  },
  {
   "t": "2026-09-29T14:03:50Z",
   "kind": "review",
   "who": "furszy",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "324ca0e8e8661194d76969379577e0411da33daf",
   "text": "Can move the check below the existing loop:\n\nhttps://github.com/bitcoin/bitcoin/blob/dc2a9987aab711abdc1a77aa6d1462c6a46deabd/src/wallet/spend.cpp#L1092-L1097\n\nand could also merge the negative amount checks there too. So it is just one loop.",
   "id": "r:5353722707",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#pullrequestreview-5353722707",
   "hash": "0173380968050738",
   "edited": null
  },
  {
   "t": "2026-09-29T14:16:57Z",
   "kind": "force_push",
   "who": "Bruce039",
   "commit": "bab579e56aa8b80a6f2923285e55cbcb2a7730b8",
   "id": "push:bab579e56aa8b80a6f2923285e55cbcb2a7730b8"
  },
  {
   "t": "2026-09-29T14:17:25Z",
   "kind": "comment",
   "who": "Bruce039",
   "assoc": "CONTRIBUTOR",
   "text": "Squashed into one commit (bab579e56a). Following furszy, the MoneyRange check now sits in the existing recipients loop in `CreateTransactionInternal`, and it also covers the negative-amount case, so the separate check in `CreateTransaction` is gone. Added a negative output to the test. (Changes prepared with AI tools.)",
   "id": "c:5892125804",
   "url": "https://github.com/bitcoin/bitcoin/pull/36342#issuecomment-5892125804",
   "hash": "63fdce0c3bd57155",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-26T09:53:15Z",
   "action": "labeled",
   "label": "Wallet",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-26T18:28:42Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-26T20:02:32Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 4646,
 "text_tokens_estimate": 1161,
 "changed_paths": [
  "src/wallet/spend.cpp",
  "test/functional/wallet_fundrawtransaction.py"
 ],
 "files": [
  {
   "path": "src/wallet/spend.cpp",
   "add": 3,
   "del": 4
  },
  {
   "path": "test/functional/wallet_fundrawtransaction.py",
   "add": 22,
   "del": 0
  }
 ],
 "test_lines": 22,
 "git": {
  "head": "bab579e56aa8b80a6f2923285e55cbcb2a7730b8",
  "head_matches_backup": true,
  "base": "ed7dd7cf4e1561a97edf72eba29a67b14e28c717",
  "patch_id": "e1cf66d8ee5d9c52",
  "commits": [
   {
    "sha": "bab579e56a",
    "subject": "wallet: reject recipient amounts outside MoneyRange",
    "files": 2,
    "add": 25,
    "del": 4
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "e1cf66d8ee5d9c52",
 "input_hash": "77cdae84a5ac3807",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}