Skip to content

relay: global max_circuits pinned to per-peer value (512) throttles total relay throughput #484

@emlautarom1-agent

Description

@emlautarom1-agent

Summary

Pluto's relay sets the global circuit ceiling (relay::Config.max_circuits) to max_res_per_peer (default 512). This is a cap across all peers, so the entire relay can host at most 512 concurrent circuits regardless of --p2p-max-connections (default 16384). At production fan-out this can cause spurious RESOURCE_LIMIT_EXCEEDED circuit denials and prevent peers from connecting via the relay.

There is already a todo(varex83) in the code questioning this exact value.

Where

crates/relay-server/src/config.rs:49-64:

relay::Config {
    max_reservations: config.max_conns,                 // 16384
    max_reservations_per_peer: config.max_res_per_peer, // 512
    ...
    max_circuits: config.max_res_per_peer,              // 512  <-- GLOBAL cap
    max_circuits_per_peer: config.max_res_per_peer,     // 512
    ...
}

rust-libp2p denies a circuit when self.circuits.len() >= self.config.max_circuits (global) or per-peer circuits exceed max_circuits_per_peer (libp2p-relay-0.21.1/src/behaviour.rs:540-548).

Semantics mismatch with go-libp2p / Charon

rust-libp2p splits circuit limits into global (max_circuits) and per-peer (max_circuits_per_peer). go-libp2p (circuitv2) has only one:

  • Resources.MaxCircuits is documented as "the maximum number of open relay connections for each peer" — i.e. per-peer, default 16. There is no global circuit cap in go-libp2p.

(go-libp2p@v0.36.2/p2p/protocol/circuitv2/relay/resources.go)

Charon's relay config (charon/cmd/relay/p2p.go:60-72, built on relay.DefaultResources()):

relayResources := relay.DefaultResources()                 // MaxCircuits default = 16 (per-peer)
relayResources.MaxReservationsPerIP = config.MaxResPerPeer // 512
relayResources.MaxReservations      = config.MaxConns      // 16384
relayResources.MaxCircuits          = config.MaxResPerPeer // 512  (PER-PEER)

Charon CLI defaults (charon/cmd/relay.go:53-54) are the same ones Pluto adopted:

  • p2p-max-reservations = 512
  • p2p-max-connections = 16384

So Charon sets a per-peer circuit limit of 512 and relies on go-libp2p having no global circuit ceiling.

Mapping today

Limit Pluto Charon (go-libp2p) Verdict
global reservations max_reservations = max_conns (16384) MaxReservations = MaxConns (16384) ✔ matches
per-peer reservations max_reservations_per_peer = max_res_per_peer (512) MaxReservationsPerIP = MaxResPerPeer (512) ✔ close analogue
per-peer circuits max_circuits_per_peer = max_res_per_peer (512) MaxCircuits = MaxResPerPeer (512, per-peer) ✔ matches
global circuits max_circuits = max_res_per_peer (512) (no equivalent) ✘ artificial throttle

Suggested fix

Decouple the global cap from the per-peer value. Keep max_circuits_per_peer = max_res_per_peer (mirrors Charon), but raise the global max_circuits so it doesn't throttle a healthy mesh:

max_circuits: config.max_conns,                 // align global circuit ceiling with the connection budget
max_circuits_per_peer: config.max_res_per_peer, // unchanged; mirrors Charon's per-peer MaxCircuits

Rationale: max_conns (16384) is the global reservation/connection budget; capping total circuits at that same value preserves a safety ceiling (which go-libp2p lacks entirely) while removing the artificial 512 throttle.

Alternative considered: usize::MAX to fully mirror Charon's "no global cap", but that removes the safety valve, so scaling with max_conns is preferred. Optionally, expose the global cap as its own CLI flag if operators need to tune it independently.

Notes

  • Not observed in current dev-cluster logs (circuit counts are well below 512); this is a latent scaling limit, not an active incident. It came up while investigating relay WARN noise: today Pluto's relay logs do not include the deny status, so RESOURCE_LIMIT_EXCEEDED (this cap) and NO_RESERVATION (benign) are indistinguishable. Logging the status is being addressed separately.
  • Resource-policy change — worth a quick review of the chosen default before merge.

References

  • crates/relay-server/src/config.rs:49-64
  • crates/cli/src/commands/relay.rs:153-169
  • libp2p-relay-0.21.1/src/behaviour.rs:540-594
  • charon/cmd/relay/p2p.go:60-72, charon/cmd/relay.go:53-54
  • go-libp2p@v0.36.2/p2p/protocol/circuitv2/relay/resources.go

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions