Skip to content

fix(libp2p): plumb advertise address to swarm.add_external_address#4261

Draft
sveitser wants to merge 3 commits intomainfrom
ma/fix-p2p-advertise-addr
Draft

fix(libp2p): plumb advertise address to swarm.add_external_address#4261
sveitser wants to merge 3 commits intomainfrom
ma/fix-p2p-advertise-addr

Conversation

@sveitser
Copy link
Copy Markdown
Collaborator

@sveitser sveitser commented May 6, 2026

  • The CLI flag --libp2p-advertise-address (env ESPRESSO_NODE_LIBP2P_ADVERTISE_ADDRESS)
    was only used in the orchestrator-bootstrap branch (lib.rs:446) and never reached
    the libp2p Swarm in the persistence-loaded or peer-fetch branches. The flag's
    docstring promised that it "advertises to other nodes" but in production paths
    it was a silent no-op.

  • Effect: validators behind NAT, K8s NodePort, or Docker bridge networking can only
    be reached by peers they have already established outbound connections to. Identify
    announces their bind-derived addresses (loopback / VPC-internal IPs), so peers that
    learn about them via DHT lookup, or that lose and try to re-establish a connection,
    cannot dial them. Coverage of the validator set is incomplete and degrades over
    time as connections drop. Observed-address from existing connections is not
    propagated by identify to third parties, and AutoNAT cannot self-confirm the
    public address because the bind-derived addresses it offers as probe candidates
    are not reachable.

  • Fix: thread Vec through Libp2pNetwork::from_config (libp2p_network.rs)
    into NetworkNodeConfig.announce_addresses (config.rs), and call
    Swarm::add_external_address for each in NetworkNode::new (node.rs). The existing
    SwarmEvent::ExternalAddrConfirmed handler at node.rs already populates the DHT
    self-record once the address is confirmed.

  • Also drop the default_value = "localhost:1769" on the CLI flag. Loopback was
    actively wrong as a default once the flag is wired through to identify; absent
    is now valid (no advertise) and bootstrapping the orchestrator without it
    errors explicitly with a clear message.

  • Drive-by: fix a "fales" typo in a comment and whitelist the bimap crate name
    in .typos.toml so the pre-commit spell-check passes.

  • References:

    rust-libp2p Swarm::add_external_address (libp2p-swarm 0.47):
    https://docs.rs/libp2p/0.56.0/libp2p/swarm/struct.Swarm.html#method.add_external_address
    "Add a confirmed external address for the local node. This function should
    only be called with addresses that are guaranteed to be reachable."

    libp2p Identify spec - the listenAddrs field that peers receive during
    identify exchange:
    https://github.com/libp2p/specs/blob/master/identify/README.md
    Without add_external_address, only listen addresses are populated, which for
    bind=0.0.0.0 means loopback and any local interface IPs.

- The CLI flag --libp2p-advertise-address (env ESPRESSO_NODE_LIBP2P_ADVERTISE_ADDRESS)
  was only used in the orchestrator-bootstrap branch (lib.rs:446) and never reached
  the libp2p Swarm in the persistence-loaded or peer-fetch branches. The flag's
  docstring promised that it "advertises to other nodes" but in production paths
  it was a silent no-op.

- Effect: validators behind NAT, K8s NodePort, or Docker bridge networking can only
  be reached by peers they have already established outbound connections to. Identify
  announces their bind-derived addresses (loopback / VPC-internal IPs), so peers that
  learn about them via DHT lookup, or that lose and try to re-establish a connection,
  cannot dial them. Coverage of the validator set is incomplete and degrades over
  time as connections drop. Observed-address from existing connections is not
  propagated by identify to third parties, and AutoNAT cannot self-confirm the
  public address because the bind-derived addresses it offers as probe candidates
  are not reachable.

- Fix: thread Vec<Multiaddr> through Libp2pNetwork::from_config (libp2p_network.rs)
  into NetworkNodeConfig.announce_addresses (config.rs), and call
  Swarm::add_external_address for each in NetworkNode::new (node.rs). The existing
  SwarmEvent::ExternalAddrConfirmed handler at node.rs already populates the DHT
  self-record once the address is confirmed.

- Also drop the default_value = "localhost:1769" on the CLI flag. Loopback was
  actively wrong as a default once the flag is wired through to identify; absent
  is now valid (no advertise) and bootstrapping the orchestrator without it
  errors explicitly with a clear message.

- Drive-by: fix a "fales" typo in a comment and whitelist the bimap crate name
  in .typos.toml so the pre-commit spell-check passes.

- References:

  rust-libp2p Swarm::add_external_address (libp2p-swarm 0.47):
    https://docs.rs/libp2p/0.56.0/libp2p/swarm/struct.Swarm.html#method.add_external_address
    "Add a confirmed external address for the local node. This function should
    only be called with addresses that are guaranteed to be reachable."

  libp2p Identify spec - the listenAddrs field that peers receive during
  identify exchange:
    https://github.com/libp2p/specs/blob/master/identify/README.md
  Without add_external_address, only listen addresses are populated, which for
  bind=0.0.0.0 means loopback and any local interface IPs.
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the Libp2p networking configuration to support announcing external addresses, which is necessary for nodes operating behind NAT, K8s NodePort, or Docker bridges. The changes include updating the libp2p_advertise_address to be optional, introducing an announce_addresses field in the network configuration, and ensuring these addresses are added to the swarm as external addresses. Additionally, a minor typo in a comment was corrected. As there were no review comments provided, I have no feedback to offer.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Nextest failures (1) in this run

Test Attempts Time (s) Main history
espresso-node::api::test::test_tx_metadata 3 4.84 passing

See the step summary for flaky tests and slowest tests.

sveitser added 2 commits May 6, 2026 17:18
- process-compose.yaml: espresso-node-4 had no libp2p env vars and was relying
  on the removed default localhost:1769. Set explicit BIND/ADVERTISE for it,
  matching nodes 0-3, using the existing ESPRESSO_DEMO_NODE_LIBP2P_PORT_4.

- crates/espresso/node/src/run.rs: test_startup_before_orchestrator did not
  pass --libp2p-advertise-address, so init returned the new orchestrator-
  bootstrap error before the API server came up. Reserve a third ephemeral
  port and pass it.

- crates/espresso/node/src/lib.rs + crates/cliquenet/src/addr.rs: warn loudly
  when the advertise address resolves to loopback, the unspecified address,
  or the literal "localhost". This catches the misconfiguration at startup
  rather than letting it manifest as silent unreachability for hours.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant