Skip to content

refactor(l1): drop the unused discovery lookup interval config - #7163

Merged
MegaRedHand merged 3 commits into
mainfrom
chore/drop-unused-discovery-lookup-interval
Aug 19, 2026
Merged

MegaRedHand merged 3 commits into
mainfrom
chore/drop-unused-discovery-lookup-interval

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

Motivation

DiscoveryConfig::initial_lookup_interval is dead data. #6511 (kademlia
k-bucket routing table v2) replaced the configurable bound in
DiscoveryServer::get_lookup_interval with its own constants, because
each iterative lookup emits ~16 FindNode messages instead of 1 and
needed a rescaled schedule:

         lookup_interval_function(
             peer_completion,
-            self.config.initial_lookup_interval,
-            super::LOOKUP_INTERVAL_MS,
+            ITERATIVE_LOOKUP_INITIAL_MS,   // 500.0
+            ITERATIVE_LOOKUP_INTERVAL_MS,  // 10_000.0
         )

The field itself survived, still threaded from --p2p.lookup-interval
through P2PContext and start_network into a DiscoveryServer that
never reads it. The only live consumer of the flag is the RLPx
initiator, so the help text was also misleading.

While tracing it, a second problem turned up: impl Default for Options
sets lookup_interval: Default::default(), which is 0.0 for f64
rather than the INITIAL_LOOKUP_INTERVAL_MS that clap uses for the
flag. default_l1(), default_l2() and the L2 node options all build
on that impl, so they hand the RLPx initiator a 0ms lower bound. With no
peers connected the easing curve returns Duration::ZERO, and
handle_look_for_peer re-arms its timer with no delay at all. Present
since #5518 added the flag.

Description

  • Remove initial_lookup_interval from DiscoveryConfig and stop
    overriding it in start_network. DiscoveryConfig's Default impl
    goes with it: both remaining fields are set explicitly at the single
    construction site, and clap already defaults them to true.
  • Reword --p2p.lookup-interval's help to describe what it actually
    controls, and refresh the now-stale doc comments on
    INITIAL_LOOKUP_INTERVAL_MS / LOOKUP_INTERVAL_MS /
    lookup_interval_function.
  • Default Options::lookup_interval to INITIAL_LOOKUP_INTERVAL_MS.

No behavior change for the discovery server, which was already running
on its own constants. The second commit does change behavior for the
dev/L2 paths that go through Options::default(), restoring the 100ms
floor they were meant to have.

Checklist

  • Updated STORE_SCHEMA_VERSION (crates/storage/lib.rs) if the PR includes breaking changes to the Store requiring a re-sync.

`DiscoveryConfig::initial_lookup_interval` stopped being read in #6511,
which gave iterative lookups their own 500ms -> 10s bounds because each
one emits ~16 FindNode messages instead of 1. The field kept being
threaded from the CLI into the discovery server, where nothing looked at
it.

`--p2p.lookup-interval` still governs the RLPx initiator's dial pacing,
which is what its help text now says.
`Options::default()` left `lookup_interval` at f64's default of 0.0.
Every path built from it (`default_l1`, `default_l2`, the L2 node
options) therefore handed the RLPx initiator a 0ms lower bound, so with
no peers connected the easing curve returns a zero delay and the
initiator re-arms its timer in a tight loop. Use the same constant clap
uses for the flag.
@MegaRedHand
MegaRedHand requested a review from a team as a code owner August 19, 2026 18:51
Copilot AI lite review requested due to automatic review settings August 19, 2026 18:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

⚠️ Known Issues — intentionally skipped tests

Source: docs/known_issues.md

rpc-compat log-bearing cases excluded

Where: KNOWN_EXCLUDED_TESTS in .github/scripts/check-hive-results.sh counts out
eight hive rpc-compat cases — the four eth_getLogs cases, eth_getBlockReceipts/get-block-receipts-latest,
and three eth_getTransactionReceipt cases. They are exactly the cases whose recorded
response contains at least one log object; every case with an empty log array still runs.
Note this leaves eth_getLogs with no rpc-compat coverage at all, since all four of its
cases are in the set.

Why: ethrex populates blockTimestamp on log objects, as geth, besu, nethermind, reth
and erigon all do. hive's rpc-compat compares responses byte-exactly (jsondiff.FullMatch;
the lenient checkJSONStructure path applies only to cases upstream marks speconly), and
the corpus is pinned to execution-apis d08382ae (2025-02-10), whose recordings predate the
field — it entered the schema in execution-apis#639 and the fixtures in #846 (2026-07-22).
So the extra key cannot match, and this is a property of the pin rather than of the response.

The pin cannot move, and this is not temporary. The pin sits one commit before
execution-apis#627, which moved the test chain to a pre-merge genesis: the current corpus has
~36 proof-of-work blocks before its terminal total difficulty. ethrex does not support
pre-merge chains and will not, so importing that chain.rlp fails at block 1 —
validate_block_header has no pre-London base-fee path. Every revision carrying
blockTimestamp in its fixtures also carries that chain, so there is no revision that
satisfies both. Nor can the corpus be patched locally: rpc-compat's Dockerfile clones
ethereum/execution-apis by hard-coded URL, so the branch buildarg cannot point at a fork.

Coverage: the field itself is pinned by
block_timestamp_is_on_the_log_and_not_on_the_receipt in
crates/networking/rpc/types/receipt.rs, which asserts it is present on each log and absent
from the receipt level.

Removal: delete the entries if ethrex ever gains pre-merge chain import, or if upstream
marks these cases speconly so they are type-checked instead of compared byte-for-byte.


The stateless schema id does not identify the encoding

Where: STATELESS_INPUT_SCHEMA_ID in crates/common/types/stateless_ssz.rs.

Upstream keeps the stateless input schema id at 0x1501
(fork_index 0x15 << 8 | revision 0x01) across incompatible body changes. Three
encodings have now shipped under it: tests-zkevm@v0.6.2, then #3248 + #3278,
then #3356, which moved state, codes and public_keys from SszList to
ProgressiveList. ethrex speaks the last one.

The consequence is that the 2-byte prefix cannot be used to detect a stale or
mismatched bundle. A wrong-dialect input is accepted by the id check and then
fails later — in SSZ decode, or on a root that does not match — rather than being
rejected up front for what it is. only_amsterdam_schema_id_decodes therefore
proves less than its name suggests.

Worth raising upstream: a revision field that does not move across a body change
provides no version negotiation at all.


ZisK guest program hash changes with the unsync_cell gate

Where: crates/common/types/block.rs, transaction.rs.

The gate on the single-threaded unsync_cell::OnceCell moved from
all(feature = "eip-8025", target_arch = "riscv64") to
all(feature = "zisk", target_arch = "riscv64") when the eip-8025 feature was removed.

The guest ELFs were previously built --features "<zkvm>-build-elf,ci", which never enabled
eip-8025, so they compiled the atomic once_cell variant. bin/zisk/Cargo.toml does enable
ethrex-common/zisk, so the ZisK guest now compiles the unsafe impl Sync cell instead.
That changes the ELF bytes and therefore the program hash and verification key.

This is intended (the guest is single-threaded, so the unsync cell is sound and cheaper), but it
is a VK change rather than a no-op refactor, and the diffstat presents it as a file rename
(eip8025_cell.rs → unsync_cell.rs). Anyone pinning a ZisK VK across this change must
re-register it. The stateless-validator crate now forwards ethrex-common/zisk from its own
zisk feature so the two ZisK guests do not disagree on the cell type.


Release signing key is an unprotected repository secret

Where: .github/workflows/tag_release.yaml.

MINISIGN_SECRET_KEY is a plain repository secret. There is no environment: on
finalize-release or dry-run-release-assets, and gh api repos/lambdaclass/ethrex/rulesets
shows only branch-targeted rulesets, so the github.ref_type == 'tag' condition is a workflow
check rather than an enforced boundary: anyone who can push a tag can reach the signing key.

This is a repository-settings change, not a code change, so it is recorded here rather than
fixed in the tree. Recommended:

  1. Move MINISIGN_SECRET_KEY / MINISIGN_PASSWORD into a GitHub Environment with required
    reviewers, and add environment: to the two jobs that sign.
  2. Add a ruleset targeting refs/tags/v* restricting who may create release tags.

Until then, the compromise of that key is silent and durable: signatures would still verify
against the committed .github/minisign.pub.

@github-actions github-actions Bot added the L1 Ethereum client label Aug 19, 2026
@github-actions

Copy link
Copy Markdown

🤖 Claude Code Review

Confirmed, no leftover DiscoveryConfig::default() calls or stale references anywhere. Everything is consistent and the removal is exhaustive.

Review: PR 7163 — drop unused discovery lookup interval config

This is a clean, well-scoped dead-code removal plus a real bug fix. I verified the changes against the full call graph (P2PContext::initial_lookup_interval → RLPx initiator, DiscoveryConfig construction sites, Options::default()), and everything checks out.

Correctness

  • The core fix (cli.rs:595, lookup_interval: INITIAL_LOOKUP_INTERVAL_MS instead of Default::default()) is a genuine bug fix. Confirmed: f64::default() is 0.0, and this value flows through P2PContext::initial_lookup_interval → RlpxInitiator::get_lookup_interval → lookup_interval_function as the lower bound. With zero peers, progress = 0, so the easing curve returns the lower bound itself — Duration::ZERO — causing the initiator to busy-loop reconnection attempts with no backoff. Good catch, and the fix is correct and minimal.
  • The DiscoveryConfig field removal is exhaustive — checked all construction sites (initializers.rs:382, discovery/server.rs:457, network.rs:137-141) and confirmed no remaining references to DiscoveryConfig::default() or initial_lookup_interval on that struct. The field on P2PContext (a distinct, still-live field feeding the RLPx initiator) is correctly left untouched.
  • No behavior change for the discovery server itself, consistent with the PR description — it already used its own ITERATIVE_LOOKUP_* constants post-feat(l1): kademlia k-bucket routing table v2 #6511, so this override was dead regardless of its value.

Style / clarity

  • Updated doc comments on INITIAL_LOOKUP_INTERVAL_MS/LOOKUP_INTERVAL_MS/lookup_interval_function and the CLI help text accurately reflect actual usage now (RLPx initiator only, not discv4/discv5).
  • Splitting into two commits (mechanical removal, then the default-value fix) makes the bug fix easy to review in isolation — good practice.

Minor observations (not blocking)

  • No test added asserting Options::default().lookup_interval != 0.0 or covering lookup_interval_function at the boundary (progress = 0 with a non-zero lower bound). Given the bug was silent for a while (since feat(l1): improved lookup interval #5518), a small regression test would be cheap insurance, but it's a reasonable judgment call to skip for a one-line default-value fix.
  • The unchecked PR checklist item about STORE_SCHEMA_VERSION is correctly not applicable here since there's no storage schema change.

No correctness, security, or consensus-rule concerns — this only touches peer-discovery/connection pacing, not block/state validation.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Kimi Code Review

Review Summary
Clean refactor separating RLPx connection intervals from discovery lookup intervals. Removes configurability from discovery (using safe constants) while keeping RLPx intervals tunable. No critical security issues, but verify RLPx initiator still receives the CLI value.

Detailed Feedback

1. Potential dead code in context (cmd/ethrex/network.rs)
The context struct likely contains initial_lookup_interval field that was previously passed to DiscoveryConfig (line 140 in original). Since this override is removed, verify:

  • The RLPx initiator reads this value directly from context (not shown in diff)
  • If unused, remove the field from context to avoid confusion

2. DiscoveryConfig Default implementation removal (crates/networking/p2p/discovery/mod.rs)
Removing Default is good for explicit configuration, but verify no other test files or modules use DiscoveryConfig::default(). The compiler will catch this, but worth checking integration tests.

3. Comment accuracy (crates/networking/p2p/discovery/mod.rs)
Lines 30-33: The comment states discovery "has its own bounds" but the constants INITIAL_LOOKUP_INTERVAL_MS and LOOKUP_INTERVAL_MS remain in this module. If discovery uses different hardcoded values elsewhere, consider moving these constants to the RLPx module to avoid confusion, or clarify that these constants are now RLPx-specific.

4. CLI help text improvement (cmd/ethrex/cli.rs)
Line 459: Good clarification. The new text accurately describes this affects "RLPx connection attempts" rather than discovery.

5. Explicit struct initialization (cmd/ethrex/initializers.rs)
Line 382-385: Good removal of ..Default::default(). With only two boolean fields, explicit initialization is clearer and safer.

6. No regression in configurability
Ensure the RLPx initiator component (not shown in diff) actually uses the lookup_interval value from CLI options. If it was previously reading from DiscoveryConfig.initial_lookup_interval, it will now need to source this from the context or CLI options directly.

Minor Suggestion
Consider renaming INITIAL_LOOKUP_INTERVAL_MS to INITIAL_RLPX_INTERVAL_MS since it's no longer used by discovery, making the code self-documenting. However, this is optional given the comments are clear.

Security Assessment

  • Positive: Removing configurable discovery intervals prevents users from accidentally setting overly aggressive values that could trigger rate limiting or blacklisting by peers.
  • Positive: 100ms floor (10 conn/sec) for RLPx is reasonable and won't cause amplification issues.

Verdict
Approve with the caveat that you verify the RLPx initiator still receives the configured interval value through a different mechanism (since it's no longer in DiscoveryConfig).


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

@github-actions

Copy link
Copy Markdown

🤖 Codex Code Review

Findings

  • Minor: crates/networking/p2p/discovery/server.rs has a stale doc comment. It still says the test helper uses a "dummy initial lookup interval", but that field was removed from DiscoveryConfig in this PR.

No material correctness, security, performance, or Ethereum-protocol issues stood out in the actual behavior change. The patch looks sound: cmd/ethrex/cli.rs now aligns Options::default() with the CLI default, and crates/networking/p2p/network.rs no longer leaks the RLPx-specific knob into discovery-server pacing.

I could not run cargo check in this environment because rustup tried to write under /home/runner/.rustup and failed on the read-only filesystem, so compile verification is still a gap.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@github-actions

Copy link
Copy Markdown

Lines of code report

Total lines added: 0
Total lines removed: 15
Total lines changed: 15

Detailed view
+--------------------------------------------------+-------+------+
| File                                             | Lines | Diff |
+--------------------------------------------------+-------+------+
| ethrex/cmd/ethrex/initializers.rs                | 979   | -1   |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/discovery/mod.rs    | 24    | -10  |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/discovery/server.rs | 410   | -1   |
+--------------------------------------------------+-------+------+
| ethrex/crates/networking/p2p/network.rs          | 724   | -3   |
+--------------------------------------------------+-------+------+

@github-project-automation github-project-automation Bot moved this to In Review in ethrex_l1 Aug 19, 2026
@MegaRedHand
MegaRedHand enabled auto-merge August 19, 2026 19:05
The `Verify CLI Help Consistency` job diffs `ethrex --help` against the
BEGIN_CLI_HELP block in docs/CLI.md, so rewording the flag's help left
the two out of step.
@MegaRedHand
MegaRedHand added this pull request to the merge queue Aug 19, 2026
Merged via the queue into main with commit 5096dea Aug 19, 2026
54 of 57 checks passed
@MegaRedHand
MegaRedHand deleted the chore/drop-unused-discovery-lookup-interval branch August 19, 2026 20:27
@github-project-automation github-project-automation Bot moved this from In Review to Done in ethrex_l1 Aug 19, 2026
ilitteri added a commit that referenced this pull request Aug 27, 2026
Resolutions, all in main's favor where main superseded the branch:

- DiscoveryConfig: main dropped initial_lookup_interval (#7163); the
  branch's nat_extip_set field is added to main's struct and every
  construction site sets all three fields explicitly, so the branch's
  Default impl is gone too.
- update_local_ip: main's edit-based version (#7143) wins. It preserves
  every ENR entry, which strictly subsumes the branch's rebuild that
  carried over only the fork id.
- Discv5State keeps main's shape minus the IP-voting predictor this
  branch extracts into discovery/ip_predictor.rs. The five in-file
  voting tests go with the mechanism; their coverage lives on in
  test/tests/p2p/discovery/ip_predictor_tests.rs, and main's three
  update_local_ip ENR-preservation tests are kept.
- DiscoveryServer drops the branch's store field: main moved ENR
  building out of discovery (#7147), so discovery no longer needs
  storage; apply_predicted_ip goes through main's update_local_ip.
- start_network/DiscoveryServer::spawn keep main's signatures plus the
  branch's shared_local_node parameter.
- The native-rollup RPC path (#6418, newer than this branch) is ported
  to the SharedLocalNode-taking init_rpc_api; no discovery runs there,
  so the shared identity is constant after construction.
AnkushinDaniil pushed a commit to AnkushinDaniil/ethrex that referenced this pull request Aug 30, 2026
…ambdaclass#6811)

Follow-up to lambdaclass#6713, which deferred geth-style devp2p endpoint
prediction. Closes lambdaclass#6714.

On discv4-only devnets the majority-vote IP predictor never reached
quorum because it only ran on discv5, so the node kept advertising its
private/bridge IP and got 0 inbound peers.

Extract `IpPredictor` out of `Discv5State` into
`discovery/ip_predictor.rs`, owned by `DiscoveryServer` and fed by both
discv4 (`PONG.to`, the peer's observed source of our PING) and discv5
PONGs. Applied via the existing `update_local_ip`, which bumps ENR seq
and re-signs. `--nat.extip` sets `ip_override_locked`, preventing any
predictor update; address-family mismatches are skipped.

Votes discard only never-routable addresses
(loopback/link-local/unspecified); RFC1918/unique-local private IPs are
kept as candidates. On finalize the predictor prefers a public winner
and falls back to a private one only if no public IP reaches quorum. A
NAT/SNAT node converges on its public IP (peers observe the routable
source); a node on a flat private network (e.g. a kurtosis enclave,
where no public IP is ever observed) converges on its reachable private
IP instead of advertising nothing.

Tests from `discv5/server.rs` are relocated to `ip_predictor.rs`, plus
discv4 PONG feeding, flat-network private convergence, and
prefer-public-over-private.

## Post-restructure merge (2026-08-27)

Main restructured discovery underneath this PR; the merge resolves in
main's favor wherever main superseded it:

- **`update_local_ip`**: main's edit-based version (lambdaclass#7143) wins — it
preserves every ENR entry, strictly subsuming this branch's rebuild that
carried over only the fork id.
- **`DiscoveryServer` keeps no `store`**: main moved ENR building out of
discovery (lambdaclass#7147), so the field this branch added for fork-id
re-derivation is no longer needed; `apply_predicted_ip` goes through
main's `update_local_ip`.
- **`DiscoveryConfig`**: main dropped `initial_lookup_interval` (lambdaclass#7163);
only `nat_extip_set` is added, and all construction sites set every
field explicitly.
- **The five in-file `Discv5State` voting tests** go with the mechanism
they tested; their coverage lives on in
`test/tests/p2p/discovery/ip_predictor_tests.rs` (8 tests), and main's
three `update_local_ip` ENR-preservation tests are kept.
- **The native-rollup RPC path** (lambdaclass#6418, newer than this branch) is
ported to the `SharedLocalNode`-taking `init_rpc_api`.

Post-merge verification: workspace clippy and `--features l2` clippy
clean, fmt clean, 105 `ethrex-p2p` unit + 151 p2p integration + 57 rpc
integration tests pass, and the vote wiring is confirmed intact (discv4
PONG → `record_ip_vote` → `apply_predicted_ip("discv4")`, discv5
likewise, prune tick → `check_timeout` →
`apply_predicted_ip("timeout")`).

## Test plan

- `cargo test -p ethrex-test ip_predictor` passes
- discv4-only devnet (discv5 disabled): node converges to the correct
external IP after 3 PONGs from distinct peers
- `--nat.extip` still hard-locks the advertised address

---------

Co-authored-by: ilitteri <ivanlitteri@hotmail.com>
Co-authored-by: Ivan Litteri <67517699+ilitteri@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L1 Ethereum client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants