Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0963454
qa: regenerate hardcoded regtest chain for kernel lib unit tests
darosior May 20, 2026
8f3b630
chainparams: encapsulate deployment configuration logic
darosior Sep 29, 2025
7f3e966
chainparams: make deployment configuration available on all test netw…
darosior Sep 29, 2025
e4ee451
chainparams: add overloads for RegTest and SigNet with no options
darosior May 25, 2026
8129ea5
qa: use NORMAL_GBT_REQUEST_PARAMS consistently
darosior Apr 30, 2026
cd7447c
======= Consensus Cleanup BEGINS HERE =======
darosior Oct 14, 2025
92167d3
chainparams: add versionbits deployment for BIP 54
darosior Apr 30, 2026
4da25b8
scripted-diff: rename MAX_TX_LEGACY_SIGOPS to MAX_TX_BIP54_SIGOPS
darosior Jan 20, 2026
8845b7c
moveonly: move CheckSigopsBIP54 from policy to consensus
darosior Sep 4, 2025
a45789e
validation: make BIP54 sigops check consensus-critical
darosior Oct 14, 2025
f3d520c
qa: add to utilities a version of SignSignature for Taproot inputs
darosior Sep 16, 2025
4890710
qa: extensive unit tests for BIP54 legacy sigops limit
darosior Sep 22, 2025
6d7b527
fuzz: add a fuzz target for the BIP54 sigops check
darosior Sep 17, 2025
eaab858
scripted-diff: rename testnet4 timewarp constant
darosior Jan 31, 2025
9026122
miner: update a timewarp comment to refer specifically to BIP 54
darosior Feb 4, 2026
d105532
validation: prevent timewarp attacks with a 2h grace period
darosior Sep 5, 2025
f17c1b3
validation: prevent negative difficulty adjustment intervals
darosior Jan 31, 2025
9e34296
qa: BIP54 test vectors for timewarp and Murch-Zawy
darosior Sep 29, 2025
0db37aa
validation: enforce that coinbase transactions are timelocked to bloc…
darosior Mar 10, 2025
a9a631a
qa: BIP54 test vectors for restrictions on coinbase transactions
darosior Oct 1, 2025
fdb18f9
Avoid creating <= 64-byte transactions in most functional tests.
TheBlueMatt Feb 25, 2019
dd76ab8
[test] Separate 64B and 63B tx size tests
ajtowns Mar 28, 2023
fe2c4de
validation: make 64 bytes transactions invalid
darosior Mar 11, 2025
eb6e3c6
qa: unit tests for BIP54 rule on 64-byte transactions (with JSON test…
darosior Sep 22, 2025
9c4e0a2
scripted-diff: qa: rename constant for BIP54 sigops limit
darosior Oct 16, 2025
f916e95
qa: end-to-end test all BIP54 mitigations
darosior Oct 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
common/system.cpp
common/url.cpp
compressor.cpp
consensus/tx_verify.cpp
core_io.cpp
deploymentinfo.cpp
external_signer.cpp
Expand Down Expand Up @@ -181,7 +182,6 @@ add_library(bitcoin_node STATIC EXCLUDE_FROM_ALL
bip324.cpp
blockencodings.cpp
blockfilter.cpp
consensus/tx_verify.cpp
dbwrapper.cpp
deploymentstatus.cpp
flatfile.cpp
Expand Down
81 changes: 53 additions & 28 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,8 @@

using util::SplitString;

void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
{
if (!args.GetArgs("-signetseednode").empty()) {
options.seeds.emplace(args.GetArgs("-signetseednode"));
}
if (!args.GetArgs("-signetchallenge").empty()) {
const auto signet_challenge = args.GetArgs("-signetchallenge");
if (signet_challenge.size() != 1) {
throw std::runtime_error("-signetchallenge cannot be multiple values.");
}
const auto val{TryParseHex<uint8_t>(signet_challenge[0])};
if (!val) {
throw std::runtime_error(strprintf("-signetchallenge must be hex, not '%s'.", signet_challenge[0]));
}
options.challenge.emplace(*val);
}
}

void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& options)
static void HandleDeploymentArgs(const ArgsManager& args, CChainParams::DeploymentOptions& options)
{
if (auto value = args.GetBoolArg("-fastprune")) options.fastprune = *value;
if (HasTestOption(args, "bip94")) options.enforce_bip94 = true;

for (const std::string& arg : args.GetArgs("-testactivationheight")) {
const auto found{arg.find('@')};
if (found == std::string::npos) {
Expand Down Expand Up @@ -107,6 +86,43 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti
}
}

void ReadMainNetArgs(const ArgsManager& args, CChainParams::MainNetOptions& options)
{
HandleDeploymentArgs(args, options.dep_opts);
}

void ReadTestNetArgs(const ArgsManager& args, CChainParams::TestNetOptions& options)
{
HandleDeploymentArgs(args, options.dep_opts);
}

void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
{
if (!args.GetArgs("-signetseednode").empty()) {
options.seeds.emplace(args.GetArgs("-signetseednode"));
}
if (!args.GetArgs("-signetchallenge").empty()) {
const auto signet_challenge = args.GetArgs("-signetchallenge");
if (signet_challenge.size() != 1) {
throw std::runtime_error("-signetchallenge cannot be multiple values.");
}
const auto val{TryParseHex<uint8_t>(signet_challenge[0])};
if (!val) {
throw std::runtime_error(strprintf("-signetchallenge must be hex, not '%s'.", signet_challenge[0]));
}
options.challenge.emplace(*val);
}
HandleDeploymentArgs(args, options.dep_opts);
}

void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& options)
{
if (auto value = args.GetBoolArg("-fastprune")) options.fastprune = *value;
if (HasTestOption(args, "bip94")) options.enforce_bip94 = true;

HandleDeploymentArgs(args, options.dep_opts);
}

static std::unique_ptr<const CChainParams> globalChainParams;

const CChainParams &Params() {
Expand All @@ -117,12 +133,21 @@ const CChainParams &Params() {
std::unique_ptr<const CChainParams> CreateChainParams(const ArgsManager& args, const ChainType chain)
{
switch (chain) {
case ChainType::MAIN:
return CChainParams::Main();
case ChainType::TESTNET:
return CChainParams::TestNet();
case ChainType::TESTNET4:
return CChainParams::TestNet4();
case ChainType::MAIN: {
auto opts = CChainParams::MainNetOptions{};
ReadMainNetArgs(args, opts);
return CChainParams::Main(opts);
}
case ChainType::TESTNET: {
auto opts = CChainParams::TestNetOptions{};
ReadTestNetArgs(args, opts);
return CChainParams::TestNet(opts);
}
case ChainType::TESTNET4: {
auto opts = CChainParams::TestNetOptions{};
ReadTestNetArgs(args, opts);
return CChainParams::TestNet4(opts);
}
case ChainType::SIGNET: {
auto opts = CChainParams::SigNetOptions{};
ReadSigNetArgs(args, opts);
Expand Down
4 changes: 2 additions & 2 deletions src/chainparamsbase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman)
argsman.AddArg("-chain=<chain>", "Use the chain <chain> (default: main). Allowed values: " LIST_CHAIN_NAMES, ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. "
"This is intended for regression testing tools and app development. Equivalent to -chain=regtest.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-testactivationheight=name@height.", "Set the activation height of 'name' (segwit, bip34, dersig, cltv, csv). (test-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-testnet", "Use the testnet3 chain. Equivalent to -chain=test. Support for testnet3 is deprecated and will be removed in an upcoming release. Consider moving to testnet4 now by using -testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-testnet4", "Use the testnet4 chain. Equivalent to -chain=testnet4.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-vbparams=deployment:start:end[:min_activation_height]", "Use given start/end times and min_activation_height for specified version bits deployment (test-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signet", "Use the signet chain. Equivalent to -chain=signet. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_ANY | ArgsManager::DISALLOW_NEGATION, OptionsCategory::CHAINPARAMS);
Expand Down
17 changes: 16 additions & 1 deletion src/consensus/consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,26 @@ static const size_t MIN_SERIALIZABLE_TRANSACTION_WEIGHT = WITNESS_SCALE_FACTOR *
/** Interpret sequence numbers as relative lock-time constraints. */
static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0);

/**
* Under BIP54, the first block in a difficulty adjustment period must not be more than 2
* hours (7200 seconds) earlier than the last block of the previous period.
*/
static constexpr int64_t MAX_TIMEWARP_BIP54{2 * 60 * 60};

/**
* Maximum number of seconds that the timestamp of the first
* block of a difficulty adjustment period is allowed to
* be earlier than the last block of the previous period (BIP94).
*/
static constexpr int64_t MAX_TIMEWARP = 600;
static constexpr int64_t MAX_TIMEWARP_TESTNET4 = 600;

/** The maximum number of potentially executed legacy signature operations in a single tx */
static constexpr unsigned int MAX_TX_BIP54_SIGOPS{2'500};

/**
* 64-byte transactions are invalid (BIP 54) due to serious flaws in the Merkle tree algorithm
* that make it so that such transactions may be re-interpreted as inner tree nodes.
*/
static constexpr unsigned int INVALID_TX_NONWITNESS_SIZE{64};

#endif // BITCOIN_CONSENSUS_CONSENSUS_H
1 change: 1 addition & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ constexpr bool ValidDeployment(BuriedDeployment dep) { return dep <= DEPLOYMENT_
enum DeploymentPos : uint16_t {
DEPLOYMENT_TESTDUMMY,
DEPLOYMENT_TAPROOT, // Deployment of Schnorr/Taproot (BIPs 340-342)
DEPLOYMENT_CONSENSUSCLEANUP, // Deployment of BIP 54
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand Down
32 changes: 31 additions & 1 deletion src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,44 @@ int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& i
return nSigOps;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee)
bool Consensus::CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs)
{
Assert(!tx.IsCoinBase());

unsigned int sigops{0};
for (const auto& txin: tx.vin) {
const auto& prev_txo{inputs.AccessCoin(txin.prevout).out};

// Unlike the existing block wide sigop limit which counts sigops present in the block
// itself (including the scriptPubKey which is not executed until spending later), BIP54
// counts sigops in the block where they are potentially executed (only).
// This means sigops in the spent scriptPubKey count toward the limit.
// `fAccurate` means correctly accounting sigops for CHECKMULTISIGs(VERIFY) with 16 pubkeys
// or fewer. This method of accounting was introduced by BIP16, and BIP54 reuses it.
// The GetSigOpCount call on the previous scriptPubKey counts both bare and P2SH sigops.
sigops += txin.scriptSig.GetSigOpCount(/*fAccurate=*/true);
sigops += prev_txo.scriptPubKey.GetSigOpCount(txin.scriptSig);

if (sigops > MAX_TX_BIP54_SIGOPS) {
return false;
}
}

return true;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, bool enforce_bip54)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
return state.Invalid(TxValidationResult::TX_MISSING_INPUTS, "bad-txns-inputs-missingorspent",
strprintf("%s: inputs missing/spent", __func__));
}

if (enforce_bip54 && !Consensus::CheckSigopsBIP54(tx, inputs)) {
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-legacy-sigops", "too many legacy sigops (BIP54)");
}

CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
Expand Down
8 changes: 7 additions & 1 deletion src/consensus/tx_verify.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ class TxValidationState;
/** Transaction validation functions */

namespace Consensus {
/**
* Check the total number of non-witness sigops across the whole transaction, as per BIP54.
*/
bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inputs);

/**
* Check whether all inputs of this transaction are valid (no double spends and amounts)
* This does not modify the UTXO set. This does not check scripts and sigs.
* @param[out] txfee Set to the transaction fee if successful.
* @param[in] enforce_bip54 Whether to perform the BIP54 sigops check.
* Preconditions: tx.IsCoinBase() is false.
*/
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee);
[[nodiscard]] bool CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, bool enforce_bip54);
} // namespace Consensus

/** Auxiliary functions for transaction validation (ideally should not be exposed) */
Expand Down
4 changes: 4 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const std::array<VBDeploymentInfo,Consensus::MAX_VERSION_BITS_DEPLOYMENTS> Versi
.name = "taproot",
.gbt_optional_rule = true,
},
VBDeploymentInfo{
.name = "consensuscleanup",
.gbt_optional_rule = false,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down
10 changes: 10 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,16 @@ bool AppInitParameterInteraction(const ArgsManager& args)
}
}

// Prevent setting deployment parameters on mainnet.
if (chainparams.GetChainType() == ChainType::MAIN) {
if (args.IsArgSet("-testactivationheight")) {
return InitError(_("The -testactivationheight option may not be used on mainnet."));
}
if (args.IsArgSet("-vbparams")) {
return InitError(_("The -vbparams option may not be used on mainnet."));
}
}

// Also report errors from parsing before daemonization
{
kernel::Notifications notifications{};
Expand Down
4 changes: 2 additions & 2 deletions src/kernel/bitcoinkernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -799,10 +799,10 @@ btck_ChainParameters* btck_chain_parameters_create(const btck_ChainType chain_ty
return btck_ChainParameters::ref(const_cast<CChainParams*>(CChainParams::TestNet4().release()));
}
case btck_ChainType_SIGNET: {
return btck_ChainParameters::ref(const_cast<CChainParams*>(CChainParams::SigNet({}).release()));
return btck_ChainParameters::ref(const_cast<CChainParams*>(CChainParams::SigNet().release()));
}
case btck_ChainType_REGTEST: {
return btck_ChainParameters::ref(const_cast<CChainParams*>(CChainParams::RegTest({}).release()));
return btck_ChainParameters::ref(const_cast<CChainParams*>(CChainParams::RegTest().release()));
}
}
assert(false);
Expand Down
Loading
Loading