{
 "number": 36255,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36255",
 "title": "Refactor CoinGrinder tests",
 "author": "murchandamus",
 "author_association": "MEMBER",
 "created_at": "2026-09-14T22:55:49Z",
 "updated_at": "2026-09-15T10:56:25Z",
 "age_days": 2,
 "draft": true,
 "labels": [
  "CI failed"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "08ee40f1deb4b45999bc49ec827dba03e0e700b6",
 "head_ref": "2026-09-refactor-coingrinder-tests",
 "head_repo": "murchandamus/bitcoin",
 "head_history": [],
 "additions": 194,
 "deletions": 274,
 "changed_files": 2,
 "commit_count": 11,
 "size_bucket": "L",
 "mergeable_state": "unknown",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "concept_ack": [
     {
      "login": "l0rinc",
      "url": "https://github.com/bitcoin/bitcoin/pull/36255#pullrequestreview-5203785391"
     },
     {
      "login": "yancyribbens",
      "url": "https://github.com/bitcoin/bitcoin/pull/36255#issuecomment-5679030380"
     }
    ]
   },
   "conflicts": [
    {
     "number": 35511,
     "title": "RFC: consensus: Make `CAmount` a class",
     "author": "hodlinator"
    }
   ]
  }
 },
 "acks_parsed": {
  "l0rinc": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-15T06:47:02Z",
   "stale": false
  },
  "yancyribbens": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-15T10:56:21Z",
   "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": 1,
  "distinct_reviewers": [
   "l0rinc",
   "yancyribbens"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": true,
  "mergeable_state": "unknown",
  "last_author_activity": "2026-09-14T23:16:17Z",
  "last_reviewer_activity": "2026-09-15T10:56:21Z",
  "last_reviewer": "yancyribbens",
  "author_silent_days": 2,
  "waiting_on_author_days": 2,
  "days_since_update": 2
 },
 "refs": {
  "mentioned": [],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [],
  "conflicts": [
   35511
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/wallet/test/coinselection_tests.cpp",
  "src/wallet/test/coinselector_tests.cpp"
 ],
 "body": "This PR migrates the CoinGrinder tests from `coinselector_tests.cpp` to the new coin selection test suite `coinselection_tests.cpp` where we use effective values, actual feerates, and avoid setting coin selection parameters that never appear in production.\n\nMost of the tests are exact replications of the originals in the new framework, just the CoinGrinder exhaustion test was slightly refactored to simplify: it was using different weights for every single UTXO, but just two different weights are sufficient to replicate the relevant behavior for the test.\n\nEach commit migrates one test to simplify review.",
 "commits": [
  {
   "sha": "9a0964378d9b5b932808dbcbbf2f3e6e4126350b",
   "date": "2026-09-04T21:40:40Z",
   "message": "refactor: Move `is_eff_value` in MakeCoin to back\n\nThe `is_eff_value` parameter is never used with a non-default value, so\nmoving it to the back of the parameters allows dropping the mention of\nits default value in many instances."
  },
  {
   "sha": "29b47fbd62b0816954407e72dbaa860d1b542452",
   "date": "2026-09-04T21:51:41Z",
   "message": "tests: Check amount and size of equivalent inputs"
  },
  {
   "sha": "c147d3e3af2dc1a7d4cb4575f3376caaf2cd44b0",
   "date": "2026-09-14T21:39:51Z",
   "message": "test: migrate CoinGrinder insufficient funds coverage"
  },
  {
   "sha": "1856acc2124b99e8115591bede74a0fa31c6c6fa",
   "date": "2026-09-14T21:39:57Z",
   "message": "test: migrate CoinGrinder maximum weight coverage"
  },
  {
   "sha": "a3c07a50f7b7d8845de34b7ee5682b7aef3a1d40",
   "date": "2026-09-14T21:40:00Z",
   "message": "test: migrate CoinGrinder bounded weight selection coverage"
  },
  {
   "sha": "8b6a75edc8014498c1b11b29f45b06b8cecfbd83",
   "date": "2026-09-14T21:40:03Z",
   "message": "test: simplify BnB custom input setup"
  },
  {
   "sha": "4e7a41964218b45a063b636f203203380efe8af9",
   "date": "2026-09-14T21:40:09Z",
   "message": "test: migrate CoinGrinder lighter input preference coverage"
  },
  {
   "sha": "a6e70bc34a0c04ff3f185b850267d2111caf6a21",
   "date": "2026-09-14T21:40:14Z",
   "message": "test: migrate CoinGrinder mixed weight coverage"
  },
  {
   "sha": "1d626e503a97733c4b9e5276646d7649c1655384",
   "date": "2026-09-14T21:40:19Z",
   "message": "test: migrate CoinGrinder cloned input coverage"
  },
  {
   "sha": "86d8c7ed1a469c2bd22a5438141c71413bb5c8fe",
   "date": "2026-09-14T21:40:23Z",
   "message": "test: migrate CoinGrinder tiny input coverage"
  },
  {
   "sha": "08ee40f1deb4b45999bc49ec827dba03e0e700b6",
   "date": "2026-09-14T21:40:27Z",
   "message": "test: migrate CoinGrinder attempt limit coverage"
  }
 ],
 "timeline": [
  {
   "t": "2026-09-14T23:14:44Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "9a0964378d9b5b932808dbcbbf2f3e6e4126350b",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nHere we can remove the `default_cs_params` as well now, right?"
  },
  {
   "t": "2026-09-14T23:16:17Z",
   "kind": "comment",
   "who": "murchandamus",
   "assoc": "MEMBER",
   "text": "Made available as a PR, because I was discussing it with a colleague who wanted to provide feedback, but there are still a couple minor things that I want to tweak anyway."
  },
  {
   "t": "2026-09-14T23:38:47Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "29b47fbd62b0816954407e72dbaa860d1b542452",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nThe previous implementation was indeed hard to understand.\nThis is basically a permutation check of a subset of fields, and since we have very few elements here (given it's test code), we could either simplify further to:\n```C++\n  static bool HaveEquivalentInputs(const SelectionResult& a, const SelectionResult& b)\n  {\n      return std::ranges::is_permutation(a.GetInputSet(), b.GetInputSet(), [](auto& x, auto& y) {\n          return x->txout.nValue == y->txout.nValue && x->input_bytes == y->input_bytes;\n      });\n  }\n```\nor if you don't find this readable we could extract the common parts that prepare the final comparable objects and compare those directly at the call site:\n```C++\n/** The (amount, input size) pairs of a result's inputs in sorted order, which identify a selection regardless of the prevouts of its inputs. */\nstatic std::vector<std::pair<CAmount, int>> SortedInputs(const SelectionResult& result)\n{\n    std::vector<std::pair<CAmount, int>> inputs;\n    for (auto& coin : result.GetInputSet()) {\n        inputs.emplace_back(coin->txout.nValue, coin->input_bytes);\n    }\n    std::ranges::sort(inputs);\n    return inputs;\n}\n```\n\nand in the assertion we can have\n```C++\nBOOST_TEST(SortedInputs(*result) == SortedInputs(expected_result));\n```\ndirectly."
  },
  {
   "t": "2026-09-15T06:29:35Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "9a0964378d9b5b932808dbcbbf2f3e6e4126350b",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nSince every caller uses effective values, could we remove `is_eff_value` altogether?"
  },
  {
   "t": "2026-09-15T06:30:42Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "c147d3e3af2dc1a7d4cb4575f3376caaf2cd44b0",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nAssertions inside these helpers report the helper's source line, so `test_title` has to identify the individual scenario. Could we organize the scenarios into named Boost test cases and share the repeated assertions through small macros, as in the [headers-sync tests](https://github.com/bitcoin/bitcoin/blob/08ee40f1deb4b45999bc49ec827dba03e0e700b6/src/test/headers_sync_chainwork_tests.cpp#L23-L44)? That would keep failure locations at each call site and make the test categories independently runnable. Cases with the same setup and checks can share a small table of inputs and expectations, while cases with different assertion flows can remain separate."
  },
  {
   "t": "2026-09-15T06:32:13Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "a3c07a50f7b7d8845de34b7ee5682b7aef3a1d40",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\n`HaveEquivalentInputs` already compares the input values, and `GetSelectedValue()` just sums them. Could we drop `expected_amount`, its accumulation, and this assertion? For the remaining scalar checks, Boost already prints the operands:\n```C++\nBOOST_CHECK_LE(result->GetWeight(), max_selection_weight);\nBOOST_CHECK_EQUAL(result->GetSelectionsEvaluated(), expected_attempts);\n```\n\nI would keep the weight check because `GetWeight()` reads separately maintained state. The same simplifications apply to the BnB helper."
  },
  {
   "t": "2026-09-15T06:32:51Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "a3c07a50f7b7d8845de34b7ee5682b7aef3a1d40",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCould we use integer amounts throughout the migrated tests, e.g. 33 * CENT here and 2'533 * CENT for the target below? This expresses the intended satoshi amounts directly and avoids floating-point multiplication followed by conversion to `CAmount`."
  },
  {
   "t": "2026-09-15T06:35:02Z",
   "kind": "review_comment",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "1d626e503a97733c4b9e5276646d7649c1655384",
   "in_reply_to": null,
   "text": "[quoted text omitted]\n\nCould `AddDuplicateCoins` take an optional input size so these four loops become four calls to the existing helper? We could also build `expected_inputs` once and initialize `utxo_pool` from it before adding the heavier clones, which avoids constructing the four expected coins twice."
  },
  {
   "t": "2026-09-15T06:47:02Z",
   "kind": "review",
   "who": "l0rinc",
   "assoc": "MEMBER",
   "state": "CHANGES_REQUESTED",
   "commit": "08ee40f1deb4b45999bc49ec827dba03e0e700b6",
   "text": "Concept ACK\n\nI left a few suggestions to make the test categories independently runnable (each one exercising multiple scenarios, grouping by category), untangling the separate test cases and simplified the helpers, assertions, and amount expressions.\nI've implemented them in https://github.com/l0rinc/bitcoin/pull/297/commits (see last commit) - feel free to pick and choose and split and fold them back into your commits if you agree with the direction.\n\nNote that the CI failure seems unrelated."
  },
  {
   "t": "2026-09-15T10:20:48Z",
   "kind": "review_comment",
   "who": "yancyribbens",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "08ee40f1deb4b45999bc49ec827dba03e0e700b6",
   "in_reply_to": null,
   "text": "```suggestion\n * Two results are equivalent if they are composed of inputs with the same amounts and sizes, even if they are composed from different outputs (i.e., different prevouts). */\n```"
  },
  {
   "t": "2026-09-15T10:42:30Z",
   "kind": "review_comment",
   "who": "yancyribbens",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/coinselection_tests.cpp",
   "commit": "1856acc2124b99e8115591bede74a0fa31c6c6fa",
   "in_reply_to": null,
   "text": "This is a strange max_selection_weight.  It would be better to use real values, like max transaction weight of of 400_000 I think, although that's unrelated to this change."
  },
  {
   "t": "2026-09-15T10:49:44Z",
   "kind": "review_comment",
   "who": "yancyribbens",
   "assoc": "CONTRIBUTOR",
   "path": "src/wallet/test/coinselector_tests.cpp",
   "commit": "86d8c7ed1a469c2bd22a5438141c71413bb5c8fe",
   "in_reply_to": null,
   "text": "Unrelated to this change, but why choose a set and target of 40000?  I had thought at first this was 400000 which would make more sense, so I wonder if this was a typo."
  },
  {
   "t": "2026-09-15T10:56:21Z",
   "kind": "comment",
   "who": "yancyribbens",
   "assoc": "CONTRIBUTOR",
   "text": "Concept ACK.  Thanks for following up on this.  It would be nice to see the coinselector test file removed entirely in the future since having multiple test files has caused confusion.  Also minor nit, but your older commit messages have different capitalization than your newer messages."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-14T23:55:31Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-14T23:15:13Z",
   "kind": "convert_to_draft",
   "who": "murchandamus"
  }
 ],
 "text_chars": 6159,
 "text_tokens_estimate": 1539,
 "changed_paths": [
  "src/wallet/test/coinselection_tests.cpp",
  "src/wallet/test/coinselector_tests.cpp"
 ],
 "files": [
  {
   "path": "src/wallet/test/coinselection_tests.cpp",
   "add": 194,
   "del": 21
  },
  {
   "path": "src/wallet/test/coinselector_tests.cpp",
   "add": 0,
   "del": 253
  }
 ],
 "test_lines": 468,
 "git": {
  "head": "08ee40f1deb4b45999bc49ec827dba03e0e700b6",
  "head_matches_backup": true,
  "base": "4519933391dd23dbf1a4eceec6dd53d2e9e71cc3",
  "commits": [
   {
    "sha": "9a0964378d",
    "subject": "refactor: Move `is_eff_value` in MakeCoin to back",
    "files": 1,
    "add": 10,
    "del": 10
   },
   {
    "sha": "29b47fbd62",
    "subject": "tests: Check amount and size of equivalent inputs",
    "files": 1,
    "add": 10,
    "del": 11
   },
   {
    "sha": "c147d3e3af",
    "subject": "test: migrate CoinGrinder insufficient funds coverage",
    "files": 2,
    "add": 20,
    "del": 18
   },
   {
    "sha": "1856acc212",
    "subject": "test: migrate CoinGrinder maximum weight coverage",
    "files": 2,
    "add": 14,
    "del": 21
   },
   {
    "sha": "a3c07a50f7",
    "subject": "test: migrate CoinGrinder bounded weight selection coverage",
    "files": 2,
    "add": 31,
    "del": 29
   },
   {
    "sha": "8b6a75edc8",
    "subject": "test: simplify BnB custom input setup",
    "files": 1,
    "add": 8,
    "del": 2
   },
   {
    "sha": "4e7a419642",
    "subject": "test: migrate CoinGrinder lighter input preference coverage",
    "files": 2,
    "add": 12,
    "del": 22
   },
   {
    "sha": "a6e70bc34a",
    "subject": "test: migrate CoinGrinder mixed weight coverage",
    "files": 2,
    "add": 17,
    "del": 29
   },
   {
    "sha": "1d626e503a",
    "subject": "test: migrate CoinGrinder cloned input coverage",
    "files": 2,
    "add": 27,
    "del": 39
   },
   {
    "sha": "86d8c7ed1a",
    "subject": "test: migrate CoinGrinder tiny input coverage",
    "files": 2,
    "add": 15,
    "del": 26
   },
   {
    "sha": "08ee40f1de",
    "subject": "test: migrate CoinGrinder attempt limit coverage",
    "files": 2,
    "add": 35,
    "del": 72
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "d388a3c3e8bda0bc",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}