Summary
celo-reth proofs unwind currently dispatches against OpNode via op-reth's Cli and will panic on the moment it touches a CIP-64 transaction. Unlike stage, db, p2p, prune, and re-execute (intercepted in #190), proofs unwind cannot be re-routed against CeloNode from the binary alone.
Background
PR #181 plus #190 re-route every op-reth subcommand that decodes static-file transactions or receipts so it runs against CeloNode::Primitives = CeloPrimitives instead of OpNode::Primitives = OpPrimitives. The proofs subcommand sits behind a hard trait bound the override can't satisfy:
// op-reth/crates/cli/src/commands/op_proofs/unwind.rs
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec, Primitives = OpPrimitives>>(
self,
runtime: reth_tasks::Runtime,
) -> eyre::Result<()>
CeloNode::Primitives = CeloPrimitives ≠ OpPrimitives, so a cmd.execute::<CeloNode>(...) arm doesn't compile.
Why init and prune work, unwind doesn't
The three proofs subcommands access the main DB very differently:
| Subcommand |
Touches block bodies? |
Status on Celo |
proofs init |
No — walks state trie only (InitializationJob::new(storage, db_tx, trie_layout).run(...)). Trie entries are primitives-agnostic. |
Works as-is (empirically confirmed against celo-mainnet). |
proofs prune |
No — only needs BlockHashReader for hash-by-number lookups. |
Works as-is. |
proofs unwind |
Yes — provider_factory.recovered_block(target, TransactionVariant::NoHash) decodes the target block body, including transactions. |
Panics on first CIP-64. |
The OpPrimitives trait bound is inherited from the surrounding generic environment (Environment::init::<N>, ProviderFactory<N>); in init and prune it's vacuous because no tx decoding happens, but in unwind it picks OpTxType::from_compact for the block-body decode, which panic!s on the extended-id byte 0x7b (op-alloy reth_codec.rs:66).
Operational impact
Once the --proofs-history ExEx (#185) is running on celo-mainnet, the only way to roll the proofs DB back is celo-reth proofs unwind. Today that command panics before producing useful output. Workarounds (delete + re-init from scratch) are O(billions of entries) and not viable in incident response.
Proposed fix
Port op_proofs::unwind::Command into a Celo-specific equivalent — small enough to live in crates/celo-reth/src/proofs_unwind.rs (next to state_import.rs). The port should mirror op_proofs::unwind::Command's CLI surface exactly (--proofs-history.storage-path, --proofs-history.storage-version, target block), replace the N: CliNodeTypes<..., Primitives = OpPrimitives> bound with N: CliNodeTypes<..., Primitives = CeloPrimitives> (or drop the bound entirely if the body doesn't actually need it — worth checking; reth_optimism_trie may be the constraint), re-use OpProofsStore / MdbxProofsStorage / MdbxProofsStorageV2 as-is (the proofs storage layer itself is primitives-agnostic), and be wired into CeloCommand and CELO_SUBCOMMANDS so the existing intercept covers it.
If reth_optimism_trie itself has hard OpPrimitives bounds beyond the command surface, escalate — that's a deeper port (celo_trie parallel of op_trie) and worth a separate issue.
Acceptance criteria
Summary
celo-reth proofs unwindcurrently dispatches againstOpNodevia op-reth'sCliand will panic on the moment it touches a CIP-64 transaction. Unlikestage,db,p2p,prune, andre-execute(intercepted in #190),proofs unwindcannot be re-routed againstCeloNodefrom the binary alone.Background
PR #181 plus #190 re-route every op-reth subcommand that decodes static-file transactions or receipts so it runs against
CeloNode::Primitives = CeloPrimitivesinstead ofOpNode::Primitives = OpPrimitives. Theproofssubcommand sits behind a hard trait bound the override can't satisfy:CeloNode::Primitives = CeloPrimitives ≠ OpPrimitives, so acmd.execute::<CeloNode>(...)arm doesn't compile.Why
initandprunework,unwinddoesn'tThe three
proofssubcommands access the main DB very differently:proofs initInitializationJob::new(storage, db_tx, trie_layout).run(...)). Trie entries are primitives-agnostic.proofs pruneBlockHashReaderfor hash-by-number lookups.proofs unwindprovider_factory.recovered_block(target, TransactionVariant::NoHash)decodes the target block body, including transactions.The
OpPrimitivestrait bound is inherited from the surrounding generic environment (Environment::init::<N>,ProviderFactory<N>); ininitandpruneit's vacuous because no tx decoding happens, but inunwindit picksOpTxType::from_compactfor the block-body decode, whichpanic!s on the extended-id byte0x7b(op-alloy reth_codec.rs:66).Operational impact
Once the
--proofs-historyExEx (#185) is running on celo-mainnet, the only way to roll the proofs DB back iscelo-reth proofs unwind. Today that command panics before producing useful output. Workarounds (delete + re-init from scratch) are O(billions of entries) and not viable in incident response.Proposed fix
Port
op_proofs::unwind::Commandinto a Celo-specific equivalent — small enough to live incrates/celo-reth/src/proofs_unwind.rs(next tostate_import.rs). The port should mirrorop_proofs::unwind::Command's CLI surface exactly (--proofs-history.storage-path,--proofs-history.storage-version, target block), replace theN: CliNodeTypes<..., Primitives = OpPrimitives>bound withN: CliNodeTypes<..., Primitives = CeloPrimitives>(or drop the bound entirely if the body doesn't actually need it — worth checking;reth_optimism_triemay be the constraint), re-useOpProofsStore/MdbxProofsStorage/MdbxProofsStorageV2as-is (the proofs storage layer itself is primitives-agnostic), and be wired intoCeloCommandandCELO_SUBCOMMANDSso the existing intercept covers it.If
reth_optimism_trieitself has hardOpPrimitivesbounds beyond the command surface, escalate — that's a deeper port (celo_trieparallel ofop_trie) and worth a separate issue.Acceptance criteria
celo-reth proofs unwind --proofs-history.storage-path <path> <block>runs to completion on a celo-mainnet datadir containing CIP-64 transactions (no panic).proofs initandproofs prunecontinue to work unchanged (kept on op-reth's dispatch).