feat: host-side prerequisites for the embedded beacon chain plugin (SSZ ulong limits, injectable discv5 ENR filter)#11985
Conversation
Beacon-chain lists use limits up to 2^40 (VALIDATOR_REGISTRY_LIMIT), which overflowed the int-typed SszListAttribute. The limit is now ulong end to end; decode-side count guards are omitted when the limit exceeds int.MaxValue since an int-typed count can never violate them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shared discv5 stack hard-dropped consensus-only node records in the response handler, adapter acceptance check, and node source — fatal for a consumer that wants CL peers. Discv5RecordFilter makes the policy injectable with the execution-layer behavior as the default (unchanged); a beacon-chain scope can register AcceptAll. Needed by the embedded beacon chain driver plugin (#11976). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @asdacap's task in 5m 12s —— View job Review: feat: host-side prerequisites for the embedded beacon chain plugin
SummaryBoth changes are well-scoped and correct for the stated purpose. The SSZ ulong-limit change accurately propagates One Medium inconsistency was found: FindingsMedium
The Suggested fix — apply the same "skip guard for limits beyond Kind.BitList => property.Limit is <= int.MaxValue
? $"ValidateSszBitlistLimit({expression}, {property.Limit}, nameof({decl.TypeReferenceName}), nameof({property.Name}));"
: string.Empty,Or lift Low
The only call site is Notes (no action required)
|
| @@ -646,7 +646,7 @@ private static string ValidationStatement(SszType decl, SszProperty property, st | |||
| Kind.Vector when property.Type.Name == "BitArray" => $"ValidateSszBitvectorLength({expression}, {property.Length}, nameof({decl.TypeReferenceName}), nameof({property.Name}));", | |||
| Kind.Vector => $"ValidateSszVectorLength({SpanExpression(property, expression)}, {property.Length}, nameof({decl.TypeReferenceName}), nameof({property.Name}));", | |||
| Kind.List when property.Type.Name == "BitArray" => $"ValidateSszBitlistLimit({expression}, {property.Limit}, nameof({decl.TypeReferenceName}), nameof({property.Name}));", | |||
There was a problem hiding this comment.
Medium — ValidateSszBitlistLimit still takes int limit, but property.Limit is now ulong?.
Line 648 (Kind.List when "BitArray" — dead code per Kind getter, but still present) and line 651 (Kind.BitList) both emit {property.Limit} without a UL suffix into a call to ValidateSszBitlistLimit(BitArray? bits, int limit, ...). If a beacon chain type ever declares a bitlist with a limit > int.MaxValue, the generator will emit code that doesn't compile.
The Kind.List when "BitArray" arm can never fire (because a BitArray + [SszList] property resolves to Kind.BitList in the Kind getter), but the Kind.BitList arm on line 651 is live.
Consistent fix (same approach used everywhere else in this PR):
Kind.BitList => property.Limit is <= int.MaxValue
? $"ValidateSszBitlistLimit({expression}, {property.Limit}, nameof({decl.TypeReferenceName}), nameof({property.Name}));"
: string.Empty,Or change ValidateSszBitlistLimit to take ulong limit and update the comparison to (ulong)bits.Length > limit.
Beacon chain bitlists currently have limits ≤ int.MaxValue (MAX_VALIDATORS_PER_COMMITTEE = 2048), so this doesn't affect the PR's stated use case — but the inconsistency is real and would produce a compile error rather than a silent bug if triggered.
There was a problem hiding this comment.
Fixed in 2d06a9f (note: this reply is from the agent — Claude Code, model Claude Fable 5 — working on @asdacap's behalf). ValidateSszBitlistLimit now takes ulong limit with a (ulong)bits.Length comparison, the Kind.BitList emission carries the UL suffix, and the bitlist Encode emission clamps to int.MaxValue (that runtime parameter is int-typed and unused, and a BitArray cannot exceed int.MaxValue bits anyway). The dead Kind.List when BitArray arm is removed — such properties always resolve to Kind.BitList in the Kind getter. The huge-limit regression test now also declares a 2^40-limit bitlist, which fails to compile without the fix.
| namespace Nethermind.Network.Discovery.Discv5.Kademlia.Handlers; | ||
|
|
||
| internal sealed class NodesResponseHandler(Node receiver, Distances requestedDistances, IKademliaDistance<Hash256> distanceCalculator) | ||
| internal sealed class NodesResponseHandler(Node receiver, Distances requestedDistances, IKademliaDistance<Hash256> distanceCalculator, Discv5RecordFilter? recordFilter = null) |
There was a problem hiding this comment.
Low — nullable Discv5RecordFilter? inconsistency. NodesResponseHandler takes Discv5RecordFilter? recordFilter = null with a null fallback inside Handle, while NodeSource and KademliaAdapter take a non-nullable Discv5RecordFilter recordFilter. The nullable default is never exercised in production (the only caller is KademliaAdapter.FindNeighbours, which always passes recordFilter). Consider making it required (non-nullable, no default) to match the rest of the API.
There was a problem hiding this comment.
Already addressed in b08af23, which landed after this review ran (reply from the agent — Claude Code, model Claude Fable 5 — on @asdacap's behalf): the filter is now a dedicated IDiscv5RecordFilter interface and a required, non-nullable dependency at every record-handling component — no optional parameters or null-coalesced defaults remain, including in NodesResponseHandler.
Replace the bool-parameterized filter class with an interface and two implementations, and make the dependency non-optional at every record-handling component (no null-coalesced defaults) so each discv5 instance states its policy explicitly. The interface documents why the policy exists: discv5 is one DHT shared by EL and CL nodes, and which records are useful is per instance — the EL must drop consensus-only ENRs it can never dial over RLPx, while a CL instance exists precisely to find them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r arm Review follow-up: ValidateSszBitlistLimit now takes ulong like the list variant, the BitList validation emission carries the UL suffix, and the bitlist Encode emission clamps to int.MaxValue (the runtime parameter is int-typed and unused). The Kind.List-with-BitArray arm could never fire — such properties resolve to Kind.BitList — and is removed. Regression: the huge-limit test now also covers a 2^40-limit bitlist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onchain-core-prereqs # Conflicts: # src/Nethermind/Nethermind.Network.Discovery.Test/Discv5/KademliaAdapterTests.cs
|
Claude finished @asdacap's task in 4m 52s —— View job Review: feat: host-side prerequisites for the embedded beacon chain plugin
SummaryBoth changes are correct and complete. The SSZ Previous findings — resolved ✓
Low
This is intentionally out of this PR's stated scope (which targeted three Kademlia-layer sites: Notes (no action required)
|
| result.Limit = listAttr.ConstructorArguments.FirstOrDefault().Value switch | ||
| { | ||
| ulong limit => limit, | ||
| int limit => (ulong)limit, |
There was a problem hiding this comment.
Why do we need to care about int if the attribute receives ulong now?
There was a problem hiding this comment.
src\Nethermind\Nethermind.Serialization.SszGenerator\Attributes.cs need to be changed too for consistency ig.
There was a problem hiding this comment.
There was a problem hiding this comment.
|
|
||
| /// <summary>Protocol-level ENR acceptance policy of a discv5 instance.</summary> | ||
| /// <remarks> | ||
| /// Discv5 is one DHT shared by execution- and consensus-layer nodes, so every instance sees both |
| arguments += $", {property.Limit}"; | ||
| // The bitlist Encode limit parameter is int-typed (and unused); clamp so bitlists | ||
| // declared with limits beyond int.MaxValue still emit compilable code. | ||
| arguments += $", {Math.Min(property.Limit!.Value, int.MaxValue)}"; |
There was a problem hiding this comment.
Probably should be a generator error about not supporting it
There was a problem hiding this comment.
Agreed — done in 4cd7a63. Instead of silently clamping, the generator now rejects a BitArray list whose limit exceeds int.MaxValue at parse time (in SszProperty.From), surfaced through the existing SSZ003 generator error — a BitArray cannot hold more than int.MaxValue bits, so such a limit is meaningless. The Encode site then just emits property.Limit.Value (now guaranteed in range). Dropped the huge-limit bitlist test type and added a Bitlist_with_limit_beyond_int_range_reports_diagnostic regression test.
(Reply from the agent — Claude Code, model Claude Opus 4.8 (1M context) — on @asdacap's behalf.)
- Generator's SszListAttribute mirror now takes `ulong limit`, matching the runtime attribute (flcl42). - SszProperty parses the limit as `ulong` directly (the attribute is `ulong`, so the value is never `int`) and now rejects a `BitArray` list whose limit exceeds `int.MaxValue` with an SSZ003 generator error instead of silently clamping the int-typed Encode parameter — a BitArray cannot hold more than int.MaxValue bits (flcl42). - Drop the now-unsupported huge-limit bitlist test type; add a generator diagnostic regression test for the rejection. - Trim the IDiscv5RecordFilter <remarks> block (flcl42). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Kademlia codes * Commit before I do something bad * Better default * It does work, just really slow * It does work. Just need some metric. * More discovery message * Allow disabling static labels * Allow disabling static labels * Address comment * This is confusing AF * Ok, I see where the problem is * Fix the neighbour issue * Cleaner * Slight cleanup * Poor simplification attempt * Remove bucket list * Reducing change * Fix tests * Fix tests * Slight cleanup * Simplification * Exit logic * Configurable limit * Some cleanup * Refinement * Refinement * Respond with two nodes message. * Fix build * Fix some test * Fix unit tests * Fix E2E * Slight cleanup * Hide some error * Improved candidate count metric * Experiment * Remove unnecessary code * Send optional enr * Fix test build * Additional todo * Remove nodes locator * Nodestats * Remove discovery manager * Remove content extension * Reducing change * Lets just save for now * Remove routing table * Move session into one class * Remove NodeFilter * Pass cancellation token * Fix * Fix throttle logic * ISolating * Cleanup attempt * Some refinement * Change the unit test * Reduce timeout * Unit tests * NSubstitute * Whitespace * Remove some code * Remove more metric * Gonna commit this first * Extract node record provider * DiscV5 to DI * DiscV4 to DI * Consolidate the network storage initialization * Allow more margin * Override discovery db in test also * Whitespace * Fix build * Tests * Slight code reduction * Reducing change * Reducing change * Reducing change * Reducing change * Nodes to array segment * Extract some logic out to DiscoveryPersistenceManager.cs * Remove unnecessary comment * Remove unnecessary class * Fix simulation * Fix build * More attempted lookup * Trying to simplify * KBucket does not need * Slight cleanup * Fix build * Rename and comment * Remove original lookup code * Fix test * Reduce code * More xor test * Whitespace * Make the node lookup generic * Address comment * Remove strange * Fix build * Some fixes (#11654) * Review * Simplify * Fix spelling * test(discovery): dedupe discv4/kademlia tests - NodeSessionTests: collapse three flag-and-timeout tests into one [TestCaseSource] - IteratorNodeLookupTests: extract routing-table / find-neighbours stub helpers - KademliaDiscv4AdapterTests: reuse ConfigureBondCallback in ping test - KademliaSimulation: drop accidentally duplicated [TestFixture(3, 0)] Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(discovery): address review comments - EnrResponseHandler / PongMsgHandler: collapse Handle to expression body - DoubleEndedLru.GetByHash: collapse to ternary expression body - IteratorNodeLookup.ShouldStop: drop redundant if, return condition directly - IteratorNodeLookup.FindNeighbour: collapse unreachable check + send to single return - NodeSession.RecordStatsFor*Msg: dedupe via shared helper with switch expression (relies on Discovery*Out/Discovery*In pairs being adjacent in NodeStatsEventType) - KademliaDiscv4Adapter: drop #region - ITaskCompleter<T>: move to its own file - TalkReqAndRespHandler.HandleResponse: collapse to one line Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(discovery): address review findings + fill test gaps Important: - KBucketTree: raise OnNodeAdded/OnNodeRemoved after releasing _lock to avoid re-entrancy deadlocks via external subscribers. - LookupKNearestNeighbour: atomic CAS swap of roundComplete TCS and drop the misused (token) state argument; mark `finished` access with Volatile. - Kademlia.Bootstrap: catch non-OCE exceptions from individual ping calls and from the Bootstrap iteration in Run, so a transient network error no longer kills the discovery loop. - EnrResponseHandler: validate resp.RequestKeccak against the MDC of the EnrRequest just sent. EnrRequestMsg.Hash is now also set by the serializer on send. - KademliaNodeSource: unsubscribe Handler and complete the channel writer before awaiting the producer task in the finally block. - DoubleEndedLru/KBucket: GetAll / GetAllWithHash now materialize arrays under the inner lock rather than returning the live LinkedList. Nits: - Rename LookupFindNeighbourHardTimout -> LookupFindNeighbourHardTimeout. - Rename KademliaNodeSource ctor param lookup2 -> lookup. - Replace LINQ chains on KBucketTree hot paths (GetAllAtDistance, GetKNearestNeighbour, SplitBucket) with manual loops. - Hash256XorUtils.MaxDistance -> public const int. - Drop two stale comments and fix `does not seems` typo. Tests: - KBucketTreeTests: split preserves LRU order; GetAllAtDistance covers the deeper-bucket branch. - LookupKNearestNeighbourTests: mid-flight cancellation across Alpha=1/3. - NodeHealthTrackerTests: TryRefresh removes node on ping timeout, keeps it on success. - KademliaDiscv4AdapterTests: SendEnrRequest now wires request hash; added rejection test for unsolicited / wrong-keccak ENR responses. - DiscoveryPersistenceManagerTests: skip nodes that fail Node ctor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(discovery): dedupe new Kademlia and persistence tests - Extract IdentityNodeHashProvider into a shared file so KBucketTreeTests and LookupKNearestNeighbourTests stop redefining it. - KBucketTreeTests: pull common KBucketTree construction into CreateTree and Add helpers; replace duplicated Assert.That index lookups with single Is.EqualTo array comparisons. - LookupKNearestNeighbourTests: extract CreateLookup factory and named seed/neighbour hash constants so the two test methods only differ in what they're actually asserting. - NodeHealthTrackerTests: extract a CreateTracker helper (and Self/Remote /Stale constants) so each test body only sets the bits unique to its scenario; promote StringNodeHashProvider to a singleton. - DiscoveryPersistenceManagerTests: shared NodeA / NodeB fixtures plus a CreateManager helper and a PingReceived assertion helper; drop the per-test ad-hoc NetworkNode arrays. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Extract generic Kademlia and harden native discv5 * Align discv5 discovery lifecycle * Set Ethereum discovery versions * Harden discv5 ENR handling * Drain Kademlia background operations * Avoid peer penalties on lookup cancellation * Validate ENR key ordering and size * Require ENR v4 identity * Validate discv5 relayed ENR addresses * Harden discv5 node ingestion * Harden discv5 ENR relay validation * Recover stale discv5 sessions * Gate discv5 shared node ingestion * Bound discv4 discovery node dedupe * Require discv4 bond before node health updates * Harden discovery receive paths * Reduce discv5 allocation pressure * Add distance service * Reduce discv5 message allocations * Fix PR validation failures * Refactor more * Rename * Refactor more * More moves and cleanup * Add discv5 trace logging * Fix PR CI failures * Fix and cut * Use native compressed ECDH * Stabilize discovery E2E test * Fix review * Harden discv5 packet dispatch * Restore Kademlia distance SIMD * Remove unused Kademlia factory * Reduce discovery duplication * Harden Kademlia cleanup * Tidy Kademlia tests * Add async node health disposal * More unification * Tidy discovery follow-ups * Harden discovery lookup nodes * Review * More fixes * Clean NodeTests imports * Refactor discovery types * Better naming for double lru * Review * Fix enodes in discv5 and build * Use pooled set in Kademlia * Consolidate ENR builders in discovery tests 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> * Remove duplicate ToHash test helpers 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> * More pooled * More locals init skipped * Remove unused discovery usings * More fixes * Null not expected * Fix tests * Fix dos vector * Fix discovery serialization review * Reduce discovery token allocations * Prevent a malformed discv5 packet from killing a worker loop 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> * Test cases * Better rlp encoder init * Reduce discovery retention * feat: host-side prerequisites for the embedded beacon chain plugin (SSZ ulong limits, injectable discv5 ENR filter) (#11985) * feat(ssz): support list limits beyond int range in the SSZ generator Beacon-chain lists use limits up to 2^40 (VALIDATOR_REGISTRY_LIMIT), which overflowed the int-typed SszListAttribute. The limit is now ulong end to end; decode-side count guards are omitted when the limit exceeds int.MaxValue since an int-typed count can never violate them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(discv5): make the consensus-only ENR drop policy injectable The shared discv5 stack hard-dropped consensus-only node records in the response handler, adapter acceptance check, and node source — fatal for a consumer that wants CL peers. Discv5RecordFilter makes the policy injectable with the execution-layer behavior as the default (unchanged); a beacon-chain scope can register AcceptAll. Needed by the embedded beacon chain driver plugin (#11976). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(discv5): dedicated IDiscv5RecordFilter, required everywhere Replace the bool-parameterized filter class with an interface and two implementations, and make the dependency non-optional at every record-handling component (no null-coalesced defaults) so each discv5 instance states its policy explicitly. The interface documents why the policy exists: discv5 is one DHT shared by EL and CL nodes, and which records are useful is per instance — the EL must drop consensus-only ENRs it can never dial over RLPx, while a CL instance exists precisely to find them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ssz): lift the bitlist limit guard to ulong; drop a dead generator arm Review follow-up: ValidateSszBitlistLimit now takes ulong like the list variant, the BitList validation emission carries the UL suffix, and the bitlist Encode emission clamps to int.MaxValue (the runtime parameter is int-typed and unused). The Kind.List-with-BitArray arm could never fire — such properties resolve to Kind.BitList — and is removed. Regression: the huge-limit test now also covers a 2^40-limit bitlist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(ssz): reject huge bitlist limits; address review feedback - Generator's SszListAttribute mirror now takes `ulong limit`, matching the runtime attribute (flcl42). - SszProperty parses the limit as `ulong` directly (the attribute is `ulong`, so the value is never `int`) and now rejects a `BitArray` list whose limit exceeds `int.MaxValue` with an SSZ003 generator error instead of silently clamping the int-typed Encode parameter — a BitArray cannot hold more than int.MaxValue bits (flcl42). - Drop the now-unsupported huge-limit bitlist test type; add a generator diagnostic regression test for the rejection. - Trim the IDiscv5RecordFilter <remarks> block (flcl42). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * Harden discv4 unsolicited responses * More magic words * Some fixes * Simplify Kademlia test keys * Refine discovery review fixes * Refine LRU eviction * Deduplicate ENR signer tests * Move more discovery into kad * Fix stab config * Fix discv4 node source self emission * Remove unused merge usings * Address discv5 review feedback * Refine discovery review fixes --------- Co-authored-by: Amirul Ashraf <asdacap@gmail.com> Co-authored-by: lukasz.rozmej <lukasz.rozmej@gmail.com>
Changes
The two host-side prerequisites of the embedded beacon-chain driver plugin (#11976), isolated so the plugin can be compiled and loaded as a standalone DLL (via the
plugins/directory) without the main PR:intrange. Beacon-chain lists use limits up to 2^40 (VALIDATOR_REGISTRY_LIMIT), which overflowed theint-typedSszListAttribute. The limit is nowulongend to end; decode-side count guards are omitted when the limit exceedsint.MaxValue(anint-typed count can never violate them). Regression test asserts the spec merkle root for a 2^40-limit list (basic and composite element paths).NodesResponseHandler,KademliaAdapteracceptance,NodeSource).Discv5RecordFiltermakes the policy injectable, defaulting to the existing execution-layer behavior (no functional change); a beacon-chain discovery scope registersAcceptAll.No plugin code and no plugin registration here — with this branch in the host build,
Nethermind.BeaconChain.dll(from #11976) is a drop-in plugin.Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Notes on testing
Nethermind.Serialization.SszGenerator.Test: 52/52 (incl. the new 2^40-limit known-answer test).Nethermind.Network.Discovery.Test: 242/242 (1 pre-existing skip) — EL default filter behavior unchanged.Documentation
Requires documentation update
Requires explanation in Release Notes
Remarks
Based on
generic-kad-2(the discv5 filter touches that branch's code). Extracted from #11976, which builds on this.🤖 Generated with Claude Code