Repository navigation
fix(p2p): wait out the peers' backoff before regrafting a left topic - #728
Conversation
A rust-libp2p peer that sees a mesh peer unsubscribe backs off from it for its prune backoff (60 s by default), and the leaver's PRUNE, which carries the shorter unsubscribe backoff (10 s by default), cannot shorten it. A node that leaves an aggregator subnet when its duty expires and rejoins it within that window grafts into peers that reject the graft and penalize it. On a 7-node all-ethlambda kurtosis devnet, every node penalized two peers at one epoch boundary for exactly this. Set both backoffs to the prune value, so the rejoining node waits as long as its peers do. The mesh on that subnet would stay empty for the window either way, since the early grafts are rejected; this only removes the penalty.
🤖 Claude Code ReviewReview of PR 728: gossipsub backoff alignment The change is small, correct for the problem it describes, and I have no blocking concerns. What it does
Correctness
Nits (non-blocking)
The code and docs are consistent, and the PR is ready to merge as is. I did not build or run the tests, and I did not independently check the claim about rust-libp2p's receiver-side handling at rev Automated review by Claude (Anthropic) · sonnet · custom prompt |
🤖 Kimi Code ReviewI'll review this PR focusing on the areas you specified. This is a targeted change to gossipsub backoff parameters with documentation and test updates. Overall AssessmentThe PR is conceptually sound and addresses a real interoperability issue with Lighthouse/rust-libp2p peers. The core logic is correct: aligning However, I found several issues ranging from documentation clarity to test robustness. Detailed Findings1. Constant naming is misleading (crates/net/p2p/src/lib.rs:820-833)The doc comment and constant name
The comment acknowledges they're "the same on purpose" but the naming suggests they serve the same purpose. This hurts maintainability. Suggestion: Rename to 2. Test assertion is weaker than the documented intent (crates/net/p2p/src/lib.rs:3378-3385)assert!(config.unsubscribe_backoff() >= peers_backoff);
assert!(config.unsubscribe_backoff() >= config.prune_backoff());The test name says "waits out the backoff peers apply to a leaver," but the first assertion only checks Line 3382: This should assert exact equality or at least verify the specific expected duration: assert_eq!(config.unsubscribe_backoff(), Duration::from_secs(60));
assert_eq!(config.prune_backoff(), Duration::from_secs(60));The current 3. Missing test for the actual penalty scenario (crates/net/p2p/src/lib.rs:3378-3385)The test validates configuration values but doesn't test the behavioral outcome: that rejoining after the configured backoff avoids penalties. Consider adding an integration-level test or at least a comment referencing where this is tested (if anywhere). If no such test exists, this is a gap—configuration correctness doesn't guarantee the gossipsub state machine behaves as expected. 4. Documentation inconsistency (docs/beacon_wire.md:299-312)The new section states:
But the rust-libp2p Risk: If rust-libp2p changes defaults, the documentation becomes incorrect without any code change in this repo. Suggestion: Add a code comment or doc note that these defaults are rust-libp2p version-dependent, or reference the specific rust-libp2p version verified. 5. No
|
| Item | Severity | File | Lines | Issue |
|---|---|---|---|---|
| 1 | Low | lib.rs |
820-833 | Misleading constant name |
| 2 | Medium | lib.rs |
3382-3383 | Weak test assertions (>= vs ==) |
| 3 | Low | lib.rs |
3378-3385 | Missing behavioral test |
| 4 | Low | beacon_wire.md |
299-312 | Implicit dependency on external defaults |
| 6 | Informational | lib.rs |
820-833 | Timing analysis could be explicit |
Recommended changes before merge:
- Item 2: Strengthen test assertions to exact equality
- Item 1: Consider renaming
PRUNE_BACKOFFto clarify dual use
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🤖 Codex Code ReviewLooks good overall. The change is small, targeted, and the reasoning matches libp2p gossipsub behavior for unsubscribe vs prune backoff.
I did not spot:
If you want, I can also do a second pass specifically on whether the 60s constant should be derived from libp2p defaults or exposed as a named networking parameter. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🗒️ Description / Motivation
On a 7-node all-ethlambda kurtosis devnet (
ethlambda-all.yamlfrom our ethereum-package fork, image 1.0.0), every CL logged this at the start of epoch 5, against the same two peers:The two penalized nodes had each left an attestation subnet when an aggregator duty expired at the end of epoch 4. At the epoch 5 boundary, the VC's duties for epoch 6 asked for that subnet again, and the node rejoined about 23 s (and, on the other node, 55 s) after leaving. The trigger is a duty on a subnet in the last slots of epoch N, none in N+1, and one in N+2.
The cause is an asymmetry in rust-libp2p gossipsub (verified at our pinned rev
2f14d0e):unsubscribe_backoff; won't graft for that longremove_peer_from_mesh(.., None, ..);Nonemeansprune_backoff. Backoffs are only ever extended, so the PRUNE's shorter value can't shorten itSo the leaver regrafts after 10 s into peers still holding a 60 s backoff. They reject the graft and add a behaviour penalty (P7).
What Changed
crates/net/p2p/src/lib.rs: aPRUNE_BACKOFFconstant (60 s, gossipsub's default) set as bothprune_backoffandunsubscribe_backoffingossipsub_config, so this node waits as long after leaving a topic as its peers do. Plus a unit test checking that the unsubscribe backoff is at least a default peer's prune backoff.docs/beacon_wire.md: a third "difference from lighthouse" bullet in Peer scoring explaining the choice.Correctness / Behavior Guarantees
What the specs say
v1.7.0-beta.2,phase0/p2p-interface.md) fixesD,D_low,D_high,D_lazy, the heartbeat,fanout_ttl,mcache_*andseen_ttl, and leaves gossipsub v1.1's extra parameters (backoffs, scoring) "under investigation". For subnets, it asks forSUBNETS_PER_NODElong-lived subscriptions held forEPOCHS_PER_SUBNET_SUBSCRIPTIONepochs. An aggregator joins its duty's subnet (phase0/validator.md), but nothing says how long to stay.PruneBackoffand a 10 sUnsubscribeBackoff, and expects the leaver's PRUNE to carry the shorter one. rust-libp2p's receiver doesn't honor it, which is the asymmetry above.UNSUBSCRIBE_AFTER_AGGREGATOR_DUTY).An alternative is to keep an expired aggregator subnet until the next epoch's duty refresh, so the leave never happens in the first place. I didn't take it because it changes subscription logic rather than one parameter, and still leaves other leave-and-rejoin paths exposed.
Tests Added / Run
rejoining_a_topic_waits_out_the_backoff_peers_apply_to_a_leaver(new)cargo test -p ethlambda-p2p --lib --profile release-fast: 361 passed, 1 ignored (an existing devnet3 fixture)make fmt,make lintRelated Issues / PRs
ethlambda-all.yamlafter lambdaclass/ethereum-package@1b7aa4d6, which lets an ethlambda CL be the bootnode.✅ Verification Checklist
make fmt— cleanmake lint(clippy with-D warnings) — cleanmake test(test-consensusplustest-node, atrelease-fast) — all passing (not run: onlyethlambda-p2p's lib tests above)