Skip to content

celo-reth: proofs unwind panics on CIP-64 decode #189

@seolaoh

Description

@seolaoh

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 Yesprovider_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

  • celo-reth proofs unwind --proofs-history.storage-path <path> <block> runs to completion on a celo-mainnet datadir containing CIP-64 transactions (no panic).
  • Unit tests covering the unwind path against a fixture that includes a CIP-64 block.
  • proofs init and proofs prune continue to work unchanged (kept on op-reth's dispatch).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions