Repository: libp2p/go-libp2p
Pull request #2000 (2023-01-19 06:09:58)

feat!: rcmgr: Change LimitConfig to use LimitVal type
This solves the problem around the encoding having different semantics than the config that is passed into the resource manager [context](https://github.com/libp2p/go-libp2p/pull/1998#issuecomment-1386348540). This makes special values explicit (Unlimited, default, block all). And I think it's a more ergonomic API (create a struct with sane defaults, rather than having to copy some pre-initialized struct, then modify relevant values).

The high level idea is you have some custom configuration called `PartialLimitConfig` that gets constructed with a `ConcreteLimitConfig` (a configuration that has no special or missing values, zero means zero, max means max). The `.Build` method fills in any missing information from the `PartialLimitConfig` and returns a complete `ConcreteLimitConfig`. Another way to think about it is that `PartialLimitConfig` is a partial overrides config, and you add it to a complete `ConcreteLimitConfig` to get another `ConcreteLimitConfig`.  

Also take a look at the README for some updates on the description there.

Changes:
* Introduces a new type `LimitVal` which can explicitly specify "use default", "unlimited", "block all", as well as any positive number. The zero value of `LimitVal` (the value when you create the object in Go) is "Use default".
  * The JSON marshalling of this is straightforward.
* Introduces a new `ResourceLimits` type which uses `LimitVal` instead of ints so it can encode the above for the resources.
* Changes `LimitConfig` to `PartialLimitConfig` and uses `ResourceLimits`. This along with the marshalling changes means you can now marshal the fact that some resource limit is set to block all.
  * Because the default is to use the defaults, this avoids the footgun of initializing the resource manager with 0 limits (that would block everything).

Before:
```
	cfg := rcmgr.DefaultLimits.AutoScale()
	cfg.System.ConnsInbound = 3
	cfg.System.ConnsOutbound = 1024
	cfg.System.Conns = 1024
	cfg.PeerDefault.Conns = 1
	cfg.PeerDefault.ConnsInbound = 1
	cfg.PeerDefault.ConnsOutbound = 1
```

After:
```
	cfg := rcmgr.PartialLimitConfig{
		System: &rcmgr.ResourceLimits{
			ConnsInbound:  3,
			ConnsOutbound: 1024,
			Conns:         1024,
		},
		PeerDefault: &rcmgr.ResourceLimits{
			ConnsInbound:  1,
			ConnsOutbound: 1,
			Conns:         1,
		},
	}.Build(rcmgr.DefaultLimits.AutoScale())
```

And the JSON encoding:

```
	bl := ResourceLimits{
		Streams:         DefaultLimit,
		StreamsInbound:  10,
		StreamsOutbound: BlockAllLimit,
		Conns:           10,
		// ConnsInbound:    DefaultLimit,
		ConnsOutbound: Unlimited,
		Memory:        Unlimited64,
	}

	jsonEncoded, err := json.Marshal(bl)
	require.NoError(t, err)
	require.Equal(t, string(jsonEncoded), `{"StreamsInbound":10,"StreamsOutbound":"blockAll","Conns":10,"ConnsOutbound":"unlimited","Memory":"unlimited"}`)
```

This doesn't affect the ScalingLimitConfig. That has a lot of knobs and represents the developer's knowledge of how the resource usage should scale. LimitConfig on the other hand is more like the manual tweaks an end-user would make. It's used in conjunction with the defaults created from a ScalingLimitConfig.


Patch:
diff --git a/README.md b/README.md
index b1d846a1..bb0d26d7 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,6 @@
   <a href="http://libp2p.io/"><img src="https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square" /></a>
   <a href="https://pkg.go.dev/github.com/libp2p/go-libp2p"><img src="https://pkg.go.dev/badge/github.com/libp2p/go-libp2p.svg" alt="Go Reference"></a>
   <a href="https://discuss.libp2p.io"><img src="https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg"/></a>
-  <a href="https://marcopolo.github.io/FlakyTests/"><img src="https://marcopolo.github.io/FlakyTests/current-score.svg"/></a>
 </p>
 
 # Table of Contents
diff --git a/config/config.go b/config/config.go
index 874bcb5a..94e18be2 100644
--- a/config/config.go
+++ b/config/config.go
@@ -119,7 +119,7 @@ type Config struct {
 	HolePunchingOptions []holepunch.Option
 }
 
-func (cfg *Config) makeSwarm(enableMetrics bool) (*swarm.Swarm, error) {
+func (cfg *Config) makeSwarm() (*swarm.Swarm, error) {
 	if cfg.Peerstore == nil {
 		return nil, fmt.Errorf("no peerstore specified")
 	}
@@ -151,7 +151,7 @@ func (cfg *Config) makeSwarm(enableMetrics bool) (*swarm.Swarm, error) {
 		return nil, err
 	}
 
-	opts := make([]swarm.Option, 0, 6)
+	opts := make([]swarm.Option, 0, 3)
 	if cfg.Reporter != nil {
 		opts = append(opts, swarm.WithMetrics(cfg.Reporter))
 	}
@@ -167,9 +167,6 @@ func (cfg *Config) makeSwarm(enableMetrics bool) (*swarm.Swarm, error) {
 	if cfg.MultiaddrResolver != nil {
 		opts = append(opts, swarm.WithMultiaddrResolver(cfg.MultiaddrResolver))
 	}
-	if enableMetrics {
-		opts = append(opts, swarm.WithMetricsTracer(swarm.NewMetricsTracer()))
-	}
 	// TODO: Make the swarm implementation configurable.
 	return swarm.NewSwarm(pid, cfg.Peerstore, opts...)
 }
@@ -279,7 +276,7 @@ func (cfg *Config) addTransports(h host.Host) error {
 //
 // This function consumes the config. Do not reuse it (really!).
 func (cfg *Config) NewNode() (host.Host, error) {
-	swrm, err := cfg.makeSwarm(true)
+	swrm, err := cfg.makeSwarm()
 	if err != nil {
 		return nil, err
 	}
@@ -385,7 +382,7 @@ func (cfg *Config) NewNode() (host.Host, error) {
 			Peerstore:          ps,
 		}
 
-		dialer, err := autoNatCfg.makeSwarm(false)
+		dialer, err := autoNatCfg.makeSwarm()
 		if err != nil {
 			h.Close()
 			return nil, err
diff --git a/core/host/host.go b/core/host/host.go
index e62be281..5fac751a 100644
--- a/core/host/host.go
+++ b/core/host/host.go
@@ -52,7 +52,7 @@ type Host interface {
 
 	// SetStreamHandlerMatch sets the protocol handler on the Host's Mux
 	// using a matching function for protocol selection.
-	SetStreamHandlerMatch(protocol.ID, func(protocol.ID) bool, network.StreamHandler)
+	SetStreamHandlerMatch(protocol.ID, func(string) bool, network.StreamHandler)
 
 	// RemoveStreamHandler removes a handler on the mux that was set by
 	// SetStreamHandler
diff --git a/core/network/conn.go b/core/network/conn.go
index 4191a9d5..6d16d298 100644
--- a/core/network/conn.go
+++ b/core/network/conn.go
@@ -6,7 +6,6 @@ import (
 
 	ic "github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/protocol"
 
 	ma "github.com/multiformats/go-multiaddr"
 )
@@ -38,9 +37,9 @@ type Conn interface {
 // ConnectionState holds information about the connection.
 type ConnectionState struct {
 	// The stream multiplexer used on this connection (if any). For example: /yamux/1.0.0
-	StreamMultiplexer protocol.ID
+	StreamMultiplexer string
 	// The security protocol used on this connection (if any). For example: /tls/1.0.0
-	Security protocol.ID
+	Security string
 	// the transport used on this connection. For example: tcp
 	Transport string
 }
diff --git a/core/peerstore/peerstore.go b/core/peerstore/peerstore.go
index b63582af..7561f32b 100644
--- a/core/peerstore/peerstore.go
+++ b/core/peerstore/peerstore.go
@@ -11,7 +11,6 @@ import (
 
 	ic "github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/protocol"
 	"github.com/libp2p/go-libp2p/core/record"
 
 	ma "github.com/multiformats/go-multiaddr"
@@ -231,19 +230,19 @@ type Metrics interface {
 
 // ProtoBook tracks the protocols supported by peers.
 type ProtoBook interface {
-	GetProtocols(peer.ID) ([]protocol.ID, error)
-	AddProtocols(peer.ID, ...protocol.ID) error
-	SetProtocols(peer.ID, ...protocol.ID) error
-	RemoveProtocols(peer.ID, ...protocol.ID) error
+	GetProtocols(peer.ID) ([]string, error)
+	AddProtocols(peer.ID, ...string) error
+	SetProtocols(peer.ID, ...string) error
+	RemoveProtocols(peer.ID, ...string) error
 
 	// SupportsProtocols returns the set of protocols the peer supports from among the given protocols.
 	// If the returned error is not nil, the result is indeterminate.
-	SupportsProtocols(peer.ID, ...protocol.ID) ([]protocol.ID, error)
+	SupportsProtocols(peer.ID, ...string) ([]string, error)
 
 	// FirstSupportedProtocol returns the first protocol that the peer supports among the given protocols.
-	// If the peer does not support any of the given protocols, this function will return an empty protocol.ID and a nil error.
+	// If the peer does not support any of the given protocols, this function will return an empty string and a nil error.
 	// If the returned error is not nil, the result is indeterminate.
-	FirstSupportedProtocol(peer.ID, ...protocol.ID) (protocol.ID, error)
+	FirstSupportedProtocol(peer.ID, ...string) (string, error)
 
 	// RemovePeer removes all protocols associated with a peer.
 	RemovePeer(peer.ID)
diff --git a/core/protocol/switch.go b/core/protocol/switch.go
index 683ef56f..f839e016 100644
--- a/core/protocol/switch.go
+++ b/core/protocol/switch.go
@@ -3,8 +3,6 @@ package protocol
 
 import (
 	"io"
-
-	"github.com/multiformats/go-multistream"
 )
 
 // HandlerFunc is a user-provided function used by the Router to
@@ -13,7 +11,7 @@ import (
 // Will be invoked with the protocol ID string as the first argument,
 // which may differ from the ID used for registration if the handler
 // was registered using a match function.
-type HandlerFunc = multistream.HandlerFunc[ID]
+type HandlerFunc = func(protocol string, rwc io.ReadWriteCloser) error
 
 // Router is an interface that allows users to add and remove protocol handlers,
 // which will be invoked when incoming stream requests for registered protocols
@@ -27,7 +25,7 @@ type Router interface {
 
 	// AddHandler registers the given handler to be invoked for
 	// an exact literal match of the given protocol ID string.
-	AddHandler(protocol ID, handler HandlerFunc)
+	AddHandler(protocol string, handler HandlerFunc)
 
 	// AddHandlerWithFunc registers the given handler to be invoked
 	// when the provided match function returns true.
@@ -37,17 +35,17 @@ type Router interface {
 	// the protocol. Note that the protocol ID argument is not
 	// used for matching; if you want to match the protocol ID
 	// string exactly, you must check for it in your match function.
-	AddHandlerWithFunc(protocol ID, match func(ID) bool, handler HandlerFunc)
+	AddHandlerWithFunc(protocol string, match func(string) bool, handler HandlerFunc)
 
 	// RemoveHandler removes the registered handler (if any) for the
 	// given protocol ID string.
-	RemoveHandler(protocol ID)
+	RemoveHandler(protocol string)
 
 	// Protocols returns a list of all registered protocol ID strings.
 	// Note that the Router may be able to handle protocol IDs not
 	// included in this list if handlers were added with match functions
 	// using AddHandlerWithFunc.
-	Protocols() []ID
+	Protocols() []string
 }
 
 // Negotiator is a component capable of reaching agreement over what protocols
@@ -57,7 +55,7 @@ type Negotiator interface {
 	// inbound stream, returning after the protocol has been determined and the
 	// Negotiator has finished using the stream for negotiation. Returns an
 	// error if negotiation fails.
-	Negotiate(rwc io.ReadWriteCloser) (ID, HandlerFunc, error)
+	Negotiate(rwc io.ReadWriteCloser) (string, HandlerFunc, error)
 
 	// Handle calls Negotiate to determine which protocol handler to use for an
 	// inbound stream, then invokes the protocol handler function, passing it
diff --git a/defaults.go b/defaults.go
index 72f618a7..1eebd68f 100644
--- a/defaults.go
+++ b/defaults.go
@@ -34,7 +34,7 @@ var DefaultSecurity = ChainOptions(
 //
 // Use this option when you want to *extend* the set of multiplexers used by
 // libp2p instead of replacing them.
-var DefaultMuxers = Muxer(yamux.ID, yamux.DefaultTransport)
+var DefaultMuxers = Muxer("/yamux/1.0.0", yamux.DefaultTransport)
 
 // DefaultTransports are the default libp2p transports.
 //
diff --git a/go.mod b/go.mod
index a7835981..36b87781 100644
--- a/go.mod
+++ b/go.mod
@@ -42,7 +42,7 @@ require (
 	github.com/multiformats/go-multibase v0.1.1
 	github.com/multiformats/go-multicodec v0.7.0
 	github.com/multiformats/go-multihash v0.2.1
-	github.com/multiformats/go-multistream v0.4.0
+	github.com/multiformats/go-multistream v0.3.3
 	github.com/multiformats/go-varint v0.0.7
 	github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
 	github.com/prometheus/client_golang v1.14.0
diff --git a/go.sum b/go.sum
index 89d7b78c..58713e5c 100644
--- a/go.sum
+++ b/go.sum
@@ -384,8 +384,8 @@ github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6y
 github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
 github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
 github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
-github.com/multiformats/go-multistream v0.4.0 h1:5i4JbawClkbuaX+mIVXiHQYVPxUW+zjv6w7jtSRukxc=
-github.com/multiformats/go-multistream v0.4.0/go.mod h1:BS6ZSYcA4NwYEaIMeCtpJydp2Dc+fNRA6uJMSu/m8+4=
+github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o=
+github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg=
 github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
diff --git a/libp2p.go b/libp2p.go
index db23253b..d77c3b9b 100644
--- a/libp2p.go
+++ b/libp2p.go
@@ -37,7 +37,8 @@ func ChainOptions(opts ...Option) Option {
 // transport protocols;
 //
 // - If no multiplexer configuration is provided, the node is configured by
-// default to use yamux;
+// default to use the "yamux/1.0.0" and "mplux/6.7.0" stream connection
+// multiplexers;
 //
 // - If no security transport is provided, the host uses the go-libp2p's noise
 // and/or tls encrypted transport to encrypt all traffic;
diff --git a/p2p/host/autorelay/relay_finder.go b/p2p/host/autorelay/relay_finder.go
index 20d71240..851d1422 100644
--- a/p2p/host/autorelay/relay_finder.go
+++ b/p2p/host/autorelay/relay_finder.go
@@ -23,8 +23,8 @@ import (
 )
 
 const (
-	protoIDv1 = relayv1.ProtoID
-	protoIDv2 = circuitv2_proto.ProtoIDv2Hop
+	protoIDv1 = string(relayv1.ProtoID)
+	protoIDv2 = string(circuitv2_proto.ProtoIDv2Hop)
 )
 
 // Terminology:
diff --git a/p2p/host/basic/basic_host.go b/p2p/host/basic/basic_host.go
index 2a500151..82e29a37 100644
--- a/p2p/host/basic/basic_host.go
+++ b/p2p/host/basic/basic_host.go
@@ -70,7 +70,7 @@ type BasicHost struct {
 
 	network      network.Network
 	psManager    *pstoremanager.PeerstoreManager
-	mux          *msmux.MultistreamMuxer[protocol.ID]
+	mux          *msmux.MultistreamMuxer
 	ids          identify.IDService
 	hps          *holepunch.Service
 	pings        *ping.PingService
@@ -108,7 +108,7 @@ var _ host.Host = (*BasicHost)(nil)
 // customize construction of the *BasicHost.
 type HostOpts struct {
 	// MultistreamMuxer is essential for the *BasicHost and will use a sensible default value if omitted.
-	MultistreamMuxer *msmux.MultistreamMuxer[protocol.ID]
+	MultistreamMuxer *msmux.MultistreamMuxer
 
 	// NegotiationTimeout determines the read and write timeouts on streams.
 	// If 0 or omitted, it will use DefaultNegotiationTimeout.
@@ -168,7 +168,7 @@ func NewHost(n network.Network, opts *HostOpts) (*BasicHost, error) {
 	h := &BasicHost{
 		network:                 n,
 		psManager:               psManager,
-		mux:                     msmux.NewMultistreamMuxer[protocol.ID](),
+		mux:                     msmux.NewMultistreamMuxer(),
 		negtimeout:              DefaultNegotiationTimeout,
 		AddrsFactory:            DefaultAddrsFactory,
 		maResolver:              madns.DefaultResolver,
@@ -407,7 +407,7 @@ func (h *BasicHost) newStreamHandler(s network.Stream) {
 		}
 	}
 
-	if err := s.SetProtocol(protoID); err != nil {
+	if err := s.SetProtocol(protocol.ID(protoID)); err != nil {
 		log.Debugf("error setting stream protocol: %s", err)
 		s.Reset()
 		return
@@ -571,9 +571,9 @@ func (h *BasicHost) EventBus() event.Bus {
 //
 // (Threadsafe)
 func (h *BasicHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {
-	h.Mux().AddHandler(pid, func(p protocol.ID, rwc io.ReadWriteCloser) error {
+	h.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
 		is := rwc.(network.Stream)
-		is.SetProtocol(p)
+		is.SetProtocol(protocol.ID(p))
 		handler(is)
 		return nil
 	})
@@ -584,10 +584,10 @@ func (h *BasicHost) SetStreamHandler(pid protocol.ID, handler network.StreamHand
 
 // SetStreamHandlerMatch sets the protocol handler on the Host's Mux
 // using a matching function to do protocol comparisons
-func (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {
-	h.Mux().AddHandlerWithFunc(pid, m, func(p protocol.ID, rwc io.ReadWriteCloser) error {
+func (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler network.StreamHandler) {
+	h.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
 		is := rwc.(network.Stream)
-		is.SetProtocol(p)
+		is.SetProtocol(protocol.ID(p))
 		handler(is)
 		return nil
 	})
@@ -598,7 +598,7 @@ func (h *BasicHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) b
 
 // RemoveStreamHandler returns ..
 func (h *BasicHost) RemoveStreamHandler(pid protocol.ID) {
-	h.Mux().RemoveHandler(pid)
+	h.Mux().RemoveHandler(string(pid))
 	h.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
 		Removed: []protocol.ID{pid},
 	})
@@ -637,7 +637,9 @@ func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.I
 		return nil, ctx.Err()
 	}
 
-	pref, err := h.preferredProtocol(p, pids)
+	pidStrings := protocol.ConvertToStrings(pids)
+
+	pref, err := h.preferredProtocol(p, pidStrings)
 	if err != nil {
 		_ = s.Reset()
 		return nil, err
@@ -645,7 +647,7 @@ func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.I
 
 	if pref != "" {
 		s.SetProtocol(pref)
-		lzcon := msmux.NewMSSelect(s, pref)
+		lzcon := msmux.NewMSSelect(s, string(pref))
 		return &streamWrapper{
 			Stream: s,
 			rw:     lzcon,
@@ -653,10 +655,10 @@ func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.I
 	}
 
 	// Negotiate the protocol in the background, obeying the context.
-	var selected protocol.ID
+	var selected string
 	errCh := make(chan error, 1)
 	go func() {
-		selected, err = msmux.SelectOneOf(pids, s)
+		selected, err = msmux.SelectOneOf(pidStrings, s)
 		errCh <- err
 	}()
 	select {
@@ -672,12 +674,13 @@ func (h *BasicHost) NewStream(ctx context.Context, p peer.ID, pids ...protocol.I
 		return nil, ctx.Err()
 	}
 
-	s.SetProtocol(selected)
+	selpid := protocol.ID(selected)
+	s.SetProtocol(selpid)
 	h.Peerstore().AddProtocols(p, selected)
 	return s, nil
 }
 
-func (h *BasicHost) preferredProtocol(p peer.ID, pids []protocol.ID) (protocol.ID, error) {
+func (h *BasicHost) preferredProtocol(p peer.ID, pids []string) (protocol.ID, error) {
 	supported, err := h.Peerstore().SupportsProtocols(p, pids...)
 	if err != nil {
 		return "", err
@@ -685,7 +688,7 @@ func (h *BasicHost) preferredProtocol(p peer.ID, pids []protocol.ID) (protocol.I
 
 	var out protocol.ID
 	if len(supported) > 0 {
-		out = supported[0]
+		out = protocol.ID(supported[0])
 	}
 	return out, nil
 }
diff --git a/p2p/host/blank/blank.go b/p2p/host/blank/blank.go
index 08f13326..16753eb0 100644
--- a/p2p/host/blank/blank.go
+++ b/p2p/host/blank/blank.go
@@ -27,7 +27,7 @@ var log = logging.Logger("blankhost")
 // BlankHost is the thinnest implementation of the host.Host interface
 type BlankHost struct {
 	n        network.Network
-	mux      *mstream.MultistreamMuxer[protocol.ID]
+	mux      *mstream.MultistreamMuxer
 	cmgr     connmgr.ConnManager
 	eventbus event.Bus
 	emitters struct {
@@ -65,7 +65,7 @@ func NewBlankHost(n network.Network, options ...Option) *BlankHost {
 	bh := &BlankHost{
 		n:    n,
 		cmgr: cfg.cmgr,
-		mux:  mstream.NewMultistreamMuxer[protocol.ID](),
+		mux:  mstream.NewMultistreamMuxer(),
 	}
 	if bh.eventbus == nil {
 		bh.eventbus = eventbus.NewBus()
@@ -158,29 +158,35 @@ func (bh *BlankHost) NewStream(ctx context.Context, p peer.ID, protos ...protoco
 		return nil, err
 	}
 
-	selected, err := mstream.SelectOneOf(protos, s)
+	protoStrs := make([]string, len(protos))
+	for i, pid := range protos {
+		protoStrs[i] = string(pid)
+	}
+
+	selected, err := mstream.SelectOneOf(protoStrs, s)
 	if err != nil {
 		s.Reset()
 		return nil, err
 	}
 
-	s.SetProtocol(selected)
+	selpid := protocol.ID(selected)
+	s.SetProtocol(selpid)
 	bh.Peerstore().AddProtocols(p, selected)
 
 	return s, nil
 }
 
 func (bh *BlankHost) RemoveStreamHandler(pid protocol.ID) {
-	bh.Mux().RemoveHandler(pid)
+	bh.Mux().RemoveHandler(string(pid))
 	bh.emitters.evtLocalProtocolsUpdated.Emit(event.EvtLocalProtocolsUpdated{
 		Removed: []protocol.ID{pid},
 	})
 }
 
 func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) {
-	bh.Mux().AddHandler(pid, func(p protocol.ID, rwc io.ReadWriteCloser) error {
+	bh.Mux().AddHandler(string(pid), func(p string, rwc io.ReadWriteCloser) error {
 		is := rwc.(network.Stream)
-		is.SetProtocol(p)
+		is.SetProtocol(protocol.ID(p))
 		handler(is)
 		return nil
 	})
@@ -189,10 +195,10 @@ func (bh *BlankHost) SetStreamHandler(pid protocol.ID, handler network.StreamHan
 	})
 }
 
-func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {
-	bh.Mux().AddHandlerWithFunc(pid, m, func(p protocol.ID, rwc io.ReadWriteCloser) error {
+func (bh *BlankHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler network.StreamHandler) {
+	bh.Mux().AddHandlerWithFunc(string(pid), m, func(p string, rwc io.ReadWriteCloser) error {
 		is := rwc.(network.Stream)
-		is.SetProtocol(p)
+		is.SetProtocol(protocol.ID(p))
 		handler(is)
 		return nil
 	})
@@ -210,7 +216,7 @@ func (bh *BlankHost) newStreamHandler(s network.Stream) {
 		return
 	}
 
-	s.SetProtocol(protoID)
+	s.SetProtocol(protocol.ID(protoID))
 
 	go handle(protoID, s)
 }
diff --git a/p2p/host/peerstore/pstoreds/addr_book.go b/p2p/host/peerstore/pstoreds/addr_book.go
index a16053b5..33ff1f5e 100644
--- a/p2p/host/peerstore/pstoreds/addr_book.go
+++ b/p2p/host/peerstore/pstoreds/addr_book.go
@@ -276,7 +276,7 @@ func (ab *dsAddrBook) AddAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duratio
 	if ttl <= 0 {
 		return
 	}
-	addrs = cleanAddrs(addrs, p)
+	addrs = cleanAddrs(addrs)
 	ab.setAddrs(p, addrs, ttl, ttlExtend, false)
 }
 
@@ -302,7 +302,7 @@ func (ab *dsAddrBook) ConsumePeerRecord(recordEnvelope *record.Envelope, ttl tim
 		return false, nil
 	}
 
-	addrs := cleanAddrs(rec.Addrs, rec.PeerID)
+	addrs := cleanAddrs(rec.Addrs)
 	err = ab.setAddrs(rec.PeerID, addrs, ttl, ttlExtend, true)
 	if err != nil {
 		return false, err
@@ -385,7 +385,7 @@ func (ab *dsAddrBook) SetAddr(p peer.ID, addr ma.Multiaddr, ttl time.Duration) {
 
 // SetAddrs will add or update the TTLs of addresses in the AddrBook.
 func (ab *dsAddrBook) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Duration) {
-	addrs = cleanAddrs(addrs, p)
+	addrs = cleanAddrs(addrs)
 	if ttl <= 0 {
 		ab.deleteAddrs(p, addrs)
 		return
@@ -598,17 +598,10 @@ func (ab *dsAddrBook) deleteAddrs(p peer.ID, addrs []ma.Multiaddr) (err error) {
 	return pr.flush(ab.ds)
 }
 
-func cleanAddrs(addrs []ma.Multiaddr, pid peer.ID) []ma.Multiaddr {
+func cleanAddrs(addrs []ma.Multiaddr) []ma.Multiaddr {
 	clean := make([]ma.Multiaddr, 0, len(addrs))
 	for _, addr := range addrs {
-		// Remove suffix of /p2p/peer-id from address
-		addr, addrPid := peer.SplitAddr(addr)
 		if addr == nil {
-			log.Warnw("Was passed a nil multiaddr", "peer", pid)
-			continue
-		}
-		if addrPid != "" && addrPid != pid {
-			log.Warnf("Was passed p2p address with a different peerId. found: %s, expected: %s", addrPid, pid)
 			continue
 		}
 		clean = append(clean, addr)
diff --git a/p2p/host/peerstore/pstoreds/metadata.go b/p2p/host/peerstore/pstoreds/metadata.go
index c4f28194..64c24fd7 100644
--- a/p2p/host/peerstore/pstoreds/metadata.go
+++ b/p2p/host/peerstore/pstoreds/metadata.go
@@ -8,7 +8,6 @@ import (
 	pool "github.com/libp2p/go-buffer-pool"
 	"github.com/libp2p/go-libp2p/core/peer"
 	pstore "github.com/libp2p/go-libp2p/core/peerstore"
-	"github.com/libp2p/go-libp2p/core/protocol"
 
 	ds "github.com/ipfs/go-datastore"
 	"github.com/ipfs/go-datastore/query"
@@ -29,7 +28,7 @@ func init() {
 	// Gob registers basic types by default.
 	//
 	// Register complex types used by the peerstore itself.
-	gob.Register(make(map[protocol.ID]struct{}))
+	gob.Register(make(map[string]struct{}))
 }
 
 // NewPeerMetadata creates a metadata store backed by a persistent db. It uses gob for serialisation.
diff --git a/p2p/host/peerstore/pstoreds/protobook.go b/p2p/host/peerstore/pstoreds/protobook.go
index f5d76573..cfc6ef7d 100644
--- a/p2p/host/peerstore/pstoreds/protobook.go
+++ b/p2p/host/peerstore/pstoreds/protobook.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/libp2p/go-libp2p/core/peer"
 	pstore "github.com/libp2p/go-libp2p/core/peerstore"
-	"github.com/libp2p/go-libp2p/core/protocol"
 )
 
 type protoSegment struct {
@@ -59,12 +58,12 @@ func NewProtoBook(meta pstore.PeerMetadata, opts ...ProtoBookOption) (*dsProtoBo
 	return pb, nil
 }
 
-func (pb *dsProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *dsProtoBook) SetProtocols(p peer.ID, protos ...string) error {
 	if len(protos) > pb.maxProtos {
 		return errTooManyProtocols
 	}
 
-	protomap := make(map[protocol.ID]struct{}, len(protos))
+	protomap := make(map[string]struct{}, len(protos))
 	for _, proto := range protos {
 		protomap[proto] = struct{}{}
 	}
@@ -76,7 +75,7 @@ func (pb *dsProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error {
 	return pb.meta.Put(p, "protocols", protomap)
 }
 
-func (pb *dsProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *dsProtoBook) AddProtocols(p peer.ID, protos ...string) error {
 	s := pb.segments.get(p)
 	s.Lock()
 	defer s.Unlock()
@@ -96,7 +95,7 @@ func (pb *dsProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error {
 	return pb.meta.Put(p, "protocols", pmap)
 }
 
-func (pb *dsProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {
+func (pb *dsProtoBook) GetProtocols(p peer.ID) ([]string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
@@ -106,7 +105,7 @@ func (pb *dsProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {
 		return nil, err
 	}
 
-	res := make([]protocol.ID, 0, len(pmap))
+	res := make([]string, 0, len(pmap))
 	for proto := range pmap {
 		res = append(res, proto)
 	}
@@ -114,7 +113,7 @@ func (pb *dsProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {
 	return res, nil
 }
 
-func (pb *dsProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]protocol.ID, error) {
+func (pb *dsProtoBook) SupportsProtocols(p peer.ID, protos ...string) ([]string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
@@ -124,7 +123,7 @@ func (pb *dsProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]pr
 		return nil, err
 	}
 
-	res := make([]protocol.ID, 0, len(protos))
+	res := make([]string, 0, len(protos))
 	for _, proto := range protos {
 		if _, ok := pmap[proto]; ok {
 			res = append(res, proto)
@@ -134,7 +133,7 @@ func (pb *dsProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]pr
 	return res, nil
 }
 
-func (pb *dsProtoBook) FirstSupportedProtocol(p peer.ID, protos ...protocol.ID) (protocol.ID, error) {
+func (pb *dsProtoBook) FirstSupportedProtocol(p peer.ID, protos ...string) (string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
@@ -152,7 +151,7 @@ func (pb *dsProtoBook) FirstSupportedProtocol(p peer.ID, protos ...protocol.ID)
 	return "", nil
 }
 
-func (pb *dsProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *dsProtoBook) RemoveProtocols(p peer.ID, protos ...string) error {
 	s := pb.segments.get(p)
 	s.Lock()
 	defer s.Unlock()
@@ -174,15 +173,15 @@ func (pb *dsProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) error {
 	return pb.meta.Put(p, "protocols", pmap)
 }
 
-func (pb *dsProtoBook) getProtocolMap(p peer.ID) (map[protocol.ID]struct{}, error) {
+func (pb *dsProtoBook) getProtocolMap(p peer.ID) (map[string]struct{}, error) {
 	iprotomap, err := pb.meta.Get(p, "protocols")
 	switch err {
 	default:
 		return nil, err
 	case pstore.ErrNotFound:
-		return make(map[protocol.ID]struct{}), nil
+		return make(map[string]struct{}), nil
 	case nil:
-		cast, ok := iprotomap.(map[protocol.ID]struct{})
+		cast, ok := iprotomap.(map[string]struct{})
 		if !ok {
 			return nil, fmt.Errorf("stored protocol set was not a map")
 		}
diff --git a/p2p/host/peerstore/pstoremem/addr_book.go b/p2p/host/peerstore/pstoremem/addr_book.go
index 67f9f914..28aa6d4d 100644
--- a/p2p/host/peerstore/pstoremem/addr_book.go
+++ b/p2p/host/peerstore/pstoremem/addr_book.go
@@ -46,10 +46,7 @@ type addrSegment struct {
 }
 
 func (segments *addrSegments) get(p peer.ID) *addrSegment {
-	if len(p) == 0 { // it's not terribly useful to use an empty peer ID, but at least we should not panic
-		return segments[0]
-	}
-	return segments[uint8(p[len(p)-1])]
+	return segments[byte(p[len(p)-1])]
 }
 
 type clock interface {
@@ -238,16 +235,11 @@ func (mab *memoryAddrBook) addAddrsUnlocked(s *addrSegment, p peer.ID, addrs []m
 
 	exp := mab.clock.Now().Add(ttl)
 	for _, addr := range addrs {
-		// Remove suffix of /p2p/peer-id from address
-		addr, addrPid := peer.SplitAddr(addr)
 		if addr == nil {
-			log.Warnw("Was passed nil multiaddr", "peer", p)
-			continue
-		}
-		if addrPid != "" && addrPid != p {
-			log.Warnf("Was passed p2p address with a different peerId. found: %s, expected: %s", addrPid, p)
+			log.Warnw("was passed nil multiaddr", "peer", p)
 			continue
 		}
+
 		// find the highest TTL and Expiry time between
 		// existing records and function args
 		a, found := amap[string(addr.Bytes())] // won't allocate.
@@ -288,15 +280,10 @@ func (mab *memoryAddrBook) SetAddrs(p peer.ID, addrs []ma.Multiaddr, ttl time.Du
 
 	exp := mab.clock.Now().Add(ttl)
 	for _, addr := range addrs {
-		addr, addrPid := peer.SplitAddr(addr)
 		if addr == nil {
 			log.Warnw("was passed nil multiaddr", "peer", p)
 			continue
 		}
-		if addrPid != "" && addrPid != p {
-			log.Warnf("was passed p2p address with a different peerId, found: %s wanted: %s", addrPid, p)
-			continue
-		}
 		aBytes := addr.Bytes()
 		key := string(aBytes)
 
diff --git a/p2p/host/peerstore/pstoremem/protobook.go b/p2p/host/peerstore/pstoremem/protobook.go
index 0000f97f..7a955c07 100644
--- a/p2p/host/peerstore/pstoremem/protobook.go
+++ b/p2p/host/peerstore/pstoremem/protobook.go
@@ -6,12 +6,11 @@ import (
 
 	"github.com/libp2p/go-libp2p/core/peer"
 	pstore "github.com/libp2p/go-libp2p/core/peerstore"
-	"github.com/libp2p/go-libp2p/core/protocol"
 )
 
 type protoSegment struct {
 	sync.RWMutex
-	protocols map[peer.ID]map[protocol.ID]struct{}
+	protocols map[peer.ID]map[string]struct{}
 }
 
 type protoSegments [256]*protoSegment
@@ -28,7 +27,7 @@ type memoryProtoBook struct {
 	maxProtos int
 
 	lk       sync.RWMutex
-	interned map[protocol.ID]protocol.ID
+	interned map[string]string
 }
 
 var _ pstore.ProtoBook = (*memoryProtoBook)(nil)
@@ -44,11 +43,11 @@ func WithMaxProtocols(num int) ProtoBookOption {
 
 func NewProtoBook(opts ...ProtoBookOption) (*memoryProtoBook, error) {
 	pb := &memoryProtoBook{
-		interned: make(map[protocol.ID]protocol.ID, 256),
+		interned: make(map[string]string, 256),
 		segments: func() (ret protoSegments) {
 			for i := range ret {
 				ret[i] = &protoSegment{
-					protocols: make(map[peer.ID]map[protocol.ID]struct{}),
+					protocols: make(map[peer.ID]map[string]struct{}),
 				}
 			}
 			return ret
@@ -64,7 +63,7 @@ func NewProtoBook(opts ...ProtoBookOption) (*memoryProtoBook, error) {
 	return pb, nil
 }
 
-func (pb *memoryProtoBook) internProtocol(proto protocol.ID) protocol.ID {
+func (pb *memoryProtoBook) internProtocol(proto string) string {
 	// check if it is interned with the read lock
 	pb.lk.RLock()
 	interned, ok := pb.interned[proto]
@@ -88,12 +87,12 @@ func (pb *memoryProtoBook) internProtocol(proto protocol.ID) protocol.ID {
 	return proto
 }
 
-func (pb *memoryProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *memoryProtoBook) SetProtocols(p peer.ID, protos ...string) error {
 	if len(protos) > pb.maxProtos {
 		return errTooManyProtocols
 	}
 
-	newprotos := make(map[protocol.ID]struct{}, len(protos))
+	newprotos := make(map[string]struct{}, len(protos))
 	for _, proto := range protos {
 		newprotos[pb.internProtocol(proto)] = struct{}{}
 	}
@@ -106,14 +105,14 @@ func (pb *memoryProtoBook) SetProtocols(p peer.ID, protos ...protocol.ID) error
 	return nil
 }
 
-func (pb *memoryProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *memoryProtoBook) AddProtocols(p peer.ID, protos ...string) error {
 	s := pb.segments.get(p)
 	s.Lock()
 	defer s.Unlock()
 
 	protomap, ok := s.protocols[p]
 	if !ok {
-		protomap = make(map[protocol.ID]struct{})
+		protomap = make(map[string]struct{})
 		s.protocols[p] = protomap
 	}
 	if len(protomap)+len(protos) > pb.maxProtos {
@@ -126,12 +125,12 @@ func (pb *memoryProtoBook) AddProtocols(p peer.ID, protos ...protocol.ID) error
 	return nil
 }
 
-func (pb *memoryProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {
+func (pb *memoryProtoBook) GetProtocols(p peer.ID) ([]string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
 
-	out := make([]protocol.ID, 0, len(s.protocols[p]))
+	out := make([]string, 0, len(s.protocols[p]))
 	for k := range s.protocols[p] {
 		out = append(out, k)
 	}
@@ -139,7 +138,7 @@ func (pb *memoryProtoBook) GetProtocols(p peer.ID) ([]protocol.ID, error) {
 	return out, nil
 }
 
-func (pb *memoryProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) error {
+func (pb *memoryProtoBook) RemoveProtocols(p peer.ID, protos ...string) error {
 	s := pb.segments.get(p)
 	s.Lock()
 	defer s.Unlock()
@@ -156,12 +155,12 @@ func (pb *memoryProtoBook) RemoveProtocols(p peer.ID, protos ...protocol.ID) err
 	return nil
 }
 
-func (pb *memoryProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) ([]protocol.ID, error) {
+func (pb *memoryProtoBook) SupportsProtocols(p peer.ID, protos ...string) ([]string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
 
-	out := make([]protocol.ID, 0, len(protos))
+	out := make([]string, 0, len(protos))
 	for _, proto := range protos {
 		if _, ok := s.protocols[p][proto]; ok {
 			out = append(out, proto)
@@ -171,7 +170,7 @@ func (pb *memoryProtoBook) SupportsProtocols(p peer.ID, protos ...protocol.ID) (
 	return out, nil
 }
 
-func (pb *memoryProtoBook) FirstSupportedProtocol(p peer.ID, protos ...protocol.ID) (protocol.ID, error) {
+func (pb *memoryProtoBook) FirstSupportedProtocol(p peer.ID, protos ...string) (string, error) {
 	s := pb.segments.get(p)
 	s.RLock()
 	defer s.RUnlock()
diff --git a/p2p/host/resource-manager/README.md b/p2p/host/resource-manager/README.md
index 96edd1d0..f81b90d9 100644
--- a/p2p/host/resource-manager/README.md
+++ b/p2p/host/resource-manager/README.md
@@ -28,9 +28,21 @@ scalingLimits := rcmgr.DefaultLimits
 // Add limits around included libp2p protocols
 libp2p.SetDefaultServiceLimits(&scalingLimits)
 
-// Turn the scaling limits into a static set of limits using `.AutoScale`. This
+// Turn the scaling limits into a concrete set of limits using `.AutoScale`. This
 // scales the limits proportional to your system memory.
-limits := scalingLimits.AutoScale()
+scaledDefaultLimits := scalingLimits.AutoScale()
+
+// Tweak certain settings
+cfg := rcmgr.PartialLimitConfig{
+  System: &rcmgr.ResourceLimits{
+    // Allow unlimited outbound streams
+    StreamsOutbound: rcmgr.Unlimited,
+  },
+  // Everything else is default. The exact values will come from `scaledDefaultLimits` above.
+}
+
+// Create our limits by using our cfg and replacing the default values with values from `scaledDefaultLimits`
+limits := cfg.Build(scaledDefaultLimits)
 
 // The resource manager expects a limiter, se we create one from our limits.
 limiter := rcmgr.NewFixedLimiter(limits)
@@ -51,6 +63,54 @@ if err != nil {
 host, err := libp2p.New(libp2p.ResourceManager(rm))
 ```
 
+### Saving the limits config
+The easiest way to save the defined limits is to serialize the `PartialLimitConfig`
+type as JSON.
+
+```go
+noisyNeighbor, _ := peer.Decode("QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf")
+cfg := rcmgr.PartialLimitConfig{
+  System: &rcmgr.ResourceLimits{
+    // Allow unlimited outbound streams
+    StreamsOutbound: rcmgr.Unlimited,
+  },
+  Peer: map[peer.ID]rcmgr.ResourceLimits{
+    noisyNeighbor: {
+      // No inbound connections from this peer
+      ConnsInbound: rcmgr.BlockAllLimit,
+      // But let me open connections to them
+      Conns:         rcmgr.DefaultLimit,
+      ConnsOutbound: rcmgr.DefaultLimit,
+      // No inbound streams from this peer
+      StreamsInbound: rcmgr.BlockAllLimit,
+      // And let me open unlimited (by me) outbound streams (the peer may have their own limits on me)
+      StreamsOutbound: rcmgr.Unlimited,
+    },
+  },
+}
+jsonBytes, _ := json.Marshal(&cfg)
+
+// string(jsonBytes)
+// {
+//   "System": {
+//     "StreamsOutbound": "unlimited"
+//   },
+//   "Peer": {
+//     "QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf": {
+//       "StreamsInbound": "blockAll",
+//       "StreamsOutbound": "unlimited",
+//       "ConnsInbound": "blockAll"
+//     }
+//   }
+// }
+```
+
+This will omit defaults from the JSON output. It will also serialize the
+blockAll, and unlimited values explicitly.
+
+The `Memory` field is serialized as a string to workaround the JSON limitation
+of 32 bit integers (`Memory` is an int64).
+
 ## Basic Resources
 
 ### Memory
@@ -278,7 +338,7 @@ This is done using the `ScalingLimitConfig`. For every scope, this configuration
 struct defines the absolutely bare minimum limits, and an (optional) increase of
 these limits, which will be applied on nodes that have sufficient memory.
 
-A `ScalingLimitConfig` can be converted into a `LimitConfig` (which can then be
+A `ScalingLimitConfig` can be converted into a `ConcreteLimitConfig` (which can then be
 used to initialize a fixed limiter with `NewFixedLimiter`) by calling the `Scale` method.
 The `Scale` method takes two parameters: the amount of memory and the number of file
 descriptors that an application is willing to dedicate to libp2p.
@@ -346,7 +406,7 @@ go-libp2p process. For the default definitions see [`DefaultLimits` and
 
 If the defaults seem mostly okay, but you want to adjust one facet you can
 simply copy the default struct object and update the field you want to change. You can
-apply changes to a `BaseLimit`, `BaseLimitIncrease`, and `LimitConfig` with
+apply changes to a `BaseLimit`, `BaseLimitIncrease`, and `ConcreteLimitConfig` with
 `.Apply`.
 
 Example
diff --git a/p2p/host/resource-manager/error.go b/p2p/host/resource-manager/error.go
index 1e87e00a..4b02672b 100644
--- a/p2p/host/resource-manager/error.go
+++ b/p2p/host/resource-manager/error.go
@@ -6,13 +6,13 @@ import (
 	"github.com/libp2p/go-libp2p/core/network"
 )
 
-type ErrStreamOrConnLimitExceeded struct {
+type errStreamOrConnLimitExceeded struct {
 	current, attempted, limit int
 	err                       error
 }
 
-func (e *ErrStreamOrConnLimitExceeded) Error() string { return e.err.Error() }
-func (e *ErrStreamOrConnLimitExceeded) Unwrap() error { return e.err }
+func (e *errStreamOrConnLimitExceeded) Error() string { return e.err.Error() }
+func (e *errStreamOrConnLimitExceeded) Unwrap() error { return e.err }
 
 // edge may be "" if this is not an edge error
 func logValuesStreamLimit(scope, edge string, dir network.Direction, stat network.ScopeStat, err error) []interface{} {
@@ -22,7 +22,7 @@ func logValuesStreamLimit(scope, edge string, dir network.Direction, stat networ
 		logValues = append(logValues, "edge", edge)
 	}
 	logValues = append(logValues, "direction", dir)
-	var e *ErrStreamOrConnLimitExceeded
+	var e *errStreamOrConnLimitExceeded
 	if errors.As(err, &e) {
 		logValues = append(logValues,
 			"current", e.current,
@@ -41,7 +41,7 @@ func logValuesConnLimit(scope, edge string, dir network.Direction, usefd bool, s
 		logValues = append(logValues, "edge", edge)
 	}
 	logValues = append(logValues, "direction", dir, "usefd", usefd)
-	var e *ErrStreamOrConnLimitExceeded
+	var e *errStreamOrConnLimitExceeded
 	if errors.As(err, &e) {
 		logValues = append(logValues,
 			"current", e.current,
@@ -52,14 +52,14 @@ func logValuesConnLimit(scope, edge string, dir network.Direction, usefd bool, s
 	return append(logValues, "stat", stat, "error", err)
 }
 
-type ErrMemoryLimitExceeded struct {
+type errMemoryLimitExceeded struct {
 	current, attempted, limit int64
 	priority                  uint8
 	err                       error
 }
 
-func (e *ErrMemoryLimitExceeded) Error() string { return e.err.Error() }
-func (e *ErrMemoryLimitExceeded) Unwrap() error { return e.err }
+func (e *errMemoryLimitExceeded) Error() string { return e.err.Error() }
+func (e *errMemoryLimitExceeded) Unwrap() error { return e.err }
 
 // edge may be "" if this is not an edge error
 func logValuesMemoryLimit(scope, edge string, stat network.ScopeStat, err error) []interface{} {
@@ -68,7 +68,7 @@ func logValuesMemoryLimit(scope, edge string, stat network.ScopeStat, err error)
 	if edge != "" {
 		logValues = append(logValues, "edge", edge)
 	}
-	var e *ErrMemoryLimitExceeded
+	var e *errMemoryLimitExceeded
 	if errors.As(err, &e) {
 		logValues = append(logValues,
 			"current", e.current,
diff --git a/p2p/host/resource-manager/extapi.go b/p2p/host/resource-manager/extapi.go
index 03edcd79..302678e1 100644
--- a/p2p/host/resource-manager/extapi.go
+++ b/p2p/host/resource-manager/extapi.go
@@ -87,7 +87,7 @@ func (r *resourceManager) ListProtocols() []protocol.ID {
 	}
 
 	sort.Slice(result, func(i, j int) bool {
-		return result[i] < result[j]
+		return strings.Compare(string(result[i]), string(result[j])) < 0
 	})
 
 	return result
diff --git a/p2p/host/resource-manager/limit.go b/p2p/host/resource-manager/limit.go
index c3d6dd88..ef7fcdc9 100644
--- a/p2p/host/resource-manager/limit.go
+++ b/p2p/host/resource-manager/limit.go
@@ -12,6 +12,7 @@ package rcmgr
 import (
 	"encoding/json"
 	"io"
+	"math"
 
 	"github.com/libp2p/go-libp2p/core/network"
 	"github.com/libp2p/go-libp2p/core/peer"
@@ -56,7 +57,7 @@ func NewDefaultLimiterFromJSON(in io.Reader) (Limiter, error) {
 }
 
 // NewLimiterFromJSON creates a new limiter by parsing a json configuration.
-func NewLimiterFromJSON(in io.Reader, defaults LimitConfig) (Limiter, error) {
+func NewLimiterFromJSON(in io.Reader, defaults ConcreteLimitConfig) (Limiter, error) {
 	cfg, err := readLimiterConfigFromJSON(in, defaults)
 	if err != nil {
 		return nil, err
@@ -64,25 +65,24 @@ func NewLimiterFromJSON(in io.Reader, defaults LimitConfig) (Limiter, error) {
 	return &fixedLimiter{cfg}, nil
 }
 
-func readLimiterConfigFromJSON(in io.Reader, defaults LimitConfig) (LimitConfig, error) {
-	var cfg LimitConfig
+func readLimiterConfigFromJSON(in io.Reader, defaults ConcreteLimitConfig) (ConcreteLimitConfig, error) {
+	var cfg PartialLimitConfig
 	if err := json.NewDecoder(in).Decode(&cfg); err != nil {
-		return LimitConfig{}, err
+		return ConcreteLimitConfig{}, err
 	}
-	cfg.Apply(defaults)
-	return cfg, nil
+	return cfg.Build(defaults), nil
 }
 
 // fixedLimiter is a limiter with fixed limits.
 type fixedLimiter struct {
-	LimitConfig
+	ConcreteLimitConfig
 }
 
 var _ Limiter = (*fixedLimiter)(nil)
 
-func NewFixedLimiter(conf LimitConfig) Limiter {
+func NewFixedLimiter(conf ConcreteLimitConfig) Limiter {
 	log.Debugw("initializing new limiter with config", "limits", conf)
-	return &fixedLimiter{LimitConfig: conf}
+	return &fixedLimiter{conf}
 }
 
 // BaseLimit is a mixin type for basic resource limits.
@@ -97,6 +97,37 @@ type BaseLimit struct {
 	Memory          int64 `json:",omitempty"`
 }
 
+func valueOrBlockAll(n int) LimitVal {
+	if n == 0 {
+		return BlockAllLimit
+	} else if n == math.MaxInt {
+		return Unlimited
+	}
+	return LimitVal(n)
+}
+func valueOrBlockAll64(n int64) LimitVal64 {
+	if n == 0 {
+		return BlockAllLimit64
+	} else if n == math.MaxInt {
+		return Unlimited64
+	}
+	return LimitVal64(n)
+}
+
+// ToResourceLimits converts the BaseLimit to a ResourceLimits
+func (l BaseLimit) ToResourceLimits() ResourceLimits {
+	return ResourceLimits{
+		Streams:         valueOrBlockAll(l.Streams),
+		StreamsInbound:  valueOrBlockAll(l.StreamsInbound),
+		StreamsOutbound: valueOrBlockAll(l.StreamsOutbound),
+		Conns:           valueOrBlockAll(l.Conns),
+		ConnsInbound:    valueOrBlockAll(l.ConnsInbound),
+		ConnsOutbound:   valueOrBlockAll(l.ConnsOutbound),
+		FD:              valueOrBlockAll(l.FD),
+		Memory:          valueOrBlockAll64(l.Memory),
+	}
+}
+
 // Apply overwrites all zero-valued limits with the values of l2
 // Must not use a pointer receiver.
 func (l *BaseLimit) Apply(l2 BaseLimit) {
@@ -169,7 +200,7 @@ func (l *BaseLimitIncrease) Apply(l2 BaseLimitIncrease) {
 	}
 }
 
-func (l *BaseLimit) GetStreamLimit(dir network.Direction) int {
+func (l BaseLimit) GetStreamLimit(dir network.Direction) int {
 	if dir == network.DirInbound {
 		return l.StreamsInbound
 	} else {
@@ -177,11 +208,11 @@ func (l *BaseLimit) GetStreamLimit(dir network.Direction) int {
 	}
 }
 
-func (l *BaseLimit) GetStreamTotalLimit() int {
+func (l BaseLimit) GetStreamTotalLimit() int {
 	return l.Streams
 }
 
-func (l *BaseLimit) GetConnLimit(dir network.Direction) int {
+func (l BaseLimit) GetConnLimit(dir network.Direction) int {
 	if dir == network.DirInbound {
 		return l.ConnsInbound
 	} else {
@@ -189,78 +220,78 @@ func (l *BaseLimit) GetConnLimit(dir network.Direction) int {
 	}
 }
 
-func (l *BaseLimit) GetConnTotalLimit() int {
+func (l BaseLimit) GetConnTotalLimit() int {
 	return l.Conns
 }
 
-func (l *BaseLimit) GetFDLimit() int {
+func (l BaseLimit) GetFDLimit() int {
 	return l.FD
 }
 
-func (l *BaseLimit) GetMemoryLimit() int64 {
+func (l BaseLimit) GetMemoryLimit() int64 {
 	return l.Memory
 }
 
 func (l *fixedLimiter) GetSystemLimits() Limit {
-	return &l.System
+	return &l.system
 }
 
 func (l *fixedLimiter) GetTransientLimits() Limit {
-	return &l.Transient
+	return &l.transient
 }
 
 func (l *fixedLimiter) GetAllowlistedSystemLimits() Limit {
-	return &l.AllowlistedSystem
+	return &l.allowlistedSystem
 }
 
 func (l *fixedLimiter) GetAllowlistedTransientLimits() Limit {
-	return &l.AllowlistedTransient
+	return &l.allowlistedTransient
 }
 
 func (l *fixedLimiter) GetServiceLimits(svc string) Limit {
-	sl, ok := l.Service[svc]
+	sl, ok := l.service[svc]
 	if !ok {
-		return &l.ServiceDefault
+		return &l.serviceDefault
 	}
 	return &sl
 }
 
 func (l *fixedLimiter) GetServicePeerLimits(svc string) Limit {
-	pl, ok := l.ServicePeer[svc]
+	pl, ok := l.servicePeer[svc]
 	if !ok {
-		return &l.ServicePeerDefault
+		return &l.servicePeerDefault
 	}
 	return &pl
 }
 
 func (l *fixedLimiter) GetProtocolLimits(proto protocol.ID) Limit {
-	pl, ok := l.Protocol[proto]
+	pl, ok := l.protocol[proto]
 	if !ok {
-		return &l.ProtocolDefault
+		return &l.protocolDefault
 	}
 	return &pl
 }
 
 func (l *fixedLimiter) GetProtocolPeerLimits(proto protocol.ID) Limit {
-	pl, ok := l.ProtocolPeer[proto]
+	pl, ok := l.protocolPeer[proto]
 	if !ok {
-		return &l.ProtocolPeerDefault
+		return &l.protocolPeerDefault
 	}
 	return &pl
 }
 
 func (l *fixedLimiter) GetPeerLimits(p peer.ID) Limit {
-	pl, ok := l.Peer[p]
+	pl, ok := l.peer[p]
 	if !ok {
-		return &l.PeerDefault
+		return &l.peerDefault
 	}
 	return &pl
 }
 
 func (l *fixedLimiter) GetStreamLimits(_ peer.ID) Limit {
-	return &l.Stream
+	return &l.stream
 }
 
 func (l *fixedLimiter) GetConnLimits() Limit {
-	return &l.Conn
+	return &l.conn
 }
diff --git a/p2p/host/resource-manager/limit_defaults.go b/p2p/host/resource-manager/limit_defaults.go
index a9c73a4d..e7489c45 100644
--- a/p2p/host/resource-manager/limit_defaults.go
+++ b/p2p/host/resource-manager/limit_defaults.go
@@ -2,8 +2,11 @@ package rcmgr
 
 import (
 	"encoding/json"
+	"fmt"
 	"math"
+	"strconv"
 
+	"github.com/libp2p/go-libp2p/core/network"
 	"github.com/libp2p/go-libp2p/core/peer"
 	"github.com/libp2p/go-libp2p/core/protocol"
 
@@ -108,53 +111,339 @@ func (cfg *ScalingLimitConfig) AddProtocolPeerLimit(proto protocol.ID, base Base
 	}
 }
 
-type LimitConfig struct {
-	System    BaseLimit `json:",omitempty"`
-	Transient BaseLimit `json:",omitempty"`
+type LimitVal int
+
+const (
+	// DefaultLimit is the default value for resources. The exact value depends on the context, but will get values from `DefaultLimits`.
+	DefaultLimit LimitVal = 0
+	// Unlimited is the value for unlimited resources. An arbitrarily high number will also work.
+	Unlimited LimitVal = -1
+	// BlockAllLimit is the LimitVal for allowing no amount of resources.
+	BlockAllLimit LimitVal = -2
+)
+
+func (l LimitVal) MarshalJSON() ([]byte, error) {
+	if l == Unlimited {
+		return json.Marshal("unlimited")
+	} else if l == DefaultLimit {
+		return json.Marshal("default")
+	} else if l == BlockAllLimit {
+		return json.Marshal("blockAll")
+	}
+	return json.Marshal(int(l))
+}
+
+func (l *LimitVal) UnmarshalJSON(b []byte) error {
+	if string(b) == `"default"` {
+		*l = DefaultLimit
+		return nil
+	} else if string(b) == `"unlimited"` {
+		*l = Unlimited
+		return nil
+	} else if string(b) == `"blockAll"` {
+		*l = BlockAllLimit
+		return nil
+	}
+
+	var val int
+	if err := json.Unmarshal(b, &val); err != nil {
+		return err
+	}
+
+	if val == 0 {
+		// If there is an explicit 0 in the JSON we should interpret this as block all.
+		*l = BlockAllLimit
+		return nil
+	}
+
+	*l = LimitVal(val)
+	return nil
+}
+
+func (l LimitVal) Build(defaultVal int) int {
+	if l == DefaultLimit {
+		return defaultVal
+	}
+	if l == Unlimited {
+		return math.MaxInt
+	}
+	if l == BlockAllLimit {
+		return 0
+	}
+	return int(l)
+}
+
+type LimitVal64 int64
+
+const (
+	// Default is the default value for resources.
+	DefaultLimit64 LimitVal64 = 0
+	// Unlimited is the value for unlimited resources.
+	Unlimited64 LimitVal64 = -1
+	// BlockAllLimit64 is the LimitVal for allowing no amount of resources.
+	BlockAllLimit64 LimitVal64 = -2
+)
+
+func (l LimitVal64) MarshalJSON() ([]byte, error) {
+	if l == Unlimited64 {
+		return json.Marshal("unlimited")
+	} else if l == DefaultLimit64 {
+		return json.Marshal("default")
+	} else if l == BlockAllLimit64 {
+		return json.Marshal("blockAll")
+	}
+
+	// Convert this to a string because JSON doesn't support 64-bit integers.
+	return json.Marshal(strconv.FormatInt(int64(l), 10))
+}
+
+func (l *LimitVal64) UnmarshalJSON(b []byte) error {
+	if string(b) == `"default"` {
+		*l = DefaultLimit64
+		return nil
+	} else if string(b) == `"unlimited"` {
+		*l = Unlimited64
+		return nil
+	} else if string(b) == `"blockAll"` {
+		*l = BlockAllLimit64
+		return nil
+	}
+
+	var val string
+	if err := json.Unmarshal(b, &val); err != nil {
+		// Is this an integer? Possible because of backwards compatibility.
+		var val int
+		if err := json.Unmarshal(b, &val); err != nil {
+			return fmt.Errorf("failed to unmarshal limit value: %w", err)
+		}
+
+		if val == 0 {
+			// If there is an explicit 0 in the JSON we should interpret this as block all.
+			*l = BlockAllLimit64
+			return nil
+		}
+
+		*l = LimitVal64(val)
+		return nil
+	}
+
+	i, err := strconv.ParseInt(val, 10, 64)
+	if err != nil {
+		return err
+	}
+
+	if i == 0 {
+		// If there is an explicit 0 in the JSON we should interpret this as block all.
+		*l = BlockAllLimit64
+		return nil
+	}
+
+	*l = LimitVal64(i)
+	return nil
+}
+
+func (l LimitVal64) Build(defaultVal int64) int64 {
+	if l == DefaultLimit64 {
+		return defaultVal
+	}
+	if l == Unlimited64 {
+		return math.MaxInt64
+	}
+	if l == BlockAllLimit64 {
+		return 0
+	}
+	return int64(l)
+}
+
+// ResourceLimits is the type for basic resource limits.
+type ResourceLimits struct {
+	Streams         LimitVal   `json:",omitempty"`
+	StreamsInbound  LimitVal   `json:",omitempty"`
+	StreamsOutbound LimitVal   `json:",omitempty"`
+	Conns           LimitVal   `json:",omitempty"`
+	ConnsInbound    LimitVal   `json:",omitempty"`
+	ConnsOutbound   LimitVal   `json:",omitempty"`
+	FD              LimitVal   `json:",omitempty"`
+	Memory          LimitVal64 `json:",omitempty"`
+}
+
+func (l *ResourceLimits) IsDefault() bool {
+	if l == nil {
+		return true
+	}
+
+	if l.Streams == DefaultLimit &&
+		l.StreamsInbound == DefaultLimit &&
+		l.StreamsOutbound == DefaultLimit &&
+		l.Conns == DefaultLimit &&
+		l.ConnsInbound == DefaultLimit &&
+		l.ConnsOutbound == DefaultLimit &&
+		l.FD == DefaultLimit &&
+		l.Memory == DefaultLimit64 {
+		return true
+	}
+	return false
+}
+
+func (l *ResourceLimits) ToMaybeNilPtr() *ResourceLimits {
+	if l.IsDefault() {
+		return nil
+	}
+	return l
+}
+
+// Apply overwrites all default limits with the values of l2
+func (l *ResourceLimits) Apply(l2 ResourceLimits) {
+	if l.Streams == DefaultLimit {
+		l.Streams = l2.Streams
+	}
+	if l.StreamsInbound == DefaultLimit {
+		l.StreamsInbound = l2.StreamsInbound
+	}
+	if l.StreamsOutbound == DefaultLimit {
+		l.StreamsOutbound = l2.StreamsOutbound
+	}
+	if l.Conns == DefaultLimit {
+		l.Conns = l2.Conns
+	}
+	if l.ConnsInbound == DefaultLimit {
+		l.ConnsInbound = l2.ConnsInbound
+	}
+	if l.ConnsOutbound == DefaultLimit {
+		l.ConnsOutbound = l2.ConnsOutbound
+	}
+	if l.FD == DefaultLimit {
+		l.FD = l2.FD
+	}
+	if l.Memory == DefaultLimit64 {
+		l.Memory = l2.Memory
+	}
+}
+
+func (l *ResourceLimits) Build(defaults Limit) BaseLimit {
+	if l == nil {
+		return BaseLimit{
+			Streams:         defaults.GetStreamTotalLimit(),
+			StreamsInbound:  defaults.GetStreamLimit(network.DirInbound),
+			StreamsOutbound: defaults.GetStreamLimit(network.DirOutbound),
+			Conns:           defaults.GetConnTotalLimit(),
+			ConnsInbound:    defaults.GetConnLimit(network.DirInbound),
+			ConnsOutbound:   defaults.GetConnLimit(network.DirOutbound),
+			FD:              defaults.GetFDLimit(),
+			Memory:          defaults.GetMemoryLimit(),
+		}
+	}
+
+	return BaseLimit{
+		Streams:         l.Streams.Build(defaults.GetStreamTotalLimit()),
+		StreamsInbound:  l.StreamsInbound.Build(defaults.GetStreamLimit(network.DirInbound)),
+		StreamsOutbound: l.StreamsOutbound.Build(defaults.GetStreamLimit(network.DirOutbound)),
+		Conns:           l.Conns.Build(defaults.GetConnTotalLimit()),
+		ConnsInbound:    l.ConnsInbound.Build(defaults.GetConnLimit(network.DirInbound)),
+		ConnsOutbound:   l.ConnsOutbound.Build(defaults.GetConnLimit(network.DirOutbound)),
+		FD:              l.FD.Build(defaults.GetFDLimit()),
+		Memory:          l.Memory.Build(defaults.GetMemoryLimit()),
+	}
+}
+
+type PartialLimitConfig struct {
+	System    ResourceLimits `json:",omitempty"`
+	Transient ResourceLimits `json:",omitempty"`
 
 	// Limits that are applied to resources with an allowlisted multiaddr.
 	// These will only be used if the normal System & Transient limits are
 	// reached.
-	AllowlistedSystem    BaseLimit `json:",omitempty"`
-	AllowlistedTransient BaseLimit `json:",omitempty"`
+	AllowlistedSystem    ResourceLimits `json:",omitempty"`
+	AllowlistedTransient ResourceLimits `json:",omitempty"`
 
-	ServiceDefault BaseLimit            `json:",omitempty"`
-	Service        map[string]BaseLimit `json:",omitempty"`
+	ServiceDefault ResourceLimits            `json:",omitempty"`
+	Service        map[string]ResourceLimits `json:",omitempty"`
 
-	ServicePeerDefault BaseLimit            `json:",omitempty"`
-	ServicePeer        map[string]BaseLimit `json:",omitempty"`
+	ServicePeerDefault ResourceLimits            `json:",omitempty"`
+	ServicePeer        map[string]ResourceLimits `json:",omitempty"`
 
-	ProtocolDefault BaseLimit                 `json:",omitempty"`
-	Protocol        map[protocol.ID]BaseLimit `json:",omitempty"`
+	ProtocolDefault ResourceLimits                 `json:",omitempty"`
+	Protocol        map[protocol.ID]ResourceLimits `json:",omitempty"`
 
-	ProtocolPeerDefault BaseLimit                 `json:",omitempty"`
-	ProtocolPeer        map[protocol.ID]BaseLimit `json:",omitempty"`
+	ProtocolPeerDefault ResourceLimits                 `json:",omitempty"`
+	ProtocolPeer        map[protocol.ID]ResourceLimits `json:",omitempty"`
 
-	PeerDefault BaseLimit             `json:",omitempty"`
-	Peer        map[peer.ID]BaseLimit `json:",omitempty"`
+	PeerDefault ResourceLimits             `json:",omitempty"`
+	Peer        map[peer.ID]ResourceLimits `json:",omitempty"`
 
-	Conn   BaseLimit `json:",omitempty"`
-	Stream BaseLimit `json:",omitempty"`
+	Conn   ResourceLimits `json:",omitempty"`
+	Stream ResourceLimits `json:",omitempty"`
 }
 
-func (cfg *LimitConfig) MarshalJSON() ([]byte, error) {
+func (cfg *PartialLimitConfig) MarshalJSON() ([]byte, error) {
 	// we want to marshal the encoded peer id
-	encodedPeerMap := make(map[string]BaseLimit, len(cfg.Peer))
+	encodedPeerMap := make(map[string]ResourceLimits, len(cfg.Peer))
 	for p, v := range cfg.Peer {
 		encodedPeerMap[p.String()] = v
 	}
 
-	type Alias LimitConfig
+	type Alias PartialLimitConfig
 	return json.Marshal(&struct {
 		*Alias
-		Peer map[string]BaseLimit `json:",omitempty"`
+		// String so we can have the properly marshalled peer id
+		Peer map[string]ResourceLimits `json:",omitempty"`
+
+		// The rest of the fields as pointers so that we omit empty values in the serialized result
+		System               *ResourceLimits `json:",omitempty"`
+		Transient            *ResourceLimits `json:",omitempty"`
+		AllowlistedSystem    *ResourceLimits `json:",omitempty"`
+		AllowlistedTransient *ResourceLimits `json:",omitempty"`
+
+		ServiceDefault *ResourceLimits `json:",omitempty"`
+
+		ServicePeerDefault *ResourceLimits `json:",omitempty"`
+
+		ProtocolDefault *ResourceLimits `json:",omitempty"`
+
+		ProtocolPeerDefault *ResourceLimits `json:",omitempty"`
+
+		PeerDefault *ResourceLimits `json:",omitempty"`
+
+		Conn   *ResourceLimits `json:",omitempty"`
+		Stream *ResourceLimits `json:",omitempty"`
 	}{
 		Alias: (*Alias)(cfg),
 		Peer:  encodedPeerMap,
+
+		System:               cfg.System.ToMaybeNilPtr(),
+		Transient:            cfg.Transient.ToMaybeNilPtr(),
+		AllowlistedSystem:    cfg.AllowlistedSystem.ToMaybeNilPtr(),
+		AllowlistedTransient: cfg.AllowlistedTransient.ToMaybeNilPtr(),
+		ServiceDefault:       cfg.ServiceDefault.ToMaybeNilPtr(),
+		ServicePeerDefault:   cfg.ServicePeerDefault.ToMaybeNilPtr(),
+		ProtocolDefault:      cfg.ProtocolDefault.ToMaybeNilPtr(),
+		ProtocolPeerDefault:  cfg.ProtocolPeerDefault.ToMaybeNilPtr(),
+		PeerDefault:          cfg.PeerDefault.ToMaybeNilPtr(),
+		Conn:                 cfg.Conn.ToMaybeNilPtr(),
+		Stream:               cfg.Stream.ToMaybeNilPtr(),
 	})
 }
 
-func (cfg *LimitConfig) Apply(c LimitConfig) {
+func applyResourceLimitsMap[K comparable](this *map[K]ResourceLimits, other map[K]ResourceLimits, fallbackDefault ResourceLimits) {
+	for k, l := range *this {
+		r := fallbackDefault
+		if l2, ok := other[k]; ok {
+			r = l2
+		}
+		l.Apply(r)
+		(*this)[k] = l
+	}
+	if *this == nil && other != nil {
+		*this = make(map[K]ResourceLimits)
+	}
+	for k, l := range other {
+		if _, ok := (*this)[k]; !ok {
+			(*this)[k] = l
+		}
+	}
+}
+
+func (cfg *PartialLimitConfig) Apply(c PartialLimitConfig) {
 	cfg.System.Apply(c.System)
 	cfg.Transient.Apply(c.Transient)
 	cfg.AllowlistedSystem.Apply(c.AllowlistedSystem)
@@ -167,90 +456,123 @@ func (cfg *LimitConfig) Apply(c LimitConfig) {
 	cfg.Conn.Apply(c.Conn)
 	cfg.Stream.Apply(c.Stream)
 
-	// TODO: the following could be solved a lot nicer, if only we could use generics
-	for s, l := range cfg.Service {
-		r := cfg.ServiceDefault
-		if l2, ok := c.Service[s]; ok {
-			r = l2
-		}
-		l.Apply(r)
-		cfg.Service[s] = l
-	}
-	if c.Service != nil && cfg.Service == nil {
-		cfg.Service = make(map[string]BaseLimit)
-	}
-	for s, l := range c.Service {
-		if _, ok := cfg.Service[s]; !ok {
-			cfg.Service[s] = l
-		}
-	}
+	applyResourceLimitsMap(&cfg.Service, c.Service, cfg.ServiceDefault)
+	applyResourceLimitsMap(&cfg.ServicePeer, c.ServicePeer, cfg.ServicePeerDefault)
+	applyResourceLimitsMap(&cfg.Protocol, c.Protocol, cfg.ProtocolDefault)
+	applyResourceLimitsMap(&cfg.ProtocolPeer, c.ProtocolPeer, cfg.ProtocolPeerDefault)
+	applyResourceLimitsMap(&cfg.Peer, c.Peer, cfg.PeerDefault)
+}
 
-	for s, l := range cfg.ServicePeer {
-		r := cfg.ServicePeerDefault
-		if l2, ok := c.ServicePeer[s]; ok {
-			r = l2
-		}
-		l.Apply(r)
-		cfg.ServicePeer[s] = l
-	}
-	if c.ServicePeer != nil && cfg.ServicePeer == nil {
-		cfg.ServicePeer = make(map[string]BaseLimit)
-	}
-	for s, l := range c.ServicePeer {
-		if _, ok := cfg.ServicePeer[s]; !ok {
-			cfg.ServicePeer[s] = l
-		}
-	}
+func (cfg PartialLimitConfig) Build(defaults ConcreteLimitConfig) ConcreteLimitConfig {
+	out := defaults
+
+	out.system = cfg.System.Build(defaults.system)
+	out.transient = cfg.Transient.Build(defaults.transient)
+	out.allowlistedSystem = cfg.AllowlistedSystem.Build(defaults.allowlistedSystem)
+	out.allowlistedTransient = cfg.AllowlistedTransient.Build(defaults.allowlistedTransient)
+	out.serviceDefault = cfg.ServiceDefault.Build(defaults.serviceDefault)
+	out.servicePeerDefault = cfg.ServicePeerDefault.Build(defaults.servicePeerDefault)
+	out.protocolDefault = cfg.ProtocolDefault.Build(defaults.protocolDefault)
+	out.protocolPeerDefault = cfg.ProtocolPeerDefault.Build(defaults.protocolPeerDefault)
+	out.peerDefault = cfg.PeerDefault.Build(defaults.peerDefault)
+	out.conn = cfg.Conn.Build(defaults.conn)
+	out.stream = cfg.Stream.Build(defaults.stream)
+
+	out.service = buildMapWithDefault(cfg.Service, defaults.service, out.serviceDefault)
+	out.servicePeer = buildMapWithDefault(cfg.ServicePeer, defaults.servicePeer, out.servicePeerDefault)
+	out.protocol = buildMapWithDefault(cfg.Protocol, defaults.protocol, out.protocolDefault)
+	out.protocolPeer = buildMapWithDefault(cfg.ProtocolPeer, defaults.protocolPeer, out.protocolPeerDefault)
+	out.peer = buildMapWithDefault(cfg.Peer, defaults.peer, out.peerDefault)
+
+	return out
+}
 
-	for s, l := range cfg.Protocol {
-		r := cfg.ProtocolDefault
-		if l2, ok := c.Protocol[s]; ok {
-			r = l2
-		}
-		l.Apply(r)
-		cfg.Protocol[s] = l
-	}
-	if c.Protocol != nil && cfg.Protocol == nil {
-		cfg.Protocol = make(map[protocol.ID]BaseLimit)
-	}
-	for s, l := range c.Protocol {
-		if _, ok := cfg.Protocol[s]; !ok {
-			cfg.Protocol[s] = l
-		}
+func buildMapWithDefault[K comparable](definedLimits map[K]ResourceLimits, defaults map[K]BaseLimit, fallbackDefault BaseLimit) map[K]BaseLimit {
+	if definedLimits == nil && defaults == nil {
+		return nil
 	}
 
-	for s, l := range cfg.ProtocolPeer {
-		r := cfg.ProtocolPeerDefault
-		if l2, ok := c.ProtocolPeer[s]; ok {
-			r = l2
-		}
-		l.Apply(r)
-		cfg.ProtocolPeer[s] = l
-	}
-	if c.ProtocolPeer != nil && cfg.ProtocolPeer == nil {
-		cfg.ProtocolPeer = make(map[protocol.ID]BaseLimit)
+	out := make(map[K]BaseLimit)
+	for k, l := range defaults {
+		out[k] = l
 	}
-	for s, l := range c.ProtocolPeer {
-		if _, ok := cfg.ProtocolPeer[s]; !ok {
-			cfg.ProtocolPeer[s] = l
+
+	for k, l := range definedLimits {
+		if defaultForKey, ok := out[k]; ok {
+			out[k] = l.Build(defaultForKey)
+		} else {
+			out[k] = l.Build(fallbackDefault)
 		}
 	}
 
-	for s, l := range cfg.Peer {
-		r := cfg.PeerDefault
-		if l2, ok := c.Peer[s]; ok {
-			r = l2
-		}
-		l.Apply(r)
-		cfg.Peer[s] = l
+	return out
+}
+
+// ConcreteLimitConfig is similar to PartialLimitConfig, but all values are defined.
+// There is no unset "default" value. Commonly constructed by calling
+// PartialLimitConfig.Build(rcmgr.DefaultLimits.AutoScale())
+type ConcreteLimitConfig struct {
+	system    BaseLimit
+	transient BaseLimit
+
+	// Limits that are applied to resources with an allowlisted multiaddr.
+	// These will only be used if the normal System & Transient limits are
+	// reached.
+	allowlistedSystem    BaseLimit
+	allowlistedTransient BaseLimit
+
+	serviceDefault BaseLimit
+	service        map[string]BaseLimit
+
+	servicePeerDefault BaseLimit
+	servicePeer        map[string]BaseLimit
+
+	protocolDefault BaseLimit
+	protocol        map[protocol.ID]BaseLimit
+
+	protocolPeerDefault BaseLimit
+	protocolPeer        map[protocol.ID]BaseLimit
+
+	peerDefault BaseLimit
+	peer        map[peer.ID]BaseLimit
+
+	conn   BaseLimit
+	stream BaseLimit
+}
+
+func resourceLimitsMapFromBaseLimitMap[K comparable](baseLimitMap map[K]BaseLimit) map[K]ResourceLimits {
+	if baseLimitMap == nil {
+		return nil
 	}
-	if c.Peer != nil && cfg.Peer == nil {
-		cfg.Peer = make(map[peer.ID]BaseLimit)
+
+	out := make(map[K]ResourceLimits)
+	for k, l := range baseLimitMap {
+		out[k] = l.ToResourceLimits()
 	}
-	for s, l := range c.Peer {
-		if _, ok := cfg.Peer[s]; !ok {
-			cfg.Peer[s] = l
-		}
+
+	return out
+}
+
+// ToPartialLimitConfig converts a ConcreteLimitConfig to a PartialLimitConfig.
+// The returned PartialLimitConfig will have no default values.
+func (cfg ConcreteLimitConfig) ToPartialLimitConfig() PartialLimitConfig {
+	return PartialLimitConfig{
+		System:               cfg.system.ToResourceLimits(),
+		Transient:            cfg.transient.ToResourceLimits(),
+		AllowlistedSystem:    cfg.allowlistedSystem.ToResourceLimits(),
+		AllowlistedTransient: cfg.allowlistedTransient.ToResourceLimits(),
+		ServiceDefault:       cfg.serviceDefault.ToResourceLimits(),
+		Service:              resourceLimitsMapFromBaseLimitMap(cfg.service),
+		ServicePeerDefault:   cfg.servicePeerDefault.ToResourceLimits(),
+		ServicePeer:          resourceLimitsMapFromBaseLimitMap(cfg.servicePeer),
+		ProtocolDefault:      cfg.protocolDefault.ToResourceLimits(),
+		Protocol:             resourceLimitsMapFromBaseLimitMap(cfg.protocol),
+		ProtocolPeerDefault:  cfg.protocolPeerDefault.ToResourceLimits(),
+		ProtocolPeer:         resourceLimitsMapFromBaseLimitMap(cfg.protocolPeer),
+		PeerDefault:          cfg.peerDefault.ToResourceLimits(),
+		Peer:                 resourceLimitsMapFromBaseLimitMap(cfg.peer),
+		Conn:                 cfg.conn.ToResourceLimits(),
+		Stream:               cfg.stream.ToResourceLimits(),
 	}
 }
 
@@ -258,54 +580,54 @@ func (cfg *LimitConfig) Apply(c LimitConfig) {
 // memory is the amount of memory that the stack is allowed to consume,
 // for a dedicated node it's recommended to use 1/8 of the installed system memory.
 // If memory is smaller than 128 MB, the base configuration will be used.
-func (cfg *ScalingLimitConfig) Scale(memory int64, numFD int) LimitConfig {
-	lc := LimitConfig{
-		System:               scale(cfg.SystemBaseLimit, cfg.SystemLimitIncrease, memory, numFD),
-		Transient:            scale(cfg.TransientBaseLimit, cfg.TransientLimitIncrease, memory, numFD),
-		AllowlistedSystem:    scale(cfg.AllowlistedSystemBaseLimit, cfg.AllowlistedSystemLimitIncrease, memory, numFD),
-		AllowlistedTransient: scale(cfg.AllowlistedTransientBaseLimit, cfg.AllowlistedTransientLimitIncrease, memory, numFD),
-		ServiceDefault:       scale(cfg.ServiceBaseLimit, cfg.ServiceLimitIncrease, memory, numFD),
-		ServicePeerDefault:   scale(cfg.ServicePeerBaseLimit, cfg.ServicePeerLimitIncrease, memory, numFD),
-		ProtocolDefault:      scale(cfg.ProtocolBaseLimit, cfg.ProtocolLimitIncrease, memory, numFD),
-		ProtocolPeerDefault:  scale(cfg.ProtocolPeerBaseLimit, cfg.ProtocolPeerLimitIncrease, memory, numFD),
-		PeerDefault:          scale(cfg.PeerBaseLimit, cfg.PeerLimitIncrease, memory, numFD),
-		Conn:                 scale(cfg.ConnBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
-		Stream:               scale(cfg.StreamBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
+func (cfg *ScalingLimitConfig) Scale(memory int64, numFD int) ConcreteLimitConfig {
+	lc := ConcreteLimitConfig{
+		system:               scale(cfg.SystemBaseLimit, cfg.SystemLimitIncrease, memory, numFD),
+		transient:            scale(cfg.TransientBaseLimit, cfg.TransientLimitIncrease, memory, numFD),
+		allowlistedSystem:    scale(cfg.AllowlistedSystemBaseLimit, cfg.AllowlistedSystemLimitIncrease, memory, numFD),
+		allowlistedTransient: scale(cfg.AllowlistedTransientBaseLimit, cfg.AllowlistedTransientLimitIncrease, memory, numFD),
+		serviceDefault:       scale(cfg.ServiceBaseLimit, cfg.ServiceLimitIncrease, memory, numFD),
+		servicePeerDefault:   scale(cfg.ServicePeerBaseLimit, cfg.ServicePeerLimitIncrease, memory, numFD),
+		protocolDefault:      scale(cfg.ProtocolBaseLimit, cfg.ProtocolLimitIncrease, memory, numFD),
+		protocolPeerDefault:  scale(cfg.ProtocolPeerBaseLimit, cfg.ProtocolPeerLimitIncrease, memory, numFD),
+		peerDefault:          scale(cfg.PeerBaseLimit, cfg.PeerLimitIncrease, memory, numFD),
+		conn:                 scale(cfg.ConnBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
+		stream:               scale(cfg.StreamBaseLimit, cfg.ConnLimitIncrease, memory, numFD),
 	}
 	if cfg.ServiceLimits != nil {
-		lc.Service = make(map[string]BaseLimit)
+		lc.service = make(map[string]BaseLimit)
 		for svc, l := range cfg.ServiceLimits {
-			lc.Service[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
+			lc.service[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
 		}
 	}
 	if cfg.ProtocolLimits != nil {
-		lc.Protocol = make(map[protocol.ID]BaseLimit)
+		lc.protocol = make(map[protocol.ID]BaseLimit)
 		for proto, l := range cfg.ProtocolLimits {
-			lc.Protocol[proto] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
+			lc.protocol[proto] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
 		}
 	}
 	if cfg.PeerLimits != nil {
-		lc.Peer = make(map[peer.ID]BaseLimit)
+		lc.peer = make(map[peer.ID]BaseLimit)
 		for p, l := range cfg.PeerLimits {
-			lc.Peer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
+			lc.peer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
 		}
 	}
 	if cfg.ServicePeerLimits != nil {
-		lc.ServicePeer = make(map[string]BaseLimit)
+		lc.servicePeer = make(map[string]BaseLimit)
 		for svc, l := range cfg.ServicePeerLimits {
-			lc.ServicePeer[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
+			lc.servicePeer[svc] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
 		}
 	}
 	if cfg.ProtocolPeerLimits != nil {
-		lc.ProtocolPeer = make(map[protocol.ID]BaseLimit)
+		lc.protocolPeer = make(map[protocol.ID]BaseLimit)
 		for p, l := range cfg.ProtocolPeerLimits {
-			lc.ProtocolPeer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
+			lc.protocolPeer[p] = scale(l.BaseLimit, l.BaseLimitIncrease, memory, numFD)
 		}
 	}
 	return lc
 }
 
-func (cfg *ScalingLimitConfig) AutoScale() LimitConfig {
+func (cfg *ScalingLimitConfig) AutoScale() ConcreteLimitConfig {
 	return cfg.Scale(
 		int64(memory.TotalMemory())/8,
 		getNumFDs()/2,
@@ -540,18 +862,18 @@ var infiniteBaseLimit = BaseLimit{
 	Memory:          math.MaxInt64,
 }
 
-// InfiniteLimits are a limiter configuration that uses infinite limits, thus effectively not limiting anything.
+// InfiniteLimits are a limiter configuration that uses unlimited limits, thus effectively not limiting anything.
 // Keep in mind that the operating system limits the number of file descriptors that an application can use.
-var InfiniteLimits = LimitConfig{
-	System:               infiniteBaseLimit,
-	Transient:            infiniteBaseLimit,
-	AllowlistedSystem:    infiniteBaseLimit,
-	AllowlistedTransient: infiniteBaseLimit,
-	ServiceDefault:       infiniteBaseLimit,
-	ServicePeerDefault:   infiniteBaseLimit,
-	ProtocolDefault:      infiniteBaseLimit,
-	ProtocolPeerDefault:  infiniteBaseLimit,
-	PeerDefault:          infiniteBaseLimit,
-	Conn:                 infiniteBaseLimit,
-	Stream:               infiniteBaseLimit,
+var InfiniteLimits = ConcreteLimitConfig{
+	system:               infiniteBaseLimit,
+	transient:            infiniteBaseLimit,
+	allowlistedSystem:    infiniteBaseLimit,
+	allowlistedTransient: infiniteBaseLimit,
+	serviceDefault:       infiniteBaseLimit,
+	servicePeerDefault:   infiniteBaseLimit,
+	protocolDefault:      infiniteBaseLimit,
+	protocolPeerDefault:  infiniteBaseLimit,
+	peerDefault:          infiniteBaseLimit,
+	conn:                 infiniteBaseLimit,
+	stream:               infiniteBaseLimit,
 }
diff --git a/p2p/host/resource-manager/scope.go b/p2p/host/resource-manager/scope.go
index 60089c3a..2a83c695 100644
--- a/p2p/host/resource-manager/scope.go
+++ b/p2p/host/resource-manager/scope.go
@@ -130,7 +130,7 @@ func (rc *resources) checkMemory(rsvp int64, prio uint8) error {
 	}
 
 	if !addOk || newmem > threshold {
-		return &ErrMemoryLimitExceeded{
+		return &errMemoryLimitExceeded{
 			current:   rc.memory,
 			attempted: rsvp,
 			limit:     limit,
@@ -171,7 +171,7 @@ func (rc *resources) addStreams(incount, outcount int) error {
 	if incount > 0 {
 		limit := rc.limit.GetStreamLimit(network.DirInbound)
 		if rc.nstreamsIn+incount > limit {
-			return &ErrStreamOrConnLimitExceeded{
+			return &errStreamOrConnLimitExceeded{
 				current:   rc.nstreamsIn,
 				attempted: incount,
 				limit:     limit,
@@ -182,7 +182,7 @@ func (rc *resources) addStreams(incount, outcount int) error {
 	if outcount > 0 {
 		limit := rc.limit.GetStreamLimit(network.DirOutbound)
 		if rc.nstreamsOut+outcount > limit {
-			return &ErrStreamOrConnLimitExceeded{
+			return &errStreamOrConnLimitExceeded{
 				current:   rc.nstreamsOut,
 				attempted: outcount,
 				limit:     limit,
@@ -192,7 +192,7 @@ func (rc *resources) addStreams(incount, outcount int) error {
 	}
 
 	if limit := rc.limit.GetStreamTotalLimit(); rc.nstreamsIn+incount+rc.nstreamsOut+outcount > limit {
-		return &ErrStreamOrConnLimitExceeded{
+		return &errStreamOrConnLimitExceeded{
 			current:   rc.nstreamsIn + rc.nstreamsOut,
 			attempted: incount + outcount,
 			limit:     limit,
@@ -244,7 +244,7 @@ func (rc *resources) addConns(incount, outcount, fdcount int) error {
 	if incount > 0 {
 		limit := rc.limit.GetConnLimit(network.DirInbound)
 		if rc.nconnsIn+incount > limit {
-			return &ErrStreamOrConnLimitExceeded{
+			return &errStreamOrConnLimitExceeded{
 				current:   rc.nconnsIn,
 				attempted: incount,
 				limit:     limit,
@@ -255,7 +255,7 @@ func (rc *resources) addConns(incount, outcount, fdcount int) error {
 	if outcount > 0 {
 		limit := rc.limit.GetConnLimit(network.DirOutbound)
 		if rc.nconnsOut+outcount > limit {
-			return &ErrStreamOrConnLimitExceeded{
+			return &errStreamOrConnLimitExceeded{
 				current:   rc.nconnsOut,
 				attempted: outcount,
 				limit:     limit,
@@ -265,7 +265,7 @@ func (rc *resources) addConns(incount, outcount, fdcount int) error {
 	}
 
 	if connLimit := rc.limit.GetConnTotalLimit(); rc.nconnsIn+incount+rc.nconnsOut+outcount > connLimit {
-		return &ErrStreamOrConnLimitExceeded{
+		return &errStreamOrConnLimitExceeded{
 			current:   rc.nconnsIn + rc.nconnsOut,
 			attempted: incount + outcount,
 			limit:     connLimit,
@@ -275,7 +275,7 @@ func (rc *resources) addConns(incount, outcount, fdcount int) error {
 	if fdcount > 0 {
 		limit := rc.limit.GetFDLimit()
 		if rc.nfd+fdcount > limit {
-			return &ErrStreamOrConnLimitExceeded{
+			return &errStreamOrConnLimitExceeded{
 				current:   rc.nfd,
 				attempted: fdcount,
 				limit:     limit,
diff --git a/p2p/host/routed/routed.go b/p2p/host/routed/routed.go
index 0cedd48f..4188d2fc 100644
--- a/p2p/host/routed/routed.go
+++ b/p2p/host/routed/routed.go
@@ -188,7 +188,7 @@ func (rh *RoutedHost) SetStreamHandler(pid protocol.ID, handler network.StreamHa
 	rh.host.SetStreamHandler(pid, handler)
 }
 
-func (rh *RoutedHost) SetStreamHandlerMatch(pid protocol.ID, m func(protocol.ID) bool, handler network.StreamHandler) {
+func (rh *RoutedHost) SetStreamHandlerMatch(pid protocol.ID, m func(string) bool, handler network.StreamHandler) {
 	rh.host.SetStreamHandlerMatch(pid, m, handler)
 }
 
diff --git a/p2p/muxer/mplex/transport.go b/p2p/muxer/mplex/transport.go
index 10d54d15..b7fbe84c 100644
--- a/p2p/muxer/mplex/transport.go
+++ b/p2p/muxer/mplex/transport.go
@@ -11,8 +11,6 @@ import (
 // DefaultTransport has default settings for Transport
 var DefaultTransport = &Transport{}
 
-const ID = "/mplex/6.7.0"
-
 var _ network.Multiplexer = &Transport{}
 
 // Transport implements mux.Multiplexer that constructs
diff --git a/p2p/muxer/yamux/transport.go b/p2p/muxer/yamux/transport.go
index 32738363..df4ff1be 100644
--- a/p2p/muxer/yamux/transport.go
+++ b/p2p/muxer/yamux/transport.go
@@ -12,8 +12,6 @@ import (
 
 var DefaultTransport *Transport
 
-const ID = "/yamux/1.0.0"
-
 func init() {
 	config := yamux.DefaultConfig()
 	// We've bumped this to 16MiB as this critically limits throughput.
diff --git a/p2p/net/swarm/grafana-dashboards/swarm.json b/p2p/net/swarm/grafana-dashboards/swarm.json
deleted file mode 100644
index f0e68045..00000000
--- a/p2p/net/swarm/grafana-dashboards/swarm.json
+++ /dev/null
@@ -1,2301 +0,0 @@
-{
-  "__inputs": [
-    {
-      "name": "DS_PROMETHEUS",
-      "label": "Prometheus",
-      "description": "",
-      "type": "datasource",
-      "pluginId": "prometheus",
-      "pluginName": "Prometheus"
-    }
-  ],
-  "__elements": {},
-  "__requires": [
-    {
-      "type": "panel",
-      "id": "gauge",
-      "name": "Gauge",
-      "version": ""
-    },
-    {
-      "type": "grafana",
-      "id": "grafana",
-      "name": "Grafana",
-      "version": "9.3.6"
-    },
-    {
-      "type": "panel",
-      "id": "piechart",
-      "name": "Pie chart",
-      "version": ""
-    },
-    {
-      "type": "datasource",
-      "id": "prometheus",
-      "name": "Prometheus",
-      "version": "1.0.0"
-    },
-    {
-      "type": "panel",
-      "id": "timeseries",
-      "name": "Time series",
-      "version": ""
-    }
-  ],
-  "annotations": {
-    "list": [
-      {
-        "builtIn": 1,
-        "datasource": {
-          "type": "grafana",
-          "uid": "-- Grafana --"
-        },
-        "enable": true,
-        "hide": true,
-        "iconColor": "rgba(0, 211, 255, 1)",
-        "name": "Annotations & Alerts",
-        "target": {
-          "limit": 100,
-          "matchAny": false,
-          "tags": [],
-          "type": "dashboard"
-        },
-        "type": "dashboard"
-      },
-      {
-        "datasource": {
-          "type": "prometheus",
-          "uid": "${DS_PROMETHEUS}"
-        },
-        "enable": true,
-        "iconColor": "red",
-        "name": "New annotation"
-      }
-    ]
-  },
-  "editable": true,
-  "fiscalYearStartMonth": 0,
-  "graphTooltip": 0,
-  "id": null,
-  "links": [],
-  "liveNow": false,
-  "panels": [
-    {
-      "collapsed": false,
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 0
-      },
-      "id": 21,
-      "panels": [],
-      "title": "Currently Established Connections",
-      "type": "row"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "fixed"
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "percentage",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 50
-              }
-            ]
-          }
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 24,
-        "x": 0,
-        "y": 1
-      },
-      "id": 23,
-      "options": {
-        "orientation": "auto",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "showThresholdLabels": false,
-        "showThresholdMarkers": true
-      },
-      "pluginVersion": "9.3.6",
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum by (transport) (libp2p_swarm_connections_opened_total) - sum by (transport) (libp2p_swarm_connections_closed_total)",
-          "legendFormat": "__auto",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "Active Connections",
-      "type": "gauge"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 9
-      },
-      "id": 10,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "libp2p_swarm_connections_opened_total{dir=\"inbound\"} - libp2p_swarm_connections_closed_total{dir=\"inbound\"}",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "Active Connections: Inbound",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 9
-      },
-      "id": 11,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "libp2p_swarm_connections_opened_total{dir=\"outbound\"} - libp2p_swarm_connections_closed_total{dir=\"outbound\"}",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "Active Connections: Outgoing",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          },
-          "unit": "s"
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 17
-      },
-      "id": 29,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.50, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\"quic|quic-v1\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "50th percentile",
-          "range": true,
-          "refId": "A"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.90, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\"quic|quic-v1\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "90th percentile",
-          "range": true,
-          "refId": "B"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.95, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=~\"quic|quic-v1\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "95th percentile",
-          "range": true,
-          "refId": "C"
-        }
-      ],
-      "title": "Handshake Latency (QUIC, QUIC v1)",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          },
-          "unit": "s"
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 17
-      },
-      "id": 30,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.50, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\"tcp\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "50th percentile",
-          "range": true,
-          "refId": "A"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.90, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\"tcp\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "90th percentile",
-          "range": true,
-          "refId": "B"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.95, sum by(le) (rate(libp2p_swarm_handshake_latency_seconds_bucket{transport=\"tcp\"}[$__rate_interval])))",
-          "hide": false,
-          "legendFormat": "95th percentile",
-          "range": true,
-          "refId": "C"
-        }
-      ],
-      "title": "Handshake Latency (TCP)",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineStyle": {
-              "fill": "solid"
-            },
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "log": 2,
-              "type": "log"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green",
-                "value": null
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          },
-          "unit": "s"
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 25
-      },
-      "id": 27,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "exemplar": false,
-          "expr": "histogram_quantile(0.5, sum(rate(libp2p_swarm_connection_duration_seconds_bucket[$__rate_interval])) by (le))",
-          "instant": false,
-          "legendFormat": "50th percentile",
-          "range": true,
-          "refId": "A"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.9, sum(rate(libp2p_swarm_connection_duration_seconds_bucket[$__rate_interval])) by (le))",
-          "hide": false,
-          "legendFormat": "90th percentile",
-          "range": true,
-          "refId": "B"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "histogram_quantile(0.95, sum(rate(libp2p_swarm_connection_duration_seconds_bucket[$__rate_interval])) by (le))",
-          "hide": false,
-          "legendFormat": "95th percentile",
-          "range": true,
-          "refId": "C"
-        }
-      ],
-      "title": "Connection Duration",
-      "type": "timeseries"
-    },
-    {
-      "collapsed": false,
-      "gridPos": {
-        "h": 1,
-        "w": 24,
-        "x": 0,
-        "y": 33
-      },
-      "id": 19,
-      "panels": [],
-      "title": "Connection Establishment",
-      "type": "row"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green"
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 9,
-        "w": 12,
-        "x": 0,
-        "y": 34
-      },
-      "id": 2,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "rate(libp2p_swarm_connections_opened_total{dir=\"inbound\"}[$__rate_interval])",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "New Connections: Inbound",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green"
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          }
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 9,
-        "w": 12,
-        "x": 12,
-        "y": 34
-      },
-      "id": 5,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "rate(libp2p_swarm_connections_opened_total{dir=\"outbound\"}[$__rate_interval])",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "New Connections: Outgoing",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            }
-          },
-          "mappings": []
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 43
-      },
-      "id": 4,
-      "options": {
-        "legend": {
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "pieType": "pie",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "tooltip": {
-          "mode": "multi",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "increase(libp2p_swarm_connections_opened_total{dir=\"inbound\"}[$__range])",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "New Inbound Connections: Transports / Security / Muxers",
-      "type": "piechart"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            }
-          },
-          "mappings": [],
-          "unit": "none"
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /noise /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket /noise /yamux/1.0.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit  "
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp /tls/1.0.0 /mplex/6.7.0"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-red",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 43
-      },
-      "id": 6,
-      "options": {
-        "legend": {
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true,
-          "values": []
-        },
-        "pieType": "pie",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "tooltip": {
-          "mode": "multi",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "increase(libp2p_swarm_connections_opened_total{dir=\"outbound\"}[$__range])",
-          "legendFormat": "{{transport}} {{security}} {{muxer}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "New Outgoing Connections: Transports / Security / Muxers",
-      "type": "piechart"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            }
-          },
-          "mappings": []
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 51
-      },
-      "id": 15,
-      "options": {
-        "legend": {
-          "displayMode": "list",
-          "placement": "right",
-          "showLegend": true
-        },
-        "pieType": "donut",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "pluginVersion": "9.3.2-45365",
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum(increase(libp2p_swarm_dial_errors_total[$__range])) by (error)",
-          "legendFormat": "__auto",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "Dial Errors",
-      "type": "piechart"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "axisCenteredZero": false,
-            "axisColorMode": "text",
-            "axisLabel": "",
-            "axisPlacement": "auto",
-            "barAlignment": 0,
-            "drawStyle": "line",
-            "fillOpacity": 0,
-            "gradientMode": "none",
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            },
-            "lineInterpolation": "linear",
-            "lineWidth": 1,
-            "pointSize": 5,
-            "scaleDistribution": {
-              "type": "linear"
-            },
-            "showPoints": "auto",
-            "spanNulls": false,
-            "stacking": {
-              "group": "A",
-              "mode": "none"
-            },
-            "thresholdsStyle": {
-              "mode": "off"
-            }
-          },
-          "mappings": [],
-          "thresholds": {
-            "mode": "absolute",
-            "steps": [
-              {
-                "color": "green"
-              },
-              {
-                "color": "red",
-                "value": 80
-              }
-            ]
-          },
-          "unit": "percentunit"
-        },
-        "overrides": [
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "super-light-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "quic-v1"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "dark-blue",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "webtransport"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "purple",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "tcp"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "yellow",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "websocket"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "mode": "fixed"
-                }
-              }
-            ]
-          },
-          {
-            "matcher": {
-              "id": "byName",
-              "options": "p2p-circuit"
-            },
-            "properties": [
-              {
-                "id": "color",
-                "value": {
-                  "fixedColor": "orange",
-                  "mode": "fixed"
-                }
-              }
-            ]
-          }
-        ]
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 12,
-        "y": 51
-      },
-      "id": 17,
-      "options": {
-        "legend": {
-          "calcs": [],
-          "displayMode": "list",
-          "placement": "bottom",
-          "showLegend": true
-        },
-        "tooltip": {
-          "mode": "single",
-          "sort": "none"
-        }
-      },
-      "pluginVersion": "9.3.2-67a213dc85",
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum by (transport) (rate(libp2p_swarm_connections_opened_total{dir=\"outbound\"}[$__rate_interval])) ",
-          "hide": true,
-          "legendFormat": "{{transport}}",
-          "range": true,
-          "refId": "A"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum by(transport, error) (rate(libp2p_swarm_dial_errors_total[$__rate_interval]))",
-          "hide": true,
-          "legendFormat": "dial error ({{error}}, {{transport}})",
-          "range": true,
-          "refId": "B"
-        },
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum(rate(libp2p_swarm_connections_opened_total{dir=\"outbound\"}[$__rate_interval])) by (transport)  / (sum(rate(libp2p_swarm_connections_opened_total{dir=\"outbound\"}[$__rate_interval])) by (transport) + (sum(rate(libp2p_swarm_dial_errors_total[$__rate_interval])) by (transport)))",
-          "hide": false,
-          "legendFormat": "__auto",
-          "range": true,
-          "refId": "C"
-        }
-      ],
-      "title": "Dial Success Rates",
-      "type": "timeseries"
-    },
-    {
-      "datasource": {
-        "type": "prometheus",
-        "uid": "${DS_PROMETHEUS}"
-      },
-      "description": "on newly established connections",
-      "fieldConfig": {
-        "defaults": {
-          "color": {
-            "mode": "palette-classic"
-          },
-          "custom": {
-            "hideFrom": {
-              "legend": false,
-              "tooltip": false,
-              "viz": false
-            }
-          },
-          "mappings": []
-        },
-        "overrides": []
-      },
-      "gridPos": {
-        "h": 8,
-        "w": 12,
-        "x": 0,
-        "y": 59
-      },
-      "id": 25,
-      "options": {
-        "legend": {
-          "displayMode": "list",
-          "placement": "right",
-          "showLegend": true
-        },
-        "pieType": "pie",
-        "reduceOptions": {
-          "calcs": [
-            "lastNotNull"
-          ],
-          "fields": "",
-          "values": false
-        },
-        "tooltip": {
-          "mode": "multi",
-          "sort": "none"
-        }
-      },
-      "targets": [
-        {
-          "datasource": {
-            "type": "prometheus",
-            "uid": "${DS_PROMETHEUS}"
-          },
-          "editorMode": "code",
-          "expr": "sum by (key_type) (increase(libp2p_swarm_key_types_total[$__range]))",
-          "legendFormat": "{{key_type}}",
-          "range": true,
-          "refId": "A"
-        }
-      ],
-      "title": "libp2p key types",
-      "type": "piechart"
-    }
-  ],
-  "schemaVersion": 37,
-  "style": "dark",
-  "tags": [],
-  "templating": {
-    "list": [
-      {
-        "current": {},
-        "datasource": {
-          "type": "prometheus",
-          "uid": "${DS_PROMETHEUS}"
-        },
-        "definition": "label_values(up, instance)",
-        "hide": 0,
-        "includeAll": true,
-        "multi": true,
-        "name": "instance",
-        "options": [],
-        "query": {
-          "query": "label_values(up, instance)",
-          "refId": "StandardVariableQuery"
-        },
-        "refresh": 1,
-        "regex": "/(.*):[0-9].*/",
-        "skipUrlSync": false,
-        "sort": 0,
-        "type": "query"
-      }
-    ]
-  },
-  "time": {
-    "from": "now-15m",
-    "to": "now"
-  },
-  "timepicker": {},
-  "timezone": "",
-  "title": "libp2p Swarm",
-  "uid": "a15PyhO4z",
-  "version": 68,
-  "weekStart": ""
-}
\ No newline at end of file
diff --git a/p2p/net/swarm/swarm.go b/p2p/net/swarm/swarm.go
index 18d5183f..7606b80c 100644
--- a/p2p/net/swarm/swarm.go
+++ b/p2p/net/swarm/swarm.go
@@ -71,13 +71,6 @@ func WithMetrics(reporter metrics.Reporter) Option {
 	}
 }
 
-func WithMetricsTracer(t MetricsTracer) Option {
-	return func(s *Swarm) error {
-		s.metricsTracer = t
-		return nil
-	}
-}
-
 func WithDialTimeout(t time.Duration) Option {
 	return func(s *Swarm) error {
 		s.dialTimeout = t
@@ -158,8 +151,7 @@ type Swarm struct {
 	ctx       context.Context // is canceled when Close is called
 	ctxCancel context.CancelFunc
 
-	bwc           metrics.Reporter
-	metricsTracer MetricsTracer
+	bwc metrics.Reporter
 }
 
 // NewSwarm constructs a Swarm.
diff --git a/p2p/net/swarm/swarm_conn.go b/p2p/net/swarm/swarm_conn.go
index c24ddee3..4de2727f 100644
--- a/p2p/net/swarm/swarm_conn.go
+++ b/p2p/net/swarm/swarm_conn.go
@@ -60,10 +60,6 @@ func (c *Conn) Close() error {
 }
 
 func (c *Conn) doClose() {
-	if c.swarm.metricsTracer != nil {
-		c.swarm.metricsTracer.ClosedConnection(c.stat.Direction, time.Since(c.stat.Stats.Opened), c.ConnState())
-	}
-
 	c.swarm.removeConn(c)
 
 	// Prevent new streams from opening.
diff --git a/p2p/net/swarm/swarm_dial.go b/p2p/net/swarm/swarm_dial.go
index 01b955e8..29703f77 100644
--- a/p2p/net/swarm/swarm_dial.go
+++ b/p2p/net/swarm/swarm_dial.go
@@ -490,20 +490,11 @@ func (s *Swarm) dialAddr(ctx context.Context, p peer.ID, addr ma.Multiaddr) (tra
 		return nil, ErrNoTransport
 	}
 
-	start := time.Now()
 	connC, err := tpt.Dial(ctx, addr, p)
 	if err != nil {
-		if s.metricsTracer != nil {
-			s.metricsTracer.FailedDialing(addr, err)
-		}
 		return nil, err
 	}
 	canonicallog.LogPeerStatus(100, connC.RemotePeer(), connC.RemoteMultiaddr(), "connection_status", "established", "dir", "outbound")
-	if s.metricsTracer != nil {
-		connState := connC.ConnState()
-		s.metricsTracer.OpenedConnection(network.DirOutbound, connC.RemotePublicKey(), connState)
-		s.metricsTracer.CompletedHandshake(time.Since(start), connState)
-	}
 
 	// Trust the transport? Yeah... right.
 	if connC.RemotePeer() != p {
diff --git a/p2p/net/swarm/swarm_listen.go b/p2p/net/swarm/swarm_listen.go
index 334abb4e..9c5394d4 100644
--- a/p2p/net/swarm/swarm_listen.go
+++ b/p2p/net/swarm/swarm_listen.go
@@ -130,9 +130,6 @@ func (s *Swarm) AddListenAddr(a ma.Multiaddr) error {
 				return
 			}
 			canonicallog.LogPeerStatus(100, c.RemotePeer(), c.RemoteMultiaddr(), "connection_status", "established", "dir", "inbound")
-			if s.metricsTracer != nil {
-				s.metricsTracer.OpenedConnection(network.DirInbound, c.RemotePublicKey(), c.ConnState())
-			}
 
 			log.Debugf("swarm listener accepted connection: %s <-> %s", c.LocalMultiaddr(), c.RemoteMultiaddr())
 			s.refs.Add(1)
diff --git a/p2p/net/swarm/swarm_metrics.go b/p2p/net/swarm/swarm_metrics.go
deleted file mode 100644
index 16f431b5..00000000
--- a/p2p/net/swarm/swarm_metrics.go
+++ /dev/null
@@ -1,191 +0,0 @@
-package swarm
-
-import (
-	"context"
-	"errors"
-	"net"
-	"strings"
-	"sync"
-	"time"
-
-	"github.com/libp2p/go-libp2p/core/crypto"
-	"github.com/libp2p/go-libp2p/core/network"
-
-	ma "github.com/multiformats/go-multiaddr"
-
-	"github.com/prometheus/client_golang/prometheus"
-)
-
-const metricNamespace = "libp2p_swarm"
-
-var (
-	connsOpened = prometheus.NewCounterVec(
-		prometheus.CounterOpts{
-			Namespace: metricNamespace,
-			Name:      "connections_opened_total",
-			Help:      "Connections Opened",
-		},
-		[]string{"dir", "transport", "security", "muxer"},
-	)
-	keyTypes = prometheus.NewCounterVec(
-		prometheus.CounterOpts{
-			Namespace: metricNamespace,
-			Name:      "key_types_total",
-			Help:      "key type",
-		},
-		[]string{"dir", "key_type"},
-	)
-	connsClosed = prometheus.NewCounterVec(
-		prometheus.CounterOpts{
-			Namespace: metricNamespace,
-			Name:      "connections_closed_total",
-			Help:      "Connections Closed",
-		},
-		[]string{"dir", "transport", "security", "muxer"},
-	)
-	dialError = prometheus.NewCounterVec(
-		prometheus.CounterOpts{
-			Namespace: metricNamespace,
-			Name:      "dial_errors_total",
-			Help:      "Dial Error",
-		},
-		[]string{"transport", "error"},
-	)
-	connDuration = prometheus.NewHistogramVec(
-		prometheus.HistogramOpts{
-			Namespace: metricNamespace,
-			Name:      "connection_duration_seconds",
-			Help:      "Duration of a Connection",
-			Buckets:   prometheus.ExponentialBuckets(1.0/16, 2, 25), // up to 24 days
-		},
-		[]string{"dir", "transport", "security", "muxer"},
-	)
-	connHandshakeLatency = prometheus.NewHistogramVec(
-		prometheus.HistogramOpts{
-			Namespace: metricNamespace,
-			Name:      "handshake_latency_seconds",
-			Help:      "Duration of the libp2p Handshake",
-			Buckets:   prometheus.ExponentialBuckets(0.001, 1.3, 35),
-		},
-		[]string{"transport", "security", "muxer"},
-	)
-)
-
-var initMetricsOnce sync.Once
-
-func initMetrics() {
-	prometheus.MustRegister(connsOpened, keyTypes, connsClosed, dialError, connDuration, connHandshakeLatency)
-}
-
-type MetricsTracer interface {
-	OpenedConnection(network.Direction, crypto.PubKey, network.ConnectionState)
-	ClosedConnection(network.Direction, time.Duration, network.ConnectionState)
-	CompletedHandshake(time.Duration, network.ConnectionState)
-	FailedDialing(ma.Multiaddr, error)
-}
-
-type metricsTracer struct{}
-
-var _ MetricsTracer = &metricsTracer{}
-
-func NewMetricsTracer() *metricsTracer {
-	initMetricsOnce.Do(initMetrics)
-	return &metricsTracer{}
-}
-
-var stringPool = sync.Pool{New: func() any {
-	s := make([]string, 0, 8)
-	return &s
-}}
-
-func getStringSlice() *[]string {
-	s := stringPool.Get().(*[]string)
-	*s = (*s)[:0]
-	return s
-}
-
-func putStringSlice(s *[]string) { stringPool.Put(s) }
-
-func getDirection(dir network.Direction) string {
-	switch dir {
-	case network.DirOutbound:
-		return "outbound"
-	case network.DirInbound:
-		return "inbound"
-	default:
-		return "unknown"
-	}
-}
-
-func appendConnectionState(tags []string, cs network.ConnectionState) []string {
-	if cs.Transport == "" {
-		// This shouldn't happen, unless the transport doesn't properly set the Transport field in the ConnectionState.
-		tags = append(tags, "unknown")
-	} else {
-		tags = append(tags, string(cs.Transport))
-	}
-	// These might be empty, depending on the transport.
-	// For example, QUIC doesn't set security nor muxer.
-	tags = append(tags, string(cs.Security))
-	tags = append(tags, string(cs.StreamMultiplexer))
-	return tags
-}
-
-func (m *metricsTracer) OpenedConnection(dir network.Direction, p crypto.PubKey, cs network.ConnectionState) {
-	tags := getStringSlice()
-	defer putStringSlice(tags)
-
-	*tags = append(*tags, getDirection(dir))
-	*tags = appendConnectionState(*tags, cs)
-	connsOpened.WithLabelValues(*tags...).Inc()
-
-	*tags = (*tags)[:0]
-	*tags = append(*tags, getDirection(dir))
-	*tags = append(*tags, p.Type().String())
-	keyTypes.WithLabelValues(*tags...).Inc()
-}
-
-func (m *metricsTracer) ClosedConnection(dir network.Direction, duration time.Duration, cs network.ConnectionState) {
-	tags := getStringSlice()
-	defer putStringSlice(tags)
-	*tags = append(*tags, getDirection(dir))
-	*tags = appendConnectionState(*tags, cs)
-	connsClosed.WithLabelValues(*tags...).Inc()
-
-	*tags = (*tags)[:0]
-	*tags = append(*tags, getDirection(dir))
-	*tags = appendConnectionState(*tags, cs)
-	connDuration.WithLabelValues(*tags...).Observe(duration.Seconds())
-}
-
-func (m *metricsTracer) CompletedHandshake(t time.Duration, cs network.ConnectionState) {
-	tags := getStringSlice()
-	defer putStringSlice(tags)
-	*tags = appendConnectionState(*tags, cs)
-	connHandshakeLatency.WithLabelValues(*tags...).Observe(t.Seconds())
-}
-
-var transports = [...]int{ma.P_CIRCUIT, ma.P_WEBRTC, ma.P_WEBTRANSPORT, ma.P_QUIC, ma.P_QUIC_V1, ma.P_WSS, ma.P_WS, ma.P_TCP}
-
-func (m *metricsTracer) FailedDialing(addr ma.Multiaddr, err error) {
-	var transport string
-	for _, t := range transports {
-		if _, err := addr.ValueForProtocol(t); err == nil {
-			transport = ma.ProtocolWithCode(t).Name
-		}
-	}
-	e := "other"
-	if errors.Is(err, context.Canceled) {
-		e = "canceled"
-	} else if errors.Is(err, context.DeadlineExceeded) {
-		e = "deadline"
-	} else {
-		nerr, ok := err.(net.Error)
-		if ok && nerr.Timeout() {
-			e = "timeout"
-		} else if strings.Contains(err.Error(), "connect: connection refused") {
-			e = "connection refused"
-		}
-	}
-	dialError.WithLabelValues(transport, e).Inc()
-}
diff --git a/p2p/net/upgrader/conn.go b/p2p/net/upgrader/conn.go
index 5db21755..7a079b29 100644
--- a/p2p/net/upgrader/conn.go
+++ b/p2p/net/upgrader/conn.go
@@ -56,8 +56,8 @@ func (t *transportConn) Close() error {
 
 func (t *transportConn) ConnState() network.ConnectionState {
 	return network.ConnectionState{
-		StreamMultiplexer: t.muxer,
-		Security:          t.security,
+		StreamMultiplexer: string(t.muxer),
+		Security:          string(t.security),
 		Transport:         "tcp",
 	}
 }
diff --git a/p2p/net/upgrader/upgrader.go b/p2p/net/upgrader/upgrader.go
index 658e5df7..5a69efb0 100644
--- a/p2p/net/upgrader/upgrader.go
+++ b/p2p/net/upgrader/upgrader.go
@@ -53,13 +53,13 @@ type upgrader struct {
 	connGater connmgr.ConnectionGater
 	rcmgr     network.ResourceManager
 
-	muxerMuxer *mss.MultistreamMuxer[protocol.ID]
+	muxerMuxer *mss.MultistreamMuxer
 	muxers     []StreamMuxer
-	muxerIDs   []protocol.ID
+	muxerIDs   []string
 
 	security      []sec.SecureTransport
-	securityMuxer *mss.MultistreamMuxer[protocol.ID]
-	securityIDs   []protocol.ID
+	securityMuxer *mss.MultistreamMuxer
+	securityIDs   []string
 
 	// AcceptTimeout is the maximum duration an Accept is allowed to take.
 	// This includes the time between accepting the raw network connection,
@@ -77,10 +77,10 @@ func New(security []sec.SecureTransport, muxers []StreamMuxer, psk ipnet.PSK, rc
 		rcmgr:         rcmgr,
 		connGater:     connGater,
 		psk:           psk,
-		muxerMuxer:    mss.NewMultistreamMuxer[protocol.ID](),
+		muxerMuxer:    mss.NewMultistreamMuxer(),
 		muxers:        muxers,
 		security:      security,
-		securityMuxer: mss.NewMultistreamMuxer[protocol.ID](),
+		securityMuxer: mss.NewMultistreamMuxer(),
 	}
 	for _, opt := range opts {
 		if err := opt(u); err != nil {
@@ -90,15 +90,15 @@ func New(security []sec.SecureTransport, muxers []StreamMuxer, psk ipnet.PSK, rc
 	if u.rcmgr == nil {
 		u.rcmgr = &network.NullResourceManager{}
 	}
-	u.muxerIDs = make([]protocol.ID, 0, len(muxers))
+	u.muxerIDs = make([]string, 0, len(muxers))
 	for _, m := range muxers {
-		u.muxerMuxer.AddHandler(m.ID, nil)
-		u.muxerIDs = append(u.muxerIDs, m.ID)
+		u.muxerMuxer.AddHandler(string(m.ID), nil)
+		u.muxerIDs = append(u.muxerIDs, string(m.ID))
 	}
-	u.securityIDs = make([]protocol.ID, 0, len(security))
+	u.securityIDs = make([]string, 0, len(security))
 	for _, s := range security {
-		u.securityMuxer.AddHandler(s.ID(), nil)
-		u.securityIDs = append(u.securityIDs, s.ID())
+		u.securityMuxer.AddHandler(string(s.ID()), nil)
+		u.securityIDs = append(u.securityIDs, string(s.ID()))
 	}
 	return u, nil
 }
@@ -144,7 +144,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 		pconn, err := pnet.NewProtectedConn(u.psk, conn)
 		if err != nil {
 			conn.Close()
-			return nil, fmt.Errorf("failed to setup private network protector: %w", err)
+			return nil, fmt.Errorf("failed to setup private network protector: %s", err)
 		}
 		conn = pconn
 	} else if ipnet.ForcePrivateNetwork {
@@ -155,7 +155,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 	sconn, security, server, err := u.setupSecurity(ctx, conn, p, dir)
 	if err != nil {
 		conn.Close()
-		return nil, fmt.Errorf("failed to negotiate security protocol: %w", err)
+		return nil, fmt.Errorf("failed to negotiate security protocol: %s", err)
 	}
 
 	// call the connection gater, if one is registered.
@@ -182,7 +182,7 @@ func (u *upgrader) upgrade(ctx context.Context, t transport.Transport, maconn ma
 	muxer, smconn, err := u.setupMuxer(ctx, sconn, server, connScope.PeerScope())
 	if err != nil {
 		sconn.Close()
-		return nil, fmt.Errorf("failed to negotiate stream multiplexer: %w", err)
+		return nil, fmt.Errorf("failed to negotiate stream multiplexer: %s", err)
 	}
 
 	tc := &transportConn{
@@ -219,7 +219,7 @@ func (u *upgrader) negotiateMuxer(nc net.Conn, isServer bool) (*StreamMuxer, err
 		return nil, err
 	}
 
-	var proto protocol.ID
+	var proto string
 	if isServer {
 		selected, _, err := u.muxerMuxer.Negotiate(nc)
 		if err != nil {
@@ -244,9 +244,9 @@ func (u *upgrader) negotiateMuxer(nc net.Conn, isServer bool) (*StreamMuxer, err
 	return nil, fmt.Errorf("selected protocol we don't have a transport for")
 }
 
-func (u *upgrader) getMuxerByID(id protocol.ID) *StreamMuxer {
+func (u *upgrader) getMuxerByID(id string) *StreamMuxer {
 	for _, m := range u.muxers {
-		if m.ID == id {
+		if string(m.ID) == id {
 			return &m
 		}
 	}
@@ -265,7 +265,7 @@ func (u *upgrader) setupMuxer(ctx context.Context, conn sec.SecureConn, server b
 		if err != nil {
 			return "", nil, err
 		}
-		return muxerSelected, c, nil
+		return protocol.ID(muxerSelected), c, nil
 	}
 
 	type result struct {
@@ -298,9 +298,9 @@ func (u *upgrader) setupMuxer(ctx context.Context, conn sec.SecureConn, server b
 	}
 }
 
-func (u *upgrader) getSecurityByID(id protocol.ID) sec.SecureTransport {
+func (u *upgrader) getSecurityByID(id string) sec.SecureTransport {
 	for _, s := range u.security {
-		if s.ID() == id {
+		if string(s.ID()) == id {
 			return s
 		}
 	}
@@ -309,7 +309,7 @@ func (u *upgrader) getSecurityByID(id protocol.ID) sec.SecureTransport {
 
 func (u *upgrader) negotiateSecurity(ctx context.Context, insecure net.Conn, server bool) (sec.SecureTransport, bool, error) {
 	type result struct {
-		proto     protocol.ID
+		proto     string
 		iamserver bool
 		err       error
 	}
diff --git a/p2p/protocol/identify/id.go b/p2p/protocol/identify/id.go
index 2a1af713..4348d69c 100644
--- a/p2p/protocol/identify/id.go
+++ b/p2p/protocol/identify/id.go
@@ -500,7 +500,7 @@ func (ids *idService) createBaseIdentifyResponse(
 	localAddr := conn.LocalMultiaddr()
 
 	// set protocols this node is currently handling
-	mes.Protocols = protocol.ConvertToStrings(snapshot.protocols)
+	mes.Protocols = snapshot.protocols
 
 	// observed address so other side is informed of their
 	// "public" address, at least in relation to us.
@@ -560,7 +560,7 @@ func (ids *idService) getSignedRecord(snapshot *identifySnapshot) []byte {
 }
 
 // diff takes two slices of strings (a and b) and computes which elements were added and removed in b
-func diff(a, b []protocol.ID) (added, removed []protocol.ID) {
+func diff(a, b []string) (added, removed []string) {
 	// This is O(n^2), but it's fine because the slices are small.
 	for _, x := range b {
 		var found bool
@@ -593,14 +593,13 @@ func (ids *idService) consumeMessage(mes *pb.Identify, c network.Conn, isPush bo
 	p := c.RemotePeer()
 
 	supported, _ := ids.Host.Peerstore().GetProtocols(p)
-	mesProtocols := protocol.ConvertFromStrings(mes.Protocols)
-	added, removed := diff(supported, mesProtocols)
-	ids.Host.Peerstore().SetProtocols(p, mesProtocols...)
+	added, removed := diff(supported, mes.Protocols)
+	ids.Host.Peerstore().SetProtocols(p, mes.Protocols...)
 	if isPush {
 		ids.emitters.evtPeerProtocolsUpdated.Emit(event.EvtPeerProtocolsUpdated{
 			Peer:    p,
-			Added:   added,
-			Removed: removed,
+			Added:   protocol.ConvertFromStrings(added),
+			Removed: protocol.ConvertFromStrings(removed),
 		})
 	}
 
diff --git a/p2p/protocol/identify/peer_loop.go b/p2p/protocol/identify/peer_loop.go
index 5462dff6..589f04a4 100644
--- a/p2p/protocol/identify/peer_loop.go
+++ b/p2p/protocol/identify/peer_loop.go
@@ -16,7 +16,7 @@ import (
 var errProtocolNotSupported = errors.New("protocol not supported")
 
 type identifySnapshot struct {
-	protocols []protocol.ID
+	protocols []string
 	addrs     []ma.Multiaddr
 	record    *record.Envelope
 }
@@ -103,7 +103,7 @@ func (ph *peerHandler) sendPush(ctx context.Context) error {
 	return nil
 }
 
-func (ph *peerHandler) openStream(ctx context.Context, proto protocol.ID) (network.Stream, error) {
+func (ph *peerHandler) openStream(ctx context.Context, proto string) (network.Stream, error) {
 	// wait for the other peer to send us an Identify response on "all" connections we have with it
 	// so we can look at it's supported protocols and avoid a multistream-select roundtrip to negotiate the protocol
 	// if we know for a fact that it doesn't support the protocol.
@@ -127,5 +127,5 @@ func (ph *peerHandler) openStream(ctx context.Context, proto protocol.ID) (netwo
 	// negotiate a stream without opening a new connection as we "should" already have a connection.
 	ctx, cancel := context.WithTimeout(network.WithNoDial(ctx, "should already have connection"), 30*time.Second)
 	defer cancel()
-	return ph.ids.Host.NewStream(ctx, ph.pid, proto)
+	return ph.ids.Host.NewStream(ctx, ph.pid, protocol.ID(proto))
 }
diff --git a/p2p/security/noise/session.go b/p2p/security/noise/session.go
index 803ed7cb..93ce5217 100644
--- a/p2p/security/noise/session.go
+++ b/p2p/security/noise/session.go
@@ -12,7 +12,6 @@ import (
 	"github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/network"
 	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/protocol"
 )
 
 type secureSession struct {
@@ -135,7 +134,7 @@ func (s *secureSession) Close() error {
 	return s.insecureConn.Close()
 }
 
-func SessionWithConnState(s *secureSession, muxer protocol.ID) *secureSession {
+func SessionWithConnState(s *secureSession, muxer string) *secureSession {
 	if s != nil {
 		s.connectionState.StreamMultiplexer = muxer
 	}
diff --git a/p2p/security/noise/transport.go b/p2p/security/noise/transport.go
index e42cea1b..6e2882b8 100644
--- a/p2p/security/noise/transport.go
+++ b/p2p/security/noise/transport.go
@@ -23,7 +23,7 @@ type Transport struct {
 	protocolID protocol.ID
 	localID    peer.ID
 	privateKey crypto.PrivKey
-	muxers     []protocol.ID
+	muxers     []string
 }
 
 var _ sec.SecureTransport = &Transport{}
@@ -36,16 +36,16 @@ func New(id protocol.ID, privkey crypto.PrivKey, muxers []tptu.StreamMuxer) (*Tr
 		return nil, err
 	}
 
-	muxerIDs := make([]protocol.ID, 0, len(muxers))
+	smuxers := make([]string, 0, len(muxers))
 	for _, m := range muxers {
-		muxerIDs = append(muxerIDs, m.ID)
+		smuxers = append(smuxers, string(m.ID))
 	}
 
 	return &Transport{
 		protocolID: id,
 		localID:    localID,
 		privateKey: privkey,
-		muxers:     muxerIDs,
+		muxers:     smuxers,
 	}, nil
 }
 
@@ -87,7 +87,7 @@ func (t *Transport) ID() protocol.ID {
 	return t.protocolID
 }
 
-func matchMuxers(initiatorMuxers, responderMuxers []protocol.ID) protocol.ID {
+func matchMuxers(initiatorMuxers, responderMuxers []string) string {
 	for _, initMuxer := range initiatorMuxers {
 		for _, respMuxer := range responderMuxers {
 			if initMuxer == respMuxer {
@@ -100,7 +100,7 @@ func matchMuxers(initiatorMuxers, responderMuxers []protocol.ID) protocol.ID {
 
 type transportEarlyDataHandler struct {
 	transport      *Transport
-	receivedMuxers []protocol.ID
+	receivedMuxers []string
 }
 
 var _ EarlyDataHandler = &transportEarlyDataHandler{}
@@ -111,19 +111,19 @@ func newTransportEDH(t *Transport) *transportEarlyDataHandler {
 
 func (i *transportEarlyDataHandler) Send(context.Context, net.Conn, peer.ID) *pb.NoiseExtensions {
 	return &pb.NoiseExtensions{
-		StreamMuxers: protocol.ConvertToStrings(i.transport.muxers),
+		StreamMuxers: i.transport.muxers,
 	}
 }
 
 func (i *transportEarlyDataHandler) Received(_ context.Context, _ net.Conn, extension *pb.NoiseExtensions) error {
 	// Discard messages with size or the number of protocols exceeding extension limit for security.
 	if extension != nil && len(extension.StreamMuxers) <= maxProtoNum {
-		i.receivedMuxers = protocol.ConvertFromStrings(extension.GetStreamMuxers())
+		i.receivedMuxers = extension.GetStreamMuxers()
 	}
 	return nil
 }
 
-func (i *transportEarlyDataHandler) MatchMuxers(isInitiator bool) protocol.ID {
+func (i *transportEarlyDataHandler) MatchMuxers(isInitiator bool) string {
 	if isInitiator {
 		return matchMuxers(i.transport.muxers, i.receivedMuxers)
 	}
diff --git a/p2p/security/tls/transport.go b/p2p/security/tls/transport.go
index 3ca837e5..b036bf89 100644
--- a/p2p/security/tls/transport.go
+++ b/p2p/security/tls/transport.go
@@ -171,7 +171,7 @@ func (t *Transport) setupConn(tlsConn *tls.Conn, remotePubKey ci.PubKey) (sec.Se
 		privKey:         t.privKey,
 		remotePeer:      remotePeerID,
 		remotePubKey:    remotePubKey,
-		connectionState: network.ConnectionState{StreamMultiplexer: protocol.ID(nextProto)},
+		connectionState: network.ConnectionState{StreamMultiplexer: nextProto},
 	}, nil
 }
 


Test patch:
diff --git a/p2p/host/basic/basic_host_test.go b/p2p/host/basic/basic_host_test.go
index cd56684f..9093beea 100644
--- a/p2p/host/basic/basic_host_test.go
+++ b/p2p/host/basic/basic_host_test.go
@@ -158,8 +158,8 @@ func TestProtocolHandlerEvents(t *testing.T) {
 
 	h.SetStreamHandler(protocol.TestingID, func(s network.Stream) {})
 	assert([]protocol.ID{protocol.TestingID}, nil)
-	h.SetStreamHandler("foo", func(s network.Stream) {})
-	assert([]protocol.ID{"foo"}, nil)
+	h.SetStreamHandler(protocol.ID("foo"), func(s network.Stream) {})
+	assert([]protocol.ID{protocol.ID("foo")}, nil)
 	h.RemoveStreamHandler(protocol.TestingID)
 	assert(nil, []protocol.ID{protocol.TestingID})
 }
@@ -273,9 +273,9 @@ func TestHostProtoPreference(t *testing.T) {
 	defer h2.Close()
 
 	const (
-		protoOld   = "/testing"
-		protoNew   = "/testing/1.1.0"
-		protoMinor = "/testing/1.2.0"
+		protoOld   = protocol.ID("/testing")
+		protoNew   = protocol.ID("/testing/1.1.0")
+		protoMinor = protocol.ID("/testing/1.2.0")
 	)
 
 	connectedOn := make(chan protocol.ID)
@@ -299,7 +299,7 @@ func TestHostProtoPreference(t *testing.T) {
 	assertWait(t, connectedOn, protoOld)
 	s.Close()
 
-	h2.SetStreamHandlerMatch(protoMinor, func(protocol.ID) bool { return true }, handler)
+	h2.SetStreamHandlerMatch(protoMinor, func(string) bool { return true }, handler)
 	// remembered preference will be chosen first, even when the other side newly supports it
 	s2, err := h1.NewStream(context.Background(), h2.ID(), protoMinor, protoNew, protoOld)
 	require.NoError(t, err)
diff --git a/p2p/host/peerstore/test/addr_book_suite.go b/p2p/host/peerstore/test/addr_book_suite.go
index 27a74d96..dba2b3cd 100644
--- a/p2p/host/peerstore/test/addr_book_suite.go
+++ b/p2p/host/peerstore/test/addr_book_suite.go
@@ -135,30 +135,6 @@ func testAddAddress(ab pstore.AddrBook, clk *mockClock.Mock) func(*testing.T) {
 			ab.UpdateAddrs(id, 4*time.Second, 0)
 			AssertAddressesEqual(t, nil, ab.Addrs(id))
 		})
-
-		t.Run("accessing an empty peer ID", func(t *testing.T) {
-			addrs := GenerateAddrs(5)
-			ab.AddAddrs("", addrs, time.Hour)
-			AssertAddressesEqual(t, addrs, ab.Addrs(""))
-		})
-
-		t.Run("add a /p2p address with valid peerid", func(t *testing.T) {
-			peerId := GeneratePeerIDs(1)[0]
-			addr := GenerateAddrs(1)
-			p2pAddr := addr[0].Encapsulate(Multiaddr("/p2p/" + peerId.String()))
-			ab.AddAddr(peerId, p2pAddr, time.Hour)
-			AssertAddressesEqual(t, addr, ab.Addrs(peerId))
-		})
-
-		t.Run("add a /p2p address with invalid peerid", func(t *testing.T) {
-			pids := GeneratePeerIDs(2)
-			pid1 := pids[0]
-			pid2 := pids[1]
-			addr := GenerateAddrs(1)
-			p2pAddr := addr[0].Encapsulate(Multiaddr("/p2p/" + pid1.String()))
-			ab.AddAddr(pid2, p2pAddr, time.Hour)
-			AssertAddressesEqual(t, nil, ab.Addrs(pid2))
-		})
 	}
 }
 
diff --git a/p2p/host/peerstore/test/peerstore_suite.go b/p2p/host/peerstore/test/peerstore_suite.go
index 576b8fd4..325399de 100644
--- a/p2p/host/peerstore/test/peerstore_suite.go
+++ b/p2p/host/peerstore/test/peerstore_suite.go
@@ -12,7 +12,6 @@ import (
 	"github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
 	pstore "github.com/libp2p/go-libp2p/core/peerstore"
-	"github.com/libp2p/go-libp2p/core/protocol"
 
 	ma "github.com/multiformats/go-multiaddr"
 	"github.com/stretchr/testify/require"
@@ -45,10 +44,6 @@ func TestPeerstore(t *testing.T, factory PeerstoreFactory) {
 	}
 }
 
-func sortProtos(protos []protocol.ID) {
-	sort.Slice(protos, func(i, j int) bool { return protos[i] < protos[j] })
-}
-
 func testAddrStream(ps pstore.Peerstore) func(t *testing.T) {
 	return func(t *testing.T) {
 		addrs, pid := getAddrs(t, 100), peer.ID("testpeer")
@@ -214,14 +209,14 @@ func testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {
 	return func(t *testing.T) {
 		t.Run("adding and removing protocols", func(t *testing.T) {
 			p1 := peer.ID("TESTPEER")
-			protos := []protocol.ID{"a", "b", "c", "d"}
+			protos := []string{"a", "b", "c", "d"}
 
 			require.NoError(t, ps.AddProtocols(p1, protos...))
 			out, err := ps.GetProtocols(p1)
 			require.NoError(t, err)
 			require.Len(t, out, len(protos), "got wrong number of protocols back")
 
-			sortProtos(out)
+			sort.Strings(out)
 			for i, p := range protos {
 				if out[i] != p {
 					t.Fatal("got wrong protocol")
@@ -238,7 +233,7 @@ func testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {
 
 			b, err := ps.FirstSupportedProtocol(p1, "q", "w", "a", "y", "b")
 			require.NoError(t, err)
-			require.Equal(t, protocol.ID("a"), b)
+			require.Equal(t, "a", b)
 
 			b, err = ps.FirstSupportedProtocol(p1, "q", "x", "z")
 			require.NoError(t, err)
@@ -246,9 +241,9 @@ func testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {
 
 			b, err = ps.FirstSupportedProtocol(p1, "a")
 			require.NoError(t, err)
-			require.Equal(t, protocol.ID("a"), b)
+			require.Equal(t, "a", b)
 
-			protos = []protocol.ID{"other", "yet another", "one more"}
+			protos = []string{"other", "yet another", "one more"}
 			require.NoError(t, ps.SetProtocols(p1, protos...))
 
 			supported, err = ps.SupportsProtocols(p1, "q", "w", "a", "y", "b")
@@ -258,8 +253,8 @@ func testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {
 			supported, err = ps.GetProtocols(p1)
 			require.NoError(t, err)
 
-			sortProtos(supported)
-			sortProtos(protos)
+			sort.Strings(supported)
+			sort.Strings(protos)
 			if !reflect.DeepEqual(supported, protos) {
 				t.Fatalf("expected previously set protos; expected: %v, have: %v", protos, supported)
 			}
@@ -275,7 +270,7 @@ func testPeerstoreProtoStore(ps pstore.Peerstore) func(t *testing.T) {
 
 		t.Run("removing peer", func(t *testing.T) {
 			p := peer.ID("foobar")
-			protos := []protocol.ID{"a", "b"}
+			protos := []string{"a", "b"}
 
 			require.NoError(t, ps.SetProtocols(p, protos...))
 			out, err := ps.GetProtocols(p)
@@ -388,9 +383,9 @@ func getAddrs(t *testing.T, n int) []ma.Multiaddr {
 
 func TestPeerstoreProtoStoreLimits(t *testing.T, ps pstore.Peerstore, limit int) {
 	p := peer.ID("foobar")
-	protocols := make([]protocol.ID, limit)
+	protocols := make([]string, limit)
 	for i := 0; i < limit; i++ {
-		protocols[i] = protocol.ID(fmt.Sprintf("protocol %d", i))
+		protocols[i] = fmt.Sprintf("protocol %d", i)
 	}
 
 	t.Run("setting protocols", func(t *testing.T) {
diff --git a/p2p/host/pstoremanager/mock_peerstore_test.go b/p2p/host/pstoremanager/mock_peerstore_test.go
index fb9a2825..8bd65788 100644
--- a/p2p/host/pstoremanager/mock_peerstore_test.go
+++ b/p2p/host/pstoremanager/mock_peerstore_test.go
@@ -12,7 +12,6 @@ import (
 	gomock "github.com/golang/mock/gomock"
 	crypto "github.com/libp2p/go-libp2p/core/crypto"
 	peer "github.com/libp2p/go-libp2p/core/peer"
-	protocol "github.com/libp2p/go-libp2p/core/protocol"
 	multiaddr "github.com/multiformats/go-multiaddr"
 )
 
@@ -78,7 +77,7 @@ func (mr *MockPeerstoreMockRecorder) AddPrivKey(arg0, arg1 interface{}) *gomock.
 }
 
 // AddProtocols mocks base method.
-func (m *MockPeerstore) AddProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {
+func (m *MockPeerstore) AddProtocols(arg0 peer.ID, arg1 ...string) error {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{arg0}
 	for _, a := range arg1 {
@@ -165,14 +164,14 @@ func (mr *MockPeerstoreMockRecorder) Close() *gomock.Call {
 }
 
 // FirstSupportedProtocol mocks base method.
-func (m *MockPeerstore) FirstSupportedProtocol(arg0 peer.ID, arg1 ...protocol.ID) (protocol.ID, error) {
+func (m *MockPeerstore) FirstSupportedProtocol(arg0 peer.ID, arg1 ...string) (string, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{arg0}
 	for _, a := range arg1 {
 		varargs = append(varargs, a)
 	}
 	ret := m.ctrl.Call(m, "FirstSupportedProtocol", varargs...)
-	ret0, _ := ret[0].(protocol.ID)
+	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
@@ -200,10 +199,10 @@ func (mr *MockPeerstoreMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
 }
 
 // GetProtocols mocks base method.
-func (m *MockPeerstore) GetProtocols(arg0 peer.ID) ([]protocol.ID, error) {
+func (m *MockPeerstore) GetProtocols(arg0 peer.ID) ([]string, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetProtocols", arg0)
-	ret0, _ := ret[0].([]protocol.ID)
+	ret0, _ := ret[0].([]string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
@@ -351,7 +350,7 @@ func (mr *MockPeerstoreMockRecorder) RemovePeer(arg0 interface{}) *gomock.Call {
 }
 
 // RemoveProtocols mocks base method.
-func (m *MockPeerstore) RemoveProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {
+func (m *MockPeerstore) RemoveProtocols(arg0 peer.ID, arg1 ...string) error {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{arg0}
 	for _, a := range arg1 {
@@ -394,7 +393,7 @@ func (mr *MockPeerstoreMockRecorder) SetAddrs(arg0, arg1, arg2 interface{}) *gom
 }
 
 // SetProtocols mocks base method.
-func (m *MockPeerstore) SetProtocols(arg0 peer.ID, arg1 ...protocol.ID) error {
+func (m *MockPeerstore) SetProtocols(arg0 peer.ID, arg1 ...string) error {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{arg0}
 	for _, a := range arg1 {
@@ -413,14 +412,14 @@ func (mr *MockPeerstoreMockRecorder) SetProtocols(arg0 interface{}, arg1 ...inte
 }
 
 // SupportsProtocols mocks base method.
-func (m *MockPeerstore) SupportsProtocols(arg0 peer.ID, arg1 ...protocol.ID) ([]protocol.ID, error) {
+func (m *MockPeerstore) SupportsProtocols(arg0 peer.ID, arg1 ...string) ([]string, error) {
 	m.ctrl.T.Helper()
 	varargs := []interface{}{arg0}
 	for _, a := range arg1 {
 		varargs = append(varargs, a)
 	}
 	ret := m.ctrl.Call(m, "SupportsProtocols", varargs...)
-	ret0, _ := ret[0].([]protocol.ID)
+	ret0, _ := ret[0].([]string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
diff --git a/p2p/host/resource-manager/limit_config_test.backwards-compat.json b/p2p/host/resource-manager/limit_config_test.backwards-compat.json
new file mode 100644
index 00000000..b1a5e9ec
--- /dev/null
+++ b/p2p/host/resource-manager/limit_config_test.backwards-compat.json
@@ -0,0 +1,45 @@
+{
+    "System": {
+        "Memory": 65536,
+        "Conns": 16,
+        "ConnsInbound": 8,
+        "ConnsOutbound": 16,
+        "FD": 16
+    },
+    "ServiceDefault": {
+        "Memory": 8765
+    },
+    "Service": {
+        "A": {
+            "Memory": 8192
+        },
+        "B": {}
+    },
+    "ServicePeerDefault": {
+        "Memory": 2048
+    },
+    "ServicePeer": {
+        "A": {
+            "Memory": 4096
+        }
+    },
+    "ProtocolDefault": {
+        "Memory": 2048
+    },
+    "ProtocolPeerDefault": {
+        "Memory": 1024
+    },
+    "Protocol": {
+        "/A": {
+            "Memory": 8192
+        }
+    },
+    "PeerDefault": {
+        "Memory": 4096
+    },
+    "Peer": {
+        "12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS": {
+            "Memory": 4097
+        }
+    }
+}
\ No newline at end of file
diff --git a/p2p/host/resource-manager/limit_config_test.go b/p2p/host/resource-manager/limit_config_test.go
index 21dede56..d1c5a816 100644
--- a/p2p/host/resource-manager/limit_config_test.go
+++ b/p2p/host/resource-manager/limit_config_test.go
@@ -17,46 +17,134 @@ func withMemoryLimit(l BaseLimit, m int64) BaseLimit {
 	return l2
 }
 
+func TestLimitConfigParserBackwardsCompat(t *testing.T) {
+	// Tests that we can parse the old limit config format.
+	in, err := os.Open("limit_config_test.backwards-compat.json")
+	require.NoError(t, err)
+	defer in.Close()
+
+	defaultScaledLimits := DefaultLimits
+	defaultScaledLimits.AddServiceLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	defaultScaledLimits.AddProtocolPeerLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	defaults := defaultScaledLimits.AutoScale()
+	cfg, err := readLimiterConfigFromJSON(in, defaults)
+	require.NoError(t, err)
+
+	require.Equal(t, int64(65536), cfg.system.Memory)
+	require.Equal(t, defaults.system.Streams, cfg.system.Streams)
+	require.Equal(t, defaults.system.StreamsInbound, cfg.system.StreamsInbound)
+	require.Equal(t, defaults.system.StreamsOutbound, cfg.system.StreamsOutbound)
+	require.Equal(t, 16, cfg.system.Conns)
+	require.Equal(t, 8, cfg.system.ConnsInbound)
+	require.Equal(t, 16, cfg.system.ConnsOutbound)
+	require.Equal(t, 16, cfg.system.FD)
+
+	require.Equal(t, defaults.transient, cfg.transient)
+	require.Equal(t, int64(8765), cfg.serviceDefault.Memory)
+
+	require.Contains(t, cfg.service, "A")
+	require.Equal(t, withMemoryLimit(cfg.serviceDefault, 8192), cfg.service["A"])
+	require.Contains(t, cfg.service, "B")
+	require.Equal(t, cfg.serviceDefault, cfg.service["B"])
+	require.Contains(t, cfg.service, "C")
+	require.Equal(t, defaults.service["C"], cfg.service["C"])
+
+	require.Equal(t, int64(4096), cfg.peerDefault.Memory)
+	peerID, err := peer.Decode("12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS")
+	require.NoError(t, err)
+	require.Contains(t, cfg.peer, peerID)
+	require.Equal(t, int64(4097), cfg.peer[peerID].Memory)
+}
+
 func TestLimitConfigParser(t *testing.T) {
 	in, err := os.Open("limit_config_test.json")
 	require.NoError(t, err)
 	defer in.Close()
 
-	DefaultLimits.AddServiceLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
-	DefaultLimits.AddProtocolPeerLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
-	defaults := DefaultLimits.AutoScale()
+	defaultScaledLimits := DefaultLimits
+	defaultScaledLimits.AddServiceLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	defaultScaledLimits.AddProtocolPeerLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	defaults := defaultScaledLimits.AutoScale()
 	cfg, err := readLimiterConfigFromJSON(in, defaults)
 	require.NoError(t, err)
 
-	require.Equal(t, int64(65536), cfg.System.Memory)
-	require.Equal(t, defaults.System.Streams, cfg.System.Streams)
-	require.Equal(t, defaults.System.StreamsInbound, cfg.System.StreamsInbound)
-	require.Equal(t, defaults.System.StreamsOutbound, cfg.System.StreamsOutbound)
-	require.Equal(t, 16, cfg.System.Conns)
-	require.Equal(t, 8, cfg.System.ConnsInbound)
-	require.Equal(t, 16, cfg.System.ConnsOutbound)
-	require.Equal(t, 16, cfg.System.FD)
+	require.Equal(t, int64(65536), cfg.system.Memory)
+	require.Equal(t, defaults.system.Streams, cfg.system.Streams)
+	require.Equal(t, defaults.system.StreamsInbound, cfg.system.StreamsInbound)
+	require.Equal(t, defaults.system.StreamsOutbound, cfg.system.StreamsOutbound)
+	require.Equal(t, 16, cfg.system.Conns)
+	require.Equal(t, 8, cfg.system.ConnsInbound)
+	require.Equal(t, 16, cfg.system.ConnsOutbound)
+	require.Equal(t, 16, cfg.system.FD)
 
-	require.Equal(t, defaults.Transient, cfg.Transient)
-	require.Equal(t, int64(8765), cfg.ServiceDefault.Memory)
+	require.Equal(t, defaults.transient, cfg.transient)
+	require.Equal(t, int64(8765), cfg.serviceDefault.Memory)
 
-	require.Contains(t, cfg.Service, "A")
-	require.Equal(t, withMemoryLimit(cfg.ServiceDefault, 8192), cfg.Service["A"])
-	require.Contains(t, cfg.Service, "B")
-	require.Equal(t, cfg.ServiceDefault, cfg.Service["B"])
-	require.Contains(t, cfg.Service, "C")
-	require.Equal(t, defaults.Service["C"], cfg.Service["C"])
+	require.Contains(t, cfg.service, "A")
+	require.Equal(t, withMemoryLimit(cfg.serviceDefault, 8192), cfg.service["A"])
+	require.Contains(t, cfg.service, "B")
+	require.Equal(t, cfg.serviceDefault, cfg.service["B"])
+	require.Contains(t, cfg.service, "C")
+	require.Equal(t, defaults.service["C"], cfg.service["C"])
 
-	require.Equal(t, int64(4096), cfg.PeerDefault.Memory)
+	require.Equal(t, int64(4096), cfg.peerDefault.Memory)
 	peerID, err := peer.Decode("12D3KooWPFH2Bx2tPfw6RLxN8k2wh47GRXgkt9yrAHU37zFwHWzS")
 	require.NoError(t, err)
-	require.Contains(t, cfg.Peer, peerID)
-	require.Equal(t, int64(4097), cfg.Peer[peerID].Memory)
+	require.Contains(t, cfg.peer, peerID)
+	require.Equal(t, int64(4097), cfg.peer[peerID].Memory)
 
 	// Roundtrip
-	jsonBytes, err := json.Marshal(&cfg)
+	limitConfig := cfg.ToPartialLimitConfig()
+	jsonBytes, err := json.Marshal(&limitConfig)
 	require.NoError(t, err)
 	cfgAfterRoundTrip, err := readLimiterConfigFromJSON(bytes.NewReader(jsonBytes), defaults)
 	require.NoError(t, err)
-	require.Equal(t, cfg, cfgAfterRoundTrip)
+	require.Equal(t, limitConfig, cfgAfterRoundTrip.ToPartialLimitConfig())
+}
+
+func TestLimitConfigRoundTrip(t *testing.T) {
+	// Tests that we can roundtrip a PartialLimitConfig to a ConcreteLimitConfig and back.
+	in, err := os.Open("limit_config_test.json")
+	require.NoError(t, err)
+	defer in.Close()
+
+	defaults := DefaultLimits
+	defaults.AddServiceLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	defaults.AddProtocolPeerLimit("C", DefaultLimits.ServiceBaseLimit, BaseLimitIncrease{})
+	concreteCfg, err := readLimiterConfigFromJSON(in, defaults.AutoScale())
+	require.NoError(t, err)
+
+	// Roundtrip
+	limitConfig := concreteCfg.ToPartialLimitConfig()
+	// Using InfiniteLimits because it's different then the defaults used above.
+	// If anything was marked "default" in the round trip, it would show up as a
+	// difference here.
+	concreteCfgRT := limitConfig.Build(InfiniteLimits)
+	require.Equal(t, concreteCfg, concreteCfgRT)
+}
+
+func TestReadmeLimitConfigSerialization(t *testing.T) {
+	noisyNeighbor, _ := peer.Decode("QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf")
+	cfg := PartialLimitConfig{
+		System: ResourceLimits{
+			// Allow unlimited outbound streams
+			StreamsOutbound: Unlimited,
+		},
+		Peer: map[peer.ID]ResourceLimits{
+			noisyNeighbor: {
+				// No inbound connections from this peer
+				ConnsInbound: BlockAllLimit,
+				// But let me open connections to them
+				Conns:         DefaultLimit,
+				ConnsOutbound: DefaultLimit,
+				// No inbound streams from this peer
+				StreamsInbound: BlockAllLimit,
+				// And let me open unlimited (by me) outbound streams (the peer may have their own limits on me)
+				StreamsOutbound: Unlimited,
+			},
+		},
+	}
+	jsonBytes, err := json.Marshal(&cfg)
+	require.NoError(t, err)
+	require.Equal(t, `{"Peer":{"QmVvtzcZgCkMnSFf2dnrBPXrWuNFWNM9J3MpZQCvWPuVZf":{"StreamsInbound":"blockAll","StreamsOutbound":"unlimited","ConnsInbound":"blockAll"}},"System":{"StreamsOutbound":"unlimited"}}`, string(jsonBytes))
 }
diff --git a/p2p/host/resource-manager/limit_test.go b/p2p/host/resource-manager/limit_test.go
index 9070045b..68d6bf29 100644
--- a/p2p/host/resource-manager/limit_test.go
+++ b/p2p/host/resource-manager/limit_test.go
@@ -2,6 +2,7 @@ package rcmgr
 
 import (
 	"encoding/json"
+	"math"
 	"runtime"
 	"testing"
 
@@ -32,7 +33,7 @@ func TestScaling(t *testing.T) {
 	t.Run("no scaling if no increase is defined", func(t *testing.T) {
 		cfg := ScalingLimitConfig{ServiceBaseLimit: base}
 		scaled := cfg.Scale(8<<30, 100)
-		require.Equal(t, base, scaled.ServiceDefault)
+		require.Equal(t, base, scaled.serviceDefault)
 	})
 
 	t.Run("scaling", func(t *testing.T) {
@@ -50,14 +51,14 @@ func TestScaling(t *testing.T) {
 			},
 		}
 		scaled := cfg.Scale(128<<20+4<<30, 1000)
-		require.Equal(t, 500, scaled.Transient.FD)
-		require.Equal(t, base.Streams+4, scaled.Transient.Streams)
-		require.Equal(t, base.StreamsInbound+4*2, scaled.Transient.StreamsInbound)
-		require.Equal(t, base.StreamsOutbound+4*3, scaled.Transient.StreamsOutbound)
-		require.Equal(t, base.Conns+4*4, scaled.Transient.Conns)
-		require.Equal(t, base.ConnsInbound+4*5, scaled.Transient.ConnsInbound)
-		require.Equal(t, base.ConnsOutbound+4*6, scaled.Transient.ConnsOutbound)
-		require.Equal(t, base.Memory+4*7, scaled.Transient.Memory)
+		require.Equal(t, 500, scaled.transient.FD)
+		require.Equal(t, base.Streams+4, scaled.transient.Streams)
+		require.Equal(t, base.StreamsInbound+4*2, scaled.transient.StreamsInbound)
+		require.Equal(t, base.StreamsOutbound+4*3, scaled.transient.StreamsOutbound)
+		require.Equal(t, base.Conns+4*4, scaled.transient.Conns)
+		require.Equal(t, base.ConnsInbound+4*5, scaled.transient.ConnsInbound)
+		require.Equal(t, base.ConnsOutbound+4*6, scaled.transient.ConnsOutbound)
+		require.Equal(t, base.Memory+4*7, scaled.transient.Memory)
 	})
 
 	t.Run("scaling and using the base amounts", func(t *testing.T) {
@@ -75,14 +76,14 @@ func TestScaling(t *testing.T) {
 			},
 		}
 		scaled := cfg.Scale(1, 10)
-		require.Equal(t, 1, scaled.Transient.FD)
-		require.Equal(t, base.Streams, scaled.Transient.Streams)
-		require.Equal(t, base.StreamsInbound, scaled.Transient.StreamsInbound)
-		require.Equal(t, base.StreamsOutbound, scaled.Transient.StreamsOutbound)
-		require.Equal(t, base.Conns, scaled.Transient.Conns)
-		require.Equal(t, base.ConnsInbound, scaled.Transient.ConnsInbound)
-		require.Equal(t, base.ConnsOutbound, scaled.Transient.ConnsOutbound)
-		require.Equal(t, base.Memory, scaled.Transient.Memory)
+		require.Equal(t, 1, scaled.transient.FD)
+		require.Equal(t, base.Streams, scaled.transient.Streams)
+		require.Equal(t, base.StreamsInbound, scaled.transient.StreamsInbound)
+		require.Equal(t, base.StreamsOutbound, scaled.transient.StreamsOutbound)
+		require.Equal(t, base.Conns, scaled.transient.Conns)
+		require.Equal(t, base.ConnsInbound, scaled.transient.ConnsInbound)
+		require.Equal(t, base.ConnsOutbound, scaled.transient.ConnsOutbound)
+		require.Equal(t, base.Memory, scaled.transient.Memory)
 	})
 
 	t.Run("scaling limits in maps", func(t *testing.T) {
@@ -99,16 +100,16 @@ func TestScaling(t *testing.T) {
 		}
 		scaled := cfg.Scale(128<<20+4<<30, 1000)
 
-		require.Len(t, scaled.Service, 2)
-		require.Contains(t, scaled.Service, "A")
-		require.Equal(t, 10, scaled.Service["A"].Streams)
-		require.Equal(t, int64(100), scaled.Service["A"].Memory)
-		require.Equal(t, 9, scaled.Service["A"].FD)
+		require.Len(t, scaled.service, 2)
+		require.Contains(t, scaled.service, "A")
+		require.Equal(t, 10, scaled.service["A"].Streams)
+		require.Equal(t, int64(100), scaled.service["A"].Memory)
+		require.Equal(t, 9, scaled.service["A"].FD)
 
-		require.Contains(t, scaled.Service, "B")
-		require.Equal(t, 20+4*2, scaled.Service["B"].Streams)
-		require.Equal(t, int64(200+4*3), scaled.Service["B"].Memory)
-		require.Equal(t, 400, scaled.Service["B"].FD)
+		require.Contains(t, scaled.service, "B")
+		require.Equal(t, 20+4*2, scaled.service["B"].Streams)
+		require.Equal(t, int64(200+4*3), scaled.service["B"].Memory)
+		require.Equal(t, 400, scaled.service["B"].FD)
 
 	})
 }
@@ -139,8 +140,74 @@ func TestReadmeExample(t *testing.T) {
 
 	limitConf := scalingLimits.Scale(4<<30, 1000)
 
-	require.Equal(t, 384, limitConf.System.Conns)
-	require.Equal(t, 1000, limitConf.System.FD)
+	require.Equal(t, 384, limitConf.system.Conns)
+	require.Equal(t, 1000, limitConf.system.FD)
+}
+
+func TestJSONMarshalling(t *testing.T) {
+	bl := ResourceLimits{
+		Streams:         DefaultLimit,
+		StreamsInbound:  10,
+		StreamsOutbound: BlockAllLimit,
+		Conns:           10,
+		// ConnsInbound:    DefaultLimit,
+		ConnsOutbound: Unlimited,
+		Memory:        Unlimited64,
+	}
+
+	jsonEncoded, err := json.Marshal(bl)
+	require.NoError(t, err)
+	require.Equal(t, string(jsonEncoded), `{"StreamsInbound":10,"StreamsOutbound":"blockAll","Conns":10,"ConnsOutbound":"unlimited","Memory":"unlimited"}`)
+
+	// Roundtrip
+	var blDecoded ResourceLimits
+	err = json.Unmarshal(jsonEncoded, &blDecoded)
+	require.NoError(t, err)
+
+	require.Equal(t, bl, blDecoded)
+}
+
+func TestJSONRoundTripInt64(t *testing.T) {
+	bl := ResourceLimits{
+		Memory: math.MaxInt64,
+	}
+
+	jsonEncoded, err := json.Marshal(bl)
+	require.NoError(t, err)
+
+	require.Equal(t, string(jsonEncoded), `{"Memory":"9223372036854775807"}`)
+
+	// Roundtrip
+	var blDecoded ResourceLimits
+	err = json.Unmarshal(jsonEncoded, &blDecoded)
+	require.NoError(t, err)
+
+	require.Equal(t, bl, blDecoded)
+}
+
+func TestRoundTripFromConcreteAndBack(t *testing.T) {
+	l := PartialLimitConfig{
+		System: ResourceLimits{
+			Conns:  1234,
+			Memory: 54321,
+		},
+
+		ServiceDefault: ResourceLimits{
+			Conns: 2,
+		},
+
+		Service: map[string]ResourceLimits{
+			"foo": {
+				Conns: 3,
+			},
+		},
+	}
+
+	concrete := l.Build(InfiniteLimits)
+
+	// Roundtrip
+	fromConcrete := concrete.ToPartialLimitConfig().Build(InfiniteLimits)
+	require.Equal(t, concrete, fromConcrete)
 }
 
 func TestSerializeJSON(t *testing.T) {
@@ -160,3 +227,24 @@ func TestSerializeJSON(t *testing.T) {
 	require.NoError(t, err)
 	require.Equal(t, "{\"Streams\":10}", string(out))
 }
+
+func TestWhatIsZeroInResourceLimits(t *testing.T) {
+	l := ResourceLimits{
+		Streams: BlockAllLimit,
+		Memory:  BlockAllLimit64,
+	}
+
+	out, err := json.Marshal(l)
+	require.NoError(t, err)
+	require.Equal(t, `{"Streams":"blockAll","Memory":"blockAll"}`, string(out))
+
+	l2 := ResourceLimits{}
+	err = json.Unmarshal([]byte(`{"Streams":0,"Memory":0}`), &l2)
+	require.NoError(t, err)
+	require.Equal(t, l, l2)
+
+	l3 := ResourceLimits{}
+	err = json.Unmarshal([]byte(`{"Streams":0,"Memory":"0"}`), &l3)
+	require.NoError(t, err)
+	require.Equal(t, l, l3)
+}
diff --git a/p2p/host/resource-manager/rcmgr_test.go b/p2p/host/resource-manager/rcmgr_test.go
index 12420ac7..61d53beb 100644
--- a/p2p/host/resource-manager/rcmgr_test.go
+++ b/p2p/host/resource-manager/rcmgr_test.go
@@ -21,8 +21,8 @@ func TestResourceManager(t *testing.T) {
 	svcA := "A.svc"
 	svcB := "B.svc"
 	nmgr, err := NewResourceManager(
-		NewFixedLimiter(LimitConfig{
-			System: BaseLimit{
+		NewFixedLimiter(ConcreteLimitConfig{
+			system: BaseLimit{
 				Memory:          16384,
 				StreamsInbound:  3,
 				StreamsOutbound: 3,
@@ -32,7 +32,7 @@ func TestResourceManager(t *testing.T) {
 				Conns:           6,
 				FD:              2,
 			},
-			Transient: BaseLimit{
+			transient: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  1,
 				StreamsOutbound: 1,
@@ -42,7 +42,7 @@ func TestResourceManager(t *testing.T) {
 				Conns:           2,
 				FD:              1,
 			},
-			ServiceDefault: BaseLimit{
+			serviceDefault: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  1,
 				StreamsOutbound: 1,
@@ -52,13 +52,13 @@ func TestResourceManager(t *testing.T) {
 				Conns:           2,
 				FD:              1,
 			},
-			ServicePeerDefault: BaseLimit{
+			servicePeerDefault: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  5,
 				StreamsOutbound: 5,
 				Streams:         10,
 			},
-			Service: map[string]BaseLimit{
+			service: map[string]BaseLimit{
 				svcA: {
 					Memory:          8192,
 					StreamsInbound:  2,
@@ -80,7 +80,7 @@ func TestResourceManager(t *testing.T) {
 					FD:              1,
 				},
 			},
-			ServicePeer: map[string]BaseLimit{
+			servicePeer: map[string]BaseLimit{
 				svcB: {
 					Memory:          8192,
 					StreamsInbound:  1,
@@ -88,13 +88,13 @@ func TestResourceManager(t *testing.T) {
 					Streams:         2,
 				},
 			},
-			ProtocolDefault: BaseLimit{
+			protocolDefault: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  1,
 				StreamsOutbound: 1,
 				Streams:         2,
 			},
-			Protocol: map[protocol.ID]BaseLimit{
+			protocol: map[protocol.ID]BaseLimit{
 				protoA: {
 					Memory:          8192,
 					StreamsInbound:  2,
@@ -102,7 +102,7 @@ func TestResourceManager(t *testing.T) {
 					Streams:         2,
 				},
 			},
-			ProtocolPeer: map[protocol.ID]BaseLimit{
+			protocolPeer: map[protocol.ID]BaseLimit{
 				protoB: {
 					Memory:          8192,
 					StreamsInbound:  1,
@@ -110,7 +110,7 @@ func TestResourceManager(t *testing.T) {
 					Streams:         2,
 				},
 			},
-			PeerDefault: BaseLimit{
+			peerDefault: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  1,
 				StreamsOutbound: 1,
@@ -120,13 +120,13 @@ func TestResourceManager(t *testing.T) {
 				Conns:           2,
 				FD:              1,
 			},
-			ProtocolPeerDefault: BaseLimit{
+			protocolPeerDefault: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  5,
 				StreamsOutbound: 5,
 				Streams:         10,
 			},
-			Peer: map[peer.ID]BaseLimit{
+			peer: map[peer.ID]BaseLimit{
 				peerA: {
 					Memory:          8192,
 					StreamsInbound:  2,
@@ -138,14 +138,14 @@ func TestResourceManager(t *testing.T) {
 					FD:              1,
 				},
 			},
-			Conn: BaseLimit{
+			conn: BaseLimit{
 				Memory:        4096,
 				ConnsInbound:  1,
 				ConnsOutbound: 1,
 				Conns:         1,
 				FD:            1,
 			},
-			Stream: BaseLimit{
+			stream: BaseLimit{
 				Memory:          4096,
 				StreamsInbound:  1,
 				StreamsOutbound: 1,
@@ -979,24 +979,24 @@ func TestResourceManagerWithAllowlist(t *testing.T) {
 	peerA := test.RandPeerIDFatal(t)
 
 	limits := DefaultLimits.AutoScale()
-	limits.System.Conns = 0
-	limits.Transient.Conns = 0
+	limits.system.Conns = 0
+	limits.transient.Conns = 0
 
 	baseLimit := BaseLimit{
 		Conns:         2,
 		ConnsInbound:  2,
 		ConnsOutbound: 1,
 	}
-	baseLimit.Apply(limits.AllowlistedSystem)
-	limits.AllowlistedSystem = baseLimit
+	baseLimit.Apply(limits.allowlistedSystem)
+	limits.allowlistedSystem = baseLimit
 
 	baseLimit = BaseLimit{
 		Conns:         1,
 		ConnsInbound:  1,
 		ConnsOutbound: 1,
 	}
-	baseLimit.Apply(limits.AllowlistedTransient)
-	limits.AllowlistedTransient = baseLimit
+	baseLimit.Apply(limits.allowlistedTransient)
+	limits.allowlistedTransient = baseLimit
 
 	rcmgr, err := NewResourceManager(NewFixedLimiter(limits), WithAllowlistedMultiaddrs([]multiaddr.Multiaddr{
 		multiaddr.StringCast("/ip4/1.2.3.4"),
diff --git a/p2p/net/swarm/dial_worker_test.go b/p2p/net/swarm/dial_worker_test.go
index 88af08d2..3ec0d9ce 100644
--- a/p2p/net/swarm/dial_worker_test.go
+++ b/p2p/net/swarm/dial_worker_test.go
@@ -82,7 +82,7 @@ func makeUpgrader(t *testing.T, n *Swarm) transport.Upgrader {
 	pk := n.Peerstore().PrivKey(id)
 	st := insecure.NewWithIdentity(insecure.ID, id, pk)
 
-	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: yamux.ID, Muxer: yamux.DefaultTransport}}, nil, nil, nil)
+	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, nil)
 	require.NoError(t, err)
 	return u
 }
diff --git a/p2p/net/swarm/swarm_metrics_test.go b/p2p/net/swarm/swarm_metrics_test.go
deleted file mode 100644
index 9ccc9d10..00000000
--- a/p2p/net/swarm/swarm_metrics_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package swarm
-
-import (
-	"crypto/rand"
-	"testing"
-
-	"github.com/libp2p/go-libp2p/core/crypto"
-	"github.com/libp2p/go-libp2p/core/network"
-
-	"github.com/stretchr/testify/require"
-)
-
-func BenchmarkMetricsConnOpen(b *testing.B) {
-	b.ReportAllocs()
-	quicConnState := network.ConnectionState{Transport: "quic"}
-	tcpConnState := network.ConnectionState{
-		StreamMultiplexer: "yamux",
-		Security:          "tls",
-		Transport:         "tcp",
-	}
-	_, pub, err := crypto.GenerateEd25519Key(rand.Reader)
-	require.NoError(b, err)
-	tr := NewMetricsTracer()
-	for i := 0; i < b.N; i++ {
-		switch i % 2 {
-		case 0:
-			tr.OpenedConnection(network.DirInbound, pub, quicConnState)
-		case 1:
-			tr.OpenedConnection(network.DirInbound, pub, tcpConnState)
-		}
-	}
-}
diff --git a/p2p/net/swarm/testing/testing.go b/p2p/net/swarm/testing/testing.go
index 0f6824c4..ea062cfd 100644
--- a/p2p/net/swarm/testing/testing.go
+++ b/p2p/net/swarm/testing/testing.go
@@ -105,7 +105,7 @@ func GenUpgrader(t *testing.T, n *swarm.Swarm, connGater connmgr.ConnectionGater
 	pk := n.Peerstore().PrivKey(id)
 	st := insecure.NewWithIdentity(insecure.ID, id, pk)
 
-	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: yamux.ID, Muxer: yamux.DefaultTransport}}, nil, nil, connGater, opts...)
+	u, err := tptu.New([]sec.SecureTransport{st}, []tptu.StreamMuxer{{ID: "/yamux/1.0.0", Muxer: yamux.DefaultTransport}}, nil, nil, connGater, opts...)
 	require.NoError(t, err)
 	return u
 }
diff --git a/p2p/net/upgrader/listener_test.go b/p2p/net/upgrader/listener_test.go
index c6b1b885..ce11b606 100644
--- a/p2p/net/upgrader/listener_test.go
+++ b/p2p/net/upgrader/listener_test.go
@@ -405,7 +405,7 @@ func TestNoCommonSecurityProto(t *testing.T) {
 	}()
 
 	_, err = dial(t, ub, ln.Multiaddr(), idA, &network.NullScope{})
-	require.ErrorContains(t, err, "failed to negotiate security protocol: protocols not supported")
+	require.EqualError(t, err, "failed to negotiate security protocol: protocol not supported")
 	select {
 	case <-done:
 		t.Fatal("didn't expect to accept a connection")
diff --git a/p2p/protocol/circuitv2/client/reservation_test.go b/p2p/protocol/circuitv2/client/reservation_test.go
index 31e28654..15b0dd9d 100644
--- a/p2p/protocol/circuitv2/client/reservation_test.go
+++ b/p2p/protocol/circuitv2/client/reservation_test.go
@@ -27,7 +27,7 @@ func TestReservationFailures(t *testing.T) {
 		{
 			name:          "unsupported protocol",
 			streamHandler: nil,
-			err:           "protocols not supported",
+			err:           "protocol not supported",
 		},
 		{
 			name: "wrong message type",
diff --git a/p2p/protocol/holepunch/holepunch_test.go b/p2p/protocol/holepunch/holepunch_test.go
index da8bf7c5..0e9c5201 100644
--- a/p2p/protocol/holepunch/holepunch_test.go
+++ b/p2p/protocol/holepunch/holepunch_test.go
@@ -224,7 +224,7 @@ func TestFailuresOnInitiator(t *testing.T) {
 			hps := addHolePunchService(t, h2, opts...)
 			// wait until the hole punching protocol has actually started
 			require.Eventually(t, func() bool {
-				protos, _ := h2.Peerstore().SupportsProtocols(h1.ID(), holepunch.Protocol)
+				protos, _ := h2.Peerstore().SupportsProtocols(h1.ID(), string(holepunch.Protocol))
 				return len(protos) > 0
 			}, 200*time.Millisecond, 10*time.Millisecond)
 
diff --git a/p2p/protocol/identify/id_test.go b/p2p/protocol/identify/id_test.go
index e95333cc..2a7305c4 100644
--- a/p2p/protocol/identify/id_test.go
+++ b/p2p/protocol/identify/id_test.go
@@ -383,7 +383,7 @@ func TestIdentifyPushWhileIdentifyingConn(t *testing.T) {
 	handler := func(s network.Stream) {
 		<-block
 		w := pbio.NewDelimitedWriter(s)
-		w.WriteMsg(&pb.Identify{Protocols: protocol.ConvertToStrings(h1.Mux().Protocols())})
+		w.WriteMsg(&pb.Identify{Protocols: h1.Mux().Protocols()})
 		s.Close()
 	}
 	h1.RemoveStreamHandler(identify.ID)
@@ -587,14 +587,14 @@ func TestSendPush(t *testing.T) {
 	// h1 starts listening on a new protocol and h2 finds out about that through a push
 	h1.SetStreamHandler("rand", func(network.Stream) {})
 	require.Eventually(t, func() bool {
-		sup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []protocol.ID{"rand"}...)
+		sup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []string{"rand"}...)
 		return err == nil && len(sup) == 1 && sup[0] == "rand"
 	}, time.Second, 10*time.Millisecond)
 
 	// h1 stops listening on a protocol and h2 finds out about it via a push
 	h1.RemoveStreamHandler("rand")
 	require.Eventually(t, func() bool {
-		sup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []protocol.ID{"rand"}...)
+		sup, err := h2.Peerstore().SupportsProtocols(h1.ID(), []string{"rand"}...)
 		return err == nil && len(sup) == 0
 	}, time.Second, 10*time.Millisecond)
 }
@@ -613,9 +613,9 @@ func TestLargeIdentifyMessage(t *testing.T) {
 	// add protocol strings to make the message larger
 	// about 2K of protocol strings
 	for i := 0; i < 500; i++ {
-		r := protocol.ID(fmt.Sprintf("rand%d", i))
-		h1.SetStreamHandler(r, func(network.Stream) {})
-		h2.SetStreamHandler(r, func(network.Stream) {})
+		r := fmt.Sprintf("rand%d", i)
+		h1.SetStreamHandler(protocol.ID(r), func(network.Stream) {})
+		h2.SetStreamHandler(protocol.ID(r), func(network.Stream) {})
 	}
 
 	h1p := h1.ID()
@@ -719,9 +719,9 @@ func TestLargePushMessage(t *testing.T) {
 	// add protocol strings to make the message larger
 	// about 2K of protocol strings
 	for i := 0; i < 500; i++ {
-		r := protocol.ID(fmt.Sprintf("rand%d", i))
-		h1.SetStreamHandler(r, func(network.Stream) {})
-		h2.SetStreamHandler(r, func(network.Stream) {})
+		r := fmt.Sprintf("rand%d", i)
+		h1.SetStreamHandler(protocol.ID(r), func(network.Stream) {})
+		h2.SetStreamHandler(protocol.ID(r), func(network.Stream) {})
 	}
 
 	h1p := h1.ID()
diff --git a/p2p/security/noise/transport_test.go b/p2p/security/noise/transport_test.go
index 4006c909..0912dab4 100644
--- a/p2p/security/noise/transport_test.go
+++ b/p2p/security/noise/transport_test.go
@@ -15,7 +15,6 @@ import (
 
 	"github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
-	"github.com/libp2p/go-libp2p/core/protocol"
 	"github.com/libp2p/go-libp2p/core/sec"
 	"github.com/libp2p/go-libp2p/p2p/security/noise/pb"
 
@@ -38,7 +37,7 @@ func newTestTransport(t *testing.T, typ, bits int) *Transport {
 	}
 }
 
-func newTestTransportWithMuxers(t *testing.T, typ, bits int, muxers []protocol.ID) *Transport {
+func newTestTransportWithMuxers(t *testing.T, typ, bits int, muxers []string) *Transport {
 	transport := newTestTransport(t, typ, bits)
 	transport.muxers = muxers
 	return transport
@@ -633,9 +632,9 @@ func TestEarlyfffDataAcceptedWithNoHandler(t *testing.T) {
 }
 
 type noiseEarlyDataTestCase struct {
-	clientProtos   []protocol.ID
-	serverProtos   []protocol.ID
-	expectedResult protocol.ID
+	clientProtos   []string
+	serverProtos   []string
+	expectedResult string
 }
 
 func TestHandshakeWithTransportEarlyData(t *testing.T) {
@@ -646,43 +645,43 @@ func TestHandshakeWithTransportEarlyData(t *testing.T) {
 			expectedResult: "",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer1"},
-			serverProtos:   []protocol.ID{"muxer1"},
+			clientProtos:   []string{"muxer1"},
+			serverProtos:   []string{"muxer1"},
 			expectedResult: "muxer1",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer1"},
-			serverProtos:   []protocol.ID{},
+			clientProtos:   []string{"muxer1"},
+			serverProtos:   []string{},
 			expectedResult: "",
 		},
 		{
-			clientProtos:   []protocol.ID{},
-			serverProtos:   []protocol.ID{"muxer2"},
+			clientProtos:   []string{},
+			serverProtos:   []string{"muxer2"},
 			expectedResult: "",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer2"},
-			serverProtos:   []protocol.ID{"muxer1"},
+			clientProtos:   []string{"muxer2"},
+			serverProtos:   []string{"muxer1"},
 			expectedResult: "",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer1", "muxer2"},
-			serverProtos:   []protocol.ID{"muxer2", "muxer1"},
+			clientProtos:   []string{"muxer1", "muxer2"},
+			serverProtos:   []string{"muxer2", "muxer1"},
 			expectedResult: "muxer1",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer3", "muxer2", "muxer1"},
-			serverProtos:   []protocol.ID{"muxer2", "muxer1"},
+			clientProtos:   []string{"muxer3", "muxer2", "muxer1"},
+			serverProtos:   []string{"muxer2", "muxer1"},
 			expectedResult: "muxer2",
 		},
 		{
-			clientProtos:   []protocol.ID{"muxer1", "muxer2"},
-			serverProtos:   []protocol.ID{"muxer3"},
+			clientProtos:   []string{"muxer1", "muxer2"},
+			serverProtos:   []string{"muxer3"},
 			expectedResult: "",
 		},
 	}
 
-	noiseHandshake := func(t *testing.T, initProtos, respProtos []protocol.ID, expectedProto protocol.ID) {
+	noiseHandshake := func(t *testing.T, initProtos, respProtos []string, expectedProto string) {
 		initTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, initProtos)
 		respTransport := newTestTransportWithMuxers(t, crypto.Ed25519, 2048, respProtos)
 
diff --git a/p2p/security/tls/transport_test.go b/p2p/security/tls/transport_test.go
index ab7cb5f3..0f5247fb 100644
--- a/p2p/security/tls/transport_test.go
+++ b/p2p/security/tls/transport_test.go
@@ -180,7 +180,7 @@ func TestHandshakeSucceeds(t *testing.T) {
 type testcase struct {
 	clientProtos   []protocol.ID
 	serverProtos   []protocol.ID
-	expectedResult protocol.ID
+	expectedResult string
 }
 
 func TestHandshakeWithNextProtoSucceeds(t *testing.T) {
@@ -225,7 +225,7 @@ func TestHandshakeWithNextProtoSucceeds(t *testing.T) {
 	clientID, clientKey := createPeer(t)
 	serverID, serverKey := createPeer(t)
 
-	handshake := func(t *testing.T, clientTransport *Transport, serverTransport *Transport, expectedMuxer protocol.ID) {
+	handshake := func(t *testing.T, clientTransport *Transport, serverTransport *Transport, expectedMuxer string) {
 		clientInsecureConn, serverInsecureConn := connect(t)
 
 		serverConnChan := make(chan sec.SecureConn)
diff --git a/p2p/test/negotiation/muxer_test.go b/p2p/test/negotiation/muxer_test.go
index 9301e66c..9fc13fd3 100644
--- a/p2p/test/negotiation/muxer_test.go
+++ b/p2p/test/negotiation/muxer_test.go
@@ -69,7 +69,7 @@ func TestMuxerNegotiation(t *testing.T) {
 			Name:             "no preference overlap",
 			ServerPreference: []libp2p.Option{yamuxOpt},
 			ClientPreference: []libp2p.Option{mplexOpt},
-			Error:            "failed to negotiate stream multiplexer: protocols not supported",
+			Error:            "failed to negotiate stream multiplexer: protocol not supported",
 		},
 	}
 
@@ -119,7 +119,7 @@ func TestMuxerNegotiation(t *testing.T) {
 				require.NoError(t, err)
 				conns := client.Network().ConnsToPeer(server.ID())
 				require.Len(t, conns, 1, "expected exactly one connection")
-				require.Equal(t, tc.Expected, conns[0].ConnState().StreamMultiplexer)
+				require.Equal(t, tc.Expected, protocol.ID(conns[0].ConnState().StreamMultiplexer))
 			})
 		}
 	}
diff --git a/p2p/test/negotiation/security_test.go b/p2p/test/negotiation/security_test.go
index b7324744..3c309e24 100644
--- a/p2p/test/negotiation/security_test.go
+++ b/p2p/test/negotiation/security_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/libp2p/go-libp2p"
 	"github.com/libp2p/go-libp2p/core/crypto"
 	"github.com/libp2p/go-libp2p/core/peer"
+	"github.com/libp2p/go-libp2p/core/protocol"
 	"github.com/libp2p/go-libp2p/p2p/security/noise"
 	tls "github.com/libp2p/go-libp2p/p2p/security/tls"
 	"github.com/libp2p/go-libp2p/p2p/transport/tcp"
@@ -44,7 +45,7 @@ func TestSecurityNegotiation(t *testing.T) {
 			Name:             "no  overlap",
 			ServerPreference: []libp2p.Option{noiseOpt},
 			ClientPreference: []libp2p.Option{tlsOpt},
-			Error:            "failed to negotiate security protocol: protocols not supported",
+			Error:            "failed to negotiate security protocol: protocol not supported",
 		},
 	}
 
@@ -83,7 +84,7 @@ func TestSecurityNegotiation(t *testing.T) {
 			require.NoError(t, err)
 			conns := client.Network().ConnsToPeer(server.ID())
 			require.Len(t, conns, 1, "expected exactly one connection")
-			require.Equal(t, tc.Expected, conns[0].ConnState().Security)
+			require.Equal(t, tc.Expected, protocol.ID(conns[0].ConnState().Security))
 		})
 	}
 }
diff --git a/p2p/test/resource-manager/rcmgr_test.go b/p2p/test/resource-manager/rcmgr_test.go
index ae546c5a..017b4929 100644
--- a/p2p/test/resource-manager/rcmgr_test.go
+++ b/p2p/test/resource-manager/rcmgr_test.go
@@ -13,11 +13,12 @@ import (
 	"github.com/libp2p/go-libp2p/core/network"
 	"github.com/libp2p/go-libp2p/core/peer"
 	rcmgr "github.com/libp2p/go-libp2p/p2p/host/resource-manager"
+	rcmgrObs "github.com/libp2p/go-libp2p/p2p/host/resource-manager/obs"
 
 	"github.com/stretchr/testify/require"
 )
 
-func makeRcmgrOption(t *testing.T, cfg rcmgr.LimitConfig) func(int) libp2p.Option {
+func makeRcmgrOption(t *testing.T, cfg rcmgr.ConcreteLimitConfig) func(int) libp2p.Option {
 	return func(i int) libp2p.Option {
 		var opts []rcmgr.Option
 		if os.Getenv("LIBP2P_TEST_RCMGR_TRACE") == "1" {
@@ -46,13 +47,19 @@ func waitForConnection(t *testing.T, src, dest *Echo) {
 func TestResourceManagerConnInbound(t *testing.T) {
 	// this test checks that we can not exceed the inbound conn limit at system level
 	// we specify: 1 conn per peer, 3 conns total, and we try to create 4 conns
-	cfg := rcmgr.DefaultLimits.AutoScale()
-	cfg.System.ConnsInbound = 3
-	cfg.System.ConnsOutbound = 1024
-	cfg.System.Conns = 1024
-	cfg.PeerDefault.Conns = 1
-	cfg.PeerDefault.ConnsInbound = 1
-	cfg.PeerDefault.ConnsOutbound = 1
+	cfg := rcmgr.PartialLimitConfig{
+		System: rcmgr.ResourceLimits{
+			ConnsInbound:    3,
+			ConnsOutbound:   1024,
+			Conns:           1024,
+			StreamsOutbound: rcmgr.Unlimited,
+		},
+		PeerDefault: rcmgr.ResourceLimits{
+			ConnsInbound:  1,
+			ConnsOutbound: 1,
+			Conns:         1,
+		},
+	}.Build(rcmgr.DefaultLimits.AutoScale())
 
 	echos := createEchos(t, 5, makeRcmgrOption(t, cfg))
 	defer closeEchos(echos)
@@ -82,13 +89,18 @@ func TestResourceManagerConnInbound(t *testing.T) {
 func TestResourceManagerConnOutbound(t *testing.T) {
 	// this test checks that we can not exceed the inbound conn limit at system level
 	// we specify: 1 conn per peer, 3 conns total, and we try to create 4 conns
-	cfg := rcmgr.DefaultLimits.AutoScale()
-	cfg.System.ConnsInbound = 1024
-	cfg.System.ConnsOutbound = 3
-	cfg.System.Conns = 1024
-	cfg.PeerDefault.Conns = 1
-	cfg.PeerDefault.ConnsInbound = 1
-	cfg.PeerDefault.ConnsOutbound = 1
+	cfg := rcmgr.PartialLimitConfig{
+		System: rcmgr.ResourceLimits{
+			ConnsInbound:  1024,
+			ConnsOutbound: 3,
+			Conns:         1024,
+		},
+		PeerDefault: rcmgr.ResourceLimits{
+			ConnsInbound:  1,
+			ConnsOutbound: 1,
+			Conns:         1,
+		},
+	}.Build(rcmgr.DefaultLimits.AutoScale())
 	echos := createEchos(t, 5, makeRcmgrOption(t, cfg))
 	defer closeEchos(echos)
 	defer closeRcmgrs(echos)
@@ -117,10 +129,13 @@ func TestResourceManagerConnOutbound(t *testing.T) {
 func TestResourceManagerServiceInbound(t *testing.T) {
 	// this test checks that we can not exceed the inbound stream limit at service level
 	// we specify: 3 streams for the service, and we try to create 4 streams
-	cfg := rcmgr.DefaultLimits.AutoScale()
-	cfg.ServiceDefault.StreamsInbound = 3
-	cfg.ServiceDefault.StreamsOutbound = 1024
-	cfg.ServiceDefault.Streams = 1024
+	cfg := rcmgr.PartialLimitConfig{
+		ServiceDefault: rcmgr.ResourceLimits{
+			StreamsInbound:  3,
+			StreamsOutbound: 1024,
+			Streams:         1024,
+		},
+	}.Build(rcmgr.DefaultLimits.AutoScale())
 	echos := createEchos(t, 5, makeRcmgrOption(t, cfg))
 	defer closeEchos(echos)
 	defer closeRcmgrs(echos)
@@ -276,3 +291,50 @@ func waitForChannel(ready chan struct{}, timeout time.Duration) func() error {
 		}
 	}
 }
+
+func TestReadmeExample(t *testing.T) {
+	// Start with the default scaling limits.
+	scalingLimits := rcmgr.DefaultLimits
+
+	// Add limits around included libp2p protocols
+	libp2p.SetDefaultServiceLimits(&scalingLimits)
+
+	// Turn the scaling limits into a concrete set of limits using `.AutoScale`. This
+	// scales the limits proportional to your system memory.
+	scaledDefaultLimits := scalingLimits.AutoScale()
+
+	// Tweak certain settings
+	cfg := rcmgr.PartialLimitConfig{
+		System: rcmgr.ResourceLimits{
+			// Allow unlimited outbound streams
+			StreamsOutbound: rcmgr.Unlimited,
+		},
+		// Everything else is default. The exact values will come from `scaledDefaultLimits` above.
+	}
+
+	// Create our limits by using our cfg and replacing the default values with values from `scaledDefaultLimits`
+	limits := cfg.Build(scaledDefaultLimits)
+
+	// The resource manager expects a limiter, se we create one from our limits.
+	limiter := rcmgr.NewFixedLimiter(limits)
+
+	// (Optional if you want metrics) Construct the OpenCensus metrics reporter.
+	str, err := rcmgrObs.NewStatsTraceReporter()
+	if err != nil {
+		panic(err)
+	}
+
+	// Initialize the resource manager
+	rm, err := rcmgr.NewResourceManager(limiter, rcmgr.WithTraceReporter(str))
+	if err != nil {
+		panic(err)
+	}
+
+	// Create a libp2p host
+	host, err := libp2p.New(libp2p.ResourceManager(rm))
+	if err != nil {
+		panic(err)
+	}
+
+	host.Close()
+}
diff --git a/test-plans/cmd/ping/main.go b/test-plans/cmd/ping/main.go
index 7b10c248..acedee4f 100644
--- a/test-plans/cmd/ping/main.go
+++ b/test-plans/cmd/ping/main.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"log"
 	"os"
-	"strconv"
 	"time"
 
 	"github.com/go-redis/redis/v8"
@@ -25,33 +24,24 @@ import (
 
 func main() {
 	var (
-		transport      = os.Getenv("transport")
-		secureChannel  = os.Getenv("security")
-		muxer          = os.Getenv("muxer")
-		isDialerStr    = os.Getenv("is_dialer")
-		ip             = os.Getenv("ip")
-		testTimeoutStr = os.Getenv("test_timeout")
-		redisAddr      = os.Getenv("REDIS_ADDR")
+		transport     = os.Getenv("transport")
+		secureChannel = os.Getenv("security")
+		muxer         = os.Getenv("muxer")
+		isDialerStr   = os.Getenv("is_dialer")
+		ip            = os.Getenv("ip")
+		redisAddr     = os.Getenv("REDIS_ADDR")
 	)
 
-	testTimeout := 10 * time.Second
-	if testTimeoutStr != "" {
-		secs, err := strconv.ParseInt(testTimeoutStr, 10, 32)
-		if err == nil {
-			testTimeout = time.Duration(secs) * time.Second
-		}
-	}
-
 	if redisAddr == "" {
 		redisAddr = "redis:6379"
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
+	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
 	defer cancel()
 
 	// Get peer information via redis
 	rClient := redis.NewClient(&redis.Options{
-		DialTimeout: testTimeout,
+		DialTimeout: 10 * time.Second,
 		Addr:        redisAddr,
 		Password:    "",
 		DB:          0,
@@ -102,9 +92,9 @@ func main() {
 
 	switch muxer {
 	case "yamux":
-		options = append(options, libp2p.Muxer(yamux.ID, yamux.DefaultTransport))
+		options = append(options, libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport))
 	case "mplex":
-		options = append(options, libp2p.Muxer(mplex.ID, mplex.DefaultTransport))
+		options = append(options, libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport))
 	case "quic":
 	default:
 		log.Fatalf("Unsupported muxer: %s", muxer)
@@ -120,7 +110,7 @@ func main() {
 	fmt.Println("My multiaddr is: ", host.Addrs())
 
 	if isDialer {
-		val, err := rClient.BLPop(ctx, testTimeout, "listenerAddr").Result()
+		val, err := rClient.BLPop(ctx, 20*time.Second, "listenerAddr").Result()
 		if err != nil {
 			log.Fatal("Failed to wait for listener to be ready")
 		}
@@ -154,7 +144,7 @@ func main() {
 		if err != nil {
 			log.Fatal("Failed to send listener address")
 		}
-		_, err = rClient.BLPop(ctx, testTimeout, "dialerDone").Result()
+		_, err = rClient.BLPop(ctx, 20*time.Second, "dialerDone").Result()
 		if err != nil {
 			log.Fatal("Failed to wait for dialer conclusion")
 		}
diff --git a/test-plans/go.mod b/test-plans/go.mod
index 75557ea3..3db83ad7 100644
--- a/test-plans/go.mod
+++ b/test-plans/go.mod
@@ -67,7 +67,7 @@ require (
 	github.com/multiformats/go-multibase v0.1.1 // indirect
 	github.com/multiformats/go-multicodec v0.7.0 // indirect
 	github.com/multiformats/go-multihash v0.2.1 // indirect
-	github.com/multiformats/go-multistream v0.4.0 // indirect
+	github.com/multiformats/go-multistream v0.3.3 // indirect
 	github.com/multiformats/go-varint v0.0.7 // indirect
 	github.com/onsi/ginkgo/v2 v2.5.1 // indirect
 	github.com/opencontainers/runtime-spec v1.0.2 // indirect
diff --git a/test-plans/go.sum b/test-plans/go.sum
index 37218590..d13b82c0 100644
--- a/test-plans/go.sum
+++ b/test-plans/go.sum
@@ -335,8 +335,8 @@ github.com/multiformats/go-multicodec v0.7.0/go.mod h1:GUC8upxSBE4oG+q3kWZRw/+6y
 github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
 github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
 github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
-github.com/multiformats/go-multistream v0.4.0 h1:5i4JbawClkbuaX+mIVXiHQYVPxUW+zjv6w7jtSRukxc=
-github.com/multiformats/go-multistream v0.4.0/go.mod h1:BS6ZSYcA4NwYEaIMeCtpJydp2Dc+fNRA6uJMSu/m8+4=
+github.com/multiformats/go-multistream v0.3.3 h1:d5PZpjwRgVlbwfdTDjife7XszfZd8KYWfROYFlGcR8o=
+github.com/multiformats/go-multistream v0.3.3/go.mod h1:ODRoqamLUsETKS9BNcII4gcRsJBU5VAwRIv7O39cEXg=
 github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
 github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
 github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=