| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Add an experimental, feature-gated ("ethp2p", off by default) path that
also broadcasts gossip through ethp2p-rs's Reed-Solomon broadcast engine
over a parallel QUIC network, alongside libp2p gossipsub (unchanged).
ethlambda <-> ethlambda only; coexists with gossipsub so mixed-client
devnets keep working.
- crates/net/p2p/src/ethp2p: the Ethp2pBroadcast adapter over ethp2p-rs's
QuicNet transport + broadcast Engine, peer-id derivation
(sha256(secp256k1 pubkey)[..8]), the engine task, and delivery dispatch.
- publish_block/attestation/aggregated tee the same snappy-compressed SSZ
to the engine, keyed by sha256(ssz); reconstructed messages are decoded
and fed into the same blockchain.new_* handlers gossipsub uses (block
import and attestation handling are idempotent, so dual delivery is safe).
- peers are derived from the static bootnodes (ethp2p QUIC port = gossipsub
port + 1); the engine runs in a background task that forwards
reconstructed messages into the P2P actor.
Validated by compilation (feature on and off), clippy -D warnings, fmt, and
an isolated QUIC round-trip test. End-to-end (devnet) validation is a
follow-up. ethp2p-rs (internal repo) is pinned to rev ba3ed8b; building the
feature needs git access to it plus protoc on the build host.
🤖 Kimi Code ReviewReview: Experimental ethp2p broadcast feature (PR #466) This PR introduces an experimental, feature-gated (ethp2p) erasure-coded broadcast layer using Reed-Solomon over QUIC, running parallel to libp2p gossipsub. The implementation is clean and well-documented for devnet/experimental use. Below are specific concerns and recommendations. 1. Critical: Port arithmetic overflow (Potential runtime misconfiguration)File: crates/net/p2p/src/lib.rs Using wrapping_add(1) to derive the ethp2p QUIC port from the gossipsub port can silently wrap to port 0 if the gossip port is 65535, or to port 1 if the gossip port is 0 (ephemeral). This breaks the static mesh assumption that peers listen on bootnode.quic_port + 1. Recommendation: Use checked arithmetic or explicit bounds checking: let ethp2p_port = bootnode.quic_port.checked_add(1)
.expect("ethp2p port overflow: gossip port must be < 65535");2. Consensus safety: Idempotency assumptionFile: crates/net/p2p/src/ethp2p/mod.rs The comment states "Dual delivery is safe: block import and attestation handling are idempotent." This is a critical invariant. If the same attestation arrives via both gossipsub and ethp2p and the import logic is not strictly idempotent (e.g., double-counting in aggregation bits or reward accounting), this could cause consensus failures. Recommendation: Add a debug assertion or metric to detect duplicate deliveries in the blockchain adapter, validating the idempotency claim during testing. 3. Performance: Unnecessary payload cloningFile: crates/net/p2p/src/gossipsub/handler.rs The code clones the compressed payload to create ethp2p_payload before sending to the unbounded channel. This doubles memory pressure temporarily. Recommendation: Since publish_bytes takes &[u8] and the UnboundedSender owns the data via Vec<u8>, move the original compressed Vec into the PublishCmd instead of cloning, or use Arc<[u8]> if both paths must retain ownership. 4. Error handling: Silent dropping of delivered messagesFile: crates/net/p2p/src/ethp2p/mod.rs In dispatch_delivered, decode failures and blockchain forward failures are logged but dropped. While consistent with gossipsub behavior, ethp2p's Reed-Solomon layer guarantees data integrity, so decode failures indicate corruption or spec version mismatch that might warrant different handling than network noise. Recommendation: Consider incrementing a metric (e.g., ethp2p_decode_failures_total) to distinguish ethp2p delivery failures from gossipsub noise in monitoring. 5. Code quality: Peer ID derivationFile: crates/net/p2p/src/ethp2p/mod.rs bytes.copy_from_slice(&digest[..8]);This panics if digest is shorter than 8 bytes (impossible for SHA-256, but fragile). Recommendation: Use explicit conversion for clarity: u64::from_be_bytes(digest[..8].try_into().expect("sha256 output length"))6. Code quality: Hex encoding inefficiencyFile: crates/net/p2p/src/ethp2p/mod.rs digest.iter().map(|b| format!("{b:02x}")).collect()This allocates a String per byte and is O(n²) due to repeated concatenation. Recommendation: Use hex::encode(digest) (already a dependency via ethrex-common or similar) or const-hex. 7. Build/Dependency concernsFile: crates/net/p2p/Cargo.toml The feature depends on an internal git repo (ethp2p-rs) requiring credentials and protoc at build time. The lockfile pins to rev = "ba3ed8b". Recommendation: Document the protoc requirement in the crate-level README or building instructions. Ensure CI has CARGO_NET_GIT_FETCH_WITH_CLI=true set as noted. 8. Minor: Static channel stringsFile: crates/net/p2p/src/ethp2p/mod.rs Channel IDs ("block", "aggregation", "attestation") are hardcoded strings. If these drift from the gossipsub topic names, messages will be delivered to the wrong handlers. Recommendation: Define these as constants shared with the gossipsub module or derive them from the topic generators to ensure they remain synchronized. SummaryThe implementation is sound for an experimental feature. Item 1 (port overflow) should be fixed before any production-adjacent deployment. Item 2 (idempotency) should be verified with a test or assertion. The remaining items are optimizations or hardening suggestions. The feature gating is correct, memory safety is maintained, and the integration with the actor model is idiomatic. Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt |
Sorry, something went wrong.
🤖 Codex Code ReviewFindings
I did not run a full build in this environment: cargo check -p ethlambda-p2p --all-targets failed before compilation because rustup could not create temp files under /home/runner/.rustup due the read-only filesystem. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Sorry, something went wrong.
Greptile SummaryThis PR adds an experimental, feature-gated (ethp2p) path that tees gossipsub broadcasts through ethp2p-rs's Reed-Solomon erasure-coded broadcast engine over a parallel QUIC network; the default build is completely unaffected.
Confidence Score: 4/5Safe to merge for the default (feature-off) build; the ethp2p feature path has a few rough edges worth addressing before enabling in a real devnet. The gossipsub path is byte-for-byte unchanged and the feature gate is consistent throughout. The three issues found — silent port wraparound via wrapping_add(1), double RS-encoding per publish, and the unguarded spin risk on persistent run_one_step errors — all live exclusively inside the ethp2p feature path that is off by default. crates/net/p2p/src/ethp2p/mod.rs (double RS encode, select-loop spin risk) and crates/net/p2p/src/lib.rs (port wraparound in both the local bind address and bootnode peer addresses). Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App as Application
participant GH as gossipsub/handler.rs
participant P2PS as P2PServer (actor)
participant ETX as ethp2p_tx (UnboundedSender)
participant ET as run_engine_task
participant ENG as Ethp2pBroadcast (Engine)
participant QUIC as QUIC Network
participant ACT as P2PServer actor (delivery)
participant BC as Blockchain
App->>P2PS: PublishBlock / PublishAttestation / PublishAggregatedAttestation
P2PS->>GH: publish_block / publish_attestation / publish_aggregated_attestation
GH->>GH: SSZ encode + snappy compress
GH-->>QUIC: swarm_handle.publish() (gossipsub, unchanged)
GH->>ETX: "send(PublishCmd{channel, message_id, payload})"
ETX-->>ET: publish_rx.recv()
ET->>ENG: publish_bytes(channel, msg_id, payload)
ENG->>ENG: rs_encode (preamble) + RsStrategy::new_origin (encode again)
ENG->>QUIC: broadcast RS shards via QUIC
QUIC-->>ENG: inbound RS shards from peers
ET->>ENG: run_one_step() [drives engine]
ENG-->>ET: DeliveredMessage (on delivery_rx)
ET->>ACT: actor.send(WrappedEthp2pDelivery)
ACT->>ACT: dispatch_delivered: decompress + SSZ decode
ACT->>BC: blockchain.new_block / new_attestation / new_aggregated_attestation (idempotent)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App as Application
participant GH as gossipsub/handler.rs
participant P2PS as P2PServer (actor)
participant ETX as ethp2p_tx (UnboundedSender)
participant ET as run_engine_task
participant ENG as Ethp2pBroadcast (Engine)
participant QUIC as QUIC Network
participant ACT as P2PServer actor (delivery)
participant BC as Blockchain
App->>P2PS: PublishBlock / PublishAttestation / PublishAggregatedAttestation
P2PS->>GH: publish_block / publish_attestation / publish_aggregated_attestation
GH->>GH: SSZ encode + snappy compress
GH-->>QUIC: swarm_handle.publish() (gossipsub, unchanged)
GH->>ETX: "send(PublishCmd{channel, message_id, payload})"
ETX-->>ET: publish_rx.recv()
ET->>ENG: publish_bytes(channel, msg_id, payload)
ENG->>ENG: rs_encode (preamble) + RsStrategy::new_origin (encode again)
ENG->>QUIC: broadcast RS shards via QUIC
QUIC-->>ENG: inbound RS shards from peers
ET->>ENG: run_one_step() [drives engine]
ENG-->>ET: DeliveredMessage (on delivery_rx)
ET->>ACT: actor.send(WrappedEthp2pDelivery)
ACT->>ACT: dispatch_delivered: decompress + SSZ decode
ACT->>BC: blockchain.new_block / new_attestation / new_aggregated_attestation (idempotent)
Comments Outside Diff (2)
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
crates/net/p2p/src/lib.rs:1287-1290
**Silent port-zero wraparound for QUIC bind and dial addresses**
Both the local bind address and each bootnode's dial address use `wrapping_add(1)` on a `u16` port. If a gossipsub port is configured as `65535`, the ethp2p port becomes `0`. For the local bind (`SocketAddr::new(ip, 0)`), the OS interprets port 0 as "assign an ephemeral port", so the node silently listens on an unknown, unannounced port. For bootnode dial addresses, port 0 as a destination will fail to connect. Both failures are silent — no error is returned from `build_swarm`. A `checked_add(1)` with an explicit panic or propagated error would make misconfiguration immediately visible rather than degrading quietly.
### Issue 2 of 3
crates/net/p2p/src/ethp2p/mod.rs:188-199
**Payload is Reed-Solomon encoded twice per `publish_bytes` call**
`rs_encode(payload, &self.config)` encodes the full payload into preamble + shards, but only `preamble` is used — `_shards` is dropped. Then `RsStrategy::new_origin(payload, self.config)` is called on the same `payload`, which internally performs the same RS encoding again to produce the shards it will actually broadcast. For a 64 KiB block this doubles the encoding CPU cost per publish. Consider whether `ethp2p-rs` exposes a constructor that accepts an already-encoded `(preamble, shards)` pair so the work can be shared, or whether `new_origin` can return the preamble directly.
### Issue 3 of 3
crates/net/p2p/src/ethp2p/mod.rs:989-1031
**Two concerns in the `select!` loop: cancel safety and persistent error spin**
First, `tokio::select!` cancels the non-selected futures on each iteration. If `run_one_step` is not cancel-safe — for example, if it reads a QUIC chunk and then yields before integrating it into the engine state — cancellation when a `PublishCmd` or a delivered message arrives could silently drop in-flight chunks and corrupt RS reassembly. `tokio::select!` documentation requires callers to verify cancel-safety; this should be confirmed against the `ethp2p-rs` engine's guarantee.
Second, if `run_one_step` returns errors synchronously (e.g., after the QUIC transport is permanently broken), the loop will spin at full CPU speed while printing `warn!` on every iteration, because there is no `break` or backoff path for repeated errors — only the `publish_rx` and `delivered_rx` `None` branches exit the loop. Adding a consecutive-error counter with a break or an exponential back-off would avoid CPU saturation on transport failure.
Reviews (1): Last reviewed commit: "feat(p2p): experimental ethp2p erasure-c..." | Re-trigger Greptile |
Sorry, something went wrong.
| let (preamble, _shards) = | ||
| rs_encode(payload, &self.config).map_err(|e| Ethp2pError::Encode(format!("{e:?}")))?; | ||
| let mut preamble_bytes = Vec::with_capacity(preamble.encoded_len()); | ||
| preamble | ||
| .encode(&mut preamble_bytes) | ||
| .map_err(|e| Ethp2pError::Encode(e.to_string()))?; | ||
| let strategy = RsStrategy::new_origin(payload, self.config) | ||
| .map_err(|e| Ethp2pError::Encode(format!("{e:?}")))?; | ||
| self.engine | ||
| .publish( | ||
| &channel.to_string(), | ||
| message_id.to_string(), |
There was a problem hiding this comment.
Payload is Reed-Solomon encoded twice per publish_bytes call
rs_encode(payload, &self.config) encodes the full payload into preamble + shards, but only preamble is used — _shards is dropped. Then RsStrategy::new_origin(payload, self.config) is called on the same payload, which internally performs the same RS encoding again to produce the shards it will actually broadcast. For a 64 KiB block this doubles the encoding CPU cost per publish. Consider whether ethp2p-rs exposes a constructor that accepts an already-encoded (preamble, shards) pair so the work can be shared, or whether new_origin can return the preamble directly.
Prompt To Fix With AIThis is a comment left during a code review.
Path: crates/net/p2p/src/ethp2p/mod.rs
Line: 188-199
Comment:
**Payload is Reed-Solomon encoded twice per `publish_bytes` call**
`rs_encode(payload, &self.config)` encodes the full payload into preamble + shards, but only `preamble` is used — `_shards` is dropped. Then `RsStrategy::new_origin(payload, self.config)` is called on the same `payload`, which internally performs the same RS encoding again to produce the shards it will actually broadcast. For a 64 KiB block this doubles the encoding CPU cost per publish. Consider whether `ethp2p-rs` exposes a constructor that accepts an already-encoded `(preamble, shards)` pair so the work can be shared, or whether `new_origin` can return the preamble directly.
How can I resolve this? If you propose a fix, please make it concise.
Sorry, something went wrong.
🤖 Claude Code ReviewNow I have enough data to synthesize. Let me produce the final review. Review: feat(p2p): experimental ethp2p erasure-coded broadcast (off by default)This PR adds a feature-gated (ethp2p cargo feature, off by default) adapter that tees each gossipsub publish to a parallel Reed–Solomon broadcast engine running over its own QUIC network. The feature-gating hygiene is solid — every #[cfg(feature = "ethp2p")] guard is present and the default build is untouched. The select-loop shutdown on channel close is correct. The dispatch symmetry (gossipsub path → same blockchain handlers) and idempotent block/attestation import correctly protect against dual delivery. Four findings survive verification: crates/net/p2p/src/ethp2p/mod.rs:882–900 — publish_bytes encodes the payload twice, discarding the first result rs_encode(payload, &self.config) is called and _shards is immediately discarded; then RsStrategy::new_origin(payload, self.config) is called on the same payload, which must re-encode internally (it builds an origin session from the raw bytes). The discarded _shards variable name confirms this is unintentional dead work. For a 64 KB block this is 2× the GF(2⁸) RS computation on every publish, paid in the actor task context. Depending on whether engine.publish expects the preamble to match what new_origin will produce independently, there may also be a latent consistency hazard if the two encode calls disagree on shard count. crates/net/p2p/src/ethp2p/mod.rs:989–1031 — run_one_step errors spin the select loop at 100% CPU indefinitely When broadcast.run_one_step() returns Err, the loop logs a warning and immediately re-enters tokio::select!. If the engine enters a persistent error state where run_one_step completes without suspending (e.g. internal QUIC socket closed, internal channel broken), the run_one_step arm is always immediately ready and wins the select on every iteration. publish_rx and delivered_rx are starved; the task burns 100% of a tokio worker thread logging warnings with no backoff, no break, and no exit until the P2P actor drops publish_rx. Add a consecutive-error counter with a cap and break (or an exponential backoff sleep) to bound the damage. crates/net/p2p/src/lib.rs:1286–1306 — wrapping_add(1) silently produces port 0 when the gossipsub port is 65535 Both the local bind address and each peer's dial address are derived as gossipsub_port.wrapping_add(1). On a u16, 65535u16.wrapping_add(1) == 0. Port 0 is valid for OS-assigned ephemeral binding but is an illegal destination for an explicit QUIC connect. The engine would attempt to dial peer_ip:0 and either fail silently or connect to the wrong endpoint. Use checked arithmetic (port.checked_add(1).expect("ethp2p port overflow")) or validate at config parse time. crates/net/p2p/src/gossipsub/handler.rs:1165–1171, 1208–1214, 1248–1254 — silent channel-closed failure violates CLAUDE.md observability pattern All three publish tee paths do: let _ = tx.send(crate::ethp2p::PublishCmd { ... });UnboundedSender::send returns Err only when the engine task has exited. Discarding that error means: if the engine crashes (QUIC bind failure, internal panic, etc.), every subsequent publish silently fails with zero log output on the sender side. The node continues healthy on gossipsub while ethp2p is dead and invisible to operators. CLAUDE.md (project root) is explicit: "AVOID: Using if let Err when only performing side effects" and "Use inspect_err for side-effect-only error handling." The inbound dispatch_delivered path in the same file correctly uses inspect_err for all three message types. Fix: tx.send(crate::ethp2p::PublishCmd { ... })
.inspect_err(|_| warn!("ethp2p: publish channel closed, engine stopped"));Minor — crates/net/p2p/src/ethp2p/mod.rs:982–987 — verbose tracing field form (CLAUDE.md) info!(
local_peer = params.local_peer,
peers = params.peers.len(),
...CLAUDE.md quotes slot = %slot as the ❌ BAD verbose form. Extract locals before the macro call: let local_peer = params.local_peer;
let peer_count = params.peers.len();
info!(local_peer, peer_count, bind = %params.bind_addr, "ethp2p broadcast engine started");Automated review by Claude (Anthropic) · sonnet · custom prompt |
Sorry, something went wrong.
…p-rs path dependency, and add success-path logging so the broadcast path is observable at runtime. ethp2p-rs is a private repo, so it is consumed as a path dependency pointing at a vendored copy that is kept out of git (and out of this workspace) but copied into the Docker build context: - bin/ethlambda: add a forwarding `ethp2p` feature so `--features ethp2p` on the binary enables `ethlambda-p2p/ethp2p` (the build targets the binary, which did not previously re-export the p2p crate's feature). - Dockerfile: install protobuf-compiler (ethp2p-broadcast's build.rs compiles its .proto via prost-build) and COPY the vendored ethp2p-rs before `cargo chef cook` so the optional dependency's manifest resolves even with the feature off. - crates/net/p2p: depend on ethp2p-broadcast / ethp2p-transport via the ethp2p-rs path dependency; pin prost to match. - root Cargo.toml / .dockerignore / .gitignore: exclude the vendored ethp2p-rs from this workspace and from git, but allow it into the Docker build context. - crates/net/p2p ethp2p adapter: log on the success path (info! on a delivered message with channel + byte sizes, debug! on a successful publish). The stack otherwise logs only on failure, so this is the only runtime proof that payloads cross the erasure-coded mesh.
…and record the 3-node devnet validation.
The experimental ethp2p path now exports lean_ethp2p_mesh_peers (configured
mesh size) and lean_ethp2p_messages_total{channel,direction}, so its traffic
is distinguishable from gossipsub in Prometheus.
The P2P actor starts the broadcast engine only when at least MIN_ETHP2P_PEERS
mesh peers are configured; otherwise the node relies solely on gossipsub (no
engine, no publish channel, no per-message tee). This is a startup guard, not
a live circuit-breaker: the demo QUIC transport emits no PeerDisconnected
events, so runtime peer loss is not observable on the ethlambda side — a true
breaker needs the deferred transport-side hardening (Phase 6, cross-repo).
The module doc records the end-to-end run on a 3-node ethlambda devnet (all
gossip channels carried over the parallel QUIC mesh while the chain finalized)
and the transport's isolated-devnet-only limitations.
The branch shipped ethp2p-broadcast/-transport as a `path` dep into a gitignored, vendored ethp2p-rs/. Cargo reads a path dep's manifest during resolution even when its feature is off, so a clean checkout (no vendored copy) failed to resolve the whole workspace, breaking `make lint`. ethp2p-rs is a public repo, so pin the two crates to a git rev instead: resolution needs no credentials and no vendored copy, matching how the existing libp2p/ethrex git deps already work. Drop the now-dead local vendoring plumbing (Dockerfile COPY, .dockerignore un-ignore, workspace exclude, .gitignore entry).
Add a third build matrix variant that compiles the binary with FEATURES=ethp2p, so each published tag gets an "-ethp2p" twin alongside the regular and "-shadow" images. This lets us canary the experimental ethp2p erasure-coded broadcast path in a devnet without a bespoke build: protoc is already in the build image and the ethp2p-rs git dep is public, so the variant only needs the feature flag and can build --locked. The manifest step's suffix loop is kept in sync with the matrix.
…3rd) Rework the previous "-ethp2p" third variant: instead compile BOTH matrix variants with the ethp2p feature, so a workflow_dispatch on this branch publishes exactly two images, a regular `:ethp2p` and a Shadow-compatible `:ethp2p-shadow` (shadow-integration + ethp2p). protoc is already in the build image and the ethp2p-rs git dep is public, so the regular variant only adds FEATURES=ethp2p and builds --locked; the shadow variant adds ethp2p to shadow-integration and stays unlocked (the quinn-udp [patch] is absent from Cargo.lock; see Dockerfile).
…tures
Replace the branch-specific hardcoding of ethp2p with a parameterized
`features` workflow_dispatch input. A compose step appends the input to each
variant's mandatory base features (empty for the regular image,
`shadow-integration` for the shadow image), so:
gh workflow run docker_publish.yaml --ref <branch> \
-f features=ethp2p -f tags=ethp2p
publishes `:ethp2p` (regular + ethp2p) and `:ethp2p-shadow`
(shadow-integration + ethp2p). On push to main the input is empty, so the
built features are unchanged and this stays safe to merge.
…lomon strategy once and serialize its preamble instead of encoding the payload a second time on every publish; extract a single crate::ethp2p::tee() helper so the three gossipsub publish handlers each tee in one line (and clone the payload only when the engine is running); use hex::encode for message ids; collapse the publish result match to inspect/inspect_err; and name the gossipsub->ethp2p port offset as ETHP2P_PORT_OFFSET.
unreachable mesh peer no longer disables the whole engine. Ethp2pBroadcast::start dialed peers sequentially with `?`, so the first peer whose ethp2p endpoint was not reachable at startup (still binding, or a client not running ethp2p) aborted the engine after the transport's long QUIC connect timeout, disabling ethp2p even between reachable peers. Dial all peers concurrently under a short PEER_DIAL_TIMEOUT and log-and-continue past failures, so reachable peers (notably other ethlambda nodes) still form the mesh. Validated on a 2-ethlambda + 2-zeam devnet: the ethlambda<->ethlambda mesh carried blocks and attestations while the unreachable zeam peers were skipped and the chain finalized.
…411) Bump the ethp2p-rs pin from the demo transport (d03d2ba) to the spec-conformant wire (3d23411: per-protocol BCAST/SESS/CHUNK streams over QUIC + TLS 1.3, ALPN eth-ec-broadcast) and adapt the adapter to the new transport model: - QuicNet::bind no longer takes a local peer id, and connect(addr) mints a local per-connection peer id instead of asserting a globally-derived one. The engine now routes by those transport-minted ids, and every established connection (dialed or accepted) surfaces as a PeerConnected event that drives the BCAST handshake — so the old per-peer engine.connect loop is removed (its pre-derived u64s no longer match the transport's ids). - Because peer ids are minted, the per-remote-peer derive_peer_id was dead: reduce the mesh peer list from Vec<(u64, Option<SocketAddr>)> to plain Vec<SocketAddr> dial addresses. derive_peer_id is kept only for this node's own stable engine id (handshake string + logs), and its doc is updated to say so. - Build the engine via Engine::with_config(EngineConfig::default(), TokioClock) so it consumes the injected clock (session GC, etc.). - Refresh the module docs: the transport now emits PeerDisconnected (the engine acts on it internally), identity is the reference's self-asserted peer_id string (SPKI pinning is a later hardening slice), and the wire is spec-conformant rather than demo-only. The in-crate gate test round_trip_block_sized_payload_over_quic passes on the new wire. Wire compatibility note: this is an on-wire break versus the demo transport — a node on the old pin cannot mesh with one on the new pin over ethp2p (gossipsub is unaffected). Acceptable for an off-by-default experiment. Identity deviation from the plan: the plan called for the wire identity to become hex of the compressed secp256k1 pubkey. The receiving engine does not read the handshake peer_id string yet (it is verified only once the Hardened auth slice lands), so switching it now would be cosmetic; deferred to that slice, where it is actually checked.
The engine already tracks its live-session count for cleanup, but nothing surfaced it, so the "session count should plateau once GC keeps pace" property was not observable at runtime. Expose Ethp2pBroadcast::active_session_count (delegating to the engine) and sample it into a new lean_ethp2p_active_sessions gauge after each serviced event in the engine task. A monotonic climb now signals that cleanup is falling behind; a plateau confirms GC is healthy. Also correct the stale mesh-peers gauge doc: the transport does emit PeerDisconnected now (the engine consumes it internally); the gauge is static only because it is set once at startup and a live health signal is a follow-up.
PR #20 landed the spec-conformant transport on ethp2p-rs main (squash-merged as d852f23). Move the pin off the pre-merge branch commit (3d23411) to that main commit, which additionally brings in the hardening added after the Slice-7 bump: reconstruct-reset, per-peer bounded queues with backpressure, opt-in peer-id pinning, and — importantly — the fix for a load-dependent livelock where a backpressured chunk send spun drain_session without yielding. Pinning to a commit on main (rather than a branch that may be deleted) also gives the git dependency stable provenance. No adapter changes: the new transport APIs are additive, so bind/connect/Net::send/events are unchanged. The in-crate round-trip test and the default feature-off build both pass.
| Back | FazBrowse Home | New Git URL |
🗒️ Description / Motivation
Adds an experimental, feature-gated path that also broadcasts gossip through ethp2p-rs's Reed–Solomon erasure-coded broadcast engine over a parallel QUIC network, alongside libp2p gossipsub. It's a step toward evaluating erasure-coded broadcast for block/data propagation in ethlambda.
What Changed
Spec-wire migration (commit a74db10)
Bumps the ethp2p-rs pin from the demo transport to the spec-conformant wire (lambdaclass/ethp2p-rs#20) and adapts the adapter to the new transport model:
On-wire break: a node on the old pin cannot mesh with one on the new pin over ethp2p (the transport framing changed entirely). Gossipsub is unaffected. Acceptable for an off-by-default experiment.
Identity note: the wire identity is still the reference's self-asserted peer_id string. ethp2p-rs now offers opt-in dial-time peer-id pinning (connect_expecting), but the ethlambda adapter does not use it yet; wiring it (identity = hex of the compressed secp256k1 pubkey, pinned per bootnode) is a follow-up.
Re-pin to merged main (commit ca2a7cd)
PR #20 merged the spec transport to ethp2p-rs main. The pin moved from the pre-merge branch commit to main (d852f23), which also brings in the hardening added after the migration:
No adapter changes were needed — the new transport APIs are additive.
Correctness / Behavior Guarantees
Tests Added / Run
Devnet validation
All on a 3-node ethlambda devnet (feature ethp2p), 20 slots:
Related Issues / PRs
✅ Verification Checklist
Notes for reviewers
ethp2p-rs is a public repo, so resolving the git deps needs no credentials; building with the ethp2p feature also needs protoc on the build host (ethp2p-broadcast compiles its .proto via prost-build). The default feature-off build needs neither. Reviewer focus: the feature-gating hygiene and the publish-tee / receive-inject symmetry.