Documentation
¶
Overview ¶
Package enr implements Ethereum Node Records (ENR) as defined in EIP-778.
An ENR is a signed, versioned data structure containing information about a node's network addresses and capabilities. Each record contains:
- A sequence number (incremented on updates)
- An identity scheme and signature
- Arbitrary key-value pairs for node metadata
ENRs are limited to 300 bytes and are encoded using RLP (Recursive Length Prefix).
Index ¶
- Constants
- Variables
- func IsLANAddress(ip net.IP) bool
- func IsWANAddress(ip net.IP) bool
- func WithAttnets(attnets []byte) (string, interface{})
- func WithEth2(data Eth2ENRData) (string, interface{})
- func WithIP(ip net.IP) (string, interface{})
- func WithIP6(ip net.IP) (string, interface{})
- func WithIdentityScheme(scheme string) (string, interface{})
- func WithPublicKey(pubKey *ecdsa.PublicKey) (string, interface{})
- func WithSyncnets(syncnets []byte) (string, interface{})
- func WithTCP(port uint16) (string, interface{})
- func WithUDP(port uint16) (string, interface{})
- type ENRFilter
- type Entry
- type Eth2ENRData
- type EthENRData
- type Record
- func CreateSignedRecord(privKey *ecdsa.PrivateKey, entries ...interface{}) (*Record, error)
- func DecodeBase64(input string) (*Record, error)
- func Load(data []byte) (*Record, error)
- func New() *Record
- func NewRecord(entries ...interface{}) (*Record, error)
- func UpdateRecord(old *Record, privKey *ecdsa.PrivateKey, entries ...interface{}) (*Record, error)
- func (r *Record) Clone() (*Record, error)
- func (r *Record) DecodeRLP(s *rlp.Stream) error
- func (r *Record) DecodeRLPBytes(data []byte) error
- func (r *Record) EncodeBase64() (string, error)
- func (r *Record) EncodeRLP() ([]byte, error)
- func (r *Record) Eth() (EthENRData, bool)
- func (r *Record) Eth2() (*Eth2ENRData, bool)
- func (r *Record) Get(key string, dest interface{}) error
- func (r *Record) Has(key string) bool
- func (r *Record) IP() net.IP
- func (r *Record) IP6() net.IP
- func (r *Record) IdentityScheme() string
- func (r *Record) Keys() []string
- func (r *Record) NodeID() []byte
- func (r *Record) Pairs() map[string]interface{}
- func (r *Record) PublicKey() *ecdsa.PublicKey
- func (r *Record) Seq() uint64
- func (r *Record) Set(key string, value interface{}) error
- func (r *Record) SetSeq(seq uint64)
- func (r *Record) Sign(privKey *ecdsa.PrivateKey) error
- func (r *Record) Size() int
- func (r *Record) String() string
- func (r *Record) TCP() uint16
- func (r *Record) ToEnode() *enode.Node
- func (r *Record) UDP() uint16
- func (r *Record) VerifySignature() bool
- type ResponseFilter
Constants ¶
const ( // MaxRecordSize is the maximum allowed size of an ENR in bytes. // This limit ensures records can fit in UDP packets. MaxRecordSize = 300 )
Variables ¶
var ( // ErrRecordTooLarge is returned when an ENR exceeds MaxRecordSize. ErrRecordTooLarge = errors.New("enr: record size exceeds 300 bytes") // ErrInvalidSignature is returned when signature verification fails. ErrInvalidSignature = errors.New("enr: invalid signature") // ErrNoKey is returned when a requested key is not present in the record. ErrNoKey = errors.New("enr: key not found") // ErrInvalidRecord is returned when a record has invalid structure. ErrInvalidRecord = errors.New("enr: invalid record structure") )
Functions ¶
func IsLANAddress ¶
IsLANAddress checks if an IP address is a private/local address.
Returns true for:
- 10.0.0.0/8 (RFC1918)
- 172.16.0.0/12 (RFC1918)
- 192.168.0.0/16 (RFC1918)
- fc00::/7 (IPv6 ULA)
- fe80::/10 (IPv6 link-local)
- 127.0.0.0/8 (loopback)
- ::1 (IPv6 loopback)
func IsWANAddress ¶
IsWANAddress checks if an IP address is a publicly routable address.
This is the inverse of IsLANAddress - returns true for public internet IPs.
func WithAttnets ¶
WithAttnets sets the attnets field in the record.
This field is a bitvector indicating which attestation subnets the Ethereum 2.0 node is subscribed to (used for subnet discovery).
Example:
attnets := make([]byte, 8) // 64-bit bitvector attnets[0] = 0xFF // Subscribed to first 8 subnets record.Set(WithAttnets(attnets))
func WithEth2 ¶
func WithEth2(data Eth2ENRData) (string, interface{})
WithEth2 sets the eth2 field in the record.
This field contains Ethereum 2.0 specific metadata including the fork digest, next fork version, and next fork epoch.
Example:
eth2Data := Eth2ENRData{
ForkDigest: [4]byte{0x01, 0x02, 0x03, 0x04},
NextForkVersion: [4]byte{0x00, 0x00, 0x00, 0x00},
NextForkEpoch: 0,
}
record.Set(WithEth2(eth2Data))
func WithIP ¶
WithIP sets the IPv4 address in the record.
Example:
record.Set(WithIP(net.IPv4(192, 168, 1, 1)))
func WithIP6 ¶
WithIP6 sets the IPv6 address in the record.
Example:
record.Set(WithIP6(net.ParseIP("2001:db8::1")))
func WithIdentityScheme ¶
WithIdentityScheme sets the identity scheme in the record.
Common schemes:
- "v4": secp256k1-based identity (most common)
Example:
record.Set(WithIdentityScheme("v4"))
func WithPublicKey ¶
WithPublicKey sets the secp256k1 public key in the record.
The key is stored in compressed form (33 bytes).
Example:
privateKey, _ := crypto.GenerateKey() record.Set(WithPublicKey(&privateKey.PublicKey))
func WithSyncnets ¶
WithSyncnets sets the syncnets field in the record.
This field is a bitvector indicating which sync committee subnets the Ethereum 2.0 node is subscribed to.
Example:
syncnets := make([]byte, 1) // 4-bit bitvector syncnets[0] = 0x0F // Subscribed to all 4 subnets record.Set(WithSyncnets(syncnets))
Types ¶
type ENRFilter ¶
ENRFilter is a function that filters ENR records based on arbitrary criteria.
Filters return true if the record should be accepted, false if it should be rejected. They are used in two stages of the discovery process:
- Admission filtering: Before adding nodes to the local routing table
- Response filtering: When serving FINDNODE responses to remote peers
Example filters:
- Check for specific protocol support (e.g., eth2 fork digest)
- Verify IP address ranges
- Check client versions or capabilities
- Validate custom application-specific fields
Example:
// Filter for nodes with a specific eth2 fork digest
filter := func(r *Record) bool {
var eth2Data Eth2ENRData
if err := r.Get("eth2", ð2Data); err != nil {
return false // No eth2 field
}
return bytes.Equal(eth2Data.ForkDigest[:], expectedDigest[:])
}
func ByIP ¶
ByIP creates a filter that checks if the node's IP address is in the given range.
The range is specified as a CIDR notation string (e.g., "192.168.0.0/16").
Example:
// Filter for nodes in private network
filter := ByIP("192.168.0.0/16")
func ByIdentityScheme ¶
ByIdentityScheme creates a filter that checks the identity scheme.
Common schemes:
- "v4": secp256k1-based identity (most common)
Example:
// Filter for v4 identity scheme
filter := ByIdentityScheme("v4")
func ByKey ¶
ByKey creates a filter that checks if a key exists in the record.
This is useful for filtering nodes that support specific features or protocols.
Example:
// Filter for nodes that have UDP port set
filter := ByKey("udp")
func ByUDPPort ¶
ByUDPPort creates a filter that checks if the UDP port matches.
Example:
// Filter for nodes on port 9000 filter := ByUDPPort(9000)
func ChainFilters ¶
ChainFilters combines multiple filters with AND logic.
The combined filter returns true only if all filters return true. If any filter returns false, the combined filter returns false.
Short-circuit evaluation: Filters are evaluated in order and evaluation stops at the first filter that returns false.
Example:
// Combine eth2 fork check and IP range check
filter := ChainFilters(
Eth2ForkFilter(expectedForkDigest),
IPRangeFilter(allowedRanges),
)
func Eth2ForkFilter ¶
Eth2ForkFilter creates a filter that checks for a specific eth2 fork digest.
The fork digest identifies which Ethereum 2.0 network and fork the node is operating on (mainnet, testnet, etc.).
This is commonly used as an admission filter to ensure discovered nodes are on the same network.
Example:
// Filter for mainnet nodes (example fork digest)
mainnetForkDigest := [4]byte{0x01, 0x02, 0x03, 0x04}
filter := Eth2ForkFilter(mainnetForkDigest)
type Entry ¶
type Entry interface {
// Key returns the ENR key for this entry
Key() string
}
Entry is an interface for ENR key-value entries.
Each entry knows how to encode itself into a record and provides metadata about the entry (key name, description).
type Eth2ENRData ¶
Eth2ENRData represents the eth2 field in an ENR record.
This field contains Ethereum 2.0 specific metadata including:
- ForkDigest: 4-byte identifier for the current fork
- NextForkVersion: Version of the next planned fork
- NextForkEpoch: Epoch when the next fork activates
type EthENRData ¶
EthENRData represents the eth field in an ENR record.
This field contains Ethereum specific metadata including:
- ForkID: 4-byte identifier for the current fork
- NextForkEpoch: Epoch when the next fork activates
type Record ¶
type Record struct {
// contains filtered or unexported fields
}
Record represents an Ethereum Node Record (ENR) as defined in EIP-778.
A record consists of:
- Signature: Cryptographic signature over the record content
- Seq: Sequence number, incremented on each update
- Pairs: Key-value pairs containing node metadata
The record is immutable once created. To update a record, create a new one with an incremented sequence number.
func CreateSignedRecord ¶
func CreateSignedRecord(privKey *ecdsa.PrivateKey, entries ...interface{}) (*Record, error)
CreateSignedRecord creates and signs a new ENR record with the given entries.
This is a convenience function that:
- Creates a new record
- Sets all provided entries
- Signs the record with the private key
Example:
privateKey, _ := crypto.GenerateKey()
record, err := CreateSignedRecord(
privateKey,
WithIP(net.IPv4(192, 168, 1, 1)),
WithUDP(9000),
)
func DecodeBase64 ¶
DecodeBase64 decodes a base64-encoded record.
The input must be in the format "enr:BASE64" where BASE64 is URL-safe base64 without padding.
Example:
record := New()
if err := record.DecodeBase64("enr:-IS4QHCYrYZ..."); err != nil {
// Handle error
}
func Load ¶
Load loads a record from an RLP-encoded byte slice.
This is a convenience function equivalent to:
record := New() record.DecodeRLPBytes(data)
func New ¶
func New() *Record
New creates a new empty ENR record.
The record will have a sequence number of 0 and no entries. Use Load or Decode to create a record from existing data.
func NewRecord ¶
NewRecord creates a new ENR record with the given key-value pairs.
This is a convenience function for building records with multiple fields.
Example:
record, err := NewRecord(
WithIP(net.IPv4(192, 168, 1, 1)),
WithUDP(9000),
WithTCP(9000),
)
func UpdateRecord ¶
func UpdateRecord(old *Record, privKey *ecdsa.PrivateKey, entries ...interface{}) (*Record, error)
UpdateRecord creates an updated version of an existing record.
The new record will have:
- Incremented sequence number
- All entries from the old record
- Updates from the provided entries
- New signature
Example:
// Update IP address and UDP port
newRecord, err := UpdateRecord(
oldRecord,
privateKey,
WithIP(net.IPv4(192, 168, 1, 2)),
WithUDP(9001),
)
func (*Record) Clone ¶
Clone creates a deep copy of the record by marshaling and unmarshaling.
This is useful when you need to modify a record without affecting the original. The cloned record will have the same sequence number and all fields.
Example:
clone := record.Clone()
clone.SetSeq(record.Seq() + 1)
clone.Set("ip", newIP)
func (*Record) DecodeRLP ¶
DecodeRLP decodes an RLP stream into a record.
This method implements the rlp.Decoder interface, allowing Record to be automatically decoded when embedded in other structures using rlp.DecodeBytes.
The stream must contain the raw RLP bytes of the ENR record.
func (*Record) DecodeRLPBytes ¶
DecodeRLPBytes decodes an RLP-encoded record from a byte slice.
The input must be a valid RLP list containing: [signature, seq, k1, v1, k2, v2, ...]
After decoding, the signature is automatically verified. Returns ErrInvalidSignature if verification fails.
Example:
encoded := []byte{...}
record := New()
if err := record.DecodeRLPBytes(encoded); err != nil {
// Handle error
}
func (*Record) EncodeBase64 ¶
EncodeBase64 encodes the record in base64 format.
This is the standard format for sharing ENRs in text form. The output is URL-safe base64 without padding.
Example output: "enr:-IS4QHCYrYZ..."
func (*Record) EncodeRLP ¶
EncodeRLP returns the RLP encoding of the record.
The encoding format is: [signature, seq, k1, v1, k2, v2, ...] where keys are sorted lexicographically.
The encoding is cached after first call. The cache is invalidated when the record is modified.
Returns ErrRecordTooLarge if the encoded record exceeds 300 bytes.
func (*Record) Eth ¶
func (r *Record) Eth() (EthENRData, bool)
Eth returns the Ethereum metadata from the record.
Returns nil and false if no "eth" key is present or if decoding fails.
func (*Record) Eth2 ¶
func (r *Record) Eth2() (*Eth2ENRData, bool)
Eth2 returns the Ethereum 2.0 metadata from the record.
Returns nil and false if no "eth2" key is present or if decoding fails.
func (*Record) Get ¶
Get retrieves a value from the record by key.
The value is decoded into the provided destination pointer. Returns ErrNoKey if the key is not present in the record.
Example:
var ip net.IP
if err := r.Get("ip", &ip); err != nil {
// Key not found or decoding error
}
func (*Record) Has ¶
Has checks if a key exists in the record.
Example:
if r.Has("udp") {
var port uint16
r.Get("udp", &port)
}
func (*Record) IP ¶
IP returns the IPv4 address from the record.
Returns nil if no "ip" key is present or if the value is not a valid IP.
func (*Record) IP6 ¶
IP6 returns the IPv6 address from the record.
Returns nil if no "ip6" key is present or if the value is not a valid IP.
func (*Record) IdentityScheme ¶
IdentityScheme returns the identity scheme of the record.
Common schemes:
- "v4": secp256k1-based identity (most common)
Returns empty string if no "id" key is present.
func (*Record) Keys ¶
Keys returns all keys present in the record.
The returned slice is a copy and can be safely modified.
func (*Record) NodeID ¶
NodeID returns the node ID derived from the public key.
The node ID is the keccak256 hash of the uncompressed public key (without the 0x04 prefix). Returns nil if the record has no valid public key.
func (*Record) Pairs ¶
Pairs returns a copy of all key-value pairs in the record.
The returned map is a copy and can be safely modified.
func (*Record) PublicKey ¶
PublicKey returns the secp256k1 public key from the record.
Returns nil if no "secp256k1" key is present or if the key is invalid.
func (*Record) Seq ¶
Seq returns the sequence number of the record.
The sequence number is incremented each time the record is updated. It allows peers to determine which version of a record is newer.
func (*Record) Set ¶
Set stores a key-value pair in the record.
The key must be a string and the value must be RLP-encodable. Common keys include:
- "id": Identity scheme (e.g., "v4")
- "ip": IPv4 address
- "ip6": IPv6 address
- "tcp": TCP port
- "udp": UDP port
- "secp256k1": Compressed secp256k1 public key
- "eth2": Ethereum 2.0 metadata
Example:
r.Set("ip", net.IPv4(192, 168, 1, 1))
r.Set("udp", uint16(9000))
func (*Record) SetSeq ¶
SetSeq sets the sequence number of the record.
This should be called when creating an updated version of a record. The new sequence number must be greater than the current one.
func (*Record) Sign ¶
func (r *Record) Sign(privKey *ecdsa.PrivateKey) error
Sign signs the record with the provided private key.
This updates the signature field and invalidates any cached encoding. The identity scheme is automatically set to "v4" (secp256k1).
Example:
privateKey, _ := crypto.GenerateKey()
record := New()
record.Set("ip", net.IPv4(192, 168, 1, 1))
record.Set("udp", uint16(9000))
if err := record.Sign(privateKey); err != nil {
// Handle error
}
func (*Record) Size ¶
Size returns the RLP-encoded size of the record in bytes.
This is useful for checking if the record exceeds the maximum size (300 bytes).
func (*Record) String ¶
String returns a human-readable representation of the record.
Format: ENR[seq=X, keys=[k1, k2, ...]]
func (*Record) TCP ¶
TCP returns the TCP port from the record.
Returns 0 if no "tcp" key is present or if the value is not a valid port.
func (*Record) ToEnode ¶
ToEnode converts the ENR record to a go-ethereum enode.Node.
This is useful for interoperability with go-ethereum's p2p stack. Returns nil if the record cannot be converted (missing required fields).
func (*Record) UDP ¶
UDP returns the UDP port from the record.
Returns 0 if no "udp" key is present or if the value is not a valid port.
func (*Record) VerifySignature ¶
VerifySignature verifies the record's signature.
Returns true if the signature is valid for the record's content and public key. Returns false if the signature is missing, invalid, or doesn't match the public key.
type ResponseFilter ¶
ResponseFilter is a context-aware filter for serving FINDNODE responses.
Unlike ENRFilter which operates only on the record, ResponseFilter also receives information about the requester (their network address).
This enables filtering based on the relationship between requester and node:
- Don't serve LAN IPs to WAN requesters
- Apply geographic filtering
- Implement custom privacy policies
Example:
// Don't serve LAN nodes to WAN requesters
filter := func(requester *net.UDPAddr, r *Record) bool {
if IsWANAddress(requester.IP) && IsLANAddress(r.IP()) {
return false
}
return true
}
func ChainResponseFilters ¶
func ChainResponseFilters(filters ...ResponseFilter) ResponseFilter
ChainResponseFilters combines multiple response filters with AND logic.
Similar to ChainFilters but for ResponseFilter types.
func LANAwareResponseFilter ¶
func LANAwareResponseFilter() ResponseFilter
LANAwareResponseFilter creates a response filter that prevents serving LAN (private network) IPs to WAN (public internet) requesters.
This is a security and efficiency measure:
- WAN peers can't connect to LAN addresses
- Prevents leaking internal network topology
LAN addresses include:
- 10.0.0.0/8 (RFC1918)
- 172.16.0.0/12 (RFC1918)
- 192.168.0.0/16 (RFC1918)
- fc00::/7 (IPv6 ULA)
- fe80::/10 (IPv6 link-local)
- 127.0.0.0/8 (loopback)
- ::1 (IPv6 loopback)
Example:
config := &Config{
ResponseFilter: LANAwareResponseFilter(),
}