Skip to main content

Documentation Index

Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt

Use this file to discover all available pages before exploring further.

ChainHead

Current chain head information representing the latest block at the tip of the blockchain.

Overview

ChainHead captures essential information about the latest block: number, hash, timestamp, and difficulty. Typically obtained from eth_getBlockByNumber(“latest”).

Type Definition

type ChainHeadType = {
  readonly number: BlockNumber;
  readonly hash: BlockHash;
  readonly timestamp: Uint256;        // Unix seconds
  readonly difficulty?: Uint256;      // 0 post-merge
  readonly totalDifficulty?: Uint256; // Cumulative from genesis
};

Usage

Getting Chain Head

import { ChainHead } from '@tevm/voltaire/primitives';

// From RPC response
const block = await rpc.eth_getBlockByNumber('latest', false);
const head = ChainHead.from({
  number: block.number,
  hash: block.hash,
  timestamp: block.timestamp
});

console.log(`Block ${head.number}`);
console.log(`Hash: ${BlockHash.toHex(head.hash)}`);
console.log(`Timestamp: ${head.timestamp}`);

Post-Merge (PoS)

Post-merge blocks have zero difficulty:
const head = ChainHead.from({
  number: 18000000n,
  hash: blockHash,
  timestamp: 1699000000n,
  difficulty: 0n  // Proof of Stake
});

console.log('Post-merge block (PoS)');

Pre-Merge (PoW)

Pre-merge blocks have non-zero difficulty:
const head = ChainHead.from({
  number: 15000000n,
  hash: blockHash,
  timestamp: 1650000000n,
  difficulty: 12000000000000000n,          // Block difficulty
  totalDifficulty: 58750003716598352816469n // Cumulative
});

console.log(`Difficulty: ${head.difficulty}`);
console.log(`Total difficulty: ${head.totalDifficulty}`);

RPC Integration

eth_getBlockByNumber

Get latest block:
// Get latest block
const block = await rpc.eth_getBlockByNumber('latest', false);

const head = ChainHead.from({
  number: BigInt(block.number),
  hash: block.hash,
  timestamp: BigInt(block.timestamp),
  difficulty: block.difficulty ? BigInt(block.difficulty) : undefined,
  totalDifficulty: block.totalDifficulty ? BigInt(block.totalDifficulty) : undefined
});

eth_blockNumber

Get just the latest block number:
const blockNumber = await rpc.eth_blockNumber();
console.log(`Current block: ${blockNumber}`);

Consensus Types

Proof of Stake (Post-Merge)

The Merge (September 15, 2022) transitioned Ethereum to PoS:
  • Block 15537394+ have difficulty: 0
  • No mining, validators propose blocks
  • 12 second slot time
function isPostMerge(head) {
  return head.difficulty === 0n;
}

Proof of Work (Pre-Merge)

Pre-merge blocks had mining difficulty:
  • Variable block times (~13 seconds average)
  • Difficulty adjusted every 2016 blocks
  • Total difficulty tracked cumulative work
function isPreMerge(head) {
  return head.difficulty && head.difficulty > 0n;
}

Time Analysis

Block Age

Calculate block age:
const now = BigInt(Math.floor(Date.now() / 1000));
const age = now - head.timestamp;

console.log(`Block age: ${age} seconds`);
console.log(`Block age: ${age / 60n} minutes`);

Network Health

Recent blocks indicate healthy network:
const now = BigInt(Math.floor(Date.now() / 1000));
const age = now - head.timestamp;

if (age < 30n) {
  console.log('Network healthy: recent block');
} else if (age < 300n) {
  console.log('Network normal');
} else {
  console.log('Warning: stale block data');
}

Chain Reorganizations

Track reorgs by monitoring chain head changes:
let previousHead = null;

async function checkForReorg() {
  const block = await rpc.eth_getBlockByNumber('latest', false);
  const currentHead = ChainHead.from(block);

  if (previousHead) {
    // Same number but different hash = reorg
    if (currentHead.number === previousHead.number &&
        !BlockHash.equals(currentHead.hash, previousHead.hash)) {
      console.log('Chain reorganization detected!');
    }

    // Number went backwards = deep reorg
    if (currentHead.number < previousHead.number) {
      console.log('Deep reorganization detected!');
    }
  }

  previousHead = currentHead;
}

setInterval(checkForReorg, 12000);  // Check every 12 seconds

Finality

Gasper Finality (PoS)

Blocks finalized after 2 epochs (~12.8 minutes):
async function isFinalized(blockNumber, rpc) {
  const latest = await rpc.eth_getBlockByNumber('latest', false);
  const currentBlock = BigInt(latest.number);

  // ~64 blocks per epoch, 2 epochs for finality
  const finalityDistance = 128n;

  return currentBlock - blockNumber >= finalityDistance;
}

Safe Block

Safe block is justified (~1 epoch old):
const safeBlock = await rpc.eth_getBlockByNumber('safe', false);
const safeHead = ChainHead.from(safeBlock);

console.log(`Safe block: ${safeHead.number}`);

Common Patterns

Wait for Block

Wait for specific block number:
async function waitForBlock(targetBlock, rpc) {
  while (true) {
    const block = await rpc.eth_getBlockByNumber('latest', false);
    const head = ChainHead.from(block);

    if (head.number >= targetBlock) {
      return head;
    }

    await new Promise(resolve => setTimeout(resolve, 12000));
  }
}

Block Production Rate

Calculate blocks per second:
async function getBlockRate(rpc, samples = 10) {
  const latest = await rpc.eth_getBlockByNumber('latest', false);
  const latestHead = ChainHead.from(latest);

  const oldBlockNum = latestHead.number - BigInt(samples);
  const old = await rpc.eth_getBlockByNumber(`0x${oldBlockNum.toString(16)}`, false);
  const oldHead = ChainHead.from(old);

  const timeDelta = latestHead.timestamp - oldHead.timestamp;
  const blockDelta = latestHead.number - oldHead.number;

  const blocksPerSecond = Number(blockDelta) / Number(timeDelta);
  return blocksPerSecond;
}

API Reference

Constructors

  • ChainHead.from({ number, hash, timestamp, difficulty?, totalDifficulty? }) - Create from block data

See Also