CTransaction: Add constructor taking inputs and outputs #36696

pull purpleKarrot wants to merge 3 commits into bitcoin:master from purpleKarrot:declarative-test-data changing 4 files +87 −59
  1. purpleKarrot commented at 4:13 PM on October 29, 2026: contributor

    Part of #36692. Please view this PR in context of changes like #36694.

    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.

    It is a conscious decision that vin/vout are passed as generic input ranges and that only their elements are copied/moved. Passing the std::vector by value and then moving the container itself into place would allow the following:

    std::vector<CTxIn> inputs = ...;
    CTxIn& backdoor = inputs.back();
    
    const CTransaction tx{std::move(inputs), ...};
    
    backdoor = ...; // <- modify `tx`, potentially breaking its invariant
    
  2. DrahtBot commented at 4:13 PM on October 29, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process. A summary of reviews will appear here.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36543 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36543.svg"></sub> (validation: check the most recent AssumeUTXO commitment during IBD by l0rinc)

    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 places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

    • src/test/sigopcount_tests.cpp: CTransaction{..., ..., 1}
    • src/test/sigopcount_tests.cpp: CTransaction{..., ..., 1}
    • src/test/sigopcount_tests.cpp: AddCoins(coins, creationTx, 0)

    <sup>2026-10-30 07:17:58</sup>

  3. in src/primitives/transaction.h:316 in 7e66b30802
     311 | @@ -311,6 +312,18 @@ class CTransaction
     312 |      bool ComputeHasWitness() const;
     313 |  
     314 |  public:
     315 | +    template <RangeOf<CTxIn> In, RangeOf<CTxOut> Out>
     316 | +    CTransaction(In&& inputs, Out&& outputs, uint32_t version = CURRENT_VERSION, uint32_t locktime = 0)
    


    maflcko commented at 4:28 PM on October 29, 2026:

    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.


    purpleKarrot commented at 6:47 PM on October 29, 2026:

    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:

    auto tx = CTransaction{{}, {
        CTxOut{nValues[0], CScript{} << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG},
        CTxOut{nValues[1], CScript{} << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG},
    }};
    

    It is certainly less verbose, but I don't think it does a service to readability.


    maflcko commented at 9:27 AM on October 30, 2026:

    Sure, but my thinking is that init-lists for CTransaction must always end in a trailing comma:

    auto tx = CTransaction{
        {},
        {
            CTxOut{nValues[0], CScript{} << ToByteVector(key[0].GetPubKey()) << OP_CHECKSIG},
            CTxOut{nValues[1], CScript{} << ToByteVector(key[1].GetPubKey()) << OP_CHECKSIG},
        },
    };
    

    But again, up to other reviewers to pick the side here :sweat_smile:

  4. purpleKarrot force-pushed on Oct 29, 2026
  5. DrahtBot added the label CI failed on Oct 29, 2026
  6. DrahtBot removed the label CI failed on Oct 29, 2026
  7. in src/util/vector.h:31 in 172feb035a outdated
      26 | +{
      27 | +    std::vector<T> result;
      28 | +    if constexpr (std::ranges::sized_range<R>) {
      29 | +        result.reserve(std::ranges::size(range));
      30 | +    }
      31 | +    if constexpr (std::is_lvalue_reference_v<R>) {
    


    alexanderwiederin commented at 6:40 PM on October 29, 2026:
        if constexpr (std::is_lvalue_reference_v<R> || std::ranges::view<std::remove_cvref_t<R>>) {
    

    I think std::span{inputs} would still move the elements out of inputs. Copying for views avoids that. What do you think?


    purpleKarrot commented at 7:21 PM on October 29, 2026:

    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 #36696 (review) is resolved.


    purpleKarrot commented at 7:42 AM on October 30, 2026:

    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.

  8. in src/primitives/transaction.h:319 in 172feb035a outdated
     314 |  
     315 |      bool ComputeHasWitness() const;
     316 |  
     317 |  public:
     318 | +    template <RangeOf<CTxIn> In, RangeOf<CTxOut> Out>
     319 | +    CTransaction(In&& in, Out&& out, uint32_t ver = CURRENT_VERSION, uint32_t lt = 0)
    


    alexanderwiederin commented at 6:43 PM on October 29, 2026:
        explicit CTransaction(In&& in, Out&& out, uint32_t ver = CURRENT_VERSION, uint32_t lt = 0)
    

    Should this be explicit like the existing ones?


    purpleKarrot commented at 7:22 PM on October 29, 2026:

    Implicit conversion cannot happen because the constructor requires at least two arguments.

  9. alexanderwiederin commented at 6:49 PM on October 29, 2026: contributor

    The CTxIn witness argument is a nice addition. It would remove the “assign the witness after replacing vin” workaround I needed in several places in #36606 and #36694.

  10. CTransaction: Add constructor that takes ranges of inputs and outputs 4dc7038b55
  11. CTxIn: Accept scriptWitness in constructor 629b7da292
  12. test: Demonstrate new CTransaction constructor in sigopcount_tests 705d5d5737
  13. purpleKarrot force-pushed on Oct 30, 2026
  14. purpleKarrot commented at 8:21 AM on October 30, 2026: contributor

    I have replaced the commits that exemplify the constructor in action with one that shows multiple things in a single place:

    • It shows the new constructor of CTransaction in action.
    • It shows the new constructor of CTxIn in action.
    • It shows how CMutableTransaction is not necessary in many places, not just for building, but also as function argument.
    • It reveals GetTxSigOpCost as an example test that contains multiple blocks where each should become its own unit test.
    • 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.
    • 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).
    • 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).
    • 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:
    CTransaction creationTx{{{{}}}, {{1, scriptPubKey}}, 1};
    CTransaction spendingTx{{{{creationTx.GetHash(), 0}, scriptSig, CTxIn::SEQUENCE_FINAL, witness}}, {{1, {}}}, 1};
    

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-10-30 15:51 UTC

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