Part III · The house: building a chain The software may differ. The rules must not.
You have written a lot of code that says “the node checks this.” Now the question that has been quietly dodged all along:
With nobody in charge, who guarantees that everyone is running the same rules? And what happens when the rules need to change?
It has been dodgeable because you wrote the code, and you only wrote one copy.
The real world is not like that. There are hundreds of Bitcoin implementations in different languages by different teams, running on tens of thousands of machines, none of which answers to any of the others.
The answer is not “everyone must install the same software.” The answer is:
Every full node independently checks the same consensus rules for itself.
9.1 An implementation is not a rule¶
Software can be written a hundred ways: different interfaces, different databases, different style. None of that matters.
Exactly one thing matters: given the same block, do they reach the same verdict?
valid -> accept and relay
invalid -> rejectThose are the consensus rules. They are not suggestions. They are the hard rules each node uses to defend its own ledger.
# A stripped-down block: only the fields this chapter needs.
good_block = {
"name": "good block",
"has_pow": True, # chapter 5: it really was mined
"size": 800, # block size
"reward": 50, # chapter 8: block reward
"no_double_spend": True
}
def bitcoin_rules_v1(block):
'''Consensus rules: do I accept this block?'''
return (
block["has_pow"] and # PoW must be valid
block["size"] <= 1000 and # not too large
block["reward"] <= 50 and # no over-issuance
block["no_double_spend"] # no double-spends
)
# Two implementations, differently written, same rulebook.
def node_alice_accepts(block):
return bitcoin_rules_v1(block)
def node_bob_accepts(block):
checks = [
block["has_pow"],
block["size"] <= 1000,
block["reward"] <= 50,
block["no_double_spend"]
]
return all(checks)
print("does Alice's software accept it?", node_alice_accepts(good_block))
print("does Bob's software accept it? ", node_bob_accepts(good_block))
does Alice's software accept it? True
does Bob's software accept it? True
node_alice_accepts and node_bob_accepts look nothing alike — one delegates,
the other builds a list and calls all().
They return the same answer for the same block. So they are on the same network.
Bitcoin does not rely on “trust that everyone installed the official build.” It relies on: my node checks your block, itself.
(A corollary worth internalizing: because rule agreement matters far more than code agreement, an unintentional behavioural difference in one implementation — even a single edge case — can split the network. This actually happened. In 2013, versions 0.7 and 0.8 disagreed about one block because of an undocumented lock limit in the underlying database, and the chain forked for six hours. The boundaries of a consensus rule include the parts you never wrote down.)
9.2 Nodes do not trust miners. Nodes check miners.¶
A miner may propose the next block. A miner has no power to make anyone accept it.
That sentence is worth rereading. Many people assume miners are Bitcoin’s centre of power. They are not. Miners decide the order transactions go into blocks. Nodes decide whether a block counts at all.
blocks = [
good_block,
{**good_block, "name": "too much reward", "reward": 500},
{**good_block, "name": "double spend", "no_double_spend": False},
{**good_block, "name": "no proof of work", "has_pow": False},
]
for block in blocks:
verdict = "accept" if bitcoin_rules_v1(block) else "reject"
print(f"{block['name']:<18} -> {verdict}")
good block -> accept
too much reward -> reject
double spend -> reject
no proof of work -> reject
A miner wants to pay themselves 500? Nodes reject it and the mining was wasted.
That is the first reason this works without a boss:
Miners submit blocks.
Nodes verify blocks.
If the rules fail, the miner can shout all day. Nothing happens.9.3 Two kinds of fork¶
Chapter 7 showed two candidate blocks appearing after the same parent:
A -> B -> C1
\
-> C2That is a fork. But there are two completely different species, and confusing them is the main obstacle to understanding upgrades.
Temporary fork: same rules, slow network¶
# Temporary fork: identical rules, different blocks seen first.
chain_C1 = ["A", "B", "C1"]
chain_C2 = ["A", "B", "C2", "D2"]
def choose_longest(chain1, chain2):
return chain1 if len(chain1) >= len(chain2) else chain2
winner = choose_longest(chain_C1, chain_C2)
print("chain C1:", chain_C1)
print("chain C2:", chain_C2)
print("nodes end up following:", winner)
chain C1: ['A', 'B', 'C1']
chain C2: ['A', 'B', 'C2', 'D2']
nodes end up following: ['A', 'B', 'C2', 'D2']
A temporary fork requires no meeting and no decision from anyone.
Everyone has the same rules; whichever side accumulates more work wins, and nodes switch over
by themselves. Chapter 7’s choose already solved it.
This happens on the real Bitcoin network every week and nobody thinks about it.
Rule fork: somebody changed the rules¶
This is the problem. Somebody changed the rules, and old and new nodes now reach different verdicts on the same block.
choose cannot help here — the two sides no longer agree on what “valid” means.
9.4 Soft fork: the new rules are stricter¶
The intuition: the set of blocks the new rules accept is a subset of what the old rules accept.
Toy example: old rules allow size up to 1000; new rules are stricter and allow only 500.
def old_rules(block):
return block["has_pow"] and block["size"] <= 1000
def new_softfork_rules(block):
return old_rules(block) and block["size"] <= 500
small_block = {"name": "small block", "has_pow": True, "size": 400}
medium_block = {"name": "medium block", "has_pow": True, "size": 800}
for block in [small_block, medium_block]:
print(block["name"])
print(" old node:", old_rules(block))
print(" new node:", new_softfork_rules(block))
small block
old node: True
new node: True
medium block
old node: True
new node: False
Look at the shape of new_softfork_rules:
return old_rules(block) and (an extra restriction)New rules = old rules + more constraints. That shape is the definition of a soft fork.
Why can it go smoothly? If upgraded miners only produce small blocks, old nodes accept them too — small blocks were already valid under the old rules. Old nodes may not understand what the new rule means, but they do not fall off the chain.
(Real soft forks do not change block size this way; this toy isolates the idea of “stricter.” The real examples are P2SH, SegWit, and Taproot — each redefines a location that used to be “put whatever you like here” into something with meaning and constraints. An old node sees a script it considers trivially valid; a new node sees a rule that must be satisfied.)
9.5 Hard fork: the new rules are looser¶
The intuition is the mirror image: the new rules accept something old nodes reject.
def new_hardfork_rules(block):
return block["has_pow"] and block["size"] <= 2000
big_block = {"name": "big block", "has_pow": True, "size": 1500}
print("big_block size:", big_block["size"])
print("old node:", old_rules(big_block))
print("new node:", new_hardfork_rules(big_block))
big_block size: 1500
old node: False
new node: True
Old node False, new node True.
This is dangerous. One side says valid, the other says invalid.
If such a block gets mined and extended, the network becomes two chains with different rules:
old nodes: A -> B -> C_old
new nodes: A -> B -> C_big -> D_bigThey will never merge again. To an old node that big block is permanently invalid,
no matter how much work piles up behind it. Chapter 7’s choose has failed here — choose
selects among valid chains, and the two sides no longer share a definition of valid.
So a hard fork requires social coordination: users, nodes, miners, wallets and exchanges all have to know which rulebook they are following. Technically it is one inequality changing from 1000 to 2000. Socially it is a divorce.
9.6 One block, three verdicts¶
# Three nodes have chosen different rulebooks.
nodes = {
"Alice (old node)": old_rules,
"Bob (soft fork)": new_softfork_rules,
"Carol (hard fork)": new_hardfork_rules,
}
for block in [small_block, medium_block, big_block]:
print("\nblock:", block["name"], "size =", block["size"])
for node_name, rulebook in nodes.items():
print(f" {node_name:<18} ->", "accept" if rulebook(block) else "reject")
block: small block size = 400
Alice (old node) -> accept
Bob (soft fork) -> accept
Carol (hard fork) -> accept
block: medium block size = 800
Alice (old node) -> accept
Bob (soft fork) -> reject
Carol (hard fork) -> accept
block: big block size = 1500
Alice (old node) -> reject
Bob (soft fork) -> reject
Carol (hard fork) -> accept
That output is the entire upgrade problem.
A three-by-three grid holds every argument Bitcoin has had in fifteen years. One block, different rulebooks, different verdicts. Whether the network stays together does not depend on who argues loudest. It depends on whether people end up choosing compatible rules.
Look at the medium_block row (size 800): the old node accepts, the soft-fork node rejects,
the hard-fork node accepts. Three nodes, two opinions, and no referee.
9.7 Upgrading with nobody in charge¶
A Bitcoin upgrade is not a server-side announcement:
Management: everyone upgrade by tomorrow.It looks more like:
Someone proposes a rule change.
Developers write code.
Users, nodes, miners, wallets and exchanges each assess the risk.
Each of them independently chooses whether to run software that accepts those rules.Not one step is compulsory. Nobody can make you upgrade, and nobody can stop you.
Which is why this is not merely cryptography, and not even merely distributed systems. It is distributed systems + consensus + economic incentives + social coordination.
And that last layer — social coordination — cannot be written as code. This book can teach you everything before it. It cannot teach you that.
9.8 The ledger¶
| What you wrote | Field |
|---|---|
bitcoin_rules_v1() — consensus rules | Systems design |
| Two implementations, one verdict | Distributed systems |
| Nodes checking miners | Power structure design |
| Soft fork = old rules + more constraints | Protocol evolution |
| Hard fork = loosened constraints | Protocol evolution + social coordination |
Zero cryptography.
Line up the questions from the last several chapters and label them:
| Question | Solved by | Field |
|---|---|---|
| Has this block been altered? | Hashing, PoW | Cryptography + systems |
| Did the owner really sign this? | ECDSA | Cryptography |
| Two chains conflict — which do I believe? | Most accumulated work | Consensus + game theory |
| Why do miners maintain the network? | Rewards, fees | Economic incentives |
| What if the software differs? | Each node verifies the same rules | Distributed systems |
| What if the rules must change? | Soft/hard fork + social coordination | Systems + consensus + society |
Six questions. One is cryptography.
The sentence for this chapter:
Bitcoin does not run because a boss commands it, and not because everyone trusts one piece of software. It runs because every node checks the rules itself, while longest-chain consensus, incentives, and social coordination keep strangers on one history.
Next¶
Fees and difficulty adjustment.
Part III ends here. Your chain resists forgery and double-spending, reaches consensus, sustains itself, and can be upgraded.
But it cannot yet survive the long run. Part IV takes on two “long-term” problems:
Block rewards halve to zero. Why would anyone mine then?
More miners, more hashpower — blocks arrive faster and faster. How does the pace hold?
Both are negative feedback designs: the system taking care of itself.