Part IV · Surviving the long run Two negative feedback loops that let the system take care of itself.
Nine chapters have produced a working Bitcoin. To keep it running long-term, two practical problems remain:
Block rewards halve to zero eventually — why would miners keep mining? → fees
More miners and more hashpower mean faster and faster blocks — how does the pace hold? → difficulty adjustment
Both are systems design. Neither is cryptography.
10.1 Fees: what takes over when the reward dies¶
Look at what a fee actually is in code — it is startlingly simple.
def tx_fee(inputs_total, outputs_total):
'''The fee is whatever is left over. It goes to whoever mines it.'''
return inputs_total - outputs_total
# Alice spends a 10-coin note but only sends out 9.5. The 0.5 is the fee.
fee = tx_fee(inputs_total=10, outputs_total=9.5)
print("this transaction's fee:", fee, "-> goes to the miner")
this transaction's fee: 0.5 -> goes to the miner
A fee is not a field.
There is no fee entry in a Bitcoin transaction. A fee is the difference between inputs
and outputs — money you did not assign to any recipient. Whoever includes the transaction in
a block picks it up.
Recall chapter 6’s check 3 in apply:
if total_out != total_in:
return False, "value not conserved"We required exact equality. Real Bitcoin requires total_out <= total_in,
and the gap that inequality permits is the fee.
One relational operator is the entire mechanism of the fee market.
def miner_income(block_reward, total_fees):
'''Miner income = block reward + all fees in that block.'''
return block_reward + total_fees
print("early days (reward 50 + fees 11):", miner_income(50, 11))
print("far future (reward 0, fees only) :", miner_income(0, 11))
print("-> once the reward is gone, miners live on fees.")
early days (reward 50 + fees 11): 61
far future (reward 0, fees only) : 11
-> once the reward is gone, miners live on fees.
The open question chapter 8 left you has its shape right here: after the reward reaches
zero, miner_income(0, fees) is the miner’s entire revenue.
Whether fees can sustain a large enough security budget is Bitcoin’s biggest long-term unknown. Nobody knows.
10.2 A market nobody designed¶
A block cannot hold every pending transaction. Miners are rational: they take the highest fees first.
# The mempool: transactions waiting, each carrying a fee.
mempool = [("txA", 5), ("txB", 1), ("txC", 10), ("txD", 3)]
def pick_txs(mempool, capacity):
'''The miner picks highest-fee-first until the block is full.'''
return sorted(mempool, key=lambda t: -t[1])[:capacity]
# Suppose a block fits 2 transactions.
picked = pick_txs(mempool, capacity=2)
print("miner selects:", picked)
print("fee income for this block:", sum(fee for _, fee in picked))
print("left behind (paid too little):", [t for t in mempool if t not in picked], "-> wait for the next block")
miner selects: [('txC', 10), ('txA', 5)]
fee income for this block: 15
left behind (paid too little): [('txB', 1), ('txD', 3)] -> wait for the next block
Notice there is no “market” code in pick_txs.
It is a sorted and a slice. The miner is not participating in a market. He is maximizing
his own revenue.
But when every miner does that, users discover that paying more confirms faster, so people who want speed voluntarily pay more — and a market grows by itself.
Nobody designed this market. There is no exchange, no market maker, not one line of code
labelled fee_market. It is the inevitable product of two conditions:
rational self-interest and finite capacity.
This is the second appearance of chapter 8’s idea: do not legislate behaviour. Design incentives, and the behaviour grows on its own.
YOUR TURN¶
# YOUR TURN:
# 1) txB pays 1 and keeps losing. How much must it pay to make the top 2?
# 2) Think: why do fees rise when the network is congested?
# (Hint: many people want in, block space is finite -- supply and demand.)
Question 2 is the key to understanding real fee spikes. Block space is a commodity with a hard supply cap. Demand rises, the price rises. It is no different from concert tickets.
10.3 Difficulty adjustment: holding the pace¶
Bitcoin wants roughly one block every ten minutes. But hashpower keeps changing:
Hashpower rises → blocks come faster → difficulty must go up
Hashpower falls → blocks come slower → difficulty must come down
This is negative feedback: observe the recent rate, correct automatically.
def adjust_difficulty(current_diff, actual_time, target_time):
'''Adjust difficulty from the observed block time.'''
if actual_time < target_time / 2:
return current_diff + 1 # too fast -> harder
elif actual_time > target_time * 2:
return max(1, current_diff - 1) # too slow -> easier
else:
return current_diff # about right -> leave it
# Target: 10 seconds per block (real Bitcoin uses 10 minutes).
target = 10.0
diff = 3
print(f"target block time: {target}s, starting difficulty: {diff}\n")
for actual in [11, 4, 3, 25, 9]:
new = adjust_difficulty(diff, actual, target)
change = "harder" if new > diff else ("easier" if new < diff else "unchanged")
print(f"actual block time {actual:>2}s -> difficulty {diff} -> {new} ({change})")
diff = new
target block time: 10.0s, starting difficulty: 3
actual block time 11s -> difficulty 3 -> 3 (unchanged)
actual block time 4s -> difficulty 3 -> 4 (harder)
actual block time 3s -> difficulty 4 -> 5 (harder)
actual block time 25s -> difficulty 5 -> 4 (easier)
actual block time 9s -> difficulty 4 -> 4 (unchanged)
Watch it self-correct: a 4-second block (too fast — hashpower grew) pushes difficulty up; a 25-second block (too slow — hashpower left) pulls it down.
No administrator. That rule alone holds the pace.
This is why Bitcoin’s block time has stayed around ten minutes for over a decade, even though total network hashpower has grown by more than a trillionfold. Difficulty adjustment quietly drags it back to target.
Why the rule works at all¶
Because of chapter 5: mining time grows exponentially with difficulty.
import hashlib, time
def H(s): return hashlib.sha256(s.encode()).hexdigest()
def mine_timed(data, difficulty):
t0 = time.time(); n = 0; target = "0"*difficulty
while not H(f"{data}{n}").startswith(target): n += 1
return time.time()-t0, n
for d in [1, 2, 3, 4]:
dt, n = mine_timed("bitcoin block header", d)
print(f"difficulty {d}: {dt:7.4f}s, {n:>7} tries")
difficulty 1: 0.0000s, 4 tries
difficulty 2: 0.0004s, 246 tries
difficulty 3: 0.0110s, 6959 tries
difficulty 4: 0.0535s, 33169 tries
Do not expect those numbers to look tidy. Section 5.2 explained why: a single mining run is one sample from a geometric distribution whose variance equals its mean. You may see one level take only three times the previous, and you may see eighty.
The expectation is ×16. You already saw in chapter 5 how far a single sample can stray.
And that variance is precisely why difficulty cannot be adjusted every block.
10.4 Why real Bitcoin waits 2016 blocks¶
This is the most important design detail in the chapter, and it follows directly from chapter 5’s variance.
Suppose you adjusted difficulty after every block. One block happens to get lucky and takes two seconds — that does not mean network hashpower grew. That is luck. But your rule takes it seriously and raises difficulty. The next block gets unlucky at forty seconds, so the rule lowers it again.
Your difficulty ends up chasing noise around the room while actual hashpower has not moved at all.
Real Bitcoin adjusts every 2016 blocks (about two weeks), based on how long those 2016 blocks took in total.
Averaging two thousand samples cuts the variance to roughly 1/√2016 ≈ 1/45. The noise flattens out and what remains is the real trend in hashpower.
This is a classic control-systems tradeoff: sample too quickly and you are measuring noise; sample too slowly and you react too late. Two weeks is the compromise Satoshi chose.
YOUR TURN¶
# YOUR TURN:
# 1) If global hashpower suddenly doubles, do blocks speed up or slow down?
# What will the next difficulty adjustment do?
# 2) Following 10.3: what happens if difficulty is adjusted after every single block?
# (Hint: go back and look at section 5.2's "min 1, max 23386" table.)
# 3) What are the pros and cons of shortening the adjustment window from two weeks to one day?
10.5 The ledger¶
| What you wrote | Field |
|---|---|
tx_fee() — inputs minus outputs | Economic design |
pick_txs() — sort by fee | Economics + game theory (a market emerging on its own) |
miner_income(0, fees) — life after the reward | Economics |
adjust_difficulty() — negative feedback | Control theory |
| The 2016-block sampling window | Statistics (noise reduction) |
Zero cryptography — and this chapter has outgrown “systems design” too. It needs economics, game theory, control theory, and statistics.
By chapter 10 a pattern should be obvious: the further you get from cryptography, the closer you get to what is genuinely hard about Bitcoin.
Next¶
Merkle trees.
The second chapter of Part IV detours back into cryptography — chapter 1’s ledger table noted that only two of the eighteen chapters do this, and here is the first.
You will build a tree out of hashes, turning “prove this transaction is in this block” from “download the whole block and check every entry” into “look at 20 hashes.”
A million transactions, a twenty-step proof. That is why a phone can verify Bitcoin.