Caching Block Hashes
How AxiomV2Core caches block hashes and how to interact with them.
Last updated
How AxiomV2Core caches block hashes and how to interact with them.
Last updated
The AxiomV2Core
smart contract caches block hashes from Ethereum's 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)
where startBlockNumber
is a multiple of 1024
, and numFinal
is in [1,1024]
. This is stored in historicalRoots
.
As a padded of the Merkle roots of batches of 1024 block hashes starting from genesis to a recent block.
AxiomV2Core
caches the Merkle roots of consecutive sequences of blocks, up to 1024 in total, in the mapping
Here historicalRoots[startBlockNumber]
is 0x0
unless the block hashes for block numbers [startBlockNumber, startBlockNumber + numFinal)
have already been verified. In the latter case,
where .
denotes concatenation,
prevHash
is the block hash of block startBlockNumber - 1
,
root
is the Merkle root of the block hashes with block numbers in [startBlockNumber, startBlockNumber + numFinal)
, padded with bytes32(0x0)
to form the 1024
leaves of a Merkle tree, and
numFinal
in [1, 1024]
is the number of block hashes verified in this range of blocks.
The cache is updated via the interface IAxiomV2Update
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:
updateOld
: Verifies a zero-knowledge proof that proves the block header commitment chain from [startBlockNumber, startBlockNumber + 1024)
is correct, where block startBlockNumber + 1024
must already be cached by the smart contract. This stores a single new Merkle root in the cache.
updateHistorical
: Same as updateOld
except that it uses a different zero-knowledge proof to prove the block header commitment chain from [startBlockNumber, startBlockNumber + 2 ** 17)
. Requires block startBlockNumber + 2 ** 17
to already be cached by the smart contract. This stores 2 ** 7 = 128
new Merkle roots in the cache.
These functions emit the event
for each update of historicalRoots
.
A Merkle mountain range over Merkle roots of 1024 consecutive block hashes
A padded Merkle root of part of the most recent 1024 block hashes.
The latest padded Merkle mountain range is stored in blockhashPmmr
. The mapping
caches commitments to recent values of blockhashPmmr
to faciliate asynchronous proving against a Merkle mountain range which may be updated on-chain during proving.
This function batch appends new Merkle roots in historicalRoots
which are not already committed to in blockhashPmmr
(usually because they were added by updateOld
).
There are two ways to read from the cache, encapsulated by the IAxiomV2Verifier
interface:
Verifying the block hash of a block within the last 256
most recent blocks can be done through isRecentBlockHashValid
.
To verify a historical block hash, one should use the isBlockHashValid
method which takes in a struct IAxiomV2Verifier.BlockHashWitness
. This provides a Merkle proof of a block hash into one of the Merkle roots stored in historicalRoots
. The isBlockHashValid
method verifies that the Merkle proof is a valid Merkle path for the relevant block hash and checks that the Merkle root lies in the AxiomV2Core
cache.
updateRecent
: Verifies a zero-knowledge proof that proves the block header commitment chain from [startBlockNumber, startBlockNumber + numFinal)
is correct, where startBlockNumber
is a multiple of 1024
, and numFinal
is in [1,1024]
. This reverts unless startBlockNumber + numFinal - 1
is in 256 most recent block hashes, i.e., if blockhash(startBlockNumber + numFinal - 1)
is 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 of startBlockNumber + numFinal - 1
, according to the zero-knowledge proof, matches the block hash according to the EVM.
In order to allow access to block hashes across large block ranges, AxiomV2Core
stores historic block hashes in a second redundant form by maintaining a padded which commits to a continguous chain of block hashes starting from genesis using:
Updates to blockhashPmmr
are made using newly verified Merkle roots added to . Updates are made either alongside historicalRoots
updates in or by calling appendHistoricalMMR
, which has the following function signature: