protocol

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 17 Imported by: 0

Documentation

Overview

Package protocol implements the Discovery v4 wire protocol.

The discv4 protocol uses UDP packets with the following structure:

  • MAC (32 bytes): Keccak256 hash of signature + packet data
  • Signature (65 bytes): ECDSA signature over packet hash
  • Packet type (1 byte): Message type identifier
  • RLP-encoded message data

Total header size: 97 bytes (32 + 65)

Index

Constants

View Source
const (
	PingPacket = iota + 1 // zero is reserved
	PongPacket
	FindnodePacket
	NeighborsPacket
	ENRRequestPacket
	ENRResponsePacket
)

Packet type constants

View Source
const MaxNeighbors = 12

MaxNeighbors is the maximum number of nodes in a Neighbors packet. This limit ensures packets fit within the 1280 byte MTU.

Variables

View Source
var (
	// ErrPacketTooSmall is returned when a packet is smaller than the minimum size
	ErrPacketTooSmall = errors.New("packet too small")

	// ErrBadHash is returned when the MAC hash doesn't match
	ErrBadHash = errors.New("bad hash")

	// ErrBadSignature is returned when signature recovery fails
	ErrBadSignature = errors.New("bad signature")

	// ErrUnknownPacket is returned for unknown packet types
	ErrUnknownPacket = errors.New("unknown packet type")

	// ErrExpired is returned when a packet has expired
	ErrExpired = errors.New("packet expired")
)

Functions

func Decode

func Decode(input []byte) (Packet, Pubkey, []byte, error)

Decode decodes a discv4 UDP packet.

Returns:

  • packet: The decoded packet message
  • fromKey: The sender's public key (64 bytes, uncompressed without 0x04)
  • hash: The packet hash (used as reply token)
  • error: Any decoding error

The packet structure is:

[MAC 32][Signature 65][Type 1][RLP Data...]

func DecodePubkey

func DecodePubkey(curve elliptic.Curve, p Pubkey) (*ecdsa.PublicKey, error)

DecodePubkey decodes a wire-format public key to ECDSA format.

func Encode

func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error)

Encode encodes a discv4 packet with signature.

Returns:

  • packet: The complete encoded packet
  • hash: The packet hash (used as reply token)
  • error: Any encoding error

The packet structure is:

[MAC 32][Signature 65][Type 1][RLP Data...]

func EncodePacket

func EncodePacket(priv *ecdsa.PrivateKey, req Packet) ([]byte, error)

EncodePacket is a convenience function to encode a packet.

This is useful when you just need the packet bytes without the hash.

func Expired

func Expired(ts uint64) bool

Expired checks if a UNIX timestamp is in the past.

func MakeExpiration

func MakeExpiration(d time.Duration) uint64

MakeExpiration creates an expiration timestamp for the given duration from now.

Types

type ENRRequest

type ENRRequest struct {
	// Expiration is the UNIX timestamp when this packet expires
	Expiration uint64

	// Rest allows forward compatibility
	Rest []rlp.RawValue `rlp:"tail"`
}

ENRRequest represents an ENRREQUEST message.

ENRRequest queries for the node's current ENR record. This allows nodes to retrieve updated ENR information (added by EIP-868).

func (*ENRRequest) Kind

func (e *ENRRequest) Kind() byte

Kind returns the packet type byte.

func (*ENRRequest) Name

func (e *ENRRequest) Name() string

Name returns the packet name.

type ENRResponse

type ENRResponse struct {
	// ReplyTok is the hash of the ENRREQUEST packet we're responding to
	ReplyTok []byte

	// Record is the sender's ENR record
	Record *enr.Record

	// Rest allows forward compatibility
	Rest []rlp.RawValue `rlp:"tail"`
}

ENRResponse represents an ENRRESPONSE message (reply to ENRREQUEST).

ENRResponse contains the node's current ENR record.

func (*ENRResponse) Kind

func (e *ENRResponse) Kind() byte

Kind returns the packet type byte.

func (*ENRResponse) Name

func (e *ENRResponse) Name() string

Name returns the packet name.

type Endpoint

type Endpoint struct {
	// IP is the IP address (4 bytes for IPv4, 16 for IPv6)
	IP net.IP

	// UDP is the UDP port
	UDP uint16

	// TCP is the TCP port
	TCP uint16
}

Endpoint represents a network endpoint.

This is used in PING/PONG messages to communicate address information.

func NewEndpoint

func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint

NewEndpoint creates an endpoint from a UDP address.

func (Endpoint) TCPAddr

func (e Endpoint) TCPAddr() *net.TCPAddr

TCPAddr converts the endpoint to a TCPAddr.

func (Endpoint) UDPAddr

func (e Endpoint) UDPAddr() *net.UDPAddr

UDPAddr converts the endpoint to a UDPAddr.

type Findnode

type Findnode struct {
	// Target is the public key we're searching for neighbors of
	// The response should contain nodes close to this target
	Target Pubkey

	// Expiration is the UNIX timestamp when this packet expires
	Expiration uint64

	// Rest allows forward compatibility
	Rest []rlp.RawValue `rlp:"tail"`
}

Findnode represents a FINDNODE request.

Findnode queries for nodes close to a target in the Kademlia DHT. The sender must have an active bond (recent PING/PONG exchange) before the recipient will respond.

func (*Findnode) Kind

func (f *Findnode) Kind() byte

Kind returns the packet type byte.

func (*Findnode) Name

func (f *Findnode) Name() string

Name returns the packet name.

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler handles incoming and outgoing discv4 protocol messages.

The handler is responsible for:

  • Encoding/decoding packets
  • Processing PING, PONG, FINDNODE, NEIGHBORS, ENRREQUEST, ENRRESPONSE
  • Bond tracking and validation
  • Request/response matching
  • Callbacks for application logic

func NewHandler

func NewHandler(ctx context.Context, config HandlerConfig, transport Transport) *Handler

NewHandler creates a new protocol handler.

func (*Handler) AllNodes

func (h *Handler) AllNodes() []*node.Node

AllNodes returns all known nodes.

func (*Handler) Findnode

func (h *Handler) Findnode(n *node.Node, target []byte) ([]*node.Node, error)

Findnode sends a FINDNODE request to a node.

func (*Handler) GetNode

func (h *Handler) GetNode(id node.ID) *node.Node

GetNode returns a node by ID.

func (*Handler) GetStats added in v0.0.4

func (h *Handler) GetStats() HandlerStats

GetStats returns current statistics.

func (*Handler) HandlePacket

func (h *Handler) HandlePacket(data []byte, from *net.UDPAddr, localAddr *net.UDPAddr) (err error)

HandlePacket processes an incoming UDP packet.

This is called by the transport layer when a packet is received. The localAddr parameter is the local address that received the packet.

func (*Handler) LocalRecord added in v0.0.4

func (h *Handler) LocalRecord() *enr.Record

LocalRecord returns the ENR the handler currently advertises.

func (*Handler) Ping

func (h *Handler) Ping(n *node.Node) (*Pong, error)

Ping sends a PING request to a node at its canonical address.

func (*Handler) RequestENR

func (h *Handler) RequestENR(n *node.Node) (*enr.Record, error)

RequestENR sends an ENRREQUEST to a node.

func (*Handler) SetLocalENR added in v0.0.4

func (h *Handler) SetLocalENR(record *enr.Record)

SetLocalENR replaces the advertised ENR in place, so a fork transition or an IP-discovery update does not have to rebuild the handler and lose its bonds, known nodes, pending requests and stats.

type HandlerConfig

type HandlerConfig struct {
	// PrivateKey is our node's private key
	PrivateKey *ecdsa.PrivateKey

	// LocalENR is our node's ENR record (optional). It seeds the handler's
	// record; SetLocalENR replaces it while running, so handler code must read
	// LocalRecord() rather than this field.
	LocalENR *enr.Record

	// LocalAddr is our listening address
	LocalAddr *net.UDPAddr

	// BondExpiration is how long bonds last (default 24 hours)
	BondExpiration time.Duration

	// RequestTimeout is how long to wait for responses (default 500ms)
	RequestTimeout time.Duration

	// ExpirationWindow is the acceptable time range for packet expiration (default 20s)
	ExpirationWindow time.Duration

	// MaxNodes is the maximum number of nodes to track (default 50000).
	// Once reached, new nodes are handled but not retained until a slot frees up,
	// keeping memory bounded under floods of distinct node IDs.
	MaxNodes int

	// NodeTTL is how long an unbonded node is retained since it was last seen
	// before it becomes eligible for eviction (default 5 minutes). Bonded nodes
	// are kept until their bond expires.
	NodeTTL time.Duration

	// Callbacks (all optional)
	OnPing         OnPingCallback
	OnPongReceived OnPongReceivedCallback
	OnFindnode     OnFindnodeCallback
	OnENRRequest   OnENRRequestCallback
	OnNodeSeen     OnNodeSeenCallback
}

HandlerConfig contains configuration for the protocol handler.

type HandlerStats added in v0.0.4

type HandlerStats struct {
	PacketsReceived       uint64
	PacketsSent           uint64
	InvalidPackets        uint64
	ExpiredPackets        uint64
	UnbondedFindnode      uint64
	FindnodeRequestsRecv  uint64
	FindnodeResponsesRecv uint64
	KnownNodes            int
	PendingRequests       int
	PendingNeighbors      int
}

HandlerStats is a snapshot of the handler's counters.

type Neighbors

type Neighbors struct {
	// Nodes is the list of nodes close to the target
	Nodes []NodeRecord

	// Expiration is the UNIX timestamp when this packet expires
	Expiration uint64

	// Rest allows forward compatibility
	Rest []rlp.RawValue `rlp:"tail"`
}

Neighbors represents a NEIGHBORS response (reply to FINDNODE).

Neighbors contains up to 12 node records. If more nodes need to be sent, multiple Neighbors packets are used.

func (*Neighbors) Kind

func (n *Neighbors) Kind() byte

Kind returns the packet type byte.

func (*Neighbors) Name

func (n *Neighbors) Name() string

Name returns the packet name.

type NodeRecord

type NodeRecord struct {
	// IP is the node's IP address (4 bytes for IPv4, 16 for IPv6)
	IP net.IP

	// UDP is the UDP port for discovery
	UDP uint16

	// TCP is the TCP port for RLPx
	TCP uint16

	// ID is the node's public key
	ID Pubkey
}

NodeRecord represents information about a node in the DHT.

This is used in NEIGHBORS responses to communicate node information.

type OnENRRequestCallback

type OnENRRequestCallback func(from *node.Node) error

OnENRRequestCallback is called when an ENRREQUEST is received.

type OnFindnodeCallback

type OnFindnodeCallback func(from *node.Node, target []byte, requester *net.UDPAddr) []*node.Node

OnFindnodeCallback is called when a FINDNODE request is received. Returns the list of nodes to include in the response.

type OnNodeSeenCallback

type OnNodeSeenCallback func(n *node.Node, timestamp time.Time)

OnNodeSeenCallback is called when we receive any valid packet from a node.

type OnPingCallback

type OnPingCallback func(from *node.Node, ping *Ping) error

OnPingCallback is called when a PING request is received.

type OnPongReceivedCallback

type OnPongReceivedCallback func(from *node.Node, provenAddr *net.UDPAddr, ip net.IP, port uint16)

OnPongReceivedCallback is called when a PONG response is received. The ip and port parameters contain our external address as seen by the remote peer.

provenAddr is the address the answered PING was sent to. Callers must use it rather than from.Addr(), which any later inbound packet rewrites, including a spoofed one; attributing a report to that address would undo the endpoint proof this callback is gated on.

type Packet

type Packet interface {
	// Name returns the packet name for logging
	Name() string
	// Kind returns the packet type byte
	Kind() byte
}

Packet is the interface for all discv4 messages.

func DecodePacket

func DecodePacket(input []byte) (Packet, error)

DecodePacket is a convenience function to decode a packet.

This is useful when you don't need the sender key or hash.

type PendingNeighborsResponse

type PendingNeighborsResponse struct {
	// Nodes accumulated so far
	Nodes []*node.Node

	// Reserved counts nodes a handler has claimed room for but may not have
	// appended yet. Reserving before decoding makes the persistence cap exact
	// even when packets are dispatched concurrently.
	Reserved int

	// Closed marks a delivered entry. Packets processed after delivery must
	// not reserve against it; cleanup evicts the tombstone by CreatedAt.
	Closed bool

	// CreatedAt is when we received the first packet
	CreatedAt time.Time
}

PendingNeighborsResponse tracks a multi-packet NEIGHBORS response.

type PendingRequest

type PendingRequest struct {
	// RequestHash is the hash of the outgoing packet (used as reply token)
	RequestHash []byte

	// ToNode is the destination node
	ToNode *node.Node

	// DestIP is the IP the request was sent to, snapshotted at send time; see
	// lookupOrCreateNode. ToNode.Addr() cannot serve here because promoteAddr can
	// move it between send and response.
	DestIP net.IP

	// PacketType is the type of request
	PacketType byte

	// CreatedAt is when the request was created
	CreatedAt time.Time

	// Timeout is when the request expires
	Timeout time.Time

	// ResponseChan receives the response (or nil on timeout)
	ResponseChan chan interface{}
}

PendingRequest tracks an outgoing request waiting for a response.

type Ping

type Ping struct {
	// Version is the discovery protocol version (currently 4)
	Version uint

	// From is the sender's endpoint information
	From Endpoint

	// To is the recipient's endpoint (as known by sender)
	To Endpoint

	// Expiration is the UNIX timestamp when this packet expires
	Expiration uint64

	// ENRSeq is the sender's ENR sequence number (optional, EIP-868)
	ENRSeq uint64 `rlp:"optional"`

	// Rest allows forward compatibility with future fields
	Rest []rlp.RawValue `rlp:"tail"`
}

Ping represents a PING message.

Ping is used for:

  1. Liveness check
  2. Endpoint verification (NAT detection)
  3. Establishing bonds before FINDNODE
  4. ENR sequence number exchange (EIP-868)

func (*Ping) Kind

func (p *Ping) Kind() byte

Kind returns the packet type byte.

func (*Ping) Name

func (p *Ping) Name() string

Name returns the packet name.

type Pong

type Pong struct {
	// To is the recipient's endpoint as seen by the sender
	// This allows the recipient to discover their external address
	To Endpoint

	// ReplyTok is the hash of the PING packet we're responding to
	// This allows the requester to match PONG to PING
	ReplyTok []byte

	// Expiration is the UNIX timestamp when this packet expires
	Expiration uint64

	// ENRSeq is the sender's ENR sequence number (optional, EIP-868)
	ENRSeq uint64 `rlp:"optional"`

	// Rest allows forward compatibility
	Rest []rlp.RawValue `rlp:"tail"`
}

Pong represents a PONG message (reply to PING).

Pong is used to:

  1. Confirm liveness
  2. Report the sender's external IP/port (for NAT traversal)
  3. Establish a bond with the requester
  4. Exchange ENR sequence numbers

func (*Pong) Kind

func (p *Pong) Kind() byte

Kind returns the packet type byte.

func (*Pong) Name

func (p *Pong) Name() string

Name returns the packet name.

type Pubkey

type Pubkey [64]byte

Pubkey represents an encoded 64-byte secp256k1 public key.

This is the uncompressed public key format (X and Y coordinates, 32 bytes each) without the 0x04 prefix.

func EncodePubkey

func EncodePubkey(key *ecdsa.PublicKey) Pubkey

EncodePubkey encodes an ECDSA public key to the wire format.

func (Pubkey) ID

func (p Pubkey) ID() []byte

ID returns the node ID (Keccak256 hash of the public key).

type Transport

type Transport interface {
	SendTo(data []byte, to *net.UDPAddr) error
	Send(data []byte, to *net.UDPAddr, from *net.UDPAddr) error
}

Transport interface for sending packets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL