Skip to main content

PeerInfo

Type-safe structure for connected peer information returned by admin_peers RPC method.

Overview

PeerInfo represents metadata about a connected Ethereum peer, including identity, capabilities, network connection details, and protocol-specific state. This data is returned by the admin_peers RPC method available in Geth and compatible clients.

Quick Start

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

// Parse peer info from RPC response
const peerInfo = PeerInfo.from({
  id: "enode://[email protected]:30303",
  name: "Geth/v1.13.0-stable/linux-amd64/go1.21.0",
  caps: ["eth/67", "eth/68", "snap/1"],
  network: {
    localAddress: "192.168.1.1:30303",
    remoteAddress: "192.168.1.100:30303",
    inbound: false,
    trusted: false,
    static: true
  },
  protocols: {
    eth: {
      version: 68,
      difficulty: 58750003716598352816469n,
      head: "0x1234..."
    }
  }
})

// Check capabilities
PeerInfo.hasCapability(peerInfo, "eth/68")  // true
PeerInfo.hasCapability(peerInfo, "snap/1")  // true

// Check connection direction
PeerInfo.isInbound(peerInfo)  // false

Type Definition

type PeerInfoType = {
  /** Peer ID (enode URL) */
  readonly id: PeerIdType
  /** Remote client identifier */
  readonly name: string
  /** Supported capabilities (e.g., ["eth/67", "snap/1"]) */
  readonly caps: readonly string[]
  /** Network connection information */
  readonly network: {
    /** Local endpoint (IP:PORT) */
    readonly localAddress: string
    /** Remote endpoint (IP:PORT) */
    readonly remoteAddress: string
    /** True if inbound connection */
    readonly inbound: boolean
    /** True if trusted peer */
    readonly trusted: boolean
    /** True if static node */
    readonly static: boolean
  }
  /** Protocol-specific information */
  readonly protocols: {
    /** Ethereum protocol info */
    readonly eth?: {
      readonly version: ProtocolVersionType
      readonly difficulty: BrandedUint
      readonly head: BlockHashType
    }
    readonly [protocol: string]: unknown
  }
}

API Reference

Constructors

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

// From RPC response object
const peerInfo = PeerInfo.from(rpcResponse)

Methods

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

const peerInfo = PeerInfo.from(data)

// Check if peer supports a capability
PeerInfo.hasCapability(peerInfo, "eth/68")  // boolean
PeerInfo.hasCapability(peerInfo, "snap/1")  // boolean

// Check connection direction
PeerInfo.isInbound(peerInfo)  // boolean

Peer Properties

Capabilities

The caps array lists protocols the peer supports:
peerInfo.caps
// ["eth/66", "eth/67", "eth/68", "snap/1"]

// Common capabilities:
// eth/66, eth/67, eth/68 - Ethereum wire protocol versions
// snap/1 - Snap sync protocol
// les/4 - Light client protocol
// wit/0 - Witness protocol

Network Information

peerInfo.network.localAddress   // "192.168.1.1:30303"
peerInfo.network.remoteAddress  // "203.0.113.50:30303"
peerInfo.network.inbound        // true if they connected to us
peerInfo.network.trusted        // true if in trusted peers list
peerInfo.network.static         // true if in static nodes list

Client Name

peerInfo.name
// "Geth/v1.13.0-stable/linux-amd64/go1.21.0"
// "Nethermind/v1.21.0/linux-x64/dotnet7.0.11"
// "Erigon/v2.55.0/linux-amd64/go1.21.0"

Protocol State

const eth = peerInfo.protocols.eth

eth?.version     // Protocol version (e.g., 68)
eth?.difficulty  // Peer's total difficulty
eth?.head        // Peer's head block hash

Use Cases

Peer Monitoring Dashboard

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

interface PeerStats {
  totalPeers: number
  inboundCount: number
  outboundCount: number
  clientDistribution: Record<string, number>
  protocolSupport: Record<string, number>
}

async function getPeerStats(rpcUrl: string): Promise<PeerStats> {
  const response = await fetch(rpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'admin_peers',
      params: [],
      id: 1
    })
  })
  const { result } = await response.json()
  const peers = result.map(PeerInfo.from)

  const clientDist: Record<string, number> = {}
  const protocolSupport: Record<string, number> = {}

  for (const peer of peers) {
    // Count clients
    const client = peer.name.split('/')[0]
    clientDist[client] = (clientDist[client] || 0) + 1

    // Count protocol support
    for (const cap of peer.caps) {
      protocolSupport[cap] = (protocolSupport[cap] || 0) + 1
    }
  }

  return {
    totalPeers: peers.length,
    inboundCount: peers.filter(p => PeerInfo.isInbound(p)).length,
    outboundCount: peers.filter(p => !PeerInfo.isInbound(p)).length,
    clientDistribution: clientDist,
    protocolSupport
  }
}

Filter Peers by Capability

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

function getPeersWithCapability(
  peers: PeerInfo.PeerInfoType[],
  capability: string
): PeerInfo.PeerInfoType[] {
  return peers.filter(p => PeerInfo.hasCapability(p, capability))
}

// Get peers that support snap sync
const snapPeers = getPeersWithCapability(peers, "snap/1")

// Get peers with latest eth protocol
const eth68Peers = getPeersWithCapability(peers, "eth/68")

Identify Sync Partners

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

function findBestSyncPeers(
  peers: PeerInfo.PeerInfoType[],
  count: number = 5
): PeerInfo.PeerInfoType[] {
  // Filter peers with eth protocol info
  const ethPeers = peers.filter(p => p.protocols.eth)

  // Sort by total difficulty (highest first)
  ethPeers.sort((a, b) => {
    const diffA = a.protocols.eth?.difficulty ?? 0n
    const diffB = b.protocols.eth?.difficulty ?? 0n
    return diffB > diffA ? 1 : diffB < diffA ? -1 : 0
  })

  // Return top N peers
  return ethPeers.slice(0, count)
}

Connection Health Check

import * as PeerInfo from '@tevm/voltaire/PeerInfo'

interface ConnectionHealth {
  healthy: boolean
  issues: string[]
}

function checkConnectionHealth(
  peers: PeerInfo.PeerInfoType[]
): ConnectionHealth {
  const issues: string[] = []

  // Check peer count
  if (peers.length < 3) {
    issues.push(`Low peer count: ${peers.length}`)
  }

  // Check inbound/outbound balance
  const inbound = peers.filter(p => PeerInfo.isInbound(p)).length
  const outbound = peers.length - inbound
  if (inbound === 0) {
    issues.push('No inbound connections (may be behind NAT)')
  }

  // Check client diversity
  const clients = new Set(peers.map(p => p.name.split('/')[0]))
  if (clients.size < 2 && peers.length > 5) {
    issues.push('Low client diversity')
  }

  // Check protocol support
  const eth68Support = peers.filter(p =>
    PeerInfo.hasCapability(p, "eth/68")
  ).length
  if (eth68Support === 0) {
    issues.push('No peers support eth/68 protocol')
  }

  return {
    healthy: issues.length === 0,
    issues
  }
}

References