Context
An external technical review pointed out (correctly) that the prototype today trusts state.json as the source of truth: Chain._load reads blocks, state and mempool as three independent files and never re-validates the hash chaining (previous_hash), the block signatures, or that the state is actually the result of applying the blocks' transactions. For a single-node Phase 0 prototype that's defensible, but a chain verify command would close the gap and make the "it's a chain, not just a JSON of balances" claim demonstrable.
What to do
Add a chain verify CLI subcommand (and a Chain.verify() method) that, starting from genesis, checks:
- Hash chaining — each block's
previous_hash equals the previous block's hash.
- Block signatures — each block's proposer signature is valid and the proposer pubkey matches the proposer address (reuse
Block.is_valid).
- State derivability — replaying all transactions from genesis on a fresh
WorldState reproduces exactly the persisted state.json (balances and nonces).
Print a clear OK/where-it-failed summary and exit 0/1 accordingly.
Acceptance criteria
Good to know
Look at how propose_block applies transactions and at WorldState.apply_transaction — replaying should reuse that exact logic so the check is meaningful. Keep the output readable.
Context
An external technical review pointed out (correctly) that the prototype today trusts
state.jsonas the source of truth:Chain._loadreads blocks, state and mempool as three independent files and never re-validates the hash chaining (previous_hash), the block signatures, or that the state is actually the result of applying the blocks' transactions. For a single-node Phase 0 prototype that's defensible, but achain verifycommand would close the gap and make the "it's a chain, not just a JSON of balances" claim demonstrable.What to do
Add a
chain verifyCLI subcommand (and aChain.verify()method) that, starting from genesis, checks:previous_hashequals the previous block's hash.Block.is_valid).WorldStatereproduces exactly the persistedstate.json(balances and nonces).Print a clear OK/where-it-failed summary and exit
0/1accordingly.Acceptance criteria
python qrb_cli.py chain verifypasses on a freshly built chain.state.jsonis hand-edited (e.g. give an address free balance),chain verifyfails and says why.previous_hashis tampered, it fails.prototype/tests/test_basic.py(registered inrun_all()) covers the happy path and at least one tampering case.Good to know
Look at how
propose_blockapplies transactions and atWorldState.apply_transaction— replaying should reuse that exact logic so the check is meaningful. Keep the output readable.