EIP-2935 provides standardized access to historical block hashes through contract storage, enabling smart contracts to reliably access block hashes beyond the traditional 256-block limit.

Overview

What is EIP-2935?

EIP-2935 introduces a system contract that stores block hashes in contract storage, making them accessible to smart contracts for extended periods. This enables protocols requiring verifiable randomness, block-based logic, or historical verification.

Key Benefits

  • Extended History: Access up to 8192 block hashes (configurable)
  • Reliable Access: No longer limited to recent 256 blocks
  • Smart Contract Compatibility: Works with existing BLOCKHASH opcode
  • Verifiable Randomness: Enable protocols requiring historical entropy

Implementation Details

Block Hash Storage Mechanism

Cosmos EVM implements EIP-2935 by storing block hashes in a special system contract storage:
Contract Address: 0x0aae40965e6800cd9b1f4b05ff21581047e3f91e (EIP-2935 system contract)
Storage Layout:   block_number % HISTORY_SERVE_WINDOW → block_hash

Configuration Parameter

The history_serve_window parameter controls how many historical block hashes are stored:
{
  "vm": {
    "params": {
      "history_serve_window": 8192  // Default: 8192 blocks
    }
  }
}
Parameter Details:
  • Type: uint64
  • Default: 8192 (source)
  • Range: Must be > 0, recommended ≤ 8192
  • Storage Impact: Higher values increase storage requirements linearly

Comparison with Ethereum

Similarities

  • Same storage contract address: 0x0aae40965e6800cd9b1f4b05ff21581047e3f91e
  • Same storage layout: Ring buffer using modulo arithmetic
  • Same opcode behavior: BLOCKHASH queries contract storage
  • Same gas costs: Standard SLOAD costs apply (2100 gas for cold access)

Differences

  • Configurable window: Cosmos EVM allows custom history_serve_window (vs fixed 8192 in Ethereum)
  • Instant activation: Available immediately on upgrade (not block-based activation)
  • No fork logic: Uses consensus parameter instead of fork activation
  • Performance tuning: Can be optimized per chain requirements
  • Traditional 256-block limit: Ethereum’s original BLOCKHASH opcode only provided access to the most recent 256 blocks; EIP-2935 extends this significantly

Usage in Smart Contracts

Basic Block Hash Access

Basic Block Hash Access
pragma solidity ^0.8.0;

contract BlockHashExample {
    function getRecentBlockHash(uint256 blockNumber)
        external
        view
        returns (bytes32)
    {
        // Works for blocks within history_serve_window range
        return blockhash(blockNumber);
    }

    function getCurrentBlockNumber() external view returns (uint256) {
        return block.number;
    }

    function getHistoricalRange() external view returns (uint256) {
        // Can access block hashes for last 8192 blocks (default)
        return 8192;
    }
}

Verifiable Randomness Protocol

Verifiable Randomness Protocol
contract VerifiableRandomness {
    struct RandomnessCommit {
        bytes32 blockHash;
        uint256 blockNumber;
        uint256 timestamp;
        bool revealed;
    }

    mapping(bytes32 => RandomnessCommit) public commits;

    function commitRandomness(bytes32 commitment) external {
        // Commit to using future block hash as randomness source
        uint256 revealBlock = block.number + 10; // Reveal 10 blocks later

        commits[commitment] = RandomnessCommit({
            blockHash: bytes32(0), // Will be filled during reveal
            blockNumber: revealBlock,
            timestamp: block.timestamp,
            revealed: false
        });
    }

    function revealRandomness(
        bytes32 commitment,
        uint256 nonce
    ) external returns (bytes32 randomness) {
        RandomnessCommit storage commit = commits[commitment];
        require(!commit.revealed, "Already revealed");
        require(block.number >= commit.blockNumber, "Too early to reveal");

        // Get block hash from historical storage (EIP-2935)
        bytes32 blockHash = blockhash(commit.blockNumber);
        require(blockHash != bytes32(0), "Block hash not available");

        commit.blockHash = blockHash;
        commit.revealed = true;

        // Generate verifiable randomness
        randomness = keccak256(abi.encodePacked(
            blockHash,
            commitment,
            nonce
        ));

        return randomness;
    }
}

Block-Based State Transitions

Block-Based State Transitions
contract BlockBasedLogic {
    uint256 public constant EPOCH_LENGTH = 1000; // blocks
    mapping(uint256 => bytes32) public epochSeeds;

    function updateEpochSeed() external {
        uint256 currentEpoch = block.number / EPOCH_LENGTH;

        if (epochSeeds[currentEpoch] == bytes32(0)) {
            // Use block hash from start of epoch as seed
            uint256 epochStartBlock = currentEpoch * EPOCH_LENGTH;
            bytes32 seedHash = blockhash(epochStartBlock);

            require(seedHash != bytes32(0), "Epoch seed block not available");
            epochSeeds[currentEpoch] = seedHash;
        }
    }

    function getEpochSeed(uint256 epoch) external view returns (bytes32) {
        return epochSeeds[epoch];
    }
}

Configuration Examples

High Compatibility (Default)

{
  "vm": {
    "params": {
      "history_serve_window": 8192
    }
  }
}
Use Case: Full EIP-2935 compatibility for protocols requiring extended block history Storage: ~256KB additional storage (32 bytes × 8192 blocks) Performance: Standard performance, suitable for most chains

Performance Optimized

{
  "vm": {
    "params": {
      "history_serve_window": 1024
    }
  }
}
Use Case: Resource-constrained nodes or chains prioritizing performance Storage: ~32KB additional storage (32 bytes × 1024 blocks) Trade-off: Shorter historical access but better performance

Extended History

{
  "vm": {
    "params": {
      "history_serve_window": 16384
    }
  }
}
Use Case: Protocols requiring very long historical access Storage: ~512KB additional storage (32 bytes × 16384 blocks) Performance: Higher storage overhead but maximum compatibility

Technical Implementation

BLOCKHASH Opcode Behavior

Before EIP-2935:
blockhash(n) → Returns hash only if (block.number - 257) < n < block.number
               Returns 0x0 for older blocks
After EIP-2935:
blockhash(n) → Returns hash if (block.number - history_serve_window) < n < block.number
               Uses contract storage for historical hashes
               Returns 0x0 only if outside window or invalid

Storage Layout

The EIP-2935 system contract uses a simple storage mapping:
Key:   block_number % history_serve_window
Value: block_hash
Example with history_serve_window = 8192:
Storage[0] = hash(block 0, 8192, 16384, ...)
Storage[1] = hash(block 1, 8193, 16385, ...)
...
Storage[8191] = hash(block 8191, 16383, 24575, ...)

Integration with EVM

The implementation integrates seamlessly with the EVM:
  1. Block Processing: Each new block stores its hash in the system contract
  2. BLOCKHASH Opcode: Modified to query contract storage for historical hashes
  3. Gas Costs: Uses standard SLOAD gas costs for historical access
  4. Compatibility: Maintains full backward compatibility

Comparison with Ethereum

Similarities

  • Same storage contract address: 0x0aae40965e6800cd9b1f4b05ff21581047e3f91e
  • Same storage layout: Ring buffer using modulo arithmetic
  • Same opcode behavior: BLOCKHASH queries contract storage
  • Same gas costs: Standard SLOAD costs apply

Differences

  • Configurable window: Cosmos EVM allows custom history_serve_window
  • Instant activation: Available immediately on upgrade (not block-based)
  • No fork logic: Uses consensus parameter instead of fork activation
  • Performance tuning: Can be optimized per chain requirements

References