Skip to content

fix(peerstore): load persisted keypairs on peerstore restore#38

Open
bhuvan-somisetty wants to merge 20 commits into
seetadev:mainfrom
bhuvan-somisetty:fix/persistent-peerstore-keypair-load
Open

fix(peerstore): load persisted keypairs on peerstore restore#38
bhuvan-somisetty wants to merge 20 commits into
seetadev:mainfrom
bhuvan-somisetty:fix/persistent-peerstore-keypair-load

Conversation

@bhuvan-somisetty

@bhuvan-somisetty bhuvan-somisetty commented May 12, 2026

Copy link
Copy Markdown

Peer identity in libp2p is not just metadata — it is the cryptographic keypair. A peer's ID is derived from its public key, and its private key is what lets it sign records, authenticate connections, and prove continuity across sessions. So when the persistent peerstore silently dropped all keypairs on every restart, peers were effectively getting a brand new identity each time the process came back up.

The bug landed in libp2p#946 when the persistent peerstore was first introduced. The write path was complete: public and private keys were serialized and stored in the datastore correctly. The read path, however, had the deserialization block entirely commented out with a TODO:

# For now, store keys as metadata until keypair serialization
# keys_metadata = deserialize_metadata(key_data)
# TODO: Implement proper keypair deserialization
pass

The helpers needed to complete this — deserialize_public_key and deserialize_private_key — already existed in libp2p.crypto.serialization and produce exactly the format the write path stores. Nothing was missing. The load path just never called them.

This fix wires up those deserializers in both the sync and async peerstore implementations. The change is small on purpose: the serialization contract was already correct, this just closes the gap between save and restore.

Three tests cover the new behavior: SQLite-backed keypair persistence for the sync implementation, the same for async, and a control case confirming the in-memory backend does not persist across restarts (by design). All existing tests pass.

Gautam Manchandani and others added 20 commits March 20, 2026 13:12
Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
…t-response-example

Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>

# Conflicts:
#	.github/workflows/tox.yml
Signed-off-by: Gautam Manchandani <gautammanch@Gautams-MacBook-Air.local>
…e bridge

- New libp2p/filecoin/address.py: Filecoin address parsing (f0/f1/f3/f4/f410),
  validation, and delegated address creation. 19 passing tests.
- Integrate Filecoin address validation into A2A payment authorization.
  Payer must be a valid f4/f410/f0/f1 address; invalid addresses are rejected.
- Add streaming task lifecycle: payment auth sets WORKING state with a
  completion deadline; GetTask auto-completes after the deadline expires.
  Libp2p demo polls GetTask to show visible state transitions.
- A2A agent card advertises streaming=True for libp2p protocol binding.
- Synapse bridge: secure command parsing (shlex), configurable timeout,
  subprocess.TimeoutExpired handling, and comprehensive architecture docs.
- All 53 related tests pass (address module, A2A payment, HTTP demo,
  agentic demo, request-response API).
Signed-off-by: bhuvan-somisetty <somisettybhuvan5@gmail.com>
* feat: add ObservedAddrManager for NAT address discovery

Add ObservedAddrManager that tracks externally observed addresses
reported by peers via Identify, confirms them once enough distinct
observer groups agree, and exposes them through BasicHost.get_addrs().
Integrates into BasicHost: records observations after Identify
exchanges and cleans up on disconnect.

* docs: add newsfragment for ObservedAddrManager (libp2p#1284)

* Address review feedback on ObservedAddrManager integration (libp2p#1284)

Follow-ups from maintainer review (acul71):

* Narrow the exception handling in BasicHost._identify_peer's observed-
  address recording path. Expected multiaddr parse errors (MultiaddrError
  subclasses) and ValueError remain at debug level; any other failure is
  now surfaced as a warning with traceback so regressions aren't hidden.
* Rename newsfragments/1284.feature.rst to newsfragments/1250.bugfix.rst
  so the release notes point at the user-facing bug (Listening Addresses
  on an AWS EC2 don't include public IPs, libp2p#1250), and update the text
  accordingly.
* Expose BasicHost.get_nat_type() as a thin pass-through to the
  ObservedAddrManager equivalent (marked experimental) and re-export
  NATDeviceType from libp2p.host.basic_host imports for easy consumption
  by future AutoNAT code. Add unit tests covering the pass-through, the
  observed-addresses-appended path of get_addrs, dedup against listen
  addrs, and the announce_addrs override branch.
* Document the invariant behind the TCP vs UDP classification loop in
  ObservedAddrManager.get_nat_type (all ext_tw_str keys in a single
  ext_map share a transport) and add a defensive guard that skips mixed
  entries instead of silently misclassifying them, matching go-libp2p's
  getNATType behaviour more explicitly.
* Document the observed-address flow in docs/libp2p.host.rst and the
  announce_addrs vs observed-address interaction in the announce_addrs
  example doc; cross-link from the announce_addrs parameter docstring
  in BasicHost.__init__.

Made-with: Cursor

* Add tests for ObservedAddrManager wiring and defensive guard (libp2p#1284)

Close integration-layer test gaps identified during PR review:

* BasicHost._identify_peer forwards observed_addr to record_observation
  (Gap 1).
* The three narrow exception branches in _identify_peer — MultiaddrError,
  ValueError, and the generic Exception fallback — are exercised and the
  expected DEBUG / WARNING logs (with exc_info for the fallback) are
  emitted without propagating (Gap 2a/2b/2c).
* BasicHost._on_notifee_disconnected calls ObservedAddrManager.remove_conn
  on peer disconnect, with a defensive no-op path when peer_id is missing
  (Gap 3).
* White-box test for the defensive TCP/UDP skip guard in
  ObservedAddrManager.get_nat_type that would otherwise be unreachable
  from record_observation (Gap 4). Constructed so the result *differs*
  between guarded and unguarded implementations: a stray concentrated
  UDP entry inside a TCP bucket would flip the classification from
  ENDPOINT_DEPENDENT to ENDPOINT_INDEPENDENT without the guard.

A small fixture (libp2p_log_propagate) re-enables propagation and lowers
the libp2p logger level so pytest's caplog can observe records — the
package's own setup_logging() sets propagate=False and WARNING at import
time when LIBP2P_DEBUG is unset.

Made-with: Cursor

* Make log-capture in _identify_peer tests xdist-safe (libp2p#1284)

The previous fixture re-enabled propagation only on the ``libp2p`` logger,
relying on ``caplog`` (attached at root) to pick records up. That's
fragile: ``libp2p/utils/logging.py`` can also set ``propagate=False`` on
intermediate loggers like ``libp2p.host`` depending on the
``LIBP2P_DEBUG`` env var, which breaks propagation from
``libp2p.host.basic_host`` to root. CI/xdist workers occasionally hit
that state and the three log-assertion tests failed with empty
``caplog.records``.

Replace the propagation trick with a handler attached directly to the
``libp2p.host.basic_host`` logger. The handler simply collects records
into a list, which tests then inspect. This sidesteps propagation and
parent-logger config entirely — the test only cares whether
``logger.debug(...)`` in ``basic_host.py`` fires, which it forces by
setting ``target.setLevel(DEBUG)`` and ``target.disabled = False``.

Verified green under:
  * plain ``pytest``
  * ``pytest -n auto`` (xdist)
  * ``LIBP2P_DEBUG=DEBUG``, ``LIBP2P_DEBUG=libp2p.host:DEBUG``, unset

Made-with: Cursor

* fix(host): run outbound Identify on all muxers and inbound conns

Match go-libp2p Connected→IdentifyWait behavior: _should_identify_peer
accepts any open SwarmConn with a muxer (not QUIC-only), and the
identify notifee schedules Identify for inbound connections too.

Adds unit tests for _should_identify_peer and inbound notifee scheduling.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(identify_push): assert push targets only connected peers

After inbound outbound Identify, host_a may retain host_c in peerstore
after disconnect; replace peer_ids() checks with
host_a not in host_c.get_connected_peers() for push_identify_to_peers.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: DEBUG for observed addr activation; fix identify-demo logging

- ObservedAddrManager: log each observation and when ACTIVATION_THRESHOLD
  is reached for an external thin waist.
- identify-demo: stop forcing libp2p to WARNING so LIBP2P_DEBUG works;
  leave commented quiet defaults for optional use.

Co-authored-by: Cursor <cursoragent@cursor.com>

* debug(host): trace Identify observed_addr and ObservedAddrManager skips

Log when outbound Identify records an observed address or the peer omits
it, and add DEBUG reasons for early returns and duplicate observations
in ObservedAddrManager.record_observation.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(yamux): expose get_remote_address on muxed conn like Mplex

Yamux IMuxedConn now delegates to secured_conn; YamuxStream uses the muxer.
ObservedAddrManager._get_remote_addr only calls muxed_conn.get_remote_address
(with callable guard and docstring). Add unit test and annotate test
DummySecuredConn.get_remote_address for pyrefly.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: add advertising_addresses page under Examples, cross-link from host and announce_addrs

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: acul71 <34693171+acul71@users.noreply.github.com>
Co-authored-by: Manu Sheel Gupta <manusheel.edu@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…response-example

feat/agentic-request-response-example
* Fix unbound stream variable in DHT provider_store.py

* Add issue, newsfragment, and test for ADD_PROVIDER stream cleanup

Fixes libp2p#1335. Completes PR libp2p#1313 follow-up: regression test when
new_stream fails, plus towncrier entry.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Tim Sheerman-Chase <orders2008@sheerman-chase.org.uk>
Co-authored-by: acul71 <34693171+acul71@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

5 participants