{
 "number": 36696,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36696",
 "title": "CTransaction: Add constructor taking inputs and outputs",
 "author": "purpleKarrot",
 "author_association": "MEMBER",
 "created_at": "2026-10-29T16:13:13Z",
 "updated_at": "2026-10-30T09:27:20Z",
 "description_hash": "13cb59b10a9034d8",
 "age_days": 3,
 "draft": false,
 "labels": [],
 "milestone": null,
 "base": "master",
 "head_sha": "705d5d573774033b7cff5f45f602fdb40850866b",
 "head_ref": "declarative-test-data",
 "head_repo": "purpleKarrot/bitcoin",
 "head_history": [
  {
   "t": "2026-10-29T16:43:54Z",
   "sha": "172feb035a76d292e75a81bf5d3b709e61796396"
  },
  {
   "t": "2026-10-30T07:17:32Z",
   "sha": "705d5d573774033b7cff5f45f602fdb40850866b"
  }
 ],
 "additions": 87,
 "deletions": 59,
 "changed_files": 4,
 "commit_count": 3,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {},
   "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": [
   "alexanderwiederin",
   "maflcko"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-10-30T08:21:43Z",
  "last_reviewer_activity": "2026-10-30T09:27:20Z",
  "last_reviewer": "maflcko",
  "author_silent_days": 2,
  "waiting_on_author_days": 2,
  "days_since_update": 2
 },
 "refs": {
  "mentioned": [
   36606,
   36692,
   36694
  ],
  "depends_on": [],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 36692,
    "type": "issue",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "Declarative construction of test data"
   },
   {
    "number": 36606,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-10-29",
    "title": "test, bench: construct inputs/outputs declaratively in context-dependent cases"
   },
   {
    "number": 36694,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "test, bench: construct mempool inputs/outputs declaratively"
   }
  ],
  "conflicts": []
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/primitives/transaction.h",
  "src/util/vector.h"
 ],
 "body": "Part of #36692. Please view this PR in context of changes like https://github.com/bitcoin/bitcoin/pull/36694.\n\n`CTransaction` gains a constructor that allows direct declarative construction as an alternative to building through `CMutableTransaction`. `CTxIn`'s constructors gain an optional argument to pass the script witness.\n\nIt is a conscious decision that `vin/vout` are passed as generic input ranges and that only *their elements* are copied/moved.\nPassing the `std::vector` by value and then moving the *container itself* into place would allow the following:\n\n```cpp\nstd::vector<CTxIn> inputs = ...;\nCTxIn& backdoor = inputs.back();\n\nconst CTransaction tx{std::move(inputs), ...};\n\nbackdoor = ...; // <- modify `tx`, potentially breaking its invariant\n```",
 "commits": [
  {
   "sha": "4dc7038b554182928b4cd0a47eee1dfb714195e3",
   "date": "2026-10-30T06:30:00Z",
   "message": "CTransaction: Add constructor that takes ranges of inputs and outputs"
  },
  {
   "sha": "629b7da292e02910da3109b8ddb334c855149716",
   "date": "2026-10-30T06:30:10Z",
   "message": "CTxIn: Accept scriptWitness in constructor"
  },
  {
   "sha": "705d5d573774033b7cff5f45f602fdb40850866b",
   "date": "2026-10-30T07:16:21Z",
   "message": "test: Demonstrate new CTransaction constructor in sigopcount_tests"
  }
 ],
 "timeline": [
  {
   "t": "2026-10-29T16:28:07Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/primitives/transaction.h",
   "commit": "7e66b30802bd8346bcbbb191eb3200c69ce5c8e0",
   "in_reply_to": null,
   "text": "nit: I'd still prefer the easier C++11 std::vector here (https://github.com/bitcoin/bitcoin/issues/36692#issuecomment-6501722895) but I'll leave it to other reviewers to decide.",
   "id": "rc:4415029088",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4415029088",
   "hash": "8a71febab27a826d",
   "edited": "2026-10-29T19:26:40Z"
  },
  {
   "t": "2026-10-29T16:43:54Z",
   "kind": "force_push",
   "who": "purpleKarrot",
   "commit": "172feb035a76d292e75a81bf5d3b709e61796396",
   "id": "push:172feb035a76d292e75a81bf5d3b709e61796396"
  },
  {
   "t": "2026-10-29T18:40:08Z",
   "kind": "review_comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "path": "src/util/vector.h",
   "commit": "4491755fdaed602c502b75c656c48fb2e966890a",
   "in_reply_to": null,
   "text": "```suggestion\n    if constexpr (std::is_lvalue_reference_v<R> || std::ranges::view<std::remove_cvref_t<R>>) {\n```\n\nI think `std::span{inputs}` would still move the elements out of `inputs`. Copying for views avoids that. What do you think?",
   "id": "rc:4416451512",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4416451512",
   "hash": "7b902f0693152791",
   "edited": "2026-10-29T18:49:08Z"
  },
  {
   "t": "2026-10-29T18:43:46Z",
   "kind": "review_comment",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "path": "src/primitives/transaction.h",
   "commit": "705d5d573774033b7cff5f45f602fdb40850866b",
   "in_reply_to": null,
   "text": "```suggestion\n    explicit CTransaction(In&& in, Out&& out, uint32_t ver = CURRENT_VERSION, uint32_t lt = 0)\n```\nShould this be explicit like the existing ones?",
   "id": "rc:4416488577",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4416488577",
   "hash": "8198edba2891d203",
   "edited": "2026-10-29T18:49:08Z"
  },
  {
   "t": "2026-10-29T18:47:40Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/primitives/transaction.h",
   "commit": "7e66b30802bd8346bcbbb191eb3200c69ce5c8e0",
   "in_reply_to": 4415029088,
   "text": "I added a few cases to show the new constructor in action. If the constructor would accept `std::vector` arguments, a transaction without inputs could be constructed as:\n\n```cpp\nauto tx = CTransaction{{}, {\n    CTxOut{nValues[0], CScript{} << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG},\n    CTxOut{nValues[1], CScript{} << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG},\n}};\n```\n\nIt is certainly less verbose, but I don't think it does a service to readability.",
   "id": "rc:4416531182",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4416531182",
   "hash": "2763d9edafc0c3c4",
   "edited": "2026-10-29T18:49:53Z"
  },
  {
   "t": "2026-10-29T18:49:08Z",
   "kind": "review",
   "who": "alexanderwiederin",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "4491755fdaed602c502b75c656c48fb2e966890a",
   "text": "The `CTxIn` witness argument is a nice addition. It would remove the \u201cassign the witness after replacing vin\u201d workaround I needed in several places in #36606 and #36694.",
   "id": "r:5703110328",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#pullrequestreview-5703110328",
   "hash": "66ce977a0dfdcf05",
   "edited": null
  },
  {
   "t": "2026-10-29T19:21:54Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/util/vector.h",
   "commit": "4491755fdaed602c502b75c656c48fb2e966890a",
   "in_reply_to": 4416451512,
   "text": "That is a good point. I think we should just invoke `std::ranges::copy` unconditionally. In C++23, we can remove this helper and use tag dispatching with `std::from_range`. All as long as we actually want range support, so I will leave this open until https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4415029088 is resolved.",
   "id": "rc:4416890613",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4416890613",
   "hash": "a77cc5827e08b613",
   "edited": null
  },
  {
   "t": "2026-10-29T19:22:45Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/primitives/transaction.h",
   "commit": "705d5d573774033b7cff5f45f602fdb40850866b",
   "in_reply_to": 4416488577,
   "text": "Implicit conversion cannot happen because the constructor requires at least two arguments.",
   "id": "rc:4416898958",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4416898958",
   "hash": "58f55be21c61a307",
   "edited": "2026-10-29T19:22:46Z"
  },
  {
   "t": "2026-10-30T07:17:32Z",
   "kind": "force_push",
   "who": "purpleKarrot",
   "commit": "705d5d573774033b7cff5f45f602fdb40850866b",
   "id": "push:705d5d573774033b7cff5f45f602fdb40850866b"
  },
  {
   "t": "2026-10-30T07:42:41Z",
   "kind": "review_comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "path": "src/util/vector.h",
   "commit": "4491755fdaed602c502b75c656c48fb2e966890a",
   "in_reply_to": 4416451512,
   "text": "Fixed it. Instead of moving when `R` is an rvalue, we want to move when R *yields* rvalues. `std::ranges::copy` already does that and so does `std::from_range`.",
   "id": "rc:4421462027",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4421462027",
   "hash": "1a8d116c40a482a1",
   "edited": "2026-10-30T07:42:42Z"
  },
  {
   "t": "2026-10-30T08:21:43Z",
   "kind": "comment",
   "who": "purpleKarrot",
   "assoc": "MEMBER",
   "text": "I have replaced the commits that exemplify the constructor in action with one that shows multiple things in a single place:\n\n- It shows the new constructor of `CTransaction` in action.\n- It shows the new constructor of `CTxIn` in action.\n- It shows how `CMutableTransaction` is not necessary in many places, not just for building, but also as function argument.\n- It reveals `GetTxSigOpCost` as an example test that contains multiple blocks where each should become its own unit test.\n- It reveals potential test dependencies: Each test setup modifies the `CCoinsViewCache`. It is not clear whether a test might depend on the modifications of tests run earlier or not.\n- It shows that `CTransaction` could benefit from applying best practices (never use `const` data members, maintain immutability by not providing write access): In `return {std::move(creationTx), std::move(spendingTx)};`, the compiler provided move constructor of `CTransaction` invokes the move constructors of all its data members, which are `const`, so they fall back to copying (this does not fire `performance-move-const-arg`, because the individual data members fall back, not the tx object).\n- It reveals the annoyance that `CTransaction` does not provide regular semantics: It is not possible to replace the existing values with `std::tie(creationTx, spendingTx) = BuildTxs(...);`, because `CTransaction` is only sort-of copyable (due to not following ... I repeat myself. This is not a big deal here, as tests should be refactored for independence anyway).\n- It also provides an example where code could be much less verbose if the constructor accepted `std::vector` and explicit types were not required at the call site, and why I claim this to be less readable:\n\n```cpp\nCTransaction creationTx{{{{}}}, {{1, scriptPubKey}}, 1};\nCTransaction spendingTx{{{{creationTx.GetHash(), 0}, scriptSig, CTxIn::SEQUENCE_FINAL, witness}}, {{1, {}}}, 1};\n```",
   "id": "c:6518387886",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#issuecomment-6518387886",
   "hash": "2e97370f29e10a9e",
   "edited": "2026-10-30T08:32:16Z"
  },
  {
   "t": "2026-10-30T09:27:20Z",
   "kind": "review_comment",
   "who": "maflcko",
   "assoc": "MEMBER",
   "path": "src/primitives/transaction.h",
   "commit": "7e66b30802bd8346bcbbb191eb3200c69ce5c8e0",
   "in_reply_to": 4415029088,
   "text": "Sure, but my thinking is that init-lists for CTransaction must always end in a trailing comma:\n\n```cpp\nauto tx = CTransaction{\n    {},\n    {\n        CTxOut{nValues[0], CScript{} << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG},\n        CTxOut{nValues[1], CScript{} << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG},\n    },\n};\n```\n\nBut again, up to other reviewers to pick the side here :sweat_smile:",
   "id": "rc:4422197450",
   "url": "https://github.com/bitcoin/bitcoin/pull/36696#discussion_r4422197450",
   "hash": "099186395a19cfc0",
   "edited": null
  }
 ],
 "labels_log": [
  {
   "t": "2026-10-29T16:44:27Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-10-29T17:57:23Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [],
 "text_chars": 5135,
 "text_tokens_estimate": 1283,
 "changed_paths": [
  "src/primitives/transaction.cpp",
  "src/primitives/transaction.h",
  "src/test/sigopcount_tests.cpp",
  "src/util/vector.h"
 ],
 "files": [
  {
   "path": "src/primitives/transaction.cpp",
   "add": 17,
   "del": 4
  },
  {
   "path": "src/primitives/transaction.h",
   "add": 12,
   "del": 2
  },
  {
   "path": "src/test/sigopcount_tests.cpp",
   "add": 38,
   "del": 53
  },
  {
   "path": "src/util/vector.h",
   "add": 20,
   "del": 0
  }
 ],
 "test_lines": 91,
 "git": {
  "head": "705d5d573774033b7cff5f45f602fdb40850866b",
  "head_matches_backup": true,
  "base": "6ed5aad06111177c39f9b9f07a52634476380ae1",
  "patch_id": "36df9383cf3e2e38",
  "commits": [
   {
    "sha": "4dc7038b55",
    "subject": "CTransaction: Add constructor that takes ranges of inputs and outputs",
    "files": 3,
    "add": 43,
    "del": 2
   },
   {
    "sha": "629b7da292",
    "subject": "CTxIn: Accept scriptWitness in constructor",
    "files": 2,
    "add": 6,
    "del": 4
   },
   {
    "sha": "705d5d5737",
    "subject": "test: Demonstrate new CTransaction constructor in sigopcount_tests",
    "files": 1,
    "add": 38,
    "del": 53
   }
  ],
  "patch_truncated": false
 },
 "patch_id": "36df9383cf3e2e38",
 "input_hash": "c369eebf1cf90d94",
 "extracted_at": "2026-11-02T01:09:49+00:00"
}