In-house discv5#11728
Conversation
…nto feature/kademlia-discv4
LukaszRozmej
left a comment
There was a problem hiding this comment.
Follow-up review pass on the new commits (8d91fb8c, d00b47fd, fb775f47, 34a5c467).
LukaszRozmej
left a comment
There was a problem hiding this comment.
Went through 50%.
I know it is a huge PR and it is hard to have full mastery of it. But my hunch is that while it is a good PR, there is still room for polish and it is not yet polished as it could be on all aspects.
…nto generic-kad-2
Six near-identical NodeRecord builders across DiscoveryV5AppTests, Discv4/Kademlia, and Discv5 test files are replaced by a single shared TestEnrBuilder.BuildSigned helper. Two duplicate TestEth2Entry nested classes are merged into one shared internal type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KBucketTests, KademliaTests, and NodeHealthTrackerTests each defined a private static ToHash(ValueHash256) that resolved to the same expression as the existing IdentityNodeHashProvider.ToHash and ValueHashKeyOperator<T>.ToHash. Call the shared helpers directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nto generic-kad-2
# Conflicts: # src/Nethermind/Nethermind.Runner.Test/DatabasePurgerTests.cs
|
@claude review again |
|
Claude finished @flcl42's task in 9m 11s —— View job PR Review: In-house discv5 — Round 4
SummaryThis is the fourth review cycle. The PR is in very good shape — the latest commits ( Status of LukaszRozmej Round 2 findings
Remaining findingsLowDefensive null guards for non-nullable Null guards exist for a type constrained to What's well done in this revision
OverallAll High and Medium findings across four review rounds have been resolved. The one remaining Low finding (null guards for non-nullable types) is a stylistic violation of the project rule that does not affect correctness or safety. This PR is ready to merge. |
A throw from packetCodec.TryDecode ran outside the per-packet handler's catch, so a single crafted packet could terminate one of the packet workers. Catch per iteration and rethrow only on shutdown cancellation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Changes
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Remarks
Change list
Nethermind.Kademlia: routing table, lookup algorithm, node health tracking, distance abstractions, and generic node/key operators now live outside discv4-specific code.FINDNODE/NEIGHBORS, ENR request/response, and XDC extension hooks in the discv4 layer.FINDNODEdistance requests map toGetAllAtDistance, while discv4 keeps nearest-node lookup semantics.eth2records before execution peer admission, skipped malformed records, and prevented stale ENR sequence downgrades.Architecture overview
The new shape separates reusable Kademlia table mechanics from protocol transport.
Nethermind.Kademliaowns generic routing, lookup, health tracking, and distance services;Nethermind.Network.Discoverysupplies Ethereum discovery-specific adapters, persistence, node sources, and lifecycle. Discv4 and discv5 each create their own protocol child scope and own Kademlia instance, then bind protocol-specific message senders and node-source behavior on top of the same core. Discv4 keeps signed RLP packet handling, bonding, and nearest-neighbour discovery, while discv5 owns packet/session encryption, ENR-based messages, and distance-bucketFINDNODEsemantics. Both versions feed discovered execution peer candidates through role-specific ENR conversion so UDP discovery endpoints stay in routing and TCP endpoints go to the peer pool.High-level architecture
flowchart TB NetworkConfig["Network config<br/>DiscoveryVersion + bootnodes"] --> Composite["CompositeDiscoveryApp"] Composite --> V4App["Discv4 DiscoveryApp"] Composite --> V5App["Discv5 DiscoveryV5App"] V4App --> V4Scope["discv4 child scope"] V5App --> V5Scope["discv5 child scope"] V4Scope --> V4Adapter["discv4 KademliaAdapter<br/>bonding + FINDNODE nearest lookup"] V5Scope --> V5Adapter["discv5 KademliaAdapter<br/>session-aware FINDNODE distances"] V4Adapter --> Kad4["Kademlia<PublicKey, Node><br/>discv4 instance"] V5Adapter --> Kad5["Kademlia<PublicKey, Node><br/>discv5 instance"] Kad4 --> Core["Nethermind.Kademlia<br/>routing table + lookup + health"] Kad5 --> Core V4App --> V4Handler["NettyDiscoveryHandler<br/>signed RLP UDP packets"] V5App --> V5Handler["NettyDiscoveryV5Handler"] V5Handler --> PacketCodec["PacketCodec<br/>WHOAREYOU + sessions + AES-GCM"] PacketCodec --> MessageCodec["MessageCodec<br/>PING/PONG/FINDNODE/NODES/TALK"] V4Adapter --> NodeSource4["discv4 NodeSource"] V5Adapter --> NodeSource5["discv5 NodeSource"] NodeSource4 --> PeerPool["PeerPool candidates"] NodeSource5 --> PeerPoolKey moment: discv5 packet handling
sequenceDiagram participant UDP as NettyDiscoveryV5Handler participant Codec as PacketCodec participant Adapter as Discv5 KademliaAdapter participant Kad as Kademlia UDP->>UDP: cheap size checks and bounded queue admission UDP->>Codec: accepted packet bytes Codec->>Codec: decode ordinary, WHOAREYOU, or handshake packet alt no usable session Codec-->>UDP: send WHOAREYOU challenge else authenticated message Codec->>Adapter: Discv5Message Adapter->>Kad: mark inbound health / add or refresh node alt FINDNODE Adapter->>Kad: GetAllAtDistance(requested distances) Kad-->>Adapter: matching nodes Adapter-->>Codec: NODES responses with ENRs else PING/PONG/TALK Adapter-->>Codec: protocol response or handler completion end Codec-->>UDP: encrypted response packet endKey moment: ENR endpoint separation
flowchart LR ENR["ENR"] --> Validate["validate secp256k1<br/>node id, routability, sequence"] Validate --> Eth2{"consensus-only<br/>eth2 record?"} Eth2 -- yes --> Reject["reject for execution discovery"] Eth2 -- no --> Record["NodeRecord"] Record --> Routing["TryFromDiscoveryEnr<br/>UDP endpoint"] Routing --> KadTable["discv5 Kademlia table"] Record --> Candidate{"has usable TCP endpoint?"} Candidate -- yes --> PeerNode["TryFromEnr<br/>TCP endpoint"] PeerNode --> PeerPool["PeerPool"] Candidate -- no --> SkipPeer["skip RLPx peer candidate"] Record --> Sequence{"newer ENR sequence?"} Sequence -- yes --> Cache["replace known ENR"] Sequence -- no --> Keep["keep newer cached ENR"]Key moment: receive-path hardening
flowchart TD Packet["UDP packet"] --> Cheap["read size/type from buffer"] Cheap --> Valid{"valid enough<br/>for this protocol?"} Valid -- no --> DropOrForward["drop or forward without copy"] Valid -- yes --> Limit["global + per-IP limits"] Limit --> Accepted{"within limits<br/>and queue capacity?"} Accepted -- no --> Drop["drop before packet copy/expensive work"] Accepted -- yes --> Queue["bounded channel"] Queue --> Workers["limited dispatch workers"] Workers --> Decode["deserialize / decrypt accepted traffic"] Decode --> Adapter["protocol adapter"] Adapter --> Kad["Kademlia + response handlers"]