BIP-110

Public guide to the code

BIP-110 Code Walkthrough

Follow BIP-110 in Bitcoin Knots with the important checks highlighted and explained in plain language. Begin with the code review, then use the rule reference below to connect each check to the seven consensus limits and the temporary activation, grandfathering, and expiry logic.

Guided source

Code with plain-language notes

Each panel shows the important C++ lines, why they matter, and a direct link to the tagged source. Notes stay attached to the lines they explain, so the source remains the main reading surface.

+ new check- replaced pathtagv29.3.knots20260210+bip110-v0.4.1
src/consensus/tx_verify.cpp

Output size checks

BIP-110 adds one small transaction-output gate, then runs it from contextual input validation when the deployment is active.

RelatedRule 1
+164
bool Consensus::CheckOutputSizes(const CTransaction& tx, TxValidationState& state)
+165
{
+166
for (const auto& txout : tx.vout) {
+167
if (txout.scriptPubKey.empty()) continue;
note

line 167

Guard the empty script case

The first opcode is read only after the script is known to contain at least one byte.

+168
if (txout.scriptPubKey.size() > ((txout.scriptPubKey[0] == OP_RETURN) ? MAX_OUTPUT_DATA_SIZE : MAX_OUTPUT_SCRIPT_SIZE)) {
note

line 168

Pick the limit by output type

Ordinary outputs use the 34-byte script limit. OP_RETURN gets the separate 83-byte limit.

+169
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "bad-txns-vout-script-toolarge");
+170
}
+171
}
+172
return true;
+173
}
...
+184
if (rules.test(CheckTxInputsRules::OutputSizeLimit) && !CheckOutputSizes(tx, state)) {
note

line 184

Run only when requested

The check is behind CheckTxInputsRules so inactive deployments do not change validation.

+185
return false;
+186
}
src/deploymentstatus.h

Temporary activation state

Deployment status keeps permanent soft forks unchanged while adding expiry and mandatory-signaling windows for temporary deployments.

20
inline bool DeploymentActiveAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, VersionBitsCache& versionbitscache)
25
const auto& deployment = params.vDeployments[dep];
+27
if (deployment.active_duration == std::numeric_limits<int>::max()) return true;
note

line 27

Permanent deployments stay simple

Existing soft forks keep the old behavior by using an unlimited active duration.

+29
const int activation_height = versionbitscache.StateSinceHeight(pindexPrev, params, dep);
note

line 29

Expiry needs the start height

Temporary deployments compare the current height with the height where ACTIVE began.

+30
const int height = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
+31
return height < activation_height + deployment.active_duration;
...
61
inline bool DeploymentMustSignalAfter(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos dep, ThresholdState state)
+65
if (deployment.max_activation_height >= std::numeric_limits<int>::max()) return false;
+66
if (state != ThresholdState::STARTED) return false;
+68
const int nHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1;
+69
return nHeight >= deployment.max_activation_height - (2 * nPeriod)
note

line 69

Mandatory signaling has a narrow window

The rule applies during the two retarget periods before maximum activation, not forever.

+70
&& nHeight < deployment.max_activation_height - nPeriod;
src/script/interpreter.cpp

Reduced script element sizes

The interpreter reuses existing push-size checks, but chooses the reduced limit when the reduced-data verification flag is set.

RelatedRule 2
+436
const unsigned int max_element_size = (flags & SCRIPT_VERIFY_REDUCED_DATA) ? MAX_SCRIPT_ELEMENT_SIZE_REDUCED : MAX_SCRIPT_ELEMENT_SIZE;
note

line 436

Reuse existing checks

The interpreter already checked push sizes. BIP-110 swaps in the lower limit when active.

...
448
if (vchPushValue.size() > max_element_size)
note

line 448

One path for old and new limits

The same error path is used whether the ordinary or reduced limit was selected.

449
return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
...
+1868
const unsigned int max_element_size = (flags & SCRIPT_VERIFY_REDUCED_DATA) ? MAX_SCRIPT_ELEMENT_SIZE_REDUCED : MAX_SCRIPT_ELEMENT_SIZE;
note

line 1868

Witness arguments get the same treatment

Witness stack elements use the reduced 256-byte limit under the same flag.

1870
if (elem.size() > max_element_size) return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
src/script/interpreter.cpp

Taproot restrictions

When BIP-110 is active, Taproot annexes fail, control blocks use the smaller cap, Tapscript OP_IF is rejected, and pay-to-anchor spends must have an empty witness stack.

+623
if (flags & SCRIPT_VERIFY_REDUCED_DATA) {
note

line 623

Tapscript conditionals fail while active

OP_IF and OP_NOTIF reuse the minimal-if error as a stricter form of the same rule.

+624
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_MINIMALIF);
+625
}
...
+1964
if (flags & SCRIPT_VERIFY_REDUCED_DATA) {
note

line 1964

Annex data is rejected

The existing annex detection remains, but reduced-data mode stops the spend there.

+1965
return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
+1966
}
...
+1983
const unsigned int max_control_size = (flags & SCRIPT_VERIFY_REDUCED_DATA) ? TAPROOT_CONTROL_MAX_SIZE_REDUCED : TAPROOT_CONTROL_MAX_SIZE;
note

line 1983

Control blocks use a reduced cap

The size check stays in one place. Only the selected maximum changes.

1984
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > max_control_size || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
...
-old
} else if (!is_p2sh && CScript::IsPayToAnchor(witversion, program)) {
+2004
} else if (stack.empty() && !is_p2sh && CScript::IsPayToAnchor(witversion, program)) {
note

line 2004

Anchor spends must be empty

Pay-to-anchor is accepted only when there is no witness stack attached.

2005
return true;
src/script/interpreter.cpp

P2SH redeemScript exception

The redeemScript push keeps its compatibility exception, then the rest of the P2SH stack is checked against the reduced element limit.

RelatedRule 2
+2033
if (scriptPubKey.IsPayToScriptHash()) {
note

line 2033

Detect the P2SH path first

The exception is scoped to P2SH instead of weakening the reduced-data flag globally.

+2035
if (!EvalScript(stack, scriptSig, flags & ~SCRIPT_VERIFY_REDUCED_DATA, checker, SigVersion::BASE, serror))
note

line 2035

Clear the flag for the redeemScript push

Only this initial scriptSig evaluation runs without SCRIPT_VERIFY_REDUCED_DATA.

+2037
return false;
+2038
} else
...
+2090
if (flags & SCRIPT_VERIFY_REDUCED_DATA) {
+2094
for (const valtype& elem : stack) {
note

line 2094

Then check what remains

After the redeemScript is removed, the rest of the stack still has to fit the reduced limit.

+2095
if (elem.size() > MAX_SCRIPT_ELEMENT_SIZE_REDUCED) return set_error(serror, SCRIPT_ERR_PUSH_SIZE);
+2096
}
+2097
}
src/validation.cpp

Block-level enforcement and grandfathering

Block connection turns deployment state into script flags, output checks, and per-input overrides for coins created before activation.

+2683
if (DeploymentActiveAt(block_index, chainman, Consensus::DEPLOYMENT_REDUCED_DATA)) {
note

line 2683

Deployment state selects script flags

When reduced data is active, mandatory verification flags are added for script checks.

+2684
flags |= REDUCED_DATA_MANDATORY_VERIFY_FLAGS;
+2685
}
...
+2902
const auto reduced_data_start_height = DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_REDUCED_DATA)
+2903
? m_chainman.m_versionbitscache.StateSinceHeight(pindex->pprev, params.GetConsensus(), Consensus::DEPLOYMENT_REDUCED_DATA)
+2906
const CheckTxInputsRules chk_input_rules{DeploymentActiveAt(*pindex, m_chainman, Consensus::DEPLOYMENT_REDUCED_DATA) ? CheckTxInputsRules::OutputSizeLimit : CheckTxInputsRules::None};
...
+2911
if (!Consensus::CheckOutputSizes(*block.vtx[0], tx_state)) {
note

line 2911

Coinbase needs a separate output check

The generation transaction has no inputs, so it does not pass through CheckTxInputs.

...
+2923
std::vector<unsigned int> flags_per_input;
+2935
if (!Consensus::CheckTxInputs(tx, tx_state, view, pindex->nHeight, txfee, chk_input_rules)) {
+2956
if (prevheights[j] < reduced_data_start_height) {
note

line 2956

Grandfathering is per input

Inputs spending coins from before activation clear the new flags without affecting other inputs.

+2957
flags_per_input.resize(tx.vin.size(), flags);
+2958
flags_per_input[j] = flags & ~REDUCED_DATA_MANDATORY_VERIFY_FLAGS;
+2984
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], m_chainman.m_validation_cache, parallel_script_checks ? &vChecks : nullptr, flags_per_input)) {
note

line 2984

Per-input flags reach script verification

The override list is passed into CheckInputScripts so each input gets the intended rules.

src/validation.cpp

Mandatory signaling check

Header validation rejects blocks that fail to signal during the mandatory-signaling window near maximum activation height.

RelatedActivation
+4631
static bool ContextualCheckBlockHeaderVolatile(const CBlockHeader& block, BlockValidationState& state, const ChainstateManager& chainman, const CBlockIndex* pindexPrev) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+4632
{
+4636
for (int i = 0; i < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) {
note

line 4636

Check every versionbits deployment

The helper is generic, so any deployment with a maximum activation height can use it.

+4640
if (DeploymentMustSignalAfter(pindexPrev, consensusParams, pos, deployment_state)) {
+4642
const bool fVersionBits = (block.nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS;
note

line 4642

Require both versionbits shape and bit

A block must use the versionbits top bits and set the deployment bit.

+4643
const bool fDeploymentBit = (block.nVersion & (uint32_t{1} << deployment.bit)) != 0;
+4645
if (!(fVersionBits && fDeploymentBit)) {
+4647
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
note

line 4647

Bad signaling becomes consensus failure

During the mandatory window, missing signaling rejects the block header.

+4655
return true;
src/versionbits.cpp

Versionbits lock-in, expiry, and threshold

Versionbits learns the maximum activation height, records when temporary deployments became active, expires them after their duration, and reads a per-deployment threshold.

+99
} else if (max_activation_height < std::numeric_limits<int>::max() && pindexPrev->nHeight + 1 >= max_activation_height - nPeriod) {
note

line 99

Force lock-in before the deadline

If ordinary signaling has not locked in, the period before maximum activation does it.

+103
stateNext = ThresholdState::LOCKED_IN;
...
+114
if (active_duration < std::numeric_limits<int>::max()) {
+115
activation_height = pindexPrev->nHeight + 1;
note

line 115

Remember activation height

Temporary deployments need the first ACTIVE height so expiry can be calculated later.

+116
}
...
+121
if (active_duration < std::numeric_limits<int>::max() &&
+122
pindexPrev->nHeight + 1 >= activation_height + active_duration) {
+123
stateNext = ThresholdState::EXPIRED;
note

line 123

Expire after the active duration

Once the active duration is reached, the deployment moves from ACTIVE to EXPIRED.

...
+239
int deploymentThreshold = params.vDeployments[id].threshold;
+240
return deploymentThreshold > 0 ? deploymentThreshold : params.nRuleChangeActivationThreshold;
note

line 240

Use the deployment threshold when set

BIP-110 can use its own threshold while other deployments keep the global default.

Rule reference

Seven rules, one enforcement flag

The easiest way to understand BIP-110 is rule by rule. Rule 1 limits new transaction outputs. Rules 2 through 7 apply while scripts are being checked, once the reduced-data flag is active.

Rule 1

Limit new output scripts

New scriptPubKeys above 34 bytes are invalid, except OP_RETURN outputs, which can be up to 83 bytes.

src/consensus/tx_verify.cpp
Rule 2

Reduce large data pushes

OP_PUSHDATA payloads and witness argument items are capped at 256 bytes, with a narrow P2SH redeemScript exception.

src/script/interpreter.cpp
Rule 3

Spend only defined witness versions

Spends using undefined witness or tapleaf versions are rejected during the deployment. Creating those outputs is still allowed.

src/script/interpreter.cpp
Rule 4

Reject Taproot annexes

Witness stacks that include a Taproot annex fail while the reduced-data rules are active.

src/script/interpreter.cpp
Rule 5

Cap Taproot control blocks

Taproot control blocks are limited to 257 bytes, enough for a tree with 128 script leaves.

src/script/interpreter.cpp
Rule 6

Disable OP_SUCCESS escape hatches

Tapscripts containing OP_SUCCESS opcodes are invalid, even when the opcode is not executed.

src/script/interpreter.cpp
Rule 7

Reject OP_IF and OP_NOTIF in Tapscript

Executed OP_IF and OP_NOTIF instructions fail in Tapscript, nudging Taproot scripts toward separate leaves.

src/script/interpreter.cpp

Deployment references

Activation

Temporary enforcement window

Deployment state decides when the reduced-data checks become active and when mandatory signaling applies.

Grandfathering

Existing coins stay spendable

Inputs spending coins created before activation clear the reduced-data flags for that input only.

Expiry

Rules turn off automatically

Versionbits records the activation height so the temporary deployment can expire after its active duration.

Threshold

BIP-110 can set its own threshold

The deployment can use a specific signaling threshold while other deployments keep the network default.

Reading order

Follow the change in five steps

This order keeps the review simple: output limits, activation, script checks, grandfathering, then expiry.

  1. 01

    Start with outputs

    Confirm that every new transaction output is checked against the 34-byte or 83-byte OP_RETURN limit.

  2. 02

    Follow the activation switch

    Watch how deployment state decides whether reduced-data verification flags are added.

  3. 03

    Trace script execution

    See the interpreter reuse existing script checks with tighter constants when BIP-110 is active.

  4. 04

    Check grandfathering

    Inputs spending coins created before activation clear the new flags for that input only.

  5. 05

    Verify the timer

    Versionbits handles forced lock-in near the deadline and expiry after the active duration.

Verification map

Where to verify each part

Use this as a checklist while reading the source. Each file has a narrow job, so the change is easier to verify piece by piece.

src/consensus/tx_verify.cpp

Output gate

Adds the transaction output-size check and wires it into input validation when the deployment says to enforce it.

Covers

Rule 1
  • Empty output scripts are skipped before reading their first opcode.
  • Non-OP_RETURN outputs use the 34-byte script limit.
  • OP_RETURN outputs use the 83-byte data limit.
src/deploymentstatus.h

Deployment state

Teaches deployments how to expire and how to require signaling during the final pre-activation window.

Covers

ActivationExpiry
  • Permanent deployments bypass the new duration logic.
  • Temporary deployments compare current height against activation height plus active duration.
  • Mandatory signaling is checked only while the deployment is still in STARTED state.
src/script/interpreter.cpp

Script rules

Applies the reduced-data flag inside script and witness evaluation, using stricter sizes and Taproot restrictions only when active.

Covers

Rules 2-7
  • Witness arguments and pushed elements use the reduced 256-byte limit.
  • Taproot annexes, oversized control blocks, OP_SUCCESS, and Tapscript OP_IF or OP_NOTIF are rejected.
  • The P2SH redeemScript push keeps its exception, then the remaining stack items are checked.
src/validation.cpp

Block connection

Turns the deployment state into block and input checks, including per-input flags for grandfathered coins.

Covers

Rule 1Rules 2-7Grandfathering
  • Coinbase outputs are checked separately because they do not pass through normal input validation.
  • Inputs spending pre-activation coins can be verified without the reduced-data flags.
  • Script-check caching is skipped when per-input flags are present, avoiding a stale cache assumption.
src/versionbits.cpp

Versionbits timer

Extends versionbits so BIP-110 can lock in by deadline, remember activation height, and later expire.

Covers

ActivationExpiryThreshold
  • A per-deployment threshold can override the default activation threshold.
  • The period before maximum activation height can force LOCKED_IN.
  • ACTIVE moves to EXPIRED once the deployment duration has passed.

Important nuance

Grandfathering is per input

BIP-110 does not treat a transaction as all old rules or all new rules. During block connection, each input is compared with the activation height. Inputs spending coins created before activation clear the reduced-data flags, while other inputs in the same transaction can still be checked under the new rules.

flags_per_input

This is why script-check caching is bypassed when per-input overrides exist. The cache key assumes one flag set for every input.

Read the original code review

This page is a structured guide to the Bitcoin Knots walkthrough. The original contains the C++ snippets beside line-by-line explanations, so it is the best place to compare this guide against the source.

Open original review