Status: complete. 18 tests passing.
| Module | Purpose |
|---|---|
ribbit/config.py |
Chain params for main/test/regtest; RPC credential loading (conf, then cookie) |
ribbit/rpc.py |
JSON-RPC client with batching, session reuse, id-based result reordering |
ribbit/db.py |
SQLite schema, WAL, and the undo journal — StateDB records a reversal for every mutation |
ribbit/chain.py |
ChainFollower — connects blocks, detects forks, rolls back reorgs |
ribbit/zmq_listener.py |
ZMQ subscriber for hashblock/rawblock/rawtx, with gap detection |
ribbit/regtest.py |
Throwaway regtest node, run as the current user, cookie auth, no root |
StateDB raises rather than allowing it.StateDB. This makes adding protocol state in M2 a
conscious act rather than something that quietly escapes the journal.PRAGMA synchronous=FULL, not NORMAL. A torn write after a crash means
silently wrong protocol state, which is far worse than a slower indexer.max_reorg_depth (default 500) raises rather than unwinding forever. A
disagreement that deep means a wrong network, a corrupt database, or something
genuinely catastrophic — all three deserve a human, not a retry loop.zmqpubsequence (Bitcoin 0.19+), so ZMQ cannot report a disconnected block.
Reorg detection stays with ChainFollower, comparing hashes over RPC.| File | Covers |
|---|---|
tests/test_undo.py (9) |
insert/update/delete reversal, reverse-order replay, multi-block rollback, atomic failure, misuse guards |
tests/test_chain.py (6) |
connect from activation, idempotency, batching limit, a real reorg on a live node, journal hygiene, depth limit |
tests/test_zmq.py (3) |
hashblock delivery, rawblock body, sequence monotonicity |
The reorg test is deliberately run against a real pepecoind, not a mock:
it mines 20 blocks, invalidateblocks three below the tip, mines a longer
competing chain, re-syncs, and asserts that state from the orphaned blocks is
gone — not merely superseded — and that every height we hold matches the node.
ChainFollower fetches blocks one at a time. RpcClient.batch() exists but is
not yet used by the follower; wire it in when block volume justifies it.MAINNET.activation_height is None by
design, and ChainFollower refuses to start without one (D-004).