#35569 Encapsulation for CTransaction

ReviewabilityReviewsAgreementSize
#35569 purpleKarrot Encapsulation for CTransactionNeeds rebase0 (+2) -2Disputed392 + 209 tests
  • Hide internal transaction data layout behind read accessors across the codebase
  • Let future work change transaction memory layouts or share block data without breaking callers
  • Worth reviewing now as no changes or rebases are pending
  • Opposed due to wide churn across consensus code without clear immediate benefit (ajtowns, l0rinc)
  • Objected that renaming protocol fields like vin and vout causes confusion (mzumsande)
  • Strong support for decoupling callers from internal transaction layout (josibake, willcl-ark)
  • Verified that the automated refactor reproduces cleanly with passing tests (alexanderwiederin)
  • 392 lines added or modified outside tests
  • 209 lines added or modified in tests
  • 506 lines removed in total
  • 94 files, 7 commits
Validation#48 of 53
P4 · cleanup
  • P4 because it is an architectural cleanup with no immediate consensus or validation benefit
  • Potential future storage optimizations remain speculative and unproven
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

Encapsulates `CTransaction` data members behind public `const` observer methods (such as `GetInputs()` and `GetOutputs()`) and makes the underlying member variables private. Callers throughout the codebase are migrated to the new observers using a newly introduced `bitcoin-tidy` check. Matching observer methods are also added to `CMutableTransaction` to support generic code.

Problem: Public data members on `CTransaction` bake the internal storage layout directly into the public API across the entire codebase. This prevents altering the underlying representation (such as referencing shared block data or shared immutable transaction memory) and hinders separating serialization and validation functionality.

Evidence of importance:

  • Description: 'Public data members make the representation of a type part of its API. Once access goes through observers, the representation can change without requiring its users to change as well. Potential changes are using shared immutable transaction state, or even an offset into shared immutable block state.'
  • Description: 'This work is part of the broader interface-segregation direction described in #35904, but is not a prerequisite for the stateless validation library proposed there.'

Scope: Large mechanical change touching 94 files, automated with a newly introduced clang-tidy check (`contrib/devtools/bitcoin-tidy/use-observers.cpp`).

Risk of a mistake: medium (consensus, validation, mempool_policy, wallet, rpc_api). A copy or reference mismatch in accessor calls in consensus or script validation paths could cause performance regressions or validation failures across the node.

Changed since the previous assessment: Replaced remaining occurrences of `decltype(CTransaction::version)` with explicit `uint32_t` in policy and RPC code.

Reviewability

Ready · Needs rebase

Conflicts with master; review the logic now, the rebase is mechanical.

Waiting on (from the thread read):

  • author: answer alexanderwiederin's question about tidy check usage in current tree 2026-09-09
  • decision: settle disputed Concept and Approach NACKs regarding high churn across critical code without immediate benefit 2026-09-21

Agreement

Disputed

objection that needs a change, the objector pushed back after a reply (ajtowns, l0rinc)

Thread notes: Author replied to sipa explaining why encapsulation should precede concrete in-memory optimization experiments. No new claims were raised.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
ajtowns (member)objection, approachEncapsulating CTransaction and removing const member variables risks mutability footguns and introduces broad churn across consensus-critical code for no immediate benefit.contested · ajtowns maintains Concept NACK, reiterating that the PR does not add enough value to warrant review and should not be mergedchangedyes2026-06-20: 'This doesn't seem like a good idea at all to me. If you want an object that can represent different transactions over its lifetime, you should be using CMutableTransaction'
Status from: 2026-09-21: ajtowns: 'I don't believe it should be, and don't believe it adds enough value to even warrant the level of review it's been given'
josibake (member)objection, safetyAs long as CTransactionRef shares the CTransaction wrapper, allowing assignment leaves a potential non-const alias footgun.fixed · fix pushed · resolved by deleting copy-assignment operatoransweredyes2026-06-22: 'As long as CTransactionRef shares the CTransaction wrapper, making that wrapper assignable leaves a possible non-const-alias footgun.'
Status from: 2026-06-23: josibake: 'Thanks for taking the suggestions regarding removing the assignment operator, and removing the default move constructor.'
josibake (member)objection, safetyDefault move constructor on non-const members could memberwise move vin/vout vectors and leave invalid cached witness/hash fields on the moved-from instance.fixed · fix pushed · resolved by not providing the default move constructoransweredyes2026-06-22: 'vin/vout, being vectors, move could actually mean move the underlying storage. In light of that, I think we should delete the move constructer during the transition.'
Status from: 2026-06-23: josibake: 'Thanks for taking the suggestions regarding removing the assignment operator, and removing the default move constructor.'
maflcko (member)objection, safetyAccessor methods returning references without LIFETIMEBOUND attribute can lead to dangling reference bugs.fixed · fix pushed · resolved by adding LIFETIMEBOUNDansweredyes2026-06-26: 'In any case, I think `LIFETIMEBOUND` could be added here?'
Status from: 2026-06-26: purpleKarrot: 'Also added `LIFETIMEBOUND`.'
mzumsande (member)objection, interfaceRenaming well-established protocol-level names like vin and vout to GetInputs and GetOutputs creates confusion between internal Core code and external RPC/protocol terms.contested · mzumsande reiterated the concern after the author explained their naming choicesansweredyes2026-07-02: 'On a language level, terms such as vin, vout etc. have long become part of the public protocol: For example encapsulating vin through GetInputs() internally within bitcoin core, just to expose them as'
Status from: 2026-08-12: mzumsande: 'pointed out the potential confusion of encapsulating things on an intermediate level under a different name, that are th'
l0rinc (member)objection, maintenanceHardcoded scaffolding clang-tidy check left in the tree adds maintenance baggage if not reusable.fixed · fix pushed · generalized tidy check to use attribute annotationsansweredyes2026-07-02: 'After the final commit, the compiler enforces the CTransaction boundary, so this check is only scaffolding unless we plan to keep using it for other classes.'
Status from: 2026-08-06: theuni: 'This is an amazing use of bitcoin-tidy. Nice work getting the attribute/annotation machinery working.'
l0rinc (member)objection, approachMassive call-site refactor in critical code without clear immediate benefit, adding duplicate accessors to CMutableTransaction to paper over generic code.contested · l0rinc reiterated Approach NACK, objecting to pushing the refactor despite multiple objectionschangedyes2026-07-03: 'Approach NACK for now. I don't see a concrete benefit here that justifies rewriting so many transaction call sites in consensus, policy, mempool, wallet, tests, fuzzers, and kernel-facing code.'
Status from: 2026-07-07: l0rinc: 'My main concern is the approach: I could agree with it if the path were broken into smaller manually reviewable steps wi'
alexanderwiederin (member)question, maintenanceDoes this check fire in the current tree, or is it left over / anticipating future use?openansweredno2026-09-09: 'Does this fire in the current tree? Left over from annotating something else, or anticipating a future use of the check?'
maflcko (member)objection, safetyRemoving const from data members loses compiler checks enforcing complete initialization.fixed · fix pushed · author agreed and pushed an update retaining const on the data memberschangedyes2026-09-18: 'I think it is nice to have minimally more compiler checks to enforce initialization, also to keep the code as-is for now, and only change this in the future, when there is a real need or real observab'
Status from: 2026-09-24: purpleKarrot: 'Pushed to resolve merge conflicts and updated the description. Removed all mentions of `const`'

Support:

  • w0xlt (contributor) (Concept ACK, 2026-06-21): Concept ACK without further details. [not substantive]
  • josibake (member) (Concept ACK, 2026-06-22) [read]: Moving data members behind observers rather than letting class layout define the API is a solid improvement towards regular value handles over immutable data.
  • janb84 (member) (Concept ACK, 2026-06-22) [read: transaction.h]: Supports encapsulation and moving towards an immutable handle body approach.
  • josibake (member) (ACK, 2026-06-23) [read]: Adding observer functions moves towards an overall better design where callers do not depend on class layout, separating serialization and transaction API from concrete layout.
  • willcl-ark (member) (Concept ACK, 2026-08-13): Concept ACK without elaboration. [not substantive]
  • ryanofsky (member) (Concept ACK, 2026-08-14) [read]: Does not see practical downsides worth blocking over, and finds the techniques and direction valuable.
  • alexanderwiederin (member) (ACK, 2026-09-09) [both]: Verified the clang-tidy fixup reproduces cleanly (86 files modified, byte-identical or minor diffs in object files), full unit and functional tests pass.
  • josibake (member) (ACK, 2026-09-21): reACK 4c7f4aed9a40f250bc6b21d51e09572a9e690b61 [not substantive]
  • hodlinator (member) (2026-09-21) [read]: Positive towards the PR and attempted to reconcile differences; values regular types and encapsulation leading to long-term possibilities like slab allocation or thin validation APIs, while acknowledging concerns about churn.
  • willcl-ark (member) (2026-09-22) [read]: Argues that better architecture is a legitimate benefit; observer functions establish a boundary between transaction users and storage, enabling CTransaction to become a regular value type backed by immutable data.

Participants:

  • fanquake (member, neutral, 1 statements 2026-06-19 to 2026-06-19): asked for relevant parts to be put in PR description
  • ajtowns (member, objection, 9 statements 2026-06-20 to 2026-09-21): Concept NACK: high churn across consensus-critical code for no immediate benefit, opposes removing const from CTransaction members
  • janb84 (member, support, 3 statements 2026-06-21 to 2026-06-22): Concept ACK; spotted missed conversion in transaction.h
  • w0xlt (contributor, support, 1 statements 2026-06-21 to 2026-06-21): Concept ACK
  • josibake (member, support, 24 statements 2026-06-22 to 2026-09-21): ACK and reACKs; strongly supports encapsulation and moving to regular value handle over immutable data
  • maflcko (member, objection, 13 statements 2026-06-25 to 2026-09-21): prefers retaining const members to enforce complete initialization; suggested closing or drafting if too controversial
  • alexanderwiederin (member, support, 6 statements 2026-06-29 to 2026-09-09): ACK; verified clang-tidy fixup reproduces cleanly, compared object files, test suite passes; asked about tidy check usage
  • ryanofsky (member, support, 2 statements 2026-06-30 to 2026-08-14): Concept ACK; sees value in techniques and direction, suggested CBlock as another candidate
  • l0rinc (member, objection, 18 statements 2026-07-02 to 2026-09-21): Approach NACK; high churn across critical code without concrete benefit, duplicate getters on CMutableTransaction, prefers smaller steps retaining protocol names
  • mzumsande (member, objection, 3 statements 2026-07-02 to 2026-08-12): disagrees with refactoring consensus types on generic guidelines alone and renaming protocol terms like vin/vout
  • jonatack (member, neutral, 1 statements 2026-07-21 to 2026-07-21): noted needs rebase
  • theuni (member, support, 1 statements 2026-08-06 to 2026-08-06): praised bitcoin-tidy attribute/annotation machinery, suggested keeping it in-tree for now
  • hodlinator (member, support, 2 statements 2026-08-13 to 2026-09-21): positive toward the PR; attempted reconciliation, highlighted potential future benefits like slab allocators while acknowledging concerns about churn
  • willcl-ark (member, support, 2 statements 2026-08-13 to 2026-09-22): Concept ACK; argued for architectural value of encapsulation and establishing storage boundaries
  • sipa (member, neutral, 2 statements 2026-08-19 to 2026-10-16): discussed CScriptWitness representation changes in #36521 and experimenting with packing CTransaction into blobs, noting encapsulation is easier to argue when paired with practical performance/memory
  • sedited (member, neutral, 1 statements 2026-08-23 to 2026-08-23): reminded participants to keep comments on topic and take meta-commentary to meta repo

Dependencies

Enables:

Files

342 lines under test/bench/ci.

  • src/validation.cpp +38/-38
  • src/wallet/wallet.cpp +35/-35
  • src/script/interpreter.cpp +34/-34
  • contrib/devtools/bitcoin-tidy/use-observers.cpp +55/-0
  • src/wallet/spend.cpp +19/-19
  • src/policy/policy.cpp +18/-18
  • src/rpc/blockchain.cpp +18/-18
  • src/txmempool.cpp +18/-18
  • src/consensus/tx_verify.cpp +17/-17
  • src/test/script_tests.cpp +17/-17
  • src/primitives/transaction.h +21/-11
  • src/wallet/receive.cpp +15/-15
  • src/kernel/bitcoinkernel.cpp +12/-12
  • src/policy/truc_policy.cpp +12/-12
  • src/test/fuzz/package_eval.cpp +12/-12
  • src/test/fuzz/txorphan.cpp +12/-12
  • src/core_io.cpp +11/-11
  • src/test/transaction_tests.cpp +11/-11
  • src/test/coinsviewoverlay_tests.cpp +9/-9
  • src/wallet/feebumper.cpp +8/-8
  • src/wallet/interfaces.cpp +8/-8
  • src/node/txorphanage.cpp +7/-7
  • src/qt/transactiondesc.cpp +7/-7
  • contrib/devtools/bitcoin-tidy/use-observers.h +13/-0
  • src/consensus/tx_check.cpp +6/-6
  • src/index/coinstatsindex.cpp +6/-6
  • src/policy/packages.cpp +6/-6
  • src/psbt.cpp +6/-6
  • src/test/fuzz/connect_block.cpp +6/-6
  • src/test/fuzz/utxo_total_supply.cpp +6/-6
  • src/coins.cpp +5/-5
  • src/signet.cpp +5/-5
  • src/wallet/rpc/spend.cpp +5/-5
  • src/attributes.h +9/-0
  • contrib/devtools/bitcoin-tidy/CMakeLists.txt +7/-1
  • src/policy/ephemeral_policy.cpp +4/-4
  • src/test/fuzz/script_flags.cpp +4/-4
  • src/test/util/txmempool.cpp +4/-4
  • src/common/bloom.cpp +3/-3
  • src/core_memusage.h +3/-3
  • src/node/miner.cpp +3/-3
  • src/test/coins_tests.cpp +3/-3
  • src/test/fuzz/coins_view.cpp +3/-3
  • src/test/fuzz/tx_pool.cpp +3/-3
  • src/test/miniminer_tests.cpp +3/-3
  • src/test/util/transaction_utils.cpp +3/-3
  • src/wallet/test/wallet_tests.cpp +3/-3
  • src/bench/mempool_stress.cpp +2/-2
  • src/consensus/validation.h +2/-2
  • src/index/txospenderindex.cpp +2/-2
  • src/node/mini_miner.cpp +2/-2
  • src/node/txdownloadman_impl.cpp +2/-2
  • src/policy/policy.h +2/-2
  • src/psbt.h +2/-2
  • src/qt/transactionrecord.cpp +2/-2
  • src/rpc/mining.cpp +2/-2
  • src/rpc/rawtransaction.cpp +2/-2
  • src/test/fuzz/cmpctblock.cpp +2/-2
  • src/test/fuzz/mini_miner.cpp +2/-2
  • src/test/fuzz/utxo_snapshot.cpp +2/-2
  • src/test/script_assets_tests.cpp +2/-2
  • src/test/txvalidation_tests.cpp +2/-2
  • src/test/validation_block_tests.cpp +2/-2
  • src/wallet/rpc/coins.cpp +2/-2
  • src/wallet/rpc/transactions.cpp +2/-2
  • src/wallet/scriptpubkeyman.cpp +2/-2
  • src/wallet/test/spend_tests.cpp +2/-2
  • contrib/devtools/bitcoin-tidy/bitcoin-tidy.cpp +2/-0
  • src/bench/blockencodings.cpp +1/-1
  • src/bench/coin_selection.cpp +1/-1
  • src/bench/mempool_ephemeral_spends.cpp +1/-1
  • src/bench/txorphanage.cpp +1/-1
  • src/blockfilter.cpp +1/-1
  • src/node/psbt.cpp +1/-1
  • src/node/transaction.cpp +1/-1
  • src/policy/truc_policy.h +1/-1
  • src/primitives/transaction.cpp +1/-1
  • src/qt/walletmodeltransaction.cpp +1/-1
  • src/rpc/mempool.cpp +1/-1
  • src/test/fuzz/script_interpreter.cpp +1/-1
  • src/test/fuzz/script_sign.cpp +1/-1
  • src/test/fuzz/txdownloadman.cpp +1/-1
  • src/test/fuzz/util.cpp +1/-1
  • src/test/miner_tests.cpp +1/-1
  • src/test/sighash_tests.cpp +1/-1
  • src/test/sigopcount_tests.cpp +1/-1
  • src/test/txospenderindex_tests.cpp +1/-1
  • src/test/txvalidationcache_tests.cpp +1/-1
  • src/test/util/setup_common.cpp +1/-1
  • src/test/validation_tests.cpp +1/-1
  • src/util/rbf.cpp +1/-1
  • src/wallet/test/coinselector_tests.cpp +1/-1
  • src/wallet/test/group_outputs_tests.cpp +1/-1
  • src/wallet/transaction.h +1/-1

Card

The short text the ranking pass compares across PRs.

Encapsulates CTransaction data members behind public observer methods (such as GetInputs() and GetOutputs()) and makes the underlying members private. Callers across the codebase currently access public members directly, freezing the internal layout into the public API and preventing representations like shared block state or immutable transaction buffers. The mechanical migration across 94 files was automated via a custom bitcoin-tidy check included in the PR. This encapsulation supports broader interface segregation efforts, including the proposed stateless validation library in #35904.

Data

dossier JSON · extract JSON · model ledger, generated 2026-10-22T01:14, confidence high, input hash 6749bd22468c0e1e
thread read through 2026-10-21 (131 statements), head 5bffd2c47a
labels: Refactoring, Validation, Needs rebase

Categories

Validation

P4 · cleanup

Encapsulates CTransaction members behind observer methods as an architectural cleanup without immediate consensus or validation benefits. The author notes that representation decoupling is speculative and that the change is 'not a prerequisite for the stateless validation library'.

Membership: Carries the Validation label and modifies the public interface of the core consensus type CTransaction in src/primitives/transaction.h.

Factors: security/stability 0, bug 0, performance 0, user value 0, leverage 1