Repository: libp2p/go-libp2p
Pull request #2767 (2024-04-12 08:28:41)

Update: update incomplete readmes
This PR addresses the incompleteness of readmes in the examples section.

- The main readme does not list all the examples in the directory
- The pubsub readme does not list all the examples in this subcategory
- the relay example was missing a readme

MarcoPolo: Thanks! ❤️ 

Patch:
diff --git a/core/event/identify.go b/core/event/identify.go
index ff149958..888572a2 100644
--- a/core/event/identify.go
+++ b/core/event/identify.go
@@ -1,11 +1,40 @@
 package event
 
-import "github.com/libp2p/go-libp2p/core/peer"
+import (
+	"github.com/libp2p/go-libp2p/core/network"
+	"github.com/libp2p/go-libp2p/core/peer"
+	"github.com/libp2p/go-libp2p/core/protocol"
+	"github.com/libp2p/go-libp2p/core/record"
+	"github.com/multiformats/go-multiaddr"
+)
 
 // EvtPeerIdentificationCompleted is emitted when the initial identification round for a peer is completed.
 type EvtPeerIdentificationCompleted struct {
 	// Peer is the ID of the peer whose identification succeeded.
 	Peer peer.ID
+
+	// Conn is the connection we identified.
+	Conn network.Conn
+
+	// ListenAddrs is the list of addresses the peer is listening on.
+	ListenAddrs []multiaddr.Multiaddr
+
+	// Protocols is the list of protocols the peer advertised on this connection.
+	Protocols []protocol.ID
+
+	// SignedPeerRecord is the provided signed peer record of the peer. May be nil.
+	SignedPeerRecord *record.Envelope
+
+	// AgentVersion is like a UserAgent string in browsers, or client version in
+	// bittorrent includes the client name and client.
+	AgentVersion string
+
+	// ProtocolVersion is the protocolVersion field in the identify message
+	ProtocolVersion string
+
+	// ObservedAddr is the our side's connection address as observed by the
+	// peer. This is not verified, the peer could return anything here.
+	ObservedAddr multiaddr.Multiaddr
 }
 
 // EvtPeerIdentificationFailed is emitted when the initial identification round for a peer failed.
diff --git a/examples/README.md b/examples/README.md
index 529122fe..f9074de2 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -2,17 +2,21 @@
 
 In this folder, you can find a variety of examples to help you get started in using go-libp2p. Every example as a specific purpose and some of each incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.
 
-Let us know if you find any issue or if you want to contribute and add a new tutorial, feel welcome to submit a pr, thank you!
+Let us know if you find any issue or if you want to contribute and add a new tutorial, feel welcome to submit a PR, thank you!
 
 ## Examples and Tutorials
 
 - [The libp2p 'host'](./libp2p-host)
 - [Building an http proxy with libp2p](./http-proxy)
 - [An echo host](./echo)
+- [Routed echo host](./routed-echo/)
 - [Multicodecs with protobufs](./multipro)
+- [Relay-based P2P Communication](./relay/)
 - [P2P chat application](./chat)
 - [P2P chat application w/ rendezvous peer discovery](./chat-with-rendezvous)
 - [P2P chat application with peer discovery using mdns](./chat-with-mdns)
+- [P2P chat using pubsub](./pubsub)
 - [A chapter based approach to building a libp2p application](./ipfs-camp-2019/) _Created for [IPFS Camp 2019](https://github.com/ipfs/camp/tree/master/CORE_AND_ELECTIVE_COURSES/CORE_COURSE_B)_
+- [View metrics using Prometheus and Grafana](./metrics-and-dashboards)
 
 For js-libp2p examples, check https://github.com/libp2p/js-libp2p-examples
diff --git a/examples/pubsub/README.md b/examples/pubsub/README.md
index 050b33f6..a41419ac 100644
--- a/examples/pubsub/README.md
+++ b/examples/pubsub/README.md
@@ -3,4 +3,5 @@
 This directory contains example projects that use [go-libp2p-pubsub](https://github.com/libp2p/go-libp2p-pubsub),
 the Go implementation of libp2p's [Publish / Subscribe system](https://docs.libp2p.io/concepts/publish-subscribe).
 
-The [chat room example](./chat) covers the basics of using the PubSub API to build a peer-to-peer chat application.
+- The [chat room example](./chat) covers the basics of using the PubSub API to build a peer-to-peer chat application.
+- The [pubsub rendezvous example](./basic-chat-with-rendezvous) allows multiple peers to chat among each other using go-libp2p-pubsub using rendezvous names.
diff --git a/examples/relay/README.md b/examples/relay/README.md
new file mode 100644
index 00000000..a4bd5c50
--- /dev/null
+++ b/examples/relay/README.md
@@ -0,0 +1,15 @@
+# Relay-based P2P Communication Example
+
+## Overview
+This project demonstrates the setup of a relay-based peer-to-peer communication using the libp2p library in Go. It features creating unreachable libp2p hosts and facilitating their communication through a relay node.
+
+## Features
+- Creation of two "unreachable" libp2p hosts.
+- Setup of a relay node to enable communication between these hosts.
+
+## Usage
+
+Run the program 
+   ```bash
+   go run . 
+   ```
\ No newline at end of file
diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go
index 21b18890..9b0fc178 100644
--- a/p2p/http/libp2phttp.go
+++ b/p2p/http/libp2phttp.go
@@ -305,7 +305,7 @@ func (h *Host) Serve() error {
 		h.httpTransport.listenAddrs = append(h.httpTransport.listenAddrs, h.StreamHost.Addrs()...)
 
 		go func() {
-			errCh <- http.Serve(listener, h.ServeMux)
+			errCh <- http.Serve(listener, connectionCloseHeaderMiddleware(h.ServeMux))
 		}()
 	}
 
@@ -423,6 +423,9 @@ func (rt *streamRoundTripper) RoundTrip(r *http.Request) (*http.Response, error)
 		return nil, err
 	}
 
+	// Write connection: close header to ensure the stream is closed after the response
+	r.Header.Add("connection", "close")
+
 	go func() {
 		defer s.CloseWrite()
 		r.Write(s)
@@ -817,3 +820,11 @@ func (h *Host) RemovePeerMetadata(server peer.ID) {
 	}
 	h.peerMetadata.Remove(server)
 }
+
+func connectionCloseHeaderMiddleware(next http.Handler) http.Handler {
+	// Sets connection: close. It's preferable to not reuse streams for HTTP.
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Connection", "close")
+		next.ServeHTTP(w, r)
+	})
+}
diff --git a/p2p/protocol/identify/id.go b/p2p/protocol/identify/id.go
index 6c07dbdc..4bb4a592 100644
--- a/p2p/protocol/identify/id.go
+++ b/p2p/protocol/identify/id.go
@@ -400,8 +400,6 @@ func (ids *idService) IdentifyWait(c network.Conn) <-chan struct{} {
 			ids.emitters.evtPeerIdentificationFailed.Emit(event.EvtPeerIdentificationFailed{Peer: c.RemotePeer(), Reason: err})
 			return
 		}
-
-		ids.emitters.evtPeerIdentificationCompleted.Emit(event.EvtPeerIdentificationCompleted{Peer: c.RemotePeer()})
 	}()
 
 	return e.IdentifyWaitChan
@@ -711,8 +709,16 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo
 		})
 	}
 
-	// mes.ObservedAddr
-	ids.consumeObservedAddress(mes.GetObservedAddr(), c)
+	obsAddr, err := ma.NewMultiaddrBytes(mes.GetObservedAddr())
+	if err != nil {
+		log.Debugf("error parsing received observed addr for %s: %s", c, err)
+		obsAddr = nil
+	}
+
+	if obsAddr != nil {
+		// TODO refactor this to use the emitted events instead of having this func call explicitly.
+		ids.observedAddrs.Record(c, obsAddr)
+	}
 
 	// mes.ListenAddrs
 	laddrs := mes.GetListenAddrs()
@@ -763,6 +769,7 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo
 		signedAddrs, err := ids.consumeSignedPeerRecord(c.RemotePeer(), signedPeerRecord)
 		if err != nil {
 			log.Debugf("failed to consume signed peer record: %s", err)
+			signedPeerRecord = nil
 		} else {
 			addrs = signedAddrs
 		}
@@ -786,6 +793,18 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo
 
 	// get the key from the other side. we may not have it (no-auth transport)
 	ids.consumeReceivedPubKey(c, mes.PublicKey)
+
+	ids.emitters.evtPeerIdentificationCompleted.Emit(event.EvtPeerIdentificationCompleted{
+		Peer:             c.RemotePeer(),
+		Conn:             c,
+		ListenAddrs:      lmaddrs,
+		Protocols:        mesProtocols,
+		SignedPeerRecord: signedPeerRecord,
+		ObservedAddr:     obsAddr,
+		ProtocolVersion:  pv,
+		AgentVersion:     av,
+	})
+
 }
 
 func (ids *idService) consumeSignedPeerRecord(p peer.ID, signedPeerRecord *record.Envelope) ([]ma.Multiaddr, error) {
@@ -919,20 +938,6 @@ func HasConsistentTransport(a ma.Multiaddr, green []ma.Multiaddr) bool {
 	return false
 }
 
-func (ids *idService) consumeObservedAddress(observed []byte, c network.Conn) {
-	if observed == nil {
-		return
-	}
-
-	maddr, err := ma.NewMultiaddrBytes(observed)
-	if err != nil {
-		log.Debugf("error parsing received observed addr for %s: %s", c, err)
-		return
-	}
-
-	ids.observedAddrs.Record(c, maddr)
-}
-
 // addConnWithLock assuems caller holds the connsMu lock
 func (ids *idService) addConnWithLock(c network.Conn) {
 	_, found := ids.conns[c]


Test patch:
diff --git a/p2p/http/libp2phttp_test.go b/p2p/http/libp2phttp_test.go
index c5616fea..46c33029 100644
--- a/p2p/http/libp2phttp_test.go
+++ b/p2p/http/libp2phttp_test.go
@@ -68,6 +68,84 @@ func TestHTTPOverStreams(t *testing.T) {
 	require.Equal(t, "hello", string(body))
 }
 
+func TestHTTPOverStreamsSendsConnectionClose(t *testing.T) {
+	serverHost, err := libp2p.New(
+		libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1"),
+	)
+	require.NoError(t, err)
+
+	httpHost := libp2phttp.Host{StreamHost: serverHost}
+
+	connectionHeaderVal := make(chan string, 1)
+	httpHost.SetHTTPHandlerAtPath("/hello", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("hello"))
+		connectionHeaderVal <- r.Header.Get("Connection")
+	}))
+
+	// Start server
+	go httpHost.Serve()
+	defer httpHost.Close()
+
+	// run client
+	clientHost, err := libp2p.New(libp2p.NoListenAddrs)
+	require.NoError(t, err)
+	clientHost.Connect(context.Background(), peer.AddrInfo{
+		ID:    serverHost.ID(),
+		Addrs: serverHost.Addrs(),
+	})
+	clientHttpHost := libp2phttp.Host{StreamHost: clientHost}
+	rt, err := clientHttpHost.NewConstrainedRoundTripper(peer.AddrInfo{ID: serverHost.ID()})
+	require.NoError(t, err)
+	client := &http.Client{Transport: rt}
+	_, err = client.Get("/")
+	require.NoError(t, err)
+
+	select {
+	case val := <-connectionHeaderVal:
+		require.Equal(t, "close", strings.ToLower(val))
+	case <-time.After(5 * time.Second):
+		t.Fatal("timeout waiting for connection header")
+	}
+}
+
+func TestHTTPOverStreamsReturnsConnectionClose(t *testing.T) {
+	serverHost, err := libp2p.New(
+		libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1"),
+	)
+	require.NoError(t, err)
+
+	httpHost := libp2phttp.Host{StreamHost: serverHost}
+
+	httpHost.SetHTTPHandlerAtPath("/hello", "/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("hello"))
+	}))
+
+	// Start server
+	go httpHost.Serve()
+	defer httpHost.Close()
+
+	// Start client
+	clientHost, err := libp2p.New(libp2p.NoListenAddrs)
+	require.NoError(t, err)
+	clientHost.Connect(context.Background(), peer.AddrInfo{
+		ID:    serverHost.ID(),
+		Addrs: serverHost.Addrs(),
+	})
+
+	s, err := clientHost.NewStream(context.Background(), serverHost.ID(), libp2phttp.ProtocolIDForMultistreamSelect)
+	require.NoError(t, err)
+	_, err = s.Write([]byte("GET / HTTP/1.1\r\nHost: \r\n\r\n"))
+	require.NoError(t, err)
+
+	out := make([]byte, 1024)
+	n, err := s.Read(out)
+	if err != io.EOF {
+		require.NoError(t, err)
+	}
+
+	require.Contains(t, strings.ToLower(string(out[:n])), "connection: close")
+}
+
 func TestRoundTrippers(t *testing.T) {
 	serverHost, err := libp2p.New(
 		libp2p.ListenAddrStrings("/ip4/127.0.0.1/udp/0/quic-v1"),
diff --git a/p2p/protocol/identify/id_test.go b/p2p/protocol/identify/id_test.go
index 0f920356..2352abef 100644
--- a/p2p/protocol/identify/id_test.go
+++ b/p2p/protocol/identify/id_test.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"slices"
 	"sync"
 	"testing"
 	"time"
@@ -200,12 +201,47 @@ func TestIDService(t *testing.T) {
 
 	// test that we received the "identify completed" event.
 	select {
-	case <-sub.Out():
+	case evtAny := <-sub.Out():
+		assertCorrectEvtPeerIdentificationCompleted(t, evtAny, h2)
 	case <-time.After(3 * time.Second):
 		t.Fatalf("expected EvtPeerIdentificationCompleted event within 10 seconds; none received")
 	}
 }
 
+func assertCorrectEvtPeerIdentificationCompleted(t *testing.T, evtAny interface{}, other host.Host) {
+	t.Helper()
+	evt := evtAny.(event.EvtPeerIdentificationCompleted)
+	require.NotNil(t, evt.Conn)
+	require.Equal(t, other.ID(), evt.Peer)
+
+	require.Equal(t, len(other.Addrs()), len(evt.ListenAddrs))
+	if len(other.Addrs()) == len(evt.ListenAddrs) {
+		otherAddrsStrings := make([]string, len(other.Addrs()))
+		evtAddrStrings := make([]string, len(evt.ListenAddrs))
+		for i, a := range other.Addrs() {
+			otherAddrsStrings[i] = a.String()
+			evtAddrStrings[i] = evt.ListenAddrs[i].String()
+		}
+		slices.Sort(otherAddrsStrings)
+		slices.Sort(evtAddrStrings)
+		require.Equal(t, otherAddrsStrings, evtAddrStrings)
+	}
+
+	otherProtos := other.Mux().Protocols()
+	slices.Sort(otherProtos)
+	evtProtos := evt.Protocols
+	slices.Sort(evtProtos)
+	require.Equal(t, otherProtos, evtProtos)
+	idFromSignedRecord, err := peer.IDFromPublicKey(evt.SignedPeerRecord.PublicKey)
+	require.NoError(t, err)
+	require.Equal(t, other.ID(), idFromSignedRecord)
+	require.Equal(t, peer.PeerRecordEnvelopePayloadType, evt.SignedPeerRecord.PayloadType)
+	var peerRecord peer.PeerRecord
+	evt.SignedPeerRecord.TypedRecord(&peerRecord)
+	require.Equal(t, other.ID(), peerRecord.PeerID)
+	require.Equal(t, other.Addrs(), peerRecord.Addrs)
+}
+
 func TestProtoMatching(t *testing.T) {
 	tcp1, _ := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/1234")
 	tcp2, _ := ma.NewMultiaddr("/ip4/1.2.3.4/tcp/2345")
@@ -665,7 +701,8 @@ func TestLargeIdentifyMessage(t *testing.T) {
 
 	// test that we received the "identify completed" event.
 	select {
-	case <-sub.Out():
+	case evtAny := <-sub.Out():
+		assertCorrectEvtPeerIdentificationCompleted(t, evtAny, h2)
 	case <-time.After(3 * time.Second):
 		t.Fatalf("expected EvtPeerIdentificationCompleted event within 3 seconds; none received")
 	}