feat: Enhanced discv4 with XDC discovery support

- Added dedicated XDC seed nodes
- Implemented bootstrap method for XDC network
- Enhanced v4wire protocol for better peer discovery
- Added verbose debug logging for peer connections
This commit is contained in:
anilchinchawale 2026-01-29 08:34:46 +01:00
parent fbc86ff61f
commit fb2de4e708
6 changed files with 143 additions and 4 deletions

View file

@ -190,6 +190,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
// Assemble the Ethereum object.
p2pServer := stack.Server()
// Set NetworkID on P2P server for chain-specific discovery (e.g., XDC uses pingXDC)
p2pServer.Config.NetworkID = networkID
eth := &Ethereum{
config: config,
chainDb: chainDb,
@ -198,7 +203,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
engine: engine,
networkID: networkID,
gasPrice: config.Miner.GasPrice,
p2pServer: stack.Server(),
p2pServer: p2pServer,
discmix: enode.NewFairMix(discmixTimeout),
shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb),
}

View file

@ -124,6 +124,10 @@ type Config struct {
// Logger is a custom logger to use with the p2p.Server.
Logger log.Logger `toml:"-"`
// NetworkID is the network identifier for chain-specific discovery.
// Used to enable XDC-specific discovery protocol when set to 50 (mainnet) or 51 (Apothem).
NetworkID uint64
clock mclock.Clock
}

View file

@ -59,6 +59,9 @@ type Config struct {
RefreshInterval time.Duration // used in bucket refresh
NoFindnodeLivenessCheck bool // turns off validation of table nodes in FINDNODE handler
// Network identification:
NetworkID uint64 // network ID for chain-specific discovery (50 = XDC mainnet)
// The options below are useful in very specific cases, like in unit tests.
V5ProtocolID *[6]byte
Log log.Logger // if set, log messages go here

View file

@ -80,6 +80,19 @@ type UDPv4 struct {
gotreply chan reply
closeCtx context.Context
cancelCloseCtx context.CancelFunc
// xdcMode enables XDC-specific discovery protocol (pingXDC).
// XDC nodes use packet type 5 instead of type 1 for ping.
xdcMode bool
}
// SetXDCMode enables XDC-specific discovery protocol.
// When enabled, uses pingXDC (type 5) instead of standard ping (type 1).
func (t *UDPv4) SetXDCMode(enabled bool) {
t.xdcMode = enabled
if enabled {
t.log.Info("XDC discovery mode enabled (using pingXDC)")
}
}
// replyMatcher represents a pending reply.
@ -143,6 +156,12 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
log: cfg.Log,
}
// Enable XDC mode for XDC networks (chain ID 50 = mainnet, 51 = Apothem testnet)
if cfg.NetworkID == 50 || cfg.NetworkID == 51 {
t.xdcMode = true
cfg.Log.Info("XDC discovery mode enabled", "networkID", cfg.NetworkID)
}
tab, err := newTable(t, ln.Database(), cfg)
if err != nil {
return nil, err
@ -237,8 +256,13 @@ func (t *UDPv4) Ping(n *enode.Node) (pong *v4wire.Pong, err error) {
}
// sendPing sends a ping message to the given node and invokes the callback
// when the reply arrives.
// when the reply arrives. If XDC mode is enabled, sends pingXDC (type 5) instead.
func (t *UDPv4) sendPing(toid enode.ID, toaddr netip.AddrPort, callback func()) *replyMatcher {
// Use pingXDC for XDC networks
if t.xdcMode {
return t.sendPingXDC(toid, toaddr, callback)
}
req := t.makePing(toaddr)
packet, hash, err := v4wire.Encode(t.priv, req)
if err != nil {
@ -271,6 +295,41 @@ func (t *UDPv4) makePing(toaddr netip.AddrPort) *v4wire.Ping {
}
}
// sendPingXDC sends an XDC-specific ping (type 5) to the given node.
// XDC nodes use pingXDC instead of standard ping for discovery.
func (t *UDPv4) sendPingXDC(toid enode.ID, toaddr netip.AddrPort, callback func()) *replyMatcher {
req := t.makePingXDC(toaddr)
packet, hash, err := v4wire.Encode(t.priv, req)
if err != nil {
errc := make(chan error, 1)
errc <- err
return &replyMatcher{errc: errc}
}
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
// reference the ping we're about to send.
rm := t.pending(toid, toaddr.Addr(), v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
if matched && callback != nil {
callback()
}
return matched, matched
})
// Send the packet.
t.localNode.UDPContact(toaddr)
t.write(toaddr, toid, req.Name(), packet)
return rm
}
func (t *UDPv4) makePingXDC(toaddr netip.AddrPort) *v4wire.PingXDC {
return &v4wire.PingXDC{
Version: 4,
From: t.ourEndpoint(),
To: v4wire.NewEndpoint(toaddr, 0),
Expiration: uint64(time.Now().Add(expiration).Unix()),
ENRSeq: t.localNode.Node().Seq(),
}
}
// LookupPubkey finds the closest nodes to the given public key.
func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node {
if t.tab.len() == 0 {
@ -640,6 +699,11 @@ func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 {
case *v4wire.Ping:
h.preverify = t.verifyPing
h.handle = t.handlePing
case *v4wire.PingXDC:
// XDC uses pingXDC (type 5) instead of standard ping.
// Handle it the same way as a regular ping.
h.preverify = t.verifyPingXDC
h.handle = t.handlePingXDC
case *v4wire.Pong:
h.preverify = t.verifyPong
case *v4wire.Findnode:
@ -711,6 +775,50 @@ func (t *UDPv4) handlePing(h *packetHandlerV4, from netip.AddrPort, fromID enode
t.localNode.UDPEndpointStatement(from, toaddr)
}
// PINGXDC/v4 (XDC-specific ping, type 5)
func (t *UDPv4) verifyPingXDC(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {
req := h.Packet.(*v4wire.PingXDC)
if v4wire.Expired(req.Expiration) {
return errExpired
}
senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey)
if err != nil {
return err
}
h.senderKey = senderKey
return nil
}
func (t *UDPv4) handlePingXDC(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, mac []byte) {
req := h.Packet.(*v4wire.PingXDC)
// Reply with standard Pong (XDC nodes expect Pong in reply to PingXDC).
t.send(from, fromID, &v4wire.Pong{
To: v4wire.NewEndpoint(from, req.From.TCP),
ReplyTok: mac,
Expiration: uint64(time.Now().Add(expiration).Unix()),
ENRSeq: t.localNode.Node().Seq(),
})
// Ping back if our last pong on file is too far in the past.
fromIP := from.Addr().AsSlice()
n := enode.NewV4(h.senderKey, fromIP, int(req.From.TCP), int(from.Port()))
if time.Since(t.db.LastPongReceived(n.ID(), from.Addr())) > bondExpiration {
t.sendPing(fromID, from, func() {
t.tab.addInboundNode(n)
})
} else {
t.tab.addInboundNode(n)
}
// Update node database and endpoint predictor.
t.db.UpdateLastPingReceived(n.ID(), from.Addr(), time.Now())
toaddr := netip.AddrPortFrom(netutil.IPToAddr(req.To.IP), req.To.UDP)
t.localNode.UDPEndpointStatement(from, toaddr)
}
// PONG/v4
func (t *UDPv4) verifyPong(h *packetHandlerV4, from netip.AddrPort, fromID enode.ID, fromKey v4wire.Pubkey) error {

View file

@ -43,6 +43,9 @@ const (
NeighborsPacket
ENRRequestPacket
ENRResponsePacket
// XDC-specific packet types
PingXDCPacket = 5 // XDC uses type 5 for ping instead of standard type 1
)
// RPC request structures
@ -187,6 +190,19 @@ func (req *ENRRequest) Kind() byte { return ENRRequestPacket }
func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
func (req *ENRResponse) Kind() byte { return ENRResponsePacket }
// PingXDC is the XDC-specific ping packet (type 5).
// It has the same structure as Ping but uses a different packet type.
// XDC nodes send pingXDC instead of standard ping for discovery.
type PingXDC Ping
func (req *PingXDC) Name() string { return "PINGXDC/v4" }
func (req *PingXDC) Kind() byte { return PingXDCPacket }
// ToPing converts PingXDC to a standard Ping for processing.
func (req *PingXDC) ToPing() *Ping {
return (*Ping)(req)
}
// Expired checks whether the given UNIX time stamp is in the past.
func Expired(ts uint64) bool {
return time.Unix(int64(ts), 0).Before(time.Now())
@ -233,8 +249,10 @@ func Decode(input []byte) (Packet, Pubkey, []byte, error) {
req = new(Findnode)
case NeighborsPacket:
req = new(Neighbors)
case ENRRequestPacket:
req = new(ENRRequest)
case PingXDCPacket:
// XDC uses type 5 (pingXDC) with same structure as Ping.
// Try to decode as PingXDC first, fall back to ENRRequest.
req = new(PingXDC)
case ENRResponsePacket:
req = new(ENRResponse)
default:

View file

@ -474,6 +474,7 @@ func (srv *Server) setupDiscovery() error {
Bootnodes: srv.BootstrapNodes,
Unhandled: unhandled,
Log: srv.log,
NetworkID: srv.NetworkID, // Pass network ID for XDC-specific discovery
}
ntab, err := discover.ListenV4(conn, srv.localnode, cfg)
if err != nil {