Skip to content

test: forkchoice testing - #28

Merged
MegaRedHand merged 19 commits into
mainfrom
forkchoice-testing
Jan 14, 2026
Merged

MegaRedHand merged 19 commits into
mainfrom
forkchoice-testing

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

No description provided.

@MegaRedHand
MegaRedHand marked this pull request as ready for review January 14, 2026 22:06
@MegaRedHand
MegaRedHand merged commit 466d7bd into main Jan 14, 2026
@MegaRedHand
MegaRedHand deleted the forkchoice-testing branch January 14, 2026 22:06
tcoratger pushed a commit to tcoratger/ethlambda that referenced this pull request Oct 30, 2026
…ss#28)

* feat(p2p): count why peers drop us, not just that they did

`lean_peer_disconnection_events_total` is leanMetrics-specified down to its
four `reason` values, and they cannot carry this question: on the mainnet
follower 92% of outbound closes land in `error`, which says only that libp2p
handed back a cause. The reason a peer actually gave was thrown away twice
over, once by bucketing the `ConnectionClosed` cause to a string test and
discarding the cause itself, and once by logging the `goodbye/1` code at
`trace!` on a node that runs at `INFO`.

That mattered because the two readings a follower most needs to tell apart are
identical from the socket alone. A peer at its inbound cap and a peer that has
scored us badly or banned us both just close, and the response to each is the
opposite of the response to the other.

Two counters beside the specified one rather than more values inside it, the
same shape `lean_peer_connections_by_transport_total` already uses to avoid
putting ethlambda off-spec:

- `lean_peer_goodbye_total{reason}`, the only signal here that is not an
  inference. No `direction` label, since `goodbye/1` is registered inbound-only
  and this node never sends one.
- `lean_peer_disconnect_cause_total{direction,cause}`, read off the
  `ConnectionError` variant and an I/O error's `ErrorKind` rather than sniffed
  out of a `Display` string. Charged on the same event as the specified
  counter, so the two total alike and can be read against each other.

Both label sets are closed, with `other`/`io_other` as the residue: the goodbye
code is a `u64` off the wire, so a remote must not be able to choose this
node's metric cardinality. Only reason codes 1, 2 and 3 are the spec's; it
reserves [4, 127] and leaves 128 up to each client, so the four above 127
follow lighthouse's `GoodbyeReason`, which is what mainnet peers send.

The residue cannot be empty, because a QUIC application close arrives as an
opaque `io::Error`. The close now logs its cause at `DEBUG` so that message is
recoverable, which is the only place it exists.

* fix(p2p): read a close's cause through the muxer error that carries it

Measured on the mainnet follower, `lean_peer_disconnect_cause_total` put every
single close in `io_other`. The label read the outer `io::ErrorKind`, and
`StreamMuxerBox` wraps every muxer error in `io::Error::other`, so the kind is
`Other` for nearly every close and the reason sits one level down. Even a plain
TCP reset arrived that way, as mplex's `ConnectionReset` inside an `Other`.

The inner error is now downcast to the two types it can be, since the swarm
builder boxes each transport on its own:

- `libp2p::quic::Error`. Its `Connection` variant keeps quinn's error in a
  private field and forwards only `Display`, so that one is read off the text.
  It is safe to match because quinn writes a fixed prefix of its own ("closed
  by peer: ", "aborted by peer: ") ahead of anything the peer supplies, so a
  close reason cannot pass for another variant; a test pins that case.
- `Either<yamux::Error, io::Error>`, the TCP muxer selection. mplex reports
  plain I/O errors. yamux hides its variants but forwards `source` to the I/O
  error under an `Io` or `Decode` failure, so the chain is walked for a kind
  before falling back to a clean `yamux_closed`.

`either` becomes a direct dependency only to name that type: libp2p-core
builds its muxer error from it and does not re-export it. It is already in the
lockfile through libp2p, so nothing new is fetched.
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