MainnetBlockView
A viewer contract deployed to Ethereum mainnet that provides access to block numbers and hashes. This contract is useful for cross-chain applications that need to verify block data from Ethereum mainnet. To prevent reorg-related issues, it only returns hashes for blocks that are at least 65 blocks old.
This contract is called off-chain via LayerZero's lzRead
functionality.
MainnetBlockView.vy
The source code for the MainnetBlockView.vy
contract can be found on GitHub. The contract is written using Vyper version 0.4.3
.
The contract is deployed on Ethereum at 0xb10CfacE69cc0B7F1AE0Dc8E6aD186914f6e7EEA
.
Contract ABI
[{"inputs":[],"name":"get_blockhash","outputs":[{"name":"","type":"uint256"},{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_block_number","type":"uint256"}],"name":"get_blockhash","outputs":[{"name":"","type":"uint256"},{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"name":"_block_number","type":"uint256"},{"name":"_avoid_failure","type":"bool"}],"name":"get_blockhash","outputs":[{"name":"","type":"uint256"},{"name":"","type":"bytes32"}],"stateMutability":"view","type":"function"}]
get_blockhash
¶
MainnetBlockView.get_blockhash(_block_number: uint256 = block.number - 65, _avoid_failure: bool = False) -> (uint256, bytes32)
Retrieves the block hash for a given block number. The valid range for historical block hashes is between the last 64 and the last 8192 blocks.
Block Range Constraints:
- Too recent: Blocks within the last 64 blocks (to mitigate Ethereum Mainnet reorg risk)
- Too old: Blocks older than 8192 blocks (EVM limit post EIP-2935)
- Valid range: Between
block.number - 8192
andblock.number - 64
Returns: A tuple containing (block_number, block_hash)
Input | Type | Description |
---|---|---|
_block_number | uint256 | Block number to get the hash for. Defaults to block.number - 65 |
_avoid_failure | bool | If True , returns (0, 0x0) on failure instead of reverting. Useful for cross-chain calls. |
Source code
from snekmate.utils import block_hash as snekmate_block_hash
@view
@external
def get_blockhash(
_block_number: uint256 = block.number - 65, _avoid_failure: bool = False
) -> (uint256, bytes32):
"""
@notice Get block hash for a given block number.
@dev The valid range for historical block hashes is between the last 64
and the last 8192 blocks.
@param _block_number Block number to get hash for, defaults to block.number - 65.
@param _avoid_failure If True, returns (0, 0x0) on failure instead of reverting.
@return Tuple of (actual block number, block hash).
"""
# Use a local variable for the requested block number.
requested_block_number: uint256 = _block_number
# If the default value was passed as 0 (e.g., from a cross-chain call
# that doesn't know the current block number), set a safe default.
if requested_block_number == 0:
requested_block_number = block.number - 65
# Check for invalid conditions first to exit early.
# The requested block must be at least 64 blocks old for reorg protection
# and not more than 8192 blocks old, which is the EVM's limit post EIP-2935.
is_too_recent: bool = requested_block_number >= block.number - 64
is_too_old: bool = requested_block_number <= block.number - 8192
if is_too_recent or is_too_old:
if _avoid_failure:
# For sensitive callers (like LayerZero), return a zeroed response
# instead of reverting the transaction.
return 0, empty(bytes32)
else:
# Revert with a descriptive custom error.
raise ("Block is too recent or too old")
# If all checks pass, retrieve and return the blockhash.
return requested_block_number, snekmate_block_hash._block_hash(requested_block_number)
Source code of this Vyper module can be found here.
# pragma version ~=0.4.3
# pragma nonreentrancy off
"""
@title Utility Functions to Access Historical Block Hashes
@custom:contract-name block_hash
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
@notice These functions can be used to access the historical block
hashes beyond the default 256-block limit. We use the EIP-2935
(https://eips.ethereum.org/EIPS/eip-2935) history contract,
which maintains a ring buffer of the last 8,191 block hashes
stored in state. For the blocks within the last 256 blocks,
we use the native `BLOCKHASH` opcode. For blocks between 257
and 8,191 blocks ago, the function `_block_hash` queries via
the specified `get` (https://eips.ethereum.org/EIPS/eip-2935#get)
method the EIP-2935 history contract. For blocks older than
8,191 or future blocks (including the current one), we return
zero, matching the `BLOCKHASH` behaviour.
Please note that after EIP-2935 is activated, it takes 8,191
blocks to fully populate the history. Before that, only block
hashes from the fork block onward are available.
"""
# @dev The `HISTORY_STORAGE_ADDRESS` contract address.
# @notice See the EIP-2935 specifications here: https://eips.ethereum.org/EIPS/eip-2935#specification.
_HISTORY_STORAGE_ADDRESS: constant(address) = 0x0000F90827F1C53a10cb7A02335B175320002935
# @dev The `keccak256` hash of the runtime bytecode of the
# history contract deployed at `HISTORY_STORAGE_ADDRESS`.
_HISTORY_STORAGE_RUNTIME_BYTECODE_HASH: constant(bytes32) = (
0x6e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d
)
@deploy
@payable
def __init__():
"""
@dev To omit the opcodes for checking the `msg.value`
in the creation-time EVM bytecode, the constructor
is declared as `payable`.
"""
pass
@internal
@view
def _block_hash(block_number: uint256) -> bytes32:
"""
@dev Returns the block hash for block number `block_number`.
@notice For blocks older than 8,191 or future blocks (including
the current one), returns zero, matching the `BLOCKHASH`
behaviour. Furthermore, this function does verify if the
history contract is deployed. If the history contract is
undeployed, the function will fallback to the `BLOCKHASH`
behaviour.
@param block_number The 32-byte block number.
@return bytes32 The 32-byte block hash for block number `block_number`.
"""
# For future blocks (including the current one), we already return
# an empty `bytes32` value here in order not to iterate through the
# remaining code.
if block_number >= block.number:
return empty(bytes32)
delta: uint256 = unsafe_sub(block.number, block_number)
if delta <= 256:
return blockhash(block_number)
elif delta > 8191 or _HISTORY_STORAGE_ADDRESS.codehash != _HISTORY_STORAGE_RUNTIME_BYTECODE_HASH:
# The Vyper built-in function `blockhash` reverts if the block number
# is more than `256` blocks behind the current block. We explicitly
# handle this case (i.e. `delta > 8191`) to ensure the function returns
# an empty `bytes32` value rather than reverting (i.e. exactly matching
# the `BLOCKHASH` opcode behaviour).
return empty(bytes32)
else:
return self._get_history_storage(block_number)
@internal
@view
def _get_history_storage(block_number: uint256) -> bytes32:
"""
@dev Returns the block hash for block number `block_number` by
calling the `HISTORY_STORAGE_ADDRESS` contract address.
@notice Please note that for any request outside the range of
`[block.number - 8191, block.number - 1]`, this function
reverts (see https://eips.ethereum.org/EIPS/eip-2935#get).
Furthermore, this function does not verify if the history
contract is deployed. If the history contract is undeployed,
the function will return an empty `bytes32` value.
@param block_number The 32-byte block number.
@return bytes32 The 32-byte block hash for block number `block_number`.
"""
return convert(
raw_call(
_HISTORY_STORAGE_ADDRESS,
abi_encode(block_number),
max_outsize=32,
is_static_call=True,
),
bytes32,
)
Get the default block hash (65 blocks ago)
Get a specific block hash
Use avoid_failure parameter