The Block Only Half the Network Accepted — Python Bug Hunt
Modelled on the Bitcoin chain fork of 11 March 2013, documented in BIP 50. Version 0.8 had moved block storage from BerkeleyDB to LevelDB.
- Language: Python
- Layer: Backend
- Difficulty: Hard
- Concepts: Consensus, Limits
- Modelled on: Bitcoin · BIP 50
- Visible tests: a small block is valid everywhere; a big but legal block is accepted by both versions
- Reward: 50 XP for a complete fix
Briefing
Modelled on the Bitcoin chain fork of 11 March 2013, documented in BIP 50. Version 0.8 had moved block storage from BerkeleyDB to LevelDB. A large block mined by a 0.8 node needed more database locks than 0.7 nodes' BerkeleyDB configuration allowed, so 0.7 nodes rejected a block that 0.8 nodes accepted, and the chain split in two. The fork was resolved within hours when miners moved back to 0.7-compatible software so the 0.7 side became the longest chain. The limit had never been a written rule — it was an accident of one version's storage layer — and BIP 50 records how later releases enforced a compatible limit explicitly for a period.
This reconstruction has two validators. The 0.7 one lets its local database lock limit decide whether a block is valid; the 0.8 one applies no limit at all.
Fix the validators so both apply the same explicit rule and can never disagree.
Bug report
BUG-BIP50 · Priority: Critical (consensus) · Reported by: core developers
A block is valid iff rules.structurally_valid(block) AND rules.locks_needed(block) <= rules.MAX_BLOCK_LOCKS (10000, inclusive).
- validate_v07(block, db_lock_limit) and validate_v08(block) must both return exactly that verdict. db_lock_limit is a node's local storage tuning; it must NOT influence validity.
- chain_splits(blocks, db_lock_limit) -> hashes of the blocks on which the two versions disagree, in input order (after the fix: always []).
Observed: a block needing 5,998 locks was accepted by 0.8 nodes and rejected by 0.7 nodes configured with 5,000 locks; a block needing 10,001 locks is accepted by 0.8.
Logs
[v0.7] block 0000…a1f2 rejected: database lock limit exceeded
[v0.8] block 0000…a1f2 accepted height=225430
[net] two chains: 0.8 tip=225431, 0.7 tip=225429The code as shipped
src/chain/validators.py (editable)
rules = bug_require("./rules.py")
def validate_v07(block, db_lock_limit):
# 0.7 connects blocks through its local database; an update that needs
# more locks than the database is configured for fails.
if not rules.structurally_valid(block):
return False
if rules.locks_needed(block) > db_lock_limit:
return False
return True
def validate_v08(block):
return rules.structurally_valid(block)
def chain_splits(blocks, db_lock_limit):
return [b["hash"] for b in blocks
if validate_v07(b, db_lock_limit) != validate_v08(b)]
Read-only context: src/chain/rules.py.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More Python bug hunts.