Caching Block Hashes
How AxiomV1 caches block hashes and how to interact with them.
The AxiomV1
smart contract caches block hashes from the Ethereum history and allows smart contracts to verify them against this cache. These historic block hashes are stored in two ways:
As a Merkle root corresponding to a batch of block numbers
[startBlockNumber, startBlockNumber + numFinal)
wherestartBlockNumber
is a multiple of1024
, andnumFinal
is in[1,1024]
. This is stored inhistoricalRoots
.As a Merkle mountain range of the Merkle roots of batches of 1024 block hashes starting from genesis to a recent block. The block hashes committed to by this Merkle mountain range will always be a subset of those whose Merkle roots are stored in the previous format.
Caching Merkle roots of block hashes
AxiomV1
caches the Keccak Merkle roots of consecutive length 1024 sequences of blocks with block numbers [1024 * x, ..., 1024 * x + 1023]
for an index x
in the mapping
Here historicalRoots[startBlockNumber]
holds the hash keccak(prevHash || root || numFinal)
, where prevHash
is the block hash of block startBlockNumber - 1
, root
is the Merkle root of the block hashes with index in [startBlockNumber, startBlockNumber + 1023]
, with block hashes after startBlockNumber + numFinal - 1
replaced by 0
, and numFinal
is the number of block hashes verified in this range of blocks.
The cache is updated by calling the updateRecent
, updateOld
, or updateHistorical
functions with the following function signatures:
These functions verify a ZK proof of the block header commitment chain and update historicalRoots
accordingly:
updateRecent
: Verifies a zero-knowledge proof that proves the block header commitment chain from[startBlockNumber, startBlockNumber + numFinal)
is correct, wherestartBlockNumber
is a multiple of1024
, andnumFinal
is in[1,1024]
. This reverts unlessstartBlockNumber + numFinal - 1
is in[block.number - 256, block.number)
, i.e., ifblockhash(startBlockNumber + numFinal - 1)
is accessible from within the smart contract at the block this function is called. The zero-knowledge proof checks that each parent hash is in the block header of the next block, and that the block header hashes to the block hash. This is accepted only if the block hash ofstartBlockNumber + numFinal - 1
, according to the zero-knowledge proof, matches the block hash according to the EVM.updateOld
: Verifies a zero-knowledge proof that proves the block header commitment chain from[startBlockNumber, startBlockNumber + 1024)
is correct, where blockstartBlockNumber + 1024
must already be cached by the smart contract. This stores a single new Merkle root in the cache.updateHistorical
: Same asupdateOld
except that it uses a different zero-knowledge proof to prove the block header commitment chain from[startBlockNumber, startBlockNumber + 2 ** 17)
. Requires blockstartBlockNumber + 2 ** 17
to already be cached by the smart contract. This stores2 ** 7 = 128
new Merkle roots in the cache.
These functions emit the event
for each update of a Merkle root.
Updating the Merkle mountain range
In order to allow access to block hashes across large block ranges, AxiomV1
stores historic block hashes in a second redundant form by maintaining a Merkle mountain range of the Merkle roots cached in historicalRoots
. The latest Merkle mountain range is stored in historicalMMR
and is kept updated by updateRecent
and appendHistoricalMMR
, which has the following function signature:
To facilitate asynchronous proving against a Merkle mountain range which may be updated on-chain during proving, we cache commitments to recent values of historicalMMR
in the ring buffer mmrRingBuffer
.
Reading from the cache
There are two ways to read from the cache. First, users may verify block hashes against Merkle roots in the cache via the isBlockHashValid
method. This requires users to provide a witness that a block hash is included in the cache, formatted via
This method verifies that merkleProof
is a valid Merkle path for the relevant block hash and checks that the Merkle root lies in the cache. To verify block hashes against a cached Merkle mountain range, we also providea mmrVerifyBlockHash
function.
Second, to access commitments to the entire cache simultaneously, users may access recent Merkle mountain ranges in mmrRingBuffer
. This is primarily used for fulfillment of queries by AxiomV1Query
, but users may also access it for their own purposes.
Last updated