Skip to content

feat(rpc): serve /eth/v1/node/peers, /peers/{peer_id} and /peer_count - #747

Draft
MegaRedHand wants to merge 3 commits into
mainfrom
feat/beacon-node-peers
Draft

MegaRedHand wants to merge 3 commits into
mainfrom
feat/beacon-node-peers

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

Summary

Serves the Beacon API's GET /eth/v1/node/peers, GET /eth/v1/node/peers/{peer_id} and GET /eth/v1/node/peer_count. ethPandaOps' contributoor (xatu sentry) polls /peers every minute and got a 404.

Design notes

  • ethlambda-network-api gains PeerTableController (Arc<RwLock<HashMap>>), ConnectedPeer and PeerDirection, mirroring MetaDataController. Strings only, so the crate stays free of libp2p types.
  • The p2p actor is the sole writer: insert on ConnectionEstablished with num_established == 1, remove on ConnectionClosed with num_established == 0. RPC reads it at request time via a new BeaconIdentity.peers: Option<PeerTableController>; absent means an empty list.
  • Only connected peers are tracked, so state is always connected. A state filter for another valid state returns an empty list, and peer_count reports "0" for the other states. Documented in docs/rpc.md.
  • last_seen_p2p_address is the first connection's remote address, with /p2p/<peer id> appended if absent. direction is the first connection's, as in connected_peers.
  • enr: DiscoveredPeer now carries the record's enr: string; the dial loop keeps it per peer (peer_enrs, dropped in forget_discovered_peer) and it is read at connect. null for inbound peers and --bootnodes dials. No new network requests.
  • Also: state/direction accept repeated and comma-separated values (values of one parameter are alternatives, parameters are AND-combined); peers are sorted by peer id for stable output; /peers/{peer_id} validates the id as non-empty base58 (the RPC crate has no libp2p), so a malformed id is a 400 and a well-formed unknown one a 404.
  • Lean surface (/lean/v0) untouched.

Test plan

  • cargo fmt --all
  • cargo clippy -p ethlambda-network-api -p ethlambda-p2p -p ethlambda-rpc -p ethlambda --all-targets --profile release-fast -- -D warnings
  • cargo test -p ethlambda-rpc --profile release-fast --lib -- beacon::node (15 passed): list, state/direction/both filters, invalid value 400, meta.count, null ENR, /peers/{id} hit/miss/malformed, peer_count quoting, absent table
  • cargo test -p ethlambda-network-api --profile release-fast --lib (3 passed)
  • The p2p write path has no test of its own; check it on a running node with curl :5052/eth/v1/node/peers

ethPandaOps' contributoor polls GET /eth/v1/node/peers once a minute against
our beacon nodes and got a 404, and any Beacon API consumer expects the
endpoint.

The p2p actor already owns the connected-peer set, so it now mirrors it into
a shared PeerTableController (same pattern as MetaDataController): p2p is the
sole writer, inserting on a peer's first connection and removing on its last
close, and RPC reads it per request through BeaconIdentity. Only connected
peers are tracked, so state is always "connected"; other state filters match
nothing and peer_count reports 0 for them. The ENR is the record discovery
supplied before the dial (kept in the dial loop's bookkeeping, no new
requests), and null for inbound peers and --bootnodes dials.
MegaRedHand added a commit that referenced this pull request Nov 2, 2026
…#747)

ethPandaOps' contributoor polls GET /eth/v1/node/peers once a minute against
our beacon nodes and got a 404, and any Beacon API consumer expects the
endpoint.

The p2p actor already owns the connected-peer set, so it now mirrors it into
a shared PeerTableController (same pattern as MetaDataController): p2p is the
sole writer, inserting on a peer's first connection and removing on its last
close, and RPC reads it per request through BeaconIdentity. Only connected
peers are tracked, so state is always "connected"; other state filters match
nothing and peer_count reports 0 for them. The ENR is the record discovery
supplied before the dial (kept in the dial loop's bookkeeping, no new
requests), and null for inbound peers and --bootnodes dials.

Cherry-picked from feat/beacon-node-peers at e12b8fd.
MegaRedHand added a commit that referenced this pull request Nov 2, 2026
ethPandaOps' contributoor subscribes to head_v2 and the gloas topics, and
ethlambda accepted every one of them without ever sending any, so the lab's
gloas views stayed empty for our nodes on Sepolia.

head_v2 goes out next to every head (pre-gloas with payload_status "full")
and again for the same block when its payload status goes from empty to
full. The block event gains builder_index and block_hash for gloas blocks.
The chain actor publishes execution_payload and execution_payload_available
on envelope import. Bids, proposer preferences, payload attestation messages
and accepted envelopes are validated in ethlambda-p2p and never reach the
actor, and p2p does not depend on the crate owning the event bus, so p2p
reports them through a GossipEventSink trait in ethlambda-network-api that
EventBus implements. The API handlers that accept the same objects publish
on the bus they already hold.

Cherry-picked from feat/beacon-gloas-events at 8274123.

Adapted onto #747: the p2p crate imports both `GossipEventSink` and the peer table types.

This branch has not been deployed

No deployments
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