Problem
Today, consensus validation is tightly coupled to the node implementation. As a result, unit tests require substantial test setup, and validation cannot be reused independently of the node's state-management implementation.
This issue proposes refactoring the validation code into a stateless layer with explicit inputs while preserving most of the existing code, behavior, and architecture.
Testing
The resulting validation interface will provide a dedicated unit-testing surface. Consensus validation can be tested by supplying only the required consensus inputs through lightweight implementations of ChainView and CoinIndex, without constructing ChainstateManager, CBlockIndex, or CCoinsViewCache.
Refactoring plan
The following actions have been identified to be necessary towards the goal of providing a standalone validation library. For a large part, those actions are independent of each other and can be done independently, in any order.
Expose the validation interface
Make the existing
CheckBlockHeader,ContextualCheckBlockHeader, andContextualCheckBlockfunctions part of the public validation interface by declaring them invalidation.h.Interface Segregation: Remove dependency on
ChainstateManagerContextualCheck*functions need aChainstateManageronly to access the consensus parameters. PassConsensus::Paramsinstead and access them at the call site.Make environmental dependencies explicit
Pass the validation time explicitly to
ContextualCheckBlockHeaderinstead of reading the system clock internally.Replace
CBlockIndexwithChainViewIntroduce a
ChainViewabstraction representing the chain history required by contextual validation. Wrap the existingCBlockIndex-based implementation to provide this interface.Introduce
CoinIndexIntroduce a read-only
CoinIndexabstraction for the UTXO lookups performed during validation. WrapCCoinsViewCacheto provide this interface.Extract validation from
ConnectBlockExtract the consensus-validation logic from
ConnectBlockinto a stateless operation depending only on the block and its explicit consensus inputs (Consensus::Params,ChainView,CoinIndex). Further abstractions may become necessary (async execution, caching).ConnectBlockremains responsible for orchestrating validation and applying the resulting state transition.
More Interface Segregation
Many consensus relevant types, like CBlock, CTransaction, or uint256 provide functionality that is not required for validation, like mutability, stringification, or construction from a string representation. Decoupling such functionality from the type and instead providing it as utilities for testing or the wallet will further narrow the surface of a validation API.
Rewrite deserialization code in a way that it does not mutate individual data members
Current serdes code is hard to reason about because it heavily relies on overload resolution. It is also one of the very few places that impose a mutability requirement on many types. Here is a proof of concept that an alternative is possible: https://github.com/purpleKarrot/std-bitcoin/blob/master/src/serdes_decode.cpp
Rewrite test code that mutates individual data members
Many tests span multiple test cases sharing the same testing data, where each test case performs an assertion after applying a modification to the shared test data. This is problematic because it is hard to diagnose test failures. This is probably the second place out of two where mutability is required. Rewriting those test cases to be independent also removes the mutability requirement.
Make primitive types like
CBlock,CTransaction,COutPoint, etc immutableTypes that no longer have the requirement that their data members need to be individually mutable, should no longer provide ways to modify them. Their data members should be made private, with public observers.
CMutableTransactionshould no longer be necessary by now.A block is not a block header, and its transactions are not optional
CBlockshould have rather than be aCBlockHeader(inheritance establishes an is-a relationship).CBlockshould further ownCTransactionobjects, rather than pointers which could benullptr.CBlockandCTransactionmay be implemented in terms ofstd::shared_ptr<const impl>internally and provide a never-empty guarantee as part of their invariant.Replace
ToStringfunctions withstd::formatterspecializationsFactor out string parsing helpers
The use case for constructing a type from a string representation is very narrow. Performing the latter in a constant expression is exclusively required for testing. Such functionality should live in utilities rather than the constructor. User-defined literals are also good candidates for such utilities.
Relation to the Bitcoin Kernel library
The validation library provides stateless consensus validation. The Bitcoin Kernel library can build on it by providing concrete implementations of ChainView and CoinIndex while retaining responsibility for state management.