Skip to content

feat: add announce addrs in host, #1250#1268

Open
asabya wants to merge 10 commits intolibp2p:mainfrom
asabya:feat/announce_address
Open

feat: add announce addrs in host, #1250#1268
asabya wants to merge 10 commits intolibp2p:mainfrom
asabya:feat/announce_address

Conversation

@asabya
Copy link
Copy Markdown

@asabya asabya commented Mar 10, 2026

What was wrong?

The public IP doesn't show up in get_addrs()

Closes #1250

How was it fixed?

Added announce addrs in host. The callers sets the announce multiaddr that the host should announce. Overrides the listen addrs in get_addrs

Cute Animal Picture

image

Copy link
Copy Markdown
Contributor

@sumanjeet0012 sumanjeet0012 left a comment

Choose a reason for hiding this comment

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

@asabya Please also add a newsfragment file and include documentation for the example file.

   - Add newsfragment documenting announce_addrs feature
   - Add README with usage examples and NAT/ngrok notes
   - Fix IHost.get_addrs() docstring: tuple → list with clearer description
   - Remove unused Callable import from libp2p/__init__.py
   - Fix logging in announce_addrs example: remove conflicting root logger setLevel(WARNING)
@asabya asabya requested a review from sumanjeet0012 March 12, 2026 11:00
self.psk = psk

# Address announcement configuration
self._announce_addrs = list(announce_addrs) if announce_addrs else None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would there be a case where a user would want to announce 0 addresses? If so, we'd need to check if announce_addrs is not None vs if announce_addrs to get that behavior. An empty sequence would result in fallback behavior.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not sure why user would like to announce nothing..

But if user wants to do it anyway, this will be the cases.

announce_addrs is None => fallback to transport_addrs
announce_addrs is [] => get_addrs return []
announce_addrs not any of the above => return announce_addrs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A better, more discoverable place for this file would be to reformat and move it to examples.announce_addrs.rst. That way it would get included in the docs. You can follow other examples there and literalinclude the example code at the bottom. See examples.chat.rst as an example.

Comment on lines +362 to +368

if self._announce_addrs is not None:
addrs = list(self._announce_addrs)
else:
addrs = self.get_transport_addrs()

return [addr.encapsulate(p2p_part) for addr in addrs]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This doesn't check before encapsulating, so we could end up with an invalid double-suffixed addr if the user provides a full p2p mulitaddr. As a minimal example:

key_pair = create_new_key_pair()
swarm = new_swarm(key_pair)
host = BasicHost(swarm)
peer_id_str = str(host.get_id())

host_with_p2p_announce = BasicHost(
    swarm,
    announce_addrs=[Multiaddr(f"/ip4/1.2.3.4/tcp/4001/p2p/{peer_id_str}")],
)

assert (
    str(host_with_p2p_announce.get_addrs()[0]).count(f"/p2p/{peer_id_str}") == 2

Comment on lines 401 to 403
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm concerned that after this PR, a user providing announce_addrs will break UPnP behavior. UPnP should still have access to the addresses returned by get_transport_addrs since it's mapping local ports.

Please let me know if I'm missing something!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

this should use self.get_transport_addrs() instead of self.get_addrs().

- Use identity check (`is not None`) for announce_addrs so empty list
  is respected instead of falling back to transport addrs
- Guard against double /p2p/ suffix in get_addrs()
- Use get_transport_addrs() in UPnP port mapping to avoid using
  announced addrs for local port extraction
- Move example docs from README.md to Sphinx rst with literalinclude
@yashksaini-coder
Copy link
Copy Markdown
Contributor

PR #1268 Review: announce_addrs Support

What This Does

Adds announce_addrs parameter to BasicHost so nodes behind NAT/proxies (AWS EC2, ngrok, etc.) can advertise publicly reachable addresses instead of their local listen addresses.

What's Good

  • ✅ Solves a real production pain point from Listening Addresses on an AWS EC2 don't include public IPs #1250
  • ✅ Clean API design with sensible fallback behavior
  • ✅ Smart handling of /p2p/ suffixes to avoid double-wrapping
  • ✅ UPnP fix included (uses get_transport_addrs() instead of get_addrs())
  • ✅ Good documentation and example code
  • ✅ Consistent parameter threading through the API

Issues Found

Critical

  1. Peer ID validation missing — If someone passes an announce addr with /ip4/1.2.3.4/tcp/4001/p2p/WRONG_ID, it gets advertised as-is. This causes identity confusion. Either validate the peer ID matches, or strip and re-encapsulate with the correct one.

Major
2. Missing examples/announce_addrs/__init__.py — Needed for module imports and Sphinx docs to work. Add an empty init file following project convention.
3. BasicHost.__init__ docstring incomplete — Missing :param announce_addrs: documentation (RoutedHost has it).
4. Insufficient test coverage — Only tests the simple case. Need edge case tests: existing /p2p/ suffix, empty list, multiple addrs, wrong peer ID.

Minor
5. Missing period in get_addrs() docstring ("Otherwise listen addresses are used")
6. Minor optimization: use any() instead of list comprehension for protocol check

Other Notes

  • Branch is 27 commits behind main — needs rebase before merge
  • No merge conflicts detected
  • Newsfragment is valid

Ready to Merge?

Not yet. The critical peer ID validation issue needs addressing, plus the missing __init__.py and expanded tests. The core idea is solid, just needs polish on edge cases.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Mar 21, 2026

@asabya

AI PR Review: #1268 — feat: add announce addrs in host


1. Summary of Changes

  • What: Adds optional announce_addrs to BasicHost and threads it through new_host() / RoutedHost so get_addrs() can advertise operator-supplied public or proxy addresses (e.g. AWS elastic IP, ngrok hostname) instead of raw listen addresses from transports.
  • Issue: #1250 — on EC2/NAT, get_addrs() / Identify showed private IPs; peers tried to dial unroutable addresses.
  • Files: libp2p/host/basic_host.py (core behavior, UPnP uses get_transport_addrs()), libp2p/host/routed_host.py, libp2p/__init__.py (new_host), libp2p/abc.py (IHost.get_addrs docstring), examples/announce_addrs/announce_addrs.py, docs/examples.announce_addrs.rst, tests/core/host/test_basic_host.py, newsfragments/1268.feature.rst.
  • Breaking changes: None intended; new optional parameter with default None.

2. Branch Sync Status and Merge Conflicts

Branch sync (git rev-list --left-right --count origin/main...HEAD)

  • Contents of branch_sync_status.txt: 0 5
  • Interpretation: 0 commits on origin/main not in HEADnot behind main; 5 commits on HEAD not in origin/main5 commits ahead. The branch includes current main and stacks the feature commits on top (contrasts with older review notes that mentioned being behind; current checkout is up to date with origin/main).

Merge conflict check

  • Result: Test merge origin/main into HEAD completed with exit code 0.
  • Log: === NO MERGE CONFLICTS DETECTED ===

No merge conflicts. Clean merge into origin/main at the time of this review.


Maintainer feedback: pacrob — fulfilled?

On PR #1268, pacrob left four concrete review threads (Mar 15, 2026). The current feat/announce_address branch addresses all four in code or docs. GitHub may still show threads as “outdated” until the author marks conversations resolved; from an implementation standpoint they are fulfilled.

# Topic pacrob’s concern Status Where it is addressed
1 Empty announce_addrs Using truthiness (if announce_addrs) would treat [] like “unset” and fall back to transport addrs; users who want to announce zero addresses need is not None. Resolved BasicHost uses identity on None when assigning _announce_addrs (None → fallback; [] → empty list). See §“Code references” below.
2 Example docs location Prefer Sphinx docs/examples.announce_addrs.rst with literalinclude (like examples.chat.rst) instead of only a README. Resolved docs/examples.announce_addrs.rst includes .. literalinclude:: ../examples/announce_addrs/announce_addrs.py and is linked from docs/examples.rst.
3 Double /p2p/ suffix Encapsulating every announce addr with /p2p/{id} could produce invalid addrs if the user already passed a full multiaddr ending in /p2p/.... Resolved get_addrs() leaves addrs that already contain a p2p component unchanged; others get encapsulate(p2p_part). Note: this avoids duplicate /p2p/ but does not require the embedded id to match the host (still flagged in §4 Critical).
4 UPnP vs announced addrs After the feature, code that used get_addrs() for port mapping could use announced addresses and break UPnP (which needs real local listener ports). Resolved UPnP add/remove port mapping iterates get_transport_addrs(), not get_addrs(). See §“Code references” below.

Code references (pacrob items 1, 3, 4):

libp2p/host/basic_host.py — lines 254–257

        # Address announcement configuration
        self._announce_addrs = (
            list(announce_addrs) if announce_addrs is not None else None
        )

libp2p/host/basic_host.py — lines 363–376

        p2p_part = multiaddr.Multiaddr(f"/p2p/{self.get_id()!s}")

        if self._announce_addrs is not None:
            addrs = list(self._announce_addrs)
        else:
            addrs = self.get_transport_addrs()

        result = []
        for addr in addrs:
            if "p2p" in [p.name for p in addr.protocols()]:
                result.append(addr)
            else:
                result.append(addr.encapsulate(p2p_part))
        return result

libp2p/host/basic_host.py — lines 408–411

                    if await upnp_manager.discover():
                        for addr in self.get_transport_addrs():
                            if port := addr.value_for_protocol("tcp"):
                                await upnp_manager.add_port_mapping(int(port), "TCP")

Summary: Yes — pacrob’s maintainer review items are implemented as requested. Nothing in that set remains an open code defect on this branch. Remaining gaps in this document (newsfragment filename, peer-id validation, BasicHost docstring, extra tests) are additional concerns — either from other reviewers (e.g. sumanjeet0012’s newsfragment/docs request — largely done except fragment naming) or from deeper edge-case / project-convention review (§4).


3. Strengths

  • Problem fit: Directly targets #1250: you cannot bind to a public IP on EC2, but you still need to advertise a dialable address; announce_addrs matches how other libp2p stacks handle this.
  • UPnP: Port mapping iterates get_transport_addrs() (local listeners), not get_addrs(), so announced addresses do not break UPnP’s need for real listening ports — aligns with maintainer feedback on the PR.
  • Double /p2p/: get_addrs() skips encapsulation when a p2p protocol component is already present, avoiding invalid double suffixes when users pass a full multiaddr.
  • Empty list semantics: _announce_addrs uses is not None, so announce_addrs=[] advertises nothing instead of falling back to transport addrs.
  • Docs: docs/examples.announce_addrs.rst with literalinclude follows the pattern requested in review (moved from a standalone README).
  • API surface: new_host(..., announce_addrs=...) and IHost documentation updated for discoverability.

4. Issues Found

Critical

  • File: newsfragments/1268.feature.rst

  • Line(s): (entire file)

  • Issue: Newsfragment is named with the PR number (1268) instead of the issue number (1250). Project convention (see other fragments: 1254.internal.rst, 1273.feature.rst, etc.) and the review prompt require newsfragments/<ISSUE_NUMBER>.<type>.rst.

  • Suggestion: Rename to 1250.feature.rst (and remove 1268.feature.rst). Adjust release-note cross-refs if any tooling assumes PR numbers.

  • File: libp2p/host/basic_host.py

  • Line(s): 365–376

  • Issue: If announce_addrs includes a /p2p/<peer-id> component that does not match self.get_id(), that wrong id is returned unchanged (branch treats any p2p component as “already complete”). Remote peers may trust advertised addresses for dialing/routing, causing identity confusion or failed dials.

  • Suggestion: Either strip existing p2p components and always re-encapsulate with self.get_id(), or validate that any embedded peer id equals self.get_id() and raise or log + replace. Prefer matching go-libp2p/js-libp2p behavior if specified elsewhere.

Major

  • File: libp2p/host/basic_host.py

  • Line(s): 195–209 (docstring)

  • Issue: BasicHost.__init__ documents many parameters but omits :param announce_addrs: even though RoutedHost and new_host() document it.

  • Suggestion: Add a :param announce_addrs: line (behavior: None → use listen addrs in get_addrs(); non-None → replace; [] → empty advertised set).

  • File: tests/core/host/test_basic_host.py

  • Line(s): 140–157

  • Issue: Tests cover the happy path (single announce addr without /p2p/). Missing: empty announce_addrs, multiple addrs, addr that already contains correct /p2p/, and wrong peer id in /p2p/ once validation is implemented.

  • Suggestion: Add parametrized or small focused tests for these cases.

  • File: PR #1268 (PR body)

  • Line(s): N/A

  • Issue: Body references “Issue Listening Addresses on an AWS EC2 don't include public IPs #1250” but does not use a closing keyword (Fixes #1250, Closes #1250). Optional for merge but improves tracking and GitHub linking.

  • Suggestion: Add Fixes #1250 (or Closes #1250) in the description.

Minor

  • File: libp2p/host/basic_host.py

  • Line(s): 354–358

  • Issue: get_addrs() docstring: “Otherwise listen addresses are used” is missing terminal punctuation after “used”.

  • Suggestion: End the sentence with a period (and optionally mention [] explicitly).

  • File: libp2p/host/basic_host.py

  • Line(s): 372

  • Issue: "p2p" in [p.name for p in addr.protocols()] allocates a list each time; minor hot-path cost.

  • Suggestion: any(p.name == "p2p" for p in addr.protocols()) (or project-preferred helper if one exists for multiaddr inspection).

Note: An earlier human review asked for examples/announce_addrs/__init__.py. Other examples under examples/chat and examples/echo do not use __init__.py either; Sphinx literalinclude works without it. Treat as optional unless maintainers require it for packaging.


5. Security Review

  • Risk: Announcing a multiaddr with /p2p/<not-self> poisons peer routing / Identify data with a conflicting peer id.

  • Impact: High for trust and connectivity (misleading advertisements); medium if most callers only pass host:port multiaddrs.

  • Mitigation: Validate or normalize peer id in get_addrs() as in §4 Critical; document that announce addrs should be transport-only or must match the host id.

  • Risk: No new network input parsing in this PR; announce_addrs is caller-supplied (trusted config). No subprocess/temp-file changes.

  • Impact: Low for classic injection; concern is semantic (wrong id), not buffer overflow.


6. Documentation and Examples

  • Sphinx: docs/examples.announce_addrs.rst is included from docs/examples.rst and builds cleanly (examples.announce_addrs read during make linux-docs).
  • Interfaces: IHost.get_addrs in abc.py documents that advertised addrs may differ when announce_addrs is set.
  • Gap: BasicHost.__init__ docstring still missing announce_addrs (see Major).

7. Newsfragment Requirement

  • Present: newsfragments/1268.feature.rst with user-facing text and trailing newline — content is appropriate.
  • BLOCKER: Filename must follow issue number 1250, not PR 1268 (see §4 Critical). Until renamed to 1250.feature.rst, this does not meet the project’s stated towncrier convention used across newsfragments/.

Maintainer checklist: PR references issue #1250 in prose; formal “Fixes #1250” still recommended.


8. Tests and Validation

All commands run as: source venv/bin/activate && make <target> with logs under downloads/AI-PR-REVIEWS/1268/.

Lint (make lint)

  • Exit code: 0
  • Result: All hooks passed (yaml, toml, ruff, ruff format, mdformat, mypy, pyrefly, .rst top-level check, path audit).

Typecheck (make typecheck)

  • Exit code: 0
  • Result: mypy and pyrefly pre-commit hooks passed.

Tests (make test)

  • Exit code: 0
  • Summary: 2563 passed, 15 skipped, 24 warnings in 98.25s
  • Notes: 24 warnings in suite (deprecations/resources/pytest); no failures attributed to this PR.

Documentation (make linux-docs)

  • Exit code: 0
  • Result: sphinx-build -b html -W succeeded; examples.announce_addrs processed.

9. Recommendations for Improvement

  1. Rename newsfragment to 1250.feature.rst and delete 1268.feature.rst.
  2. Implement peer-id normalization or validation for announce multiaddrs containing /p2p/.
  3. Complete BasicHost.__init__ docstring for announce_addrs.
  4. Extend tests for [], multiple addrs, pre-suffixed /p2p/, and wrong peer id after (2) is defined.
  5. Add Fixes #1250 to the PR body and re-request review from sumanjeet0012 (currently “changes requested”) after addressing remaining items.

10. Questions for the Author

  • Should only transport components (ip/dns/tcp/quic/…) be allowed in announce_addrs, with /p2p/ always applied by the host? That would simplify correctness and match “listen addrs + id” mental model.
  • Is there a spec or parity target (go-libp2p / js-libp2p) for how mismatched /p2p/ in announce addrs should behave (strip, error, warn)?

11. Overall Assessment

Dimension Assessment
Quality rating Good — design matches the problem; implementation is readable; CI-clean on this branch.
Security impact Low–Medium — no classic memory/injection issues; medium concern for mis-specified /p2p/ in announcements until handled.
Merge readiness Needs fixes — newsfragment naming (blocker), peer-id handling (strongly recommended before merge), docstring + tests polish.
Confidence High for behavior reviewed in code and tests; medium on edge cases until more tests land.

Reviewer status (GitHub): pacrobinline review threads are addressed in code (see Maintainer feedback: pacrob above); the author should mark those conversations resolved on GitHub if not already. sumanjeet0012 has changes requested (newsfragment + docs — docs done; confirm newsfragment rename to 1250.feature.rst and re-request review).


End of review.

Strip any existing /p2p/ component from announce addresses and always
re-encapsulate with the host's own peer ID, mirroring js-libp2p
behaviour. This prevents identity confusion when users pass announce
addrs containing a mismatched peer ID.

Also addresses remaining review items:
- Rename newsfragment from 1268 (PR) to 1250 (issue number)
- Add :param announce_addrs: to BasicHost.__init__ docstring
- Add tests for empty list, multiple addrs, correct/wrong peer ID
- Fix missing period in get_addrs() docstring
@asabya
Copy link
Copy Markdown
Author

asabya commented Mar 21, 2026

If announce_addrs includes a /p2p/ component that does not match self.get_id(), that wrong id is returned unchanged (branch treats any p2p component as “already complete”). Remote peers may trust advertised addresses for dialing/routing, causing identity confusion or failed dials.

Good catch @acul71. Updated the PR with the fixes.

@seetadev
Copy link
Copy Markdown
Contributor

This is a very solid and practical improvement, @asabya 👏 Appreciate your efforts.

Adding support for announce_addrs directly addresses a real-world pain point for nodes running behind NATs, proxies, or cloud environments, and brings py-libp2p much closer to production-ready usability.

It’s great to see the careful handling of edge cases, avoiding double /p2p/ encapsulation, preserving correct behavior for empty vs None, and ensuring UPnP continues to rely on transport addresses. The follow-up fixes, especially around peer ID normalization in announced addresses, significantly strengthen correctness and prevent subtle networking issues. Documentation and examples are also in a good place and make the feature easy to adopt.

Overall, this is a meaningful addition that improves both developer experience and network reliability.

@acul71 and @pacrob: Thank you for your review and detailed feedback. Appreciate it.

Ready for merge.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Mar 29, 2026

@seetadev @pacrob
Thanks @asabya for this PR.
I've seen that you requested a @sumanjeet0012 review, I believe his requests have been fulfilled.
As for the @pacrob requests (if confermed)
It seems ready to be merged.
Only thing when we merge this the issue is going to be auto-closed but it still need to be open for PR #1284 that refers to the same issue

Full review here:

AI PR Review: #1268 — feat: add announce addrs in host

Review date: 2026-03-28
Prompt: downloads/py-libp2p-pr-review-prompt.md
Branch reviewed: feat/announce_address (checked out via gh pr checkout 1268 --force)


Prerequisites completed

  • PR branch checked out
  • downloads/AI-PR-REVIEWS/1268/ used for logs and this report
  • branch_sync_status.txt and merge_conflict_check.log updated
  • PR body read (gh pr view 1268); comments sampled (gh pr view 1268 --comments)
  • Related issue Listening Addresses on an AWS EC2 don't include public IPs #1250 read (gh issue view 1250)
  • No GitHub Discussion referenced in the PR body (Step 3.5 skipped)
  • source venv/bin/activate && make lint|typecheck|test|linux-docs with output captured to lint_output.log, typecheck_output.log, test_output.log, docs_output.log

1. Summary of changes

What: Adds optional announce_addrs to BasicHost (and threads it through RoutedHost and new_host()) so get_addrs() can return operator-supplied public or proxy addresses (e.g. EC2 elastic IP, ngrok DNS) instead of only transport listen addresses.

Callers pass a sequence of Multiaddr objects (or None). The host stores them with None vs empty list semantics: None means “fall back to listen addresses”; [] means “advertise no addresses.”

        # Address announcement configuration
        self._announce_addrs = (
            list(announce_addrs) if announce_addrs is not None else None
        )

new_host() exposes the same knob and forwards it to RoutedHost or BasicHost:

    announce_addrs: Sequence[multiaddr.Multiaddr] | None = None,
) -> IHost:
    """
    Create a new libp2p host based on the given parameters.

    :param key_pair: optional choice of the ``KeyPair``
    :param muxer_opt: optional choice of stream muxer
    :param sec_opt: optional choice of security upgrade
    :param peerstore_opt: optional peerstore
    :param disc_opt: optional discovery
    :param muxer_preference: optional explicit muxer preference
    :param listen_addrs: optional list of multiaddrs to listen on
    :param enable_mDNS: whether to enable mDNS discovery
    :param bootstrap: optional list of bootstrap peer addresses as strings
    :param enable_quic: optinal choice to use QUIC for transport
    :param enable_autotls: optinal choice to use AutoTLS for security
    :param quic_transport_opt: optional configuration for quic transport
    :param tls_client_config: optional TLS client configuration for WebSocket transport
    :param tls_server_config: optional TLS server configuration for WebSocket transport
    :param resource_manager: optional resource manager for connection/stream limits
    :type resource_manager: :class:`libp2p.rcmgr.ResourceManager` or None
    :param psk: optional pre-shared key (PSK)
    :param bootstrap_allow_ipv6: if True, bootstrap accepts IPv6+TCP addresses
    :param bootstrap_dns_timeout: DNS resolution timeout in seconds per attempt
    :param bootstrap_dns_max_retries: max DNS resolution retries with backoff
    :param connection_config: optional connection configuration for connection manager
    :param announce_addrs: if set, these replace listen addrs in get_addrs()
    :return: return a host instance
    """
    if disc_opt is not None:
        return RoutedHost(
            network=swarm,
            router=disc_opt,
            enable_mDNS=enable_mDNS,
            enable_upnp=enable_upnp,
            bootstrap=bootstrap,
            resource_manager=resource_manager,
            bootstrap_allow_ipv6=bootstrap_allow_ipv6,
            bootstrap_dns_timeout=bootstrap_dns_timeout,
            bootstrap_dns_max_retries=bootstrap_dns_max_retries,
            announce_addrs=announce_addrs,
        )
    return BasicHost(
        network=swarm,
        enable_mDNS=enable_mDNS,
        bootstrap=bootstrap,
        enable_upnp=enable_upnp,
        negotiate_timeout=negotiate_timeout,
        resource_manager=resource_manager,
        bootstrap_allow_ipv6=bootstrap_allow_ipv6,
        bootstrap_dns_timeout=bootstrap_dns_timeout,
        bootstrap_dns_max_retries=bootstrap_dns_max_retries,
        announce_addrs=announce_addrs,
    )

Advertised addresses are built in get_addrs(): if _announce_addrs is set, those replace transport listen addresses; each address is normalized by stripping any embedded /p2p/… and re-appending this host’s peer id.

        p2p_part = multiaddr.Multiaddr(f"/p2p/{self.get_id()!s}")

        if self._announce_addrs is not None:
            addrs = list(self._announce_addrs)
        else:
            addrs = self.get_transport_addrs()

        result = []
        for addr in addrs:
            # Strip any existing /p2p/ component, then always append our own.
            # This avoids identity confusion when announce addrs contain a
            # mismatched peer ID (mirrors js-libp2p behaviour).
            try:
                p2p_value = addr.value_for_protocol("p2p")
            except ProtocolLookupError:
                p2p_value = None
            if p2p_value:
                addr = addr.decapsulate(multiaddr.Multiaddr(f"/p2p/{p2p_value}"))
            result.append(addr.encapsulate(p2p_part))
        return result

Typical usage (conceptual):

import multiaddr
from libp2p import new_host
from libp2p.crypto.ed25519 import create_new_key_pair

key_pair = create_new_key_pair()
public = multiaddr.Multiaddr("/ip4/203.0.113.50/tcp/4001")
host = new_host(key_pair=key_pair, announce_addrs=[public])
# host.get_addrs() -> [/ip4/203.0.113.50/tcp/4001/p2p/<local_peer_id>]

Issue: #1250 — on AWS/NAT, Identify and peer records showed private listen IPs; remote peers attempted unroutable dials.

Main files: libp2p/host/basic_host.py (storage, get_addrs() normalization, UPnP uses get_transport_addrs()), libp2p/host/routed_host.py, libp2p/__init__.py, libp2p/abc.py (IHost.get_addrs docs), examples/announce_addrs/announce_addrs.py, docs/examples.announce_addrs.rst (linked from docs/examples.rst), tests/core/host/test_basic_host.py, newsfragments/1250.feature.rst.

Breaking changes: None; new optional parameter defaulting to None.


2. Branch sync status and merge conflicts

Branch sync (git rev-list --left-right --count origin/main...HEAD)

Contents of branch_sync_status.txt: 0 10

  • Left (0): commits on origin/main not in HEADnot behind main.
  • Right (10): commits on HEAD not in origin/main → branch is 10 commits ahead (feature stack on current main).

Merge conflict check

  • Test merge origin/main into HEAD: exit code 0
  • merge_conflict_check.log: === NO MERGE CONFLICTS DETECTED ===

Conclusion: The PR branch merges cleanly into origin/main at review time.


3. Strengths

  • Problem fit: Directly addresses #1250: advertise dialable addresses when bind addresses are private.
  • Semantics: announce_addrs is None → fall back to transport addrs; announce_addrs=[] → advertise nothing (is not None check).
  • Identity safety: get_addrs() strips any existing /p2p/ component and re-encapsulates with the host’s peer ID, avoiding duplicate or mismatched IDs (aligned with author’s note mirroring js-libp2p).
  • UPnP: Port mapping loops use get_transport_addrs(), not get_addrs(), so announcements do not break local port discovery (pacrob’s concern on the PR).
  • Docs: Sphinx page docs/examples.announce_addrs.rst with narrative + literalinclude of the example script; IHost.get_addrs documents behavior when announce_addrs is set.
  • Tests: Coverage for replace, wrong embedded peer ID, empty list, multiple addrs, and correct pre-suffixed /p2p/.
  • Newsfragment: newsfragments/1250.feature.rst uses the issue number per towncrier convention.

4. Issues found

Critical

None identified on the current branch. Earlier review rounds flagged mismatched /p2p/ in announce addrs; this is addressed by strip + re-encapsulate in get_addrs().

Major

None required for correctness. Process: GitHub still lists sumanjeet0012 as “Changes requested” from an earlier round (newsfragment + docs). Those items appear implemented; the author should re-request review or ask the reviewer to clear the status so merge is not blocked on stale UI state.

Minor

- **File:** libp2p/host/basic_host.py
- **Line(s):** 357–366 (get_addrs docstring)
- **Issue:** The docstring does not mention that embedded `/p2p/` components in announce addrs are stripped and replaced with this host’s ID.
- **Suggestion:** One short sentence would help operators who paste full multiaddrs from other tools.

5. Security review

  • Risk: Mis-announced peer IDs in multiaddrs.

  • Impact: Was medium before normalization; low after strip + re-encapsulate with self.get_id().

  • Mitigation: Current implementation; tests assert wrong CID-like IDs are removed.

  • Risk: announce_addrs is configuration, not unauthenticated network input in this PR.

  • Impact: Low for classic injection; no new subprocess or file handling in the reviewed paths.


6. Documentation and examples

  • API: BasicHost.__init__ includes :param announce_addrs:; new_host() documents the parameter; IHost.get_addrs explains advertised vs listen addrs.
  • User docs: Example page under docs/examples.rst with install and CLI usage; NAT/ngrok notes.
  • Example script: examples/announce_addrs/announce_addrs.py included via literalinclude.

No documentation gaps that block merge.


7. Newsfragment requirement

Verdict: Satisfies the prompt’s newsfragment and issue-link requirements.


8. Tests and validation

Commands (with venv): make lint, make typecheck, make test, make linux-docs. Logs: downloads/AI-PR-REVIEWS/1268/*.log.

Lint (make lint)

  • Exit code: 0
  • Result: All pre-commit hooks passed (yaml, toml, ruff, ruff format, mdformat, mypy, pyrefly, .rst top-level check, path audit).
  • Errors: None.

Typecheck (make typecheck)

  • Exit code: 0
  • Result: mypy-local and pyrefly-local passed.
  • Errors: None.

Tests (make test)

  • Exit code: 0
  • Summary: 2593 passed, 15 skipped, 24 warnings in ~102s.
  • Warnings: 24× PytestCollectionWarning in tests/core/records/test_ipns_validator.py:489 (TestVector has __init__ — pre-existing collection quirk, not introduced by this PR).
  • Failures: None.

Documentation (make linux-docs)

  • Exit code: 0
  • Result: sphinx-build -b html -W succeeded; examples.announce_addrs built.
  • Note: Log may include benign environment messages (e.g. xdg-open, browser-related stderr); build itself completed successfully.

Example scripts smoke test (scripts/smoke_test_examples.py)

  • Mechanism: The script discovers every examples/**/*.py that has a if __name__ == "__main__" guard and smoke-loads each in a subprocess (import-only by default; optional --run-main runs argparse scripts with --help).
  • announce_addrs: examples/announce_addrs/announce_addrs.py is included in that set and passes the import-only smoke test (same convention as the other examples). No separate CI wiring is required for this PR unless the project later adds a Makefile/tox step that runs scripts/smoke_test_examples.py globally.

9. Recommendations for improvement

  1. Reviewer workflow: Ping sumanjeet0012 to confirm or dismiss the earlier “changes requested” now that newsfragment + Sphinx docs are in place.
  2. Optional doc tweak: Document /p2p/ stripping in get_addrs() (see Minor above).
  3. Optional: If maintainers want parity with other examples, an empty examples/announce_addrs/__init__.py was suggested in an older comment; other examples often omit it — follow project preference.

10. Overall assessment

Dimension Assessment
Quality rating Excellent — design matches libp2p usage; edge cases and UPnP interaction are handled; tests and docs are solid.
Security impact Low — identity confusion mitigated by normalization; config-driven surface.
Merge readiness Ready from code/CI/docs/newsfragment; needs re-review or status update from requested reviewers on GitHub if “changes requested” remains.
Confidence High — full suite green on reviewed branch; behavior verified in unit tests.

Maintainer feedback (pacrob)

Per code inspection on this branch, all four of pacrob’s review threads on PR #1268 are fulfilled in code (nothing left open as a defect). GitHub may still label comments “Outdated” after edits; the author can mark conversations resolved once satisfied.

# Topic Status Where it is addressed
1 Empty announce_addrs — use is not None so [] advertises nothing instead of falling back to transport addrs (truthiness would treat [] like unset). Done _announce_addrs uses list(announce_addrs) if announce_addrs is not None else None; get_addrs() branches on self._announce_addrs is not None.
2 Example docs — prefer Sphinx docs/examples.announce_addrs.rst with literalinclude (like examples.chat.rst), not only a standalone README.md. Done docs/examples.announce_addrs.rst exists and is linked from docs/examples.rst.
3 Double /p2p/ — avoid invalid addrs when announce multiaddrs already end with /p2p/…. Done Strip any existing p2p component, then encapsulate with this host’s id (also fixes mismatched embedded ids).
4 UPnP — port mapping must use real listener addresses (get_transport_addrs()), not get_addrs() / announced addrs. Done UPnP add/remove port mapping loops over get_transport_addrs().

Code references

  1. Empty list vs None:
        # Address announcement configuration
        self._announce_addrs = (
            list(announce_addrs) if announce_addrs is not None else None
        )
        if self._announce_addrs is not None:
            addrs = list(self._announce_addrs)
        else:
            addrs = self.get_transport_addrs()
  1. Docs: see docs/examples.announce_addrs.rst (not repeated here).

  2. /p2p/ normalization:

        result = []
        for addr in addrs:
            # Strip any existing /p2p/ component, then always append our own.
            # This avoids identity confusion when announce addrs contain a
            # mismatched peer ID (mirrors js-libp2p behaviour).
            try:
                p2p_value = addr.value_for_protocol("p2p")
            except ProtocolLookupError:
                p2p_value = None
            if p2p_value:
                addr = addr.decapsulate(multiaddr.Multiaddr(f"/p2p/{p2p_value}"))
            result.append(addr.encapsulate(p2p_part))
        return result
  1. UPnP:
                if self.upnp is not None:
                    upnp_manager = self.upnp
                    logger.debug("Starting UPnP discovery and port mapping")
                    if await upnp_manager.discover():
                        for addr in self.get_transport_addrs():
                            if port := addr.value_for_protocol("tcp"):
                                await upnp_manager.add_port_mapping(int(port), "TCP")
                    if self.upnp and self.upnp.get_external_ip():
                        upnp_manager = self.upnp
                        logger.debug("Removing UPnP port mappings")
                        for addr in self.get_transport_addrs():
                            if port := addr.value_for_protocol("tcp"):
                                await upnp_manager.remove_port_mapping(int(port), "TCP")

End of review.

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.

Listening Addresses on an AWS EC2 don't include public IPs

6 participants