Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7de9918
versionbits: add expiry support to versionbit deployments
dathonohm Nov 18, 2025
a6fc78a
versionbits: add max_activation_height for mandatory BIP9 activation
dathonohm Nov 18, 2025
8cd9085
chainparams: support regtest vbparams for max_activation_height and a…
dathonohm Nov 19, 2025
b492df6
test: add versionbits unit tests for max_activation_height and active…
dathonohm Jan 27, 2026
88c569d
versionbits: add per-deployment activation threshold
dathonohm Dec 29, 2025
becf3de
chainparams: support regtest vbparams for per-deployment threshold
dathonohm Dec 29, 2025
6a57462
versionbits: add EXPIRED state to BIP9 state machine for temporary de…
dathonohm Feb 11, 2026
1bf0c35
versionbits: add mandatory signaling enforcement for max_activation_h…
dathonohm Nov 19, 2025
f664525
script: use TAPROOT_CONTROL_MAX_NODE_COUNT_REDUCED for non-consensus …
luke-jr Oct 3, 2025
41abcea
policy: enforce SCRIPT_VERIFY_REDUCED_DATA as a policy rule
luke-jr Oct 4, 2025
f595e6a
script: Define SCRIPT_VERIFY_REDUCED_DATA verification flag (unused) …
luke-jr Oct 6, 2025
9efe141
script: forbid Taproot annex with SCRIPT_VERIFY_REDUCED_DATA
luke-jr Oct 3, 2025
e1ae973
script: Forbid OP_IF in Tapscript with SCRIPT_VERIFY_REDUCED_DATA (st…
luke-jr Oct 3, 2025
4743b66
script: Limit Taproot control block to 257 bytes for SCRIPT_VERIFY_RE…
luke-jr Oct 3, 2025
1984769
script: require empty witness for P2A spends
dathonohm Feb 12, 2026
f58d34b
consensus: Add no-op flags to CheckTxInputs function
luke-jr Oct 3, 2025
34621ba
consensus: Define CheckTxInputsRules::OutputSizeLimit flag (unused) t…
luke-jr Oct 7, 2025
091c18c
consensus: When CheckTxInputsRules::OutputSizeLimit is enforced (stil…
luke-jr Oct 7, 2025
d220041
policy: limit datacarriersize config to MAX_OUTPUT_DATA_SIZE (=83 B)
luke-jr Oct 4, 2025
7860d8d
chainparams: add DEPLOYMENT_REDUCED_DATA temporary BIP9 deployment
dathonohm Nov 19, 2025
0c9c50e
consensus: Enforce SCRIPT_VERIFY_REDUCED_DATA if DEPLOYMENT_REDUCED_D…
luke-jr Oct 4, 2025
45c004e
consensus: Enforce CheckTxInputsRules::OutputSizeLimit when DEPLOYMEN…
luke-jr Oct 4, 2025
224c1c3
consensus: apply output size limit to generation transactions
dathonohm Jan 14, 2026
881d4ab
consensus: Enforce SCRIPT_VERIFY_DISCOURAGE_{UPGRADABLE_WITNESS_PROGR…
luke-jr Oct 4, 2025
286a923
Refactor: Include all reduced_data verify flags in REDUCED_DATA_MANDA…
luke-jr Nov 3, 2025
48118a7
validation: Extend CheckInputScripts to allow overriding script valid…
luke-jr Nov 3, 2025
71722a2
validation: Exempt inputs spending UTXOs prior to reduced_data_start_…
luke-jr Nov 3, 2025
a6ac602
test: implement functional tests for ReducedData Spec
3c853b6299 Nov 5, 2025
3428f6c
test: Add UTXO height-based REDUCED_DATA enforcement test
dathonohm Nov 7, 2025
14dd104
test: add functional tests for modified BIP9 temporary deployment
dathonohm Jan 14, 2026
1b1ef4f
doc: add BIP-110 to bips.md
dathonohm Mar 5, 2026
90760ff
Policy: Do not allow user to bypass SCRIPT_VERIFY_DISCOURAGE_{UPGRADA…
luke-jr Oct 4, 2025
983555f
net: preferentially peer with nodes enforcing RDTS; ask DNS seed for …
dathonohm May 31, 2017
f62f5fd
net: allow up to 2 non-BIP110 outbound peers
dathonohm Jan 13, 2026
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
1 change: 1 addition & 0 deletions doc/bips.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ BIPs that are implemented by Bitcoin Knots:
* [`BIP 90`](https://github.com/bitcoin/bips/blob/master/bip-0090.mediawiki): Trigger mechanism for activation of BIPs 34, 65, and 66 has been simplified to block height checks since **v0.14.0** ([PR #8391](https://github.com/bitcoin/bitcoin/pull/8391)).
* [`BIP 93`](https://github.com/bitcoin/bips/blob/master/bip-0093.mediawiki): Support for importing codex32 seeds via the `importdescriptors` RPC method is available as of **v25.0.knots20230823** ([PR #27351](https://github.com/bitcoin/bitcoin/pull/27351)).
* [`BIP 94`](https://github.com/bitcoin/bips/blob/master/bip-0094.mediawiki): Testnet 4 (`-testnet4`) supported as of **v28.0** ([PR #29775](https://github.com/bitcoin/bitcoin/pull/29775)).
* [`BIP 110`](https://github.com/bitcoin/bips/blob/master/bip-0110.mediawiki): ReducedData Temporary Softfork (RDTS) consensus rules are implemented as of **FIXME: update version** ([PR #238](https://github.com/bitcoinknots/bitcoin/pull/238)).
* [`BIP 111`](https://github.com/bitcoin/bips/blob/master/bip-0111.mediawiki): `NODE_BLOOM` service bit added, and enforced for all peer versions as of **v0.13.0** ([PR #6579](https://github.com/bitcoin/bitcoin/pull/6579) and [PR #6641](https://github.com/bitcoin/bitcoin/pull/6641)).
* [`BIP 112`](https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki): The CHECKSEQUENCEVERIFY opcode has been implemented since **v0.12.1** ([PR #7524](https://github.com/bitcoin/bitcoin/pull/7524)), and has been *buried* since **v0.19.0** ([PR #16060](https://github.com/bitcoin/bitcoin/pull/16060)).
* [`BIP 113`](https://github.com/bitcoin/bips/blob/master/bip-0113.mediawiki): Median time past lock-time calculations have been implemented since **v0.12.1** ([PR #6566](https://github.com/bitcoin/bitcoin/pull/6566)), and has been *buried* since **v0.19.0** ([PR #16060](https://github.com/bitcoin/bitcoin/pull/16060)).
Expand Down
3 changes: 3 additions & 0 deletions src/bitcoin-cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ class NetinfoRequestHandler : public BaseRequestHandler
str += 'T';
} else if (s == "UTREEXO_TMP?") {
str += 'y';
} else if (s == "REDUCED_DATA?") {
str += '4';
} else {
str += ToLower(s[0]);
}
Expand Down Expand Up @@ -765,6 +767,7 @@ class NetinfoRequestHandler : public BaseRequestHandler
" \"T\" - UTREEXO_ARCHIVE peer can handle Utreexo proof requests for all historical blocks\n"
" \"y\" - UTREEXO_TMP? peer can handle Utreexo proof requests\n"
" \"r\" - REPLACE_BY_FEE? peer supports replacement of transactions without BIP 125 signalling\n"
" \"4\" - REDUCED_DATA? peer enforces the ReducedData SoftFork\n"
" \"m\" - MALICIOUS? peer openly seeks to aid in bypassing network policy/spam filters (OR to sabotage nodes that seek to)\n"
" \"u\" - UNKNOWN: unrecognized bit flag\n"
" v Version of transport protocol used for the connection\n"
Expand Down
25 changes: 22 additions & 3 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti

for (const std::string& strDeployment : args.GetArgs("-vbparams")) {
std::vector<std::string> vDeploymentParams = SplitString(strDeployment, ':');
if (vDeploymentParams.size() < 3 || 4 < vDeploymentParams.size()) {
throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height]");
if (vDeploymentParams.size() < 3 || 7 < vDeploymentParams.size()) {
throw std::runtime_error("Version bits parameters malformed, expecting deployment:start:end[:min_activation_height[:max_activation_height[:active_duration[:threshold]]]]");
}
CChainParams::VersionBitsParameters vbparams{};
if (!ParseInt64(vDeploymentParams[1], &vbparams.start_time)) {
Expand All @@ -94,12 +94,31 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti
} else {
vbparams.min_activation_height = 0;
}
if (vDeploymentParams.size() >= 5) {
if (!ParseInt32(vDeploymentParams[4], &vbparams.max_activation_height)) {
throw std::runtime_error(strprintf("Invalid max_activation_height (%s)", vDeploymentParams[4]));
}
}
if (vDeploymentParams.size() >= 6) {
if (!ParseInt32(vDeploymentParams[5], &vbparams.active_duration)) {
throw std::runtime_error(strprintf("Invalid active_duration (%s)", vDeploymentParams[5]));
}
Comment thread
dathonohm marked this conversation as resolved.
}
if (vDeploymentParams.size() >= 7) {
if (!ParseInt32(vDeploymentParams[6], &vbparams.threshold)) {
throw std::runtime_error(strprintf("Invalid threshold (%s)", vDeploymentParams[6]));
}
}
// Validate that timeout and max_activation_height are mutually exclusive
if (vbparams.timeout != Consensus::BIP9Deployment::NO_TIMEOUT && vbparams.max_activation_height < std::numeric_limits<int>::max()) {
throw std::runtime_error(strprintf("Cannot specify both timeout (%ld) and max_activation_height (%d) for deployment %s. Use timeout for BIP9 or max_activation_height for mandatory activation deadline, not both.", vbparams.timeout, vbparams.max_activation_height, vDeploymentParams[0]));
}
Comment thread
dathonohm marked this conversation as resolved.
bool found = false;
for (int j=0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) {
if (vDeploymentParams[0] == VersionBitsDeploymentInfo[j].name) {
options.version_bits_parameters[Consensus::DeploymentPos(j)] = vbparams;
found = true;
LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, min_activation_height=%d\n", vDeploymentParams[0], vbparams.start_time, vbparams.timeout, vbparams.min_activation_height);
LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, min_activation_height=%d, max_activation_height=%d, active_duration=%d, threshold=%d\n", vDeploymentParams[0], vbparams.start_time, vbparams.timeout, vbparams.min_activation_height, vbparams.max_activation_height, vbparams.active_duration, vbparams.threshold);
break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/consensus/consensus.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ static constexpr unsigned int LOCKTIME_VERIFY_SEQUENCE = (1 << 0);
*/
static constexpr int64_t MAX_TIMEWARP = 600;

static constexpr unsigned int MAX_OUTPUT_SCRIPT_SIZE{34};
static constexpr unsigned int MAX_OUTPUT_DATA_SIZE{83};

#endif // BITCOIN_CONSENSUS_CONSENSUS_H
11 changes: 11 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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_REDUCED_DATA, // ReducedData Temporary Softfork (RDTS)
// NOTE: Also add new deployments to VersionBitsDeploymentInfo in deploymentinfo.cpp
MAX_VERSION_BITS_DEPLOYMENTS
};
Expand All @@ -52,6 +53,16 @@ struct BIP9Deployment {
* boundary.
*/
int min_activation_height{0};
/** Maximum height for activation. If less than INT_MAX, the deployment will activate
* at this height regardless of signaling (similar to BIP8 flag day).
* std::numeric_limits<int>::max() means no maximum (activation only via signaling). */
int max_activation_height{std::numeric_limits<int>::max()};
/** For temporary softforks: number of blocks the deployment remains active after activation.
* std::numeric_limits<int>::max() means permanent (never expires). */
int active_duration{std::numeric_limits<int>::max()};
/** Per-deployment activation threshold. If 0, uses the global nRuleChangeActivationThreshold.
* Otherwise, specifies the number of blocks required for this specific deployment. */
int threshold{0};

/** Constant for nTimeout very far in the future. */
static constexpr int64_t NO_TIMEOUT = std::numeric_limits<int64_t>::max();
Expand Down
18 changes: 17 additions & 1 deletion src/consensus/tx_verify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,30 @@ 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::CheckOutputSizes(const CTransaction& tx, TxValidationState& state)
{
for (const auto& txout : tx.vout) {
if (txout.scriptPubKey.empty()) continue;
if (txout.scriptPubKey.size() > ((txout.scriptPubKey[0] == OP_RETURN) ? MAX_OUTPUT_DATA_SIZE : MAX_OUTPUT_SCRIPT_SIZE)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-vout-script-toolarge");
}
}
return true;
}

bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, const CheckTxInputsRules rules)
{
// 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__));
}

// NOTE: CheckTransaction is arguably the more logical place to do this, but it's context-independent, so this is probably the next best place for now
if (rules.test(CheckTxInputsRules::OutputSizeLimit) && !CheckOutputSizes(tx, state)) {
return false;
}

CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
Expand Down
33 changes: 32 additions & 1 deletion src/consensus/tx_verify.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,45 @@ class TxValidationState;

/** Transaction validation functions */

class CheckTxInputsRules {
using underlying_type = unsigned int;
underlying_type m_flags;
constexpr explicit CheckTxInputsRules(underlying_type flags) noexcept : m_flags(flags) {}

enum class Rule {
None = 0,
OutputSizeLimit = 1 << 0,
};

public:
using enum Rule;

constexpr CheckTxInputsRules(Rule rule) noexcept : m_flags(static_cast<underlying_type>(rule)) {}

[[nodiscard]] constexpr bool test(CheckTxInputsRules rules) const noexcept {
return (m_flags & rules.m_flags) == rules.m_flags;
}

[[nodiscard]] constexpr CheckTxInputsRules operator|(const CheckTxInputsRules other) const noexcept {
return CheckTxInputsRules{m_flags | other.m_flags};
}
};

namespace Consensus {
/**
* Check whether all outputs of this transaction satisfy size limits.
* Regular outputs must be <= MAX_OUTPUT_SCRIPT_SIZE (34 bytes).
* OP_RETURN outputs must be <= MAX_OUTPUT_DATA_SIZE (83 bytes).
*/
bool CheckOutputSizes(const CTransaction& tx, TxValidationState& state);

/**
* 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.
* 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, CheckTxInputsRules rules);
} // 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 struct VBDeploymentInfo VersionBitsDeploymentInfo[Consensus::MAX_VERSION_B
/*.name =*/ "taproot",
/*.gbt_force =*/ true,
},
{
/*.name =*/ "reduced_data",
/*.gbt_force =*/ true,
},
};

std::string DeploymentName(Consensus::BuriedDeployment dep)
Expand Down
23 changes: 22 additions & 1 deletion src/deploymentstatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus
inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache)
{
assert(Consensus::ValidDeployment(dep));
return ThresholdState::ACTIVE == versionbitscache.State(pindexPrev, params, dep);
if (ThresholdState::ACTIVE != versionbitscache.State(pindexPrev, params, dep)) return false;

const auto& deployment = params.vDeployments[dep];
// Permanent deployment (never expires)
if (deployment.active_duration == std::numeric_limits<int>::max()) return true;

const int activation_height = versionbitscache.StateSinceHeight(pindexPrev, params, dep);
const int height = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
return height < activation_height + deployment.active_duration;
Comment thread
dathonohm marked this conversation as resolved.
}

/** Determine if a deployment is active for this block */
Expand Down Expand Up @@ -49,4 +57,17 @@ inline bool DeploymentEnabled(const Consensus::Params& params, Consensus::Deploy
return params.vDeployments[dep].nStartTime != Consensus::BIP9Deployment::NEVER_ACTIVE;
}

/** Determine if mandatory signaling is required for a deployment at the next block */
inline bool DeploymentMustSignalAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, ThresholdState state)
{
assert(Consensus::ValidDeployment(dep));
const auto& deployment = params.vDeployments[dep];
if (deployment.max_activation_height >= std::numeric_limits<int>::max()) return false;
if (state != ThresholdState::STARTED) return false; // If must_signal height is reached before start time, abstain from enforcement
const int nPeriod = params.nMinerConfirmationWindow;
const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
return nHeight >= deployment.max_activation_height - (2 * nPeriod)
&& nHeight < deployment.max_activation_height - nPeriod;
}

#endif // BITCOIN_DEPLOYMENTSTATUS_H
5 changes: 3 additions & 2 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,8 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-datacarriercost", strprintf("Treat extra data in transactions as at least N vbytes per actual byte (default: %s)", DEFAULT_WEIGHT_PER_DATA_BYTE / 4.0), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarrierfullcount", strprintf("Apply datacarriersize limit to all known datacarrier methods (default: %u)", DEFAULT_DATACARRIER_FULLCOUNT), ArgsManager::ALLOW_ANY | (DEFAULT_DATACARRIER_FULLCOUNT ? uint32_t{ArgsManager::DEBUG_ONLY} : 0), OptionsCategory::NODE_RELAY);
argsman.AddArg("-datacarriersize",
strprintf("Maximum size of data in data carrier transactions we relay and mine, in bytes (default: %u)",
strprintf("Maximum size of data in data carrier transactions we relay and mine, in bytes (maximum %s, default: %u)",
MAX_OUTPUT_DATA_SIZE,
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
argsman.AddArg("-maxscriptsize", strprintf("Maximum size of scripts (including the entire witness stack) we relay and mine, in bytes (default: %s)", DEFAULT_SCRIPT_SIZE_POLICY_LIMIT), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
Expand Down Expand Up @@ -974,7 +975,7 @@ namespace { // Variables internal to initialization process only

int nMaxConnections;
int available_fds;
ServiceFlags g_local_services = ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS);
ServiceFlags g_local_services = ServiceFlags(NODE_NETWORK_LIMITED | NODE_WITNESS | NODE_REDUCED_DATA);
int64_t peer_connect_timeout;
std::set<BlockFilterType> g_enabled_filter_types;

Expand Down
Loading