services

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 services implements node discovery operations.

The services package provides:

  • Iterative FINDNODE lookups (discv5 + discv4)
  • PING/PONG operations (discv5 + discv4)
  • Random walks for table maintenance
  • ENR-based filtering during discovery
  • Dual protocol support (prefers v5, falls back to v4)

Index

Constants

View Source
const DefaultAlpha = 3

DefaultAlpha is the concurrency factor for lookups (Kademlia parameter).

View Source
const DefaultLookupTimeout = 30 * time.Second

DefaultLookupTimeout is the timeout for a complete lookup operation.

View Source
const DefaultMajorityThreshold = 0.75

DefaultMajorityThreshold is the percentage threshold for IP consensus (0.0-1.0)

View Source
const DefaultMinDistinctIPs = 3

DefaultMinDistinctIPs is the minimum number of distinct reporter IPs needed

View Source
const DefaultMinReports = 5

DefaultMinReports is the minimum number of PONG responses needed before considering IP valid

View Source
const DefaultRecentWindow = 5 * time.Minute

DefaultRecentWindow is the time window to consider reports "recent" for IP change detection

View Source
const DefaultReportExpiry = 30 * time.Minute

DefaultReportExpiry is how long to keep IP reports before expiring them

Variables

This section is empty.

Functions

This section is empty.

Types

type AdmissionResult added in v0.0.4

type AdmissionResult int

AdmissionResult reports why a discovered node was or was not admitted, so fork-filter rejections stay distinguishable from pool-capacity rejections.

const (
	// AdmissionAccepted means the node was admitted to the table.
	AdmissionAccepted AdmissionResult = iota

	// AdmissionRejectedFilter means the node is on this layer but announced an
	// incompatible fork.
	AdmissionRejectedFilter

	// AdmissionRejectedLayer means the node serves the other layer, so this
	// lookup was never a candidate for it. On a dual-layer network most
	// discovered nodes land here, which is healthy and must not be read as a
	// fork-compatibility problem.
	AdmissionRejectedLayer

	// AdmissionRejectedPool means the node passed validation but the table
	// declined it (capacity, per-IP limit, or self).
	AdmissionRejectedPool
)

type Config

type Config struct {
	// LocalIDs are our own node IDs. A peer that returns one of our records in
	// a NODES/NEIGHBORS response would otherwise make us dial ourselves every
	// round, racing our own handshake challenges. There is one ID per discovery
	// identity, so two when separate EL and CL keys are configured.
	LocalIDs [][32]byte

	// NodeDB is the node database
	NodeDB *nodedb.NodeDB

	// Table is the routing table
	Table *nodedb.FlatTable

	// V5Handler is the discv5 protocol handler (may be nil)
	V5Handler *protocol.Handler

	// V4Service is the discv4 service (may be nil)
	V4Service *discv4.Service

	// Database for bad node tracking (may be nil)
	Database interface {
		IsBadNode(nodeID []byte, layer db.NodeLayer, recheckInterval time.Duration) (isBad bool, shouldRecheck bool, reason string, err error)
	}

	// Layer is the layer being looked up
	Layer db.NodeLayer

	// Alpha is the concurrency factor (default 3)
	Alpha int

	// LookupTimeout is the timeout for lookup operations
	LookupTimeout time.Duration

	// OnNodeFound is called when a new node is discovered during lookup
	// The callback should handle admission checks and add the node if valid
	OnNodeFound func(*nodedb.Node) AdmissionResult

	// Logger for debug messages
	Logger logrus.FieldLogger
}

Config contains configuration for the lookup service.

type IPDiscovery

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

IPDiscovery tracks external IP addresses and ports reported by peers via PONG messages.

It implements a consensus mechanism to detect the node's public IP address and port:

  • Collects IP:Port from PONG responses (shows our address as seen by remote peer)
  • Tracks IPv4 and IPv6 independently (separate consensus for each)
  • Requires minimum number of reports before considering an address valid
  • Requires minimum number of DISTINCT reporter IPs (prevents IP spoofing)
  • Requires majority threshold (e.g., 75%) for consensus
  • Expires old reports to handle IP/port changes

Works with both discv4 and discv5 protocols.

func NewIPDiscovery

func NewIPDiscovery(cfg IPDiscoveryConfig) *IPDiscovery

NewIPDiscovery creates a new IP discovery service.

func (*IPDiscovery) GetConsensusIP

func (ipd *IPDiscovery) GetConsensusIP() net.IP

GetConsensusIP returns the current consensus IPv4 address, or nil if no consensus. For IPv6, this returns nil. Use GetStats() for complete information.

func (*IPDiscovery) GetConsensusPort

func (ipd *IPDiscovery) GetConsensusPort() uint16

GetConsensusPort returns the current consensus IPv4 port, or 0 if no consensus.

func (*IPDiscovery) GetStats

func (ipd *IPDiscovery) GetStats() IPDiscoveryStats

GetStats returns current statistics.

func (*IPDiscovery) ReportIP

func (ipd *IPDiscovery) ReportIP(ip net.IP, port uint16, reporterID string, reporterIP net.IP)

ReportIP records an IP address and port from a PONG response.

Parameters:

  • ip: The IP address as reported by the remote peer (our external address)
  • port: The port as reported by the remote peer (our external port)
  • reporterID: The node ID of the peer that sent the PONG (for tracking)
  • reporterIP: The IP address of the reporter (to enforce distinct IP threshold)

func (*IPDiscovery) Reset

func (ipd *IPDiscovery) Reset()

Reset clears all reports and resets consensus state. This can be used when the node's network changes.

type IPDiscoveryConfig

type IPDiscoveryConfig struct {
	// MinReports is the minimum number of PONG responses needed (default: 5)
	MinReports int

	// MinDistinctIPs is the minimum number of distinct reporter IPs needed (default: 3)
	// This prevents a single malicious IP running multiple nodes from reaching consensus
	MinDistinctIPs int

	// MajorityThreshold is the percentage needed for consensus (default: 0.75)
	MajorityThreshold float64

	// ReportExpiry is how long to keep reports (default: 30 minutes)
	ReportExpiry time.Duration

	// RecentWindow is the time window to consider reports "recent" (default: 5 minutes)
	// Used for detecting IP changes - recent reports get priority
	RecentWindow time.Duration

	// OnConsensusReached is called when IP:Port consensus is reached or changes
	// isIPv6 indicates whether this is an IPv6 address (true) or IPv4 (false)
	OnConsensusReached func(ip net.IP, port uint16, isIPv6 bool)

	// Logger for debug messages
	Logger logrus.FieldLogger
}

IPDiscoveryConfig contains configuration for IP discovery

type IPDiscoveryStats

type IPDiscoveryStats struct {
	TotalReportsIPv4     int
	TotalReportsIPv6     int
	UniqueIPv4Addrs      int
	UniqueIPv6Addrs      int
	ConsensusReachedIPv4 bool
	ConsensusReachedIPv6 bool
	ConsensusIPv4Addr    string         // "IP:Port" format
	ConsensusIPv6Addr    string         // "IP:Port" format
	IPv4Reports          map[string]int // "IP:Port" -> count
	IPv6Reports          map[string]int // "IP:Port" -> count
}

IPDiscoveryStats contains statistics about IP discovery.

type LookupService

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

LookupService manages node discovery operations. Supports both discv5 and discv4 protocols (prefers v5, falls back to v4).

func NewLookupService

func NewLookupService(cfg Config) *LookupService

NewLookupService creates a new lookup service.

func (*LookupService) CleanupQueryHistory

func (ls *LookupService) CleanupQueryHistory(maxAge time.Duration) int

CleanupQueryHistory removes stale entries from the query history. This should be called periodically to prevent unbounded growth. Entries older than the given duration are removed.

func (*LookupService) GetStats

func (ls *LookupService) GetStats() LookupStats

GetStats returns discovery statistics.

func (*LookupService) Lookup

func (ls *LookupService) Lookup(ctx context.Context, target node.ID, k int) ([]*nodedb.Node, error)

Lookup performs a node lookup by querying random nodes from the table.

Since we use a flat table without buckets, we simply:

  • Select 3 semi-random nodes (preferring alive ones)
  • Query them concurrently for nodes near the target
  • Add discovered nodes via callback

Parameters:

  • ctx: Context for cancellation and timeout
  • target: The target node ID to find
  • k: The number of closest nodes to return

Returns discovered nodes that were added to the table.

func (*LookupService) LookupWithFilter

func (ls *LookupService) LookupWithFilter(ctx context.Context, target node.ID, k int, filter enr.ENRFilter) ([]*nodedb.Node, error)

LookupWithFilter performs a lookup with an ENR filter.

Only nodes that pass the filter are included in the results.

func (*LookupService) RandomWalk

func (ls *LookupService) RandomWalk(ctx context.Context) ([]*nodedb.Node, error)

RandomWalk performs a random walk to discover new nodes.

This is used for routing table maintenance and exploring the network.

type LookupStats

type LookupStats struct {
	LookupsStarted   int
	LookupsCompleted int
	LookupsFailed    int
	NodesDiscovered  int
	LookupsV5        int // Lookups using discv5
	LookupsV4        int // Lookups using discv4
}

GetStats returns statistics about discovery operations.

type PingService

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

PingService handles PING/PONG operations for liveness checks. Supports both discv5 and discv4 protocols (prefers v5, falls back to v4).

func NewPingService

func NewPingService(v5Handler *protocol.Handler, v4Service *discv4.Service, logger logrus.FieldLogger) *PingService

NewPingService creates a new ping service with dual protocol support. At least one of v5Handler or v4Service must be provided.

func (*PingService) CheckProtocolSupport

func (ps *PingService) CheckProtocolSupport(n *nodedb.Node) (bool, bool, error)

CheckProtocolSupport checks which protocols (v4 and/or v5) a node supports.

This pings the node on BOTH discv4 and discv5 to determine actual protocol support. The node's V4/V5 fields are updated based on which protocols respond successfully.

This should be called at a lower frequency than regular aliveness checks (e.g., every 30 minutes) to discover protocol capabilities without excessive overhead.

Returns (v4Supported, v5Supported, error)

func (*PingService) CheckProtocolSupportMultiple

func (ps *PingService) CheckProtocolSupportMultiple(nodes []*nodedb.Node)

CheckProtocolSupportMultiple checks protocol support for multiple nodes in parallel.

This is useful for periodically verifying protocol capabilities across the table.

func (*PingService) GetStats

func (ps *PingService) GetStats() PingStats

GetStats returns PING statistics.

func (*PingService) Ping

func (ps *PingService) Ping(n *nodedb.Node) (bool, time.Duration, error)

Ping sends a PING to a node and waits for PONG.

Prefers discv5 if available, falls back to discv4. Returns true if the node responded, false on timeout. Also updates the node's RTT statistics.

func (*PingService) PingMultiple

func (ps *PingService) PingMultiple(nodes []*nodedb.Node) map[[32]byte]bool

PingMultiple sends PINGs to multiple nodes in parallel.

Returns a map of node ID to ping result (success/failure).

type PingStats

type PingStats struct {
	PingsSent     int
	PongsReceived int
	PingTimeouts  int
	PingsV5       int // Pings sent via discv5
	PingsV4       int // Pings sent via discv4
	AverageRTT    time.Duration
	SuccessRate   float64
}

PingStats returns statistics about PING operations.

Jump to

Keyboard shortcuts

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