FuelLabs/fuel-core · critical

Registry root mismatch. registry root after decompression: {

Error message

Registry root mismatch. registry root after decompression: {:?}, registry root after compression: {:?}

What it means

With fault-proving enabled, decompression recomputes the registry root over the decompression-side registry and compares it with registry_root stored in the compressed block header (V1); any difference aborts with 'Registry root mismatch…' (crates/compression/src/decompress.rs:126). This is the fault-proving integrity check firing: the registry content used to decompress differs from the content used to compress — e.g. pruned/missing registrations, divergent temporal registry retention, or the wrong DB. It signals real state divergence, not a transient failure.

Source

Thrown at crates/compression/src/decompress.rs:126

            #[allow(clippy::arithmetic_side_effects)]
            u16::try_from(transaction_count - 1)?,
        );
    } else {
        anyhow::bail!("Last transaction is not a mint");
    }

    #[cfg(feature = "fault-proving")]
    {
        match block {
            VersionedCompressedBlock::V0(_) => {}
            VersionedCompressedBlock::V1(ref block) => {
                let registry_root_after_decompression = ctx
                    .db
                    .registry_root()
                    .map_err(|e| anyhow::anyhow!("Failed to get registry root: {}", e))?;
                let registry_root_after_compression = block.header.registry_root;
                if registry_root_after_decompression != registry_root_after_compression {
                    anyhow::bail!(
                        "Registry root mismatch. registry root after decompression: {:?}, registry root after compression: {:?}",
                        registry_root_after_decompression,
                        registry_root_after_compression
                    );
                }
            }
        }
    }

    Ok(PartialFuelBlock {
        header: block.partial_block_header(),
        transactions,
    })
}

pub struct DecompressCtx<D> {
    pub config: Config,
    /// Timestamp of the block being decompressed

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Restore the registry state as of the block's height so every referenced registration is present.
  2. Align temporal_registry_retention (crates/compression/src/config.rs) between producing and verifying setups.
  3. If entries legitimately expired, re-register and re-compress the block rather than decompressing against stale registry state.
  4. Treat occurrences as a verification failure: capture the block and registry roots for forensics instead of retrying.
Defensive patterns

Strategy: try-catch

Try / catch

match compressed_block.decompress(&mut db).await {
    Err(e) if e.to_string().contains("Registry root mismatch") => {
        // integrity failure: halt verification, preserve the block + registry roots for forensics;
        // retrying against the same registry state will fail again
        return Err(e.context("registry divergence during fault proving"));
    }
    r => r?,
}

Prevention

When it happens

Trigger: Decompressing a V1 compressed block against a registry DB whose contents differ from compression time: registrations pruned after temporal_registry_retention elapsed, registrations at different indices, or an entirely different registry state.

Common situations: Replaying or fault-proving old blocks after registry entries expired; verifier pointed at a different snapshot than the producer; partial registry import; temporal_registry_retention configured differently between compressor and verifier.

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/844a560a397859ad. Report an issue: GitHub.