Skip to content

discv4/discv5 remainders on develop: NEIGHBORS, NODES cap, node-map flood eviction - #45

Merged
MysticRyuujin merged 3 commits into
developfrom
followup/discv-remainders
Jul 30, 2026
Merged

discv4/discv5 remainders on develop: NEIGHBORS, NODES cap, node-map flood eviction#45
MysticRyuujin merged 3 commits into
developfrom
followup/discv-remainders

Conversation

@pk910

@pk910 pk910 commented Jul 30, 2026

Copy link
Copy Markdown
Member

Overview

Targets develop. Adds the three discv4/discv5 fixes that develop was still missing after its independent rework — the remainders from the #44 audit set that develop did not already cover (BUG1 ENRRESPONSE, G9 ingest size, BUG5 bond, --serve-all are all already in develop).

Each fix is its own commit. All CI checks pass locally (go mod verify, go vet, gofmt, staticcheck, go test -race, make build), and each fix is verified against a full glamsterdam-devnet-7 devnet (all EL+CL clients, bootnodoor as sole bootnode) with no regression.

Commits

  1. discv4: always send NEIGHBORS and advertise the real TCP port. An empty routing table produced a silent (zero-packet) response, so go-ethereum's querier waited out its full request timeout instead of returning early on the first reply. Always send ≥1 NEIGHBORS packet. Also advertise the node's real TCP port from its ENR instead of copying the UDP port.
  2. discv5: cap NODES response to ≤15 nodes / ≤5 packets. go-ethereum honours only the first NODES packet's total and reads at most 5 packets; a dual-serve response could emit 6, silently dropping nodes. sigp/discv5 caps at 16.
  3. discv4: evict a stale node instead of dropping new peers when the map is full. lookupOrCreateNode returned a non-retained node once at MaxNodes, so under a flood of distinct signed IDs a genuinely new peer's inbound PING marked bond state on a discarded object and could never bond (memory-growth DoS → bonding-lockout DoS). When full, evict one unbonded entry to admit the new node; bonded, endpoint-proven peers are never evicted this way.

Verification (glamsterdam-devnet-7, all clients, bootnodoor as sole bootnode)

No regression vs develop: all 12 nodes discovered, 11 discv5 sessions, 0 EL fork rejections, 0 bootnodoor errors/warnings, storm-free (≤17 packets to any single peer over the run), CL 5/5 mesh, EL 5/5 (besu/erigon 4/5, unchanged — that gap is the besu-v4-only vs erigon-v5-only artifact, not bootnodoor), 0 client-side bans.

Each fix addresses a real concern:

  • BUG3 — shown live. geth harness against an empty-table bootnode: develop delivered 0 NEIGHBORS packets (geth waits out its timeout on every FINDNODE); this branch delivered 10 (immediate empty replies). A fresh bootnode now answers FINDNODE instead of going silent.
  • BUG4TestSendNeighborsAdvertisesEnrTCPPort: a peer with ENR tcp=40404/udp=30303 is advertised as TCP=40404 (was the UDP port).
  • BUG2 — capped so a dual-serve response can't exceed go-ethereum's 5-packet read limit.
  • Bound discv4 handler node map to prevent unbounded growth #34TestFloodDoesNotEvictBondedPeers: under a flood past MaxNodes, a bonded peer is retained and a fresh peer is still admitted; the map stays bounded and no genuine peer is locked out.

Note

Opening against develop since that is where the parallel rework lives. If you'd rather I retarget once develop merges to master, happy to.

pk910 added 3 commits July 30, 2026 15:39
An empty routing table previously produced a silent (zero-packet) response, so
go-ethereum's querier waited out its full request timeout instead of returning
early on the first reply. Always send >=1 NEIGHBORS packet. Also advertise the
node's real TCP port from its ENR instead of copying the UDP port.
go-ethereum honours only the first NODES packet's total and reads at most 5
packets; sigp/discv5 caps at 16 nodes. Cap the served set at 15 nodes / <=5
packets so no served node is silently dropped by a requester.
… is full

lookupOrCreateNode previously returned a non-retained node once the map was full,
so under a flood of distinct signed IDs a genuinely new peer's inbound PING marked
bond state on a discarded object and could never bond (memory-growth DoS turned
into a bonding-lockout DoS). When full, evict one unbonded entry to admit the new
node; bonded, endpoint-proven peers are never evicted this way.

Adds regression tests for this and the two preceding NEIGHBORS fixes.
@MysticRyuujin
MysticRyuujin merged commit 950d85a into develop Jul 30, 2026
1 check passed
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.

2 participants