Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions p2p/protocols/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import (
"fmt"
"reflect"
"sync"
"time"

"github.com/XinFinOrg/XDPoSChain/metrics"
"github.com/XinFinOrg/XDPoSChain/p2p"
)

Expand Down Expand Up @@ -216,6 +218,8 @@ func (p *Peer) Drop(err error) {
// this low level call will be wrapped by libraries providing routed or broadcast sends
// but often just used to forward and push messages to directly connected peers
func (p *Peer) Send(msg interface{}) error {
defer metrics.GetOrRegisterResettingTimer("peer.send_t", nil).UpdateSince(time.Now())
metrics.GetOrRegisterCounter("peer.send", nil).Inc(1)
code, found := p.spec.GetCode(msg)
if !found {
return errorf(ErrInvalidMsgType, "%v", code)
Expand Down
31 changes: 2 additions & 29 deletions p2p/rlpx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/ecies"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/pipes"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/davecgh/go-spew/spew"
"golang.org/x/crypto/sha3"
Expand Down Expand Up @@ -158,7 +159,7 @@ func TestProtocolHandshake(t *testing.T) {
wg sync.WaitGroup
)

fd0, fd1, err := tcpPipe()
fd0, fd1, err := pipes.TCPPipe()
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -597,31 +598,3 @@ func TestHandshakeForwardCompatibility(t *testing.T) {
t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash)
}
}

// tcpPipe creates an in process full duplex pipe based on a localhost TCP socket
func tcpPipe() (net.Conn, net.Conn, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, nil, err
}
defer l.Close()

var aconn net.Conn
aerr := make(chan error, 1)
go func() {
var err error
aconn, err = l.Accept()
aerr <- err
}()

dconn, err := net.Dial("tcp", l.Addr().String())
if err != nil {
<-aerr
return nil, nil, err
}
if err := <-aerr; err != nil {
dconn.Close()
return nil, nil, err
}
return aconn, dconn, nil
}
10 changes: 9 additions & 1 deletion p2p/simulations/adapters/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ import (
"github.com/docker/docker/pkg/reexec"
)

var (
ErrLinuxOnly = errors.New("DockerAdapter can only be used on Linux as it uses the current binary (which must be a Linux binary)")
)

// DockerAdapter is a NodeAdapter which runs simulation nodes inside Docker
// containers.
//
Expand All @@ -51,7 +55,7 @@ func NewDockerAdapter() (*DockerAdapter, error) {
// It is reasonable to require this because the caller can just
// compile the current binary in a Docker container.
if runtime.GOOS != "linux" {
return nil, errors.New("DockerAdapter can only be used on Linux as it uses the current binary (which must be a Linux binary)")
return nil, ErrLinuxOnly
}

if err := buildDockerImage(); err != nil {
Expand Down Expand Up @@ -95,6 +99,10 @@ func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
conf.Stack.P2P.NAT = nil
conf.Stack.Logger = log.New("node.id", config.ID.String())

// listen on all interfaces on a given port, which we set when we
// initialise NodeConfig (usually a random port)
conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)

node := &DockerNode{
ExecNode: ExecNode{
ID: config.ID,
Expand Down
60 changes: 38 additions & 22 deletions p2p/simulations/adapters/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package adapters

import (
"bufio"
"context"
"crypto/ecdsa"
"encoding/json"
Expand Down Expand Up @@ -103,9 +104,9 @@ func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
conf.Stack.P2P.NoDiscovery = true
conf.Stack.P2P.NAT = nil

// listen on a random localhost port (we'll get the actual port after
// starting the node through the RPC admin.nodeInfo method)
conf.Stack.P2P.ListenAddr = "127.0.0.1:0"
// listen on a localhost port, which we set when we
// initialise NodeConfig (usually a random port)
conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)

node := &ExecNode{
ID: config.ID,
Expand Down Expand Up @@ -189,9 +190,23 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
n.Cmd = cmd

// read the WebSocket address from the stderr logs
wsAddr, err := findWSAddr(stderrR, 10*time.Second)
if err != nil {
return fmt.Errorf("error getting WebSocket address: %s", err)
var wsAddr string
wsAddrC := make(chan string)
go func() {
s := bufio.NewScanner(stderrR)
for s.Scan() {
if strings.Contains(s.Text(), "WebSocket endpoint opened") {
wsAddrC <- wsAddrPattern.FindString(s.Text())
}
}
}()
select {
case wsAddr = <-wsAddrC:
if wsAddr == "" {
return errors.New("failed to read WebSocket address from stderr")
}
case <-time.After(10 * time.Second):
return errors.New("timed out waiting for WebSocket address on stderr")
}

// create the RPC client and load the node info
Expand Down Expand Up @@ -321,6 +336,21 @@ type execNodeConfig struct {
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
}

// ExternalIP gets an external IP address so that Enode URL is usable
func ExternalIP() net.IP {
addrs, err := net.InterfaceAddrs()
if err != nil {
log.Crit("error getting IP address", "err", err)
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() {
return ip.IP
}
}
log.Warn("unable to determine explicit IP address, falling back to loopback")
return net.IP{127, 0, 0, 1}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback IP address should use net.IPv4 for better clarity and consistency with Go conventions. Consider: return net.IPv4(127, 0, 0, 1)

Suggested change
return net.IP{127, 0, 0, 1}
return net.IPv4(127, 0, 0, 1)

Copilot uses AI. Check for mistakes.
Comment on lines +339 to +351
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ExternalIP function should prefer IPv4 addresses or be more specific about which IP version to return. Currently, it may return an IPv6 address when callers expect IPv4, which could cause issues with P2P.ListenAddr formatting. Consider filtering for IPv4 addresses specifically or handling both IP versions appropriately.

Suggested change
// ExternalIP gets an external IP address so that Enode URL is usable
func ExternalIP() net.IP {
addrs, err := net.InterfaceAddrs()
if err != nil {
log.Crit("error getting IP address", "err", err)
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() {
return ip.IP
}
}
log.Warn("unable to determine explicit IP address, falling back to loopback")
return net.IP{127, 0, 0, 1}
// ExternalIP gets an external IP address so that Enode URL is usable.
// It prefers IPv4 addresses and falls back to IPv6 if no suitable IPv4 is found.
func ExternalIP() net.IP {
addrs, err := net.InterfaceAddrs()
if err != nil {
log.Crit("error getting IP address", "err", err)
}
var ipv6Candidate net.IP
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
continue
}
// Prefer IPv4 addresses.
if ip4 := ip.To4(); ip4 != nil {
return ip4
}
// Remember the first suitable IPv6 address as a fallback.
if ipv6Candidate == nil {
ipv6Candidate = ip
}
}
if ipv6Candidate != nil {
return ipv6Candidate
}
log.Warn("unable to determine explicit IP address, falling back to loopback")
return net.IPv4(127, 0, 0, 1)

Copilot uses AI. Check for mistakes.
}

func initLogging() {
// Initialize the logging by default first.
var innerHandler slog.Handler
Expand Down Expand Up @@ -377,25 +407,11 @@ func execP2PNode() {
conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())

// use explicit IP address in ListenAddr so that Enode URL is usable
externalIP := func() string {
addrs, err := net.InterfaceAddrs()
if err != nil {
log.Crit("error getting IP address", "err", err)
}
for _, addr := range addrs {
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
return ip.IP.String()
}
}
log.Crit("unable to determine explicit IP address")
return ""
}
if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") {
conf.Stack.P2P.ListenAddr = externalIP() + conf.Stack.P2P.ListenAddr
conf.Stack.P2P.ListenAddr = ExternalIP().String() + conf.Stack.P2P.ListenAddr
}
if conf.Stack.WSHost == "0.0.0.0" {
conf.Stack.WSHost = externalIP()
conf.Stack.WSHost = ExternalIP().String()
}

// initialize the devp2p stack
Expand Down
50 changes: 41 additions & 9 deletions p2p/simulations/adapters/inproc.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ import (
"github.com/XinFinOrg/XDPoSChain/node"
"github.com/XinFinOrg/XDPoSChain/p2p"
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
"github.com/XinFinOrg/XDPoSChain/p2p/simulations/pipes"
"github.com/XinFinOrg/XDPoSChain/rpc"
"github.com/gorilla/websocket"
)

// SimAdapter is a NodeAdapter which creates in-memory simulation nodes and
// connects them using in-memory net.Pipe connections
// connects them using net.Pipe
type SimAdapter struct {
pipe func() (net.Conn, net.Conn, error)
mtx sync.RWMutex
nodes map[discover.NodeID]*SimNode
lifecycles LifecycleConstructors
Expand All @@ -43,10 +45,18 @@ type SimAdapter struct {
// NewSimAdapter creates a SimAdapter which is capable of running in-memory
// simulation nodes running any of the given services (the services to run on a
// particular node are passed to the NewNode function in the NodeConfig)
// the adapter uses a net.Pipe for in-memory simulated network connections
func NewSimAdapter(services LifecycleConstructors) *SimAdapter {
return &SimAdapter{
// nodes: make(map[discover.NodeID]*SimNode),
// lifecycles: lifecycles,
pipe: pipes.NetPipe,
nodes: make(map[discover.NodeID]*SimNode),
lifecycles: services,
}
}

func NewTCPAdapter(services LifecycleConstructors) *SimAdapter {
return &SimAdapter{
pipe: pipes.TCPPipe,
nodes: make(map[discover.NodeID]*SimNode),
lifecycles: services,
}
Expand Down Expand Up @@ -84,7 +94,7 @@ func (sa *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
MaxPeers: math.MaxInt32,
NoDiscovery: true,
Dialer: sa,
EnableMsgEvents: true,
EnableMsgEvents: config.EnableMsgEvents,
},
Logger: log.New("node.id", id),
})
Expand All @@ -105,7 +115,7 @@ func (sa *SimAdapter) NewNode(config *NodeConfig) (Node, error) {
}

// Dial implements the p2p.NodeDialer interface by connecting to the node using
// an in-memory net.Pipe connection
// an in-memory net.Pipe
func (sa *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) {
node, ok := sa.GetNode(dest.ID)
if !ok {
Expand All @@ -118,7 +128,14 @@ func (sa *SimAdapter) Dial(dest *discover.Node) (conn net.Conn, err error) {
if srv == nil {
return nil, fmt.Errorf("node not running: %s", dest.ID)
}
pipe1, pipe2 := net.Pipe()
// SimAdapter.pipe is net.Pipe (NewSimAdapter)
pipe1, pipe2, err := sa.pipe()
if err != nil {
return nil, err
}
// this is simulated 'listening'
// asynchronously call the dialed destintion node's p2p server
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: 'destintion' should be 'destination'.

Suggested change
// asynchronously call the dialed destintion node's p2p server
// asynchronously call the dialed destination node's p2p server

Copilot uses AI. Check for mistakes.
// to set up connection on the 'listening' side
go srv.SetupConn(pipe1, 0, nil)
node.connected[dest.ID] = true
return pipe2, nil
Expand All @@ -143,8 +160,8 @@ func (sa *SimAdapter) GetNode(id discover.NodeID) (*SimNode, bool) {
}

// SimNode is an in-memory simulation node which connects to other nodes using
// an in-memory net.Pipe connection (see SimAdapter.Dial), running devp2p
// protocols directly over that pipe
// net.Pipe (see SimAdapter.Dial), running devp2p protocols directly over that
// pipe
type SimNode struct {
lock sync.RWMutex
ID discover.NodeID
Expand Down Expand Up @@ -241,7 +258,7 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error {
service, err := serviceFunc(ctx, sn.node)
if err != nil {
regErr = err
return
break
}
// if the service has already been registered, don't register it again.
if _, ok := sn.running[name]; ok {
Expand Down Expand Up @@ -317,3 +334,18 @@ func (sn *SimNode) NodeInfo() *p2p.NodeInfo {
}
return server.NodeInfo()
}

func setSocketBuffer(conn net.Conn, socketReadBuffer int, socketWriteBuffer int) error {
switch v := conn.(type) {
case *net.UnixConn:
err := v.SetReadBuffer(socketReadBuffer)
if err != nil {
return err
}
err = v.SetWriteBuffer(socketWriteBuffer)
if err != nil {
return err
}
}
return nil
}
Comment on lines +337 to +351
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setSocketBuffer function is defined but never called. If this function is intended for future use, consider adding a comment explaining its purpose. Otherwise, consider removing it to avoid dead code.

Suggested change
func setSocketBuffer(conn net.Conn, socketReadBuffer int, socketWriteBuffer int) error {
switch v := conn.(type) {
case *net.UnixConn:
err := v.SetReadBuffer(socketReadBuffer)
if err != nil {
return err
}
err = v.SetWriteBuffer(socketWriteBuffer)
if err != nil {
return err
}
}
return nil
}

Copilot uses AI. Check for mistakes.
Loading