From fb2de4e708c8f698dad4a3840cfd0d7eebc4aa54 Mon Sep 17 00:00:00 2001 From: anilchinchawale Date: Thu, 29 Jan 2026 08:34:46 +0100 Subject: [PATCH] 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 --- eth/backend.go | 7 ++- p2p/config.go | 4 ++ p2p/discover/common.go | 3 + p2p/discover/v4_udp.go | 110 +++++++++++++++++++++++++++++++++- p2p/discover/v4wire/v4wire.go | 22 ++++++- p2p/server.go | 1 + 6 files changed, 143 insertions(+), 4 deletions(-) diff --git a/eth/backend.go b/eth/backend.go index aed1542aeb..7c7fb55752 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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), } diff --git a/p2p/config.go b/p2p/config.go index 17607a1f88..d037fc3229 100644 --- a/p2p/config.go +++ b/p2p/config.go @@ -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 } diff --git a/p2p/discover/common.go b/p2p/discover/common.go index 767cc23b92..cb0b5f9a5b 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -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 diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index dd3f363f7e..434042fe2a 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -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 { diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 958cca324d..8c9c7c9912 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -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: diff --git a/p2p/server.go b/p2p/server.go index 10c855f1c4..7fab3416a9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -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 {