Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
67a2c96
chainparams: encapsulate deployment configuration logic
darosior Sep 29, 2025
6665296
chainparams: make deployment configuration options always available i…
darosior Sep 29, 2025
832bf23
======= Consensus Cleanup BEGINS HERE =======
darosior Oct 14, 2025
337b5b7
binana: add Consensus Cleanup
darosior Sep 4, 2025
bba7033
scripted-diff: rename MAX_TX_LEGACY_SIGOPS to MAX_TX_BIP54_SIGOPS
darosior Jan 20, 2026
9a93ad6
moveonly: move CheckSigopsBIP54 from policy to consensus
darosior Sep 4, 2025
0c95b4e
validation: make BIP54 sigops check consensus-critical
darosior Oct 14, 2025
e042fb3
qa: add to utilities a version of SignSignature for Taproot inputs
darosior Sep 16, 2025
7cf3bcd
qa: extensive unit tests for BIP54 legacy sigops limit
darosior Sep 22, 2025
dd6ec8a
fuzz: add a fuzz target for the BIP54 sigops check
darosior Sep 17, 2025
d1513dd
scripted-diff: rename testnet4 timewarp constant
darosior Jan 31, 2025
5cb69e2
miner: update a timewarp comment to refer specifically to BIP 54
darosior Feb 4, 2026
565d62f
consensus: prevent timewarp attacks with a 2h grace period
darosior Sep 5, 2025
943cfb0
consensus: prevent negative difficulty adjustment intervals
darosior Jan 31, 2025
d003a5c
qa: BIP54 test vectors for timewarp and Murch-Zawy
darosior Sep 29, 2025
d2b456e
consensus: mandate coinbase transactions be timelocked to block height
darosior Mar 10, 2025
f44c3b4
qa: BIP54 test vectors for restrictions on coinbase transactions
darosior Oct 1, 2025
b663f62
Avoid creating <= 64-byte transactions in most functional tests.
TheBlueMatt Feb 25, 2019
98244e6
[test] Separate 64B and 63B tx size tests
ajtowns Mar 28, 2023
b975047
consensus: make 64 bytes transactions invalid
darosior Mar 11, 2025
bccf3bb
qa: unit tests for BIP54 rule on 64-byte transactions (with JSON test…
darosior Sep 22, 2025
09123d8
scripted-diff: qa: rename constant for BIP54 sigops limit
darosior Oct 16, 2025
55cd9a5
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 @@ -158,6 +158,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
common/system.cpp
common/url.cpp
compressor.cpp
consensus/tx_verify.cpp
core_read.cpp
core_write.cpp
deploymentinfo.cpp
Expand Down Expand Up @@ -233,7 +234,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
7 changes: 7 additions & 0 deletions src/binana/consensuscleanup.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"binana": [2025, 1, 0],
"deployment": "CONSENSUSCLEANUP",
"scriptverify": false,
"scriptverify_discourage": false,
"opcodes": {}
}
85 changes: 54 additions & 31 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,30 +42,8 @@ static void HandleRenounceArgs(const ArgsManager& args, CChainParams::RenouncePa
}
}

void ReadSigNetArgs(const ArgsManager& args, CChainParams::SigNetOptions& options)
static void HandleDeploymentArgs(const ArgsManager& args, CChainParams::DeploymentOptions& 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);
}
HandleRenounceArgs(args, options.renounce);
}

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;

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

HandleRenounceArgs(args, options.renounce);

for (const std::string& strDeployment : args.GetArgs("-vbparams")) {
std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':');
if (vDeploymentParams.size() != 3) {
Expand Down Expand Up @@ -115,6 +91,44 @@ 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);
}
HandleRenounceArgs(args, options.renounce);
}

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);
HandleRenounceArgs(args, options.renounce);
}

static std::unique_ptr<const CChainParams> globalChainParams;

const CChainParams &Params() {
Expand All @@ -125,12 +139,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("-renounce=deployment", "Unconditionally disable an heretical deployment attempt", 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);
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
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
10 changes: 10 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,16 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
const ArgsManager& args = *Assert(node.args);
const CChainParams& chainparams = Params();

// 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."));
}
}

// Disallow mainnet/testnet operation
if (Params().GetChainType() == ChainType::MAIN || Params().GetChainType() == ChainType::TESTNET) {
return InitError(Untranslated(strprintf("Selected network '%s' is unsupported for this client, select -regtest or -signet instead.\n", Params().GetChainTypeString())));
Expand Down
Loading
Loading