Bitcoin Development Series, Part 1 of 4
Bitcoin is heading toward an August 2026 deadline for BIP-110, a proposed temporary softfork that would restrict Ordinals-style arbitrary data from being embedded in transactions for one year. As of today, miner signaling sits at effectively zero. The proposal is almost certainly going to fail but the mechanics of why and how are worth understanding if you work anywhere near the Bitcoin protocol.
This post walks through how soft fork activation works, what BIP-110 specifically proposes, and how to inspect miner signaling yourself with code.
What Is a Soft Fork?
A soft fork is a backward-compatible change to Bitcoin's consensus rules. Nodes running old software still accept blocks from nodes running the new rules — but not vice versa. This is what makes soft forks safer than hard forks in a permissionless network: you do not force everyone to upgrade on day one.
Hard forks, by contrast, change rules in a way that causes old nodes to reject new blocks entirely. They require near-universal coordination, which is why Bitcoin has avoided them.
How Soft Fork Activation Works: BIP 9
The dominant activation mechanism used since 2016 is defined in BIP 9. The process works like this:
- A proposal is assigned a version bit (bit 0–28) in the block header's
nVersionfield. - Miners signal readiness by setting that bit in blocks they produce.
- Activation requires 95% of blocks in a 2,016-block retarget window to signal support.
- There is a
starttimeand atimeout. If the threshold is not met beforetimeout, the proposal fails and is discarded.
# Simplified BIP 9 state machine logic
THRESHOLD = 0.95 # 95% of blocks in a retarget window
WINDOW = 2016 # one retarget period
def check_activation(signaling_blocks: int, total_blocks: int) -> str:
ratio = signaling_blocks / total_blocks
if ratio >= THRESHOLD:
return "LOCKED_IN" # activates after one more window
return "STARTED" # still counting
print(check_activation(1914, 2016)) # => LOCKED_IN (95%)
print(check_activation(1800, 2016)) # => STARTED (89.3%)
print(check_activation(5, 2016)) # => STARTED (0.25%) — current BIP-110 situation
BIP-110 uses a variant of this with a hard deadline instead of an ongoing timeout. If signaling does not lock in before the August activation window closes, the proposal is considered rejected.
What BIP-110 Actually Proposes
BIP-110 is titled "Reduced Data Temporary Softfork." It proposes to cap the amount of arbitrary data that can be pushed via witness scripts to 42 bytes per input — effectively making Ordinals-style inscriptions impossible for a period of one year.
The mechanism targets the OP_FALSE OP_IF ... OP_ENDIF envelope that Ordinals uses to embed data in the tapscript witness. Under BIP-110, a transaction would be invalid during the restriction window if any witness stack item exceeds the cap.
# Standard Ordinals inscription envelope (what BIP-110 targets)
OP_FALSE
OP_IF
OP_PUSH "ord" # protocol tag
OP_PUSH 1
OP_PUSH "text/plain" # content type
OP_PUSH 0
OP_PUSH <arbitrary data up to 4MB per input>
OP_ENDIF
<actual spend script>
Under BIP-110, the <arbitrary data> push beyond 42 bytes would cause the transaction to be rejected by upgraded nodes. Non-upgraded nodes would still accept it which is what makes this a soft fork rather than a hard fork.
Checking Miner Signaling On-Chain
You can inspect signaling using Bitcoin Core's RPC interface or a block explorer API. Here is how to do it with Python and the bitcoin-rpc library:
pip install python-bitcoinrpc
from bitcoinrpc.authproxy import AuthServiceProxy
import struct
RPC_URL = "http://rpcuser:rpcpassword@127.0.0.1:8332"
BIP110_BIT = 4 # assigned version bit for BIP-110
def get_rpc():
return AuthServiceProxy(RPC_URL)
def check_version_bit(nversion: int, bit: int) -> bool:
"""
BIP 9 signals when nVersion has bit N set
and the high bits match 0x20000000.
"""
BIP9_TOP_MASK = 0xE0000000
BIP9_TOP_BITS = 0x20000000
if (nversion & BIP9_TOP_MASK) != BIP9_TOP_BITS:
return False
return bool(nversion & (1 << bit))
def scan_signaling(num_blocks: int = 2016) -> dict:
rpc = get_rpc()
tip_hash = rpc.getbestblockhash()
tip = rpc.getblock(tip_hash)
tip_height = tip["height"]
signaling = 0
total = 0
block_hash = tip_hash
for _ in range(num_blocks):
block = rpc.getblock(block_hash)
nversion = block["version"]
if check_version_bit(nversion, BIP110_BIT):
signaling += 1
total += 1
block_hash = block.get("previousblockhash")
if not block_hash:
break
return {
"blocks_scanned": total,
"signaling": signaling,
"not_signaling": total - signaling,
"signal_percentage": round((signaling / total) * 100, 2),
"threshold_needed": 95.0,
"locked_in": (signaling / total) >= 0.95,
}
result = scan_signaling(2016)
print(result)
# Current output (approximately):
# {'blocks_scanned': 2016, 'signaling': 5, 'not_signaling': 2011,
# 'signal_percentage': 0.25, 'threshold_needed': 95.0, 'locked_in': False}
If you do not run a full node, you can query a public API instead:
import httpx
def check_signaling_via_api(num_blocks: int = 100) -> dict:
"""
Uses mempool.space API — no node required.
"""
signaling = 0
# Get recent block heights
resp = httpx.get("https://mempool.space/api/blocks")
blocks = resp.json()
for block in blocks[:num_blocks]:
nversion = block["version"]
if check_version_bit(nversion, BIP110_BIT):
signaling += 1
return {
"sample_size": len(blocks[:num_blocks]),
"signaling": signaling,
"signal_percentage": round((signaling / len(blocks[:num_blocks])) * 100, 2),
}
print(check_signaling_via_api())
Why Saylor and Adam Back Oppose It
The opposition from Michael Saylor and Adam Back is not purely technical it is about governance precedent. Their argument: Bitcoin's consensus layer should not be used to resolve application-level disputes. Ordinals are a use of witness space that Taproot technically permits. If the community dislikes it, the correct response is social (pools refusing transactions) or economic (higher fee competition), not a consensus rule change.
The concern is that BIP-110 passing would establish a precedent where any sufficiently disliked use of block space can be banned by a temporary softfork which is a much larger surface area than the original dispute.
What Happens If BIP-110 Fails
If the August window closes without 95% signaling, BIP-110 is discarded with no effect on the chain. The Consensus Cleanup effort (being split into independent BIPs per the latest Bitcoin Optech discussion) continues as a separate track, focused on fixing genuine protocol vulnerabilities rather than restricting applications.
The Ordinals debate will continue but through fee pressure and mining pool policy, not consensus rules.
Further Reading
- BIP-110 full text — bitcoin/bips #2017
- Bitcoin Optech Newsletter #412 — Consensus Cleanup BIP splitting discussion
- BIP 9 specification
- mempool.space API docs
Next in this series: How Ordinals Actually Work Under the Hood — the commit-reveal pattern, witness encoding, and a full inscription walkthrough with code.
Top comments (0)