Summary
A full/archive node restored from the official archival snapshot (snapshots.akash.network/akashnet-2-archival/) deterministically computes a non-canonical AppHash when it executes the first block after the snapshot head.
The node then gets stuck: blocksync rejects every peer with wrong Block.Header.AppHash.
We isolated it with module-hash-by-height: the snapshot's pre-state matches canonical on all modules, and after executing exactly one block only 3 modules diverge -> bank, oracle, wasm. Same binary (md5 identical to the official docker image), same pre-state, same block, different result.
This is not specific to one snapshot generation: we saw the same pattern with an earlier archival snapshot too (its head block also diverged on execution).
Environment
- akash
v2.1.0, commit 151b989a58f579ac08b688b325e0dfcc4b44fecb (binary md5 c660578e1dbc7f874fe17a6a2f64cdbc, byte-identical between the .deb and the binary inside ghcr.io/akash-network/node:2.1.0)
- cosmovisor v1.7.1, upgrades ladder v2.0.0 (patched with v2.0.1) and v2.1.0
- Ubuntu 24.04.4, glibc 2.39, x86-64 (Xeon E5 v3 / Haswell, KVM guest)
- goleveldb backend,
pruning = "nothing" (archive), tx indexer kv
- Snapshot:
akashnet-2_27763931.tar.lz4 (Last-Modified 2026-07-18)
- Restored via ranged HTTP +
lz4 -dc | tar -x (reproduced across 3 independent downloads/restores; goleveldb CRCs clean, so transfer corruption is ruled out)
Steps to reproduce
- Download and extract the archival snapshot
akashnet-2_27763931.tar.lz4.
- Start the node with the official v2.1.0 binary (via cosmovisor, upgrade
v2.1.0).
- Node opens the DB at height 27763931, then executes block 27763932.
Result
wrong Block.Header.AppHash.
Expected 0AD5D4F5A8CCBC2EBB32E1EFF55820629944E2F5E576F1727AAA17FA3D18B408
got 17FE2159019F9DCCF755E93B7486EACDE7825EF1E11DA7601B12BA3B22F2A58D
(Expected is our locally-computed AppHash; got is the canonical value from the next block's header.) Node is stuck at 27763932; blocksync rejects all peers.
Root-cause isolation (module-hash-by-height)
We compared per-module store hashes between our stuck node and one of our own in-cluster pruned nodes that followed consensus through this height on the same hardware (official ghcr.io/akash-network/node:2.1.0 image).
At height 27763931 (snapshot head, before executing any block) all 26 modules match byte-for-byte. The snapshot state is correct.
At height 27763932 (after executing that one block) exactly 3 modules diverge:
module our node (stuck) canonical (in-consensus node)
bank 2B30B1944CA45CA73465D82305523D64E3BDE620C882253F78949D13CC253A1D 601E4DCC76F70E2B8DB10C5F9156073F139EF24ACE8FCACD556A83A3DC537018
oracle 35370C06FDF0304E56D45310D168A23F21B8A882B2691109DD6A802654F3224E ECE921A5CE415AFFBAFA3AB6B7445D3B4FF10C1D755E61AE8E9BAE54238C36B5
wasm 92A37A3F69445AF12EC3311E74857CB3E259C18B1E2CA6CD29D1EECCA9219772 FD5F942EC8EF69E24D2645303FBE9B009AFBFBE62C95046F8B5080062F857AB7
The other 23 modules match at 27763932 as well.
What we ruled out
- Binary provenance: the
.deb binary and the binary inside the official docker image are byte-identical (same md5), same commit.
- IAVL fastnode: reproduced with
iavl-disable-fastnode = false and = true (state rolled back between attempts). Identical non-canonical hash both times, so it is fully deterministic locally and config-independent.
- Peer/network: the block data is fine; it is our execution result that differs. Our in-cluster pruned nodes on the same hardware synced through this height without any issue.
- Transfer corruption: 3 independent restores, clean CRCs.
Hypothesis
The bank + oracle + wasm triad points at the BME oracle-price -> mint/burn -> contract path. Since the committed pre-state is provably identical and the binary is identical, the divergence looks like execution that depends on something not carried in the committed state a snapshot restores. For example ABCI++ vote-extension / extended-commit context from the previous block, or a replay-vs-consensus code path difference when the block is applied via blocksync from a snapshot head rather than in live consensus. This would explain why the head block of every archival snapshot diverges while a node that built that context live (validators, pruned nodes) processes the same block fine.
Questions
- Is this a known determinism issue when restoring the archival snapshots (or more generally when executing a block from snapshot-loaded state)?
- How is the official archival node configured (app.toml/config.toml deltas, env vars, patches)? Which exact binary artifact does it run?
- Is there a patched build or a flag that lets a restored archival node execute its head block correctly?
We can share full logs, the four module-hash-by-height dumps, or run any diagnostic build against the pre-execution state (we snapshotted it, so retry is fast).
Summary
A full/archive node restored from the official archival snapshot (
snapshots.akash.network/akashnet-2-archival/) deterministically computes a non-canonical AppHash when it executes the first block after the snapshot head.The node then gets stuck: blocksync rejects every peer with
wrong Block.Header.AppHash.We isolated it with
module-hash-by-height: the snapshot's pre-state matches canonical on all modules, and after executing exactly one block only 3 modules diverge ->bank,oracle,wasm. Same binary (md5 identical to the official docker image), same pre-state, same block, different result.This is not specific to one snapshot generation: we saw the same pattern with an earlier archival snapshot too (its head block also diverged on execution).
Environment
v2.1.0, commit151b989a58f579ac08b688b325e0dfcc4b44fecb(binary md5c660578e1dbc7f874fe17a6a2f64cdbc, byte-identical between the.deband the binary insideghcr.io/akash-network/node:2.1.0)pruning = "nothing"(archive), tx indexerkvakashnet-2_27763931.tar.lz4(Last-Modified 2026-07-18)lz4 -dc | tar -x(reproduced across 3 independent downloads/restores; goleveldb CRCs clean, so transfer corruption is ruled out)Steps to reproduce
akashnet-2_27763931.tar.lz4.v2.1.0).Result
(
Expectedis our locally-computed AppHash;gotis the canonical value from the next block's header.) Node is stuck at 27763932; blocksync rejects all peers.Root-cause isolation (module-hash-by-height)
We compared per-module store hashes between our stuck node and one of our own in-cluster pruned nodes that followed consensus through this height on the same hardware (official
ghcr.io/akash-network/node:2.1.0image).At height 27763931 (snapshot head, before executing any block) all 26 modules match byte-for-byte. The snapshot state is correct.
At height 27763932 (after executing that one block) exactly 3 modules diverge:
The other 23 modules match at 27763932 as well.
What we ruled out
.debbinary and the binary inside the official docker image are byte-identical (same md5), same commit.iavl-disable-fastnode = falseand= true(state rolled back between attempts). Identical non-canonical hash both times, so it is fully deterministic locally and config-independent.Hypothesis
The
bank+oracle+wasmtriad points at the BME oracle-price -> mint/burn -> contract path. Since the committed pre-state is provably identical and the binary is identical, the divergence looks like execution that depends on something not carried in the committed state a snapshot restores. For example ABCI++ vote-extension / extended-commit context from the previous block, or a replay-vs-consensus code path difference when the block is applied via blocksync from a snapshot head rather than in live consensus. This would explain why the head block of every archival snapshot diverges while a node that built that context live (validators, pruned nodes) processes the same block fine.Questions
We can share full logs, the four
module-hash-by-heightdumps, or run any diagnostic build against the pre-execution state (we snapshotted it, so retry is fast).