Repository: libp2p/go-libp2p
Pull request #2980 (2024-09-26 09:22:10)

basichost: ensure no duplicates in Addrs output
fixes: https://github.com/libp2p/go-libp2p/issues/2972


Patch:
diff --git a/go.mod b/go.mod
index ec6d7346..684f3544 100644
--- a/go.mod
+++ b/go.mod
@@ -60,7 +60,6 @@ require (
 	go.uber.org/fx v1.22.1
 	go.uber.org/goleak v1.3.0
 	go.uber.org/mock v0.4.0
-	go.uber.org/zap v1.27.0
 	golang.org/x/crypto v0.25.0
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
 	golang.org/x/sync v0.7.0
@@ -124,6 +123,7 @@ require (
 	github.com/wlynxg/anet v0.0.3 // indirect
 	go.uber.org/dig v1.17.1 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.27.0 // indirect
 	golang.org/x/mod v0.19.0 // indirect
 	golang.org/x/net v0.27.0 // indirect
 	golang.org/x/text v0.16.0 // indirect
diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go
index b5d252e9..761a39b6 100644
--- a/p2p/host/basic/basic_host.go
+++ b/p2p/host/basic/basic_host.go
@@ -500,6 +500,7 @@ func (h *BasicHost) makeUpdatedAddrEvent(prev, current []ma.Multiaddr) *event.Ev
 		return nil
 	}
 	prevmap := make(map[string]ma.Multiaddr, len(prev))
+	currmap := make(map[string]ma.Multiaddr, len(current))
 	evt := &event.EvtLocalAddressesUpdated{Diffs: true}
 	addrsAdded := false
 
@@ -507,6 +508,9 @@ func (h *BasicHost) makeUpdatedAddrEvent(prev, current []ma.Multiaddr) *event.Ev
 		prevmap[string(addr.Bytes())] = addr
 	}
 	for _, addr := range current {
+		currmap[string(addr.Bytes())] = addr
+	}
+	for _, addr := range currmap {
 		_, ok := prevmap[string(addr.Bytes())]
 		updated := event.UpdatedAddress{Address: addr}
 		if ok {
@@ -831,7 +835,7 @@ func (h *BasicHost) Addrs() []ma.Multiaddr {
 	// Make a copy. Consumers can modify the slice elements
 	res := make([]ma.Multiaddr, len(addrs))
 	copy(res, addrs)
-	return res
+	return ma.Unique(res)
 }
 
 // NormalizeMultiaddr returns a multiaddr suitable for equality checks.
diff --git a/p2p/transport/websocket/listener.go b/p2p/transport/websocket/listener.go
index 8071ddb8..3ff72830 100644
--- a/p2p/transport/websocket/listener.go
+++ b/p2p/transport/websocket/listener.go
@@ -4,22 +4,16 @@ import (
 	"crypto/tls"
 	"errors"
 	"fmt"
-	"go.uber.org/zap"
 	"net"
 	"net/http"
 	"sync"
 
-	logging "github.com/ipfs/go-log/v2"
-
 	"github.com/libp2p/go-libp2p/core/transport"
 
 	ma "github.com/multiformats/go-multiaddr"
 	manet "github.com/multiformats/go-multiaddr/net"
 )
 
-var log = logging.Logger("websocket-transport")
-var stdLog = zap.NewStdLog(log.Desugar())
-
 type listener struct {
 	nl     net.Listener
 	server http.Server
@@ -88,7 +82,7 @@ func newListener(a ma.Multiaddr, tlsConf *tls.Config) (*listener, error) {
 		incoming: make(chan *Conn),
 		closed:   make(chan struct{}),
 	}
-	ln.server = http.Server{Handler: ln, ErrorLog: stdLog}
+	ln.server = http.Server{Handler: ln}
 	if parsed.isWSS {
 		ln.isWss = true
 		ln.server.TLSConfig = tlsConf


Test patch:
diff --git a/p2p/host/basic/basic_host_test.go b/p2p/host/basic/basic_host_test.go
index 9014e1b4..1ab98aae 100644
--- a/p2p/host/basic/basic_host_test.go
+++ b/p2p/host/basic/basic_host_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/libp2p/go-libp2p-testing/race"
 	"github.com/libp2p/go-libp2p/core/event"
 	"github.com/libp2p/go-libp2p/core/host"
 	"github.com/libp2p/go-libp2p/core/network"
@@ -248,6 +249,63 @@ func TestAllAddrs(t *testing.T) {
 	require.True(t, ma.Contains(h.AllAddrs(), firstAddr), "should still contain the original addr")
 }
 
+func TestAllAddrsUnique(t *testing.T) {
+	if race.WithRace() {
+		t.Skip("updates addrChangeTickrInterval which might be racy")
+	}
+	oldInterval := addrChangeTickrInterval
+	addrChangeTickrInterval = 100 * time.Millisecond
+	defer func() {
+		addrChangeTickrInterval = oldInterval
+	}()
+	sendNewAddrs := make(chan struct{})
+	opts := HostOpts{
+		AddrsFactory: func(addrs []ma.Multiaddr) []ma.Multiaddr {
+			select {
+			case <-sendNewAddrs:
+				return []ma.Multiaddr{
+					ma.StringCast("/ip4/1.2.3.4/tcp/1"),
+					ma.StringCast("/ip4/1.2.3.4/tcp/1"),
+					ma.StringCast("/ip4/1.2.3.4/tcp/1"),
+					ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1"),
+					ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1"),
+				}
+			default:
+				return nil
+			}
+		},
+	}
+	// no listen addrs
+	h, err := NewHost(swarmt.GenSwarm(t, swarmt.OptDialOnly), &opts)
+	require.NoError(t, err)
+	defer h.Close()
+	h.Start()
+
+	sub, err := h.EventBus().Subscribe(&event.EvtLocalAddressesUpdated{})
+	require.NoError(t, err)
+	out := make(chan int)
+	done := make(chan struct{})
+	go func() {
+		cnt := 0
+		for {
+			select {
+			case <-sub.Out():
+				cnt++
+			case <-done:
+				out <- cnt
+				return
+			}
+		}
+	}()
+	close(sendNewAddrs)
+	require.Len(t, h.Addrs(), 2)
+	require.ElementsMatch(t, []ma.Multiaddr{ma.StringCast("/ip4/1.2.3.4/tcp/1"), ma.StringCast("/ip4/1.2.3.4/udp/1/quic-v1")}, h.Addrs())
+	time.Sleep(2*addrChangeTickrInterval + 1*time.Second) // the background loop runs every 5 seconds. Wait for 2x that time.
+	close(done)
+	cnt := <-out
+	require.Equal(t, 1, cnt)
+}
+
 // getHostPair gets a new pair of hosts.
 // The first host initiates the connection to the second host.
 func getHostPair(t *testing.T) (host.Host, host.Host) {