Skip to content

feat: host-side prerequisites for the embedded beacon chain plugin (SSZ ulong limits, injectable discv5 ENR filter)#11985

Merged
asdacap merged 6 commits into
generic-kad-2from
feature/beaconchain-core-prereqs
Jun 18, 2026
Merged

feat: host-side prerequisites for the embedded beacon chain plugin (SSZ ulong limits, injectable discv5 ENR filter)#11985
asdacap merged 6 commits into
generic-kad-2from
feature/beaconchain-core-prereqs

Conversation

@asdacap

@asdacap asdacap commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

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:

  • SSZ generator: list limits beyond int range. 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 (an int-typed count can never violate them). Regression test asserts the spec merkle root for a 2^40-limit list (basic and composite element paths).
  • discv5: injectable consensus-only-ENR drop policy. The shared stack hard-dropped consensus-only node records in three places (NodesResponseHandler, KademliaAdapter acceptance, NodeSource). Discv5RecordFilter makes the policy injectable, defaulting to the existing execution-layer behavior (no functional change); a beacon-chain discovery scope registers AcceptAll.

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?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

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

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

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

asdacap and others added 2 commits June 13, 2026 08:33
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

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Claude finished @asdacap's task in 5m 12s —— View job


Review: feat: host-side prerequisites for the embedded beacon chain plugin

  • Read changed files
  • Analyze SSZ ulong limit changes
  • Analyze discv5 injectable filter changes
  • Check tests
  • Post review findings

Summary

Both changes are well-scoped and correct for the stated purpose. The SSZ ulong-limit change accurately propagates ulong end-to-end (attribute → generator → helpers → merkleization), and the discv5 filter change is cleanly injectable with zero behavioral change to the EL default path. Tests cover the 2^40-limit known-answer case and verify EL filter behavior remains unchanged.

One Medium inconsistency was found: ValidateSszBitlistLimit was not updated alongside ValidateSszListLimit, leaving it with an int limit parameter. This doesn't affect the beacon chain use case (no bitlist has a limit > int.MaxValue) but would produce a compile error if ever triggered.


Findings

Medium

ValidateSszBitlistLimit not updated for huge limits (line 648, SszGenerator.cs)

ValidateSszListLimit was correctly updated to ulong limit, but ValidateSszBitlistLimit (called from both Kind.BitList and the dead Kind.List when "BitArray" arm) still takes int limit. The code emits {property.Limit} — a ulong? — directly as an int argument. For limits ≤ int.MaxValue this compiles fine; for larger limits it produces a compile error in the downstream generated file.

The Kind.List when "BitArray" arm on line 648 is dead code (Kind.BitList is always emitted for a BitArray + [SszList] property per the Kind getter), but line 651 (Kind.BitList) is live.

Suggested fix — apply the same "skip guard for limits beyond int.MaxValue" pattern:

Kind.BitList => property.Limit is <= int.MaxValue
    ? $"ValidateSszBitlistLimit({expression}, {property.Limit}, nameof({decl.TypeReferenceName}), nameof({property.Name}));"
    : string.Empty,

Or lift ValidateSszBitlistLimit to ulong limit. Neither approach affects the current beacon chain bitlist sizes.


Low

NodesResponseHandler takes nullable Discv5RecordFilter? (line 13, NodesResponseHandler.cs)

The only call site is KademliaAdapter.FindNeighbours, which always passes a non-null filter. The nullable default is never exercised in production. Minor inconsistency with NodeSource and KademliaAdapter, which take non-nullable.


Notes (no action required)

  • SszListAttribute(ulong limit) backward compat: The constructor change from int to ulong is non-breaking in practice. C#'s implicit constant-expression conversion allows const int values and integer literals to flow without a cast (§10.2.11 of the spec), so all existing [SszList(n)] usages compile without modification — confirmed by grepping all call sites.
  • Merkleization correctness: The MerkleizeBasicList chunkCount calculation (limit × itemSize + 31) / 32 correctly uses ulong arithmetic throughout; the test's reference root Limit / 4 matches the helper's computation for 8-byte ulongs. ✓
  • Discv5RecordFilter.Excludes() logic: Short-circuit evaluation on !acceptConsensusOnlyRecords ensures IsConsensusOnlyNodeRecord is never called for AcceptAll. ✓
  • DI override path: KademliaModule registers ExecutionLayer as a singleton; the beacon-chain plugin can override by registering AcceptAll in the same or child scope (last-registered wins in Autofac). ✓

@@ -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}));",

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.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)

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.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

asdacap and others added 3 commits June 13, 2026 08:58
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
@asdacap asdacap marked this pull request as ready for review June 17, 2026 08:32
@claude

claude Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Claude finished @asdacap's task in 4m 52s —— View job


Review: feat: host-side prerequisites for the embedded beacon chain plugin

  • Read relevant agent rules (coding-style, robustness, di-patterns, test-infrastructure)
  • Examine changed files in detail
  • Check SSZ ulong limit changes (SszGenerator.cs, SszProperty.cs, SszSerializableAttribute.cs)
  • Check discv5 injectable filter changes (IDiscv5RecordFilter.cs, KademliaAdapter.cs, NodeSource.cs, NodesResponseHandler.cs, KademliaModule.cs)
  • Check tests
  • Verify previous Medium/Low findings are resolved
  • Post final review

Summary

Both changes are correct and complete. The SSZ ulong limit change is propagated end-to-end with no gaps, and the discv5 record filter is cleanly injectable with zero behavioral change to the EL default path. The two previously-raised issues (Medium: ValidateSszBitlistLimit not updated; Low: nullable filter parameter) are fully resolved.


Previous findings — resolved ✓

  • Medium (commit 2d06a9f)ValidateSszBitlistLimit now takes ulong limit with (ulong)bits.Length comparison; the dead Kind.List when "BitArray" arm is removed; the bitlist Encode emission clamps via Math.Min(property.Limit!.Value, int.MaxValue) for the unused int-typed Encode parameter.
  • Low (commit b08af23)IDiscv5RecordFilter is now a required non-nullable dependency at all three Kademlia-layer sites; no nullable defaults or null-coalescing remain.

Low

DiscoveryV5App.TryGetAcceptableNodeFromEnr has a fourth hard-coded IsConsensusOnlyNodeRecord call (DiscoveryV5App.cs:190)

This is intentionally out of this PR's stated scope (which targeted three Kademlia-layer sites: NodesResponseHandler, KademliaAdapter, NodeSource). TryGetAcceptableNodeFromEnr is called from AddNodeToDiscovery and AddBootNode in the EL app layer — a beacon-chain plugin would have its own app layer and would not reach this code path. No action required here, but worth flagging as the one remaining hard-coded filter site for future reference.


Notes (no action required)

  • ulong propagation completeness: All limit emissions in ValidationStatement, MerkleizeRootStatement, MerkleizeEmptyCollectionRootStatement carry the UL suffix; all three decode-side pre-allocation guards are conditioned on property.Limit is <= int.MaxValue (correctly safe since int-typed counts can never exceed int.MaxValue).
  • Math.Min(property.Limit!.Value, int.MaxValue) for BitList Encode: property.Limit.Value is ulong; int.MaxValue is a compile-time constant within ulong range and implicitly converts under §10.2.11 of the C# spec. The result is emitted as a decimal string (e.g. 2147483647) without suffix, which is a valid int literal at the call site. ✓
  • SszListAttribute(ulong limit) backward compat: Existing [SszList(n)] call sites with int/constant literals continue to compile via §10.2.11 implicit constant-expression conversions; SszProperty.From() handles both ulong and int attribute argument types. ✓
  • DI registration: KademliaModule registers ExecutionLayerDiscv5RecordFilter.Instance directly (valid — passing a pre-constructed instance avoids factory overhead for a stateless singleton). A beacon-chain plugin overrides with AcceptAllDiscv5RecordFilter in the same or child scope (last-registered wins in Autofac). ✓
  • Thread safety in NodesResponseHandler.Handle(): recordFilter.Excludes() is called inside _lock. Both filter implementations are stateless. ✓
  • Test coverage: The new Supports_list_limits_beyond_int_range test covers basic list, composite list, and bitlist paths at 2^40 limit, with known-answer merkle root assertions. ✓

result.Limit = listAttr.ConstructorArguments.FirstOrDefault().Value switch
{
ulong limit => limit,
int limit => (ulong)limit,

@flcl42 flcl42 Jun 17, 2026

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.

Why do we need to care about int if the attribute receives ulong now?

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.

src\Nethermind\Nethermind.Serialization.SszGenerator\Attributes.cs need to be changed too for consistency ig.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, we don't — fixed in 4cd7a63. The attribute is ulong now, so ConstructorArguments[0].Value is always a boxed ulong; the int switch arm was dead. Parsed directly as Value as ulong? ?? 0UL.

(Reply from the agent — Claude Code, model Claude Opus 4.8 (1M context) — on @asdacap's behalf.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 4cd7a63 — the generator's Attributes.cs mirror of SszListAttribute now takes ulong limit / ulong Limit, matching the runtime attribute in Nethermind.Serialization.Ssz.

(Reply from the agent — Claude Code, model Claude Opus 4.8 (1M context) — on @asdacap's behalf.)


/// <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

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.

No need in comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the <remarks> block, kept the one-line <summary>. 4cd7a63.

(Reply from the agent — Claude Code, model Claude Opus 4.8 (1M context) — on @asdacap's behalf.)

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)}";

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.

Probably should be a generator error about not supporting it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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>
@asdacap asdacap merged commit da8eb5f into generic-kad-2 Jun 18, 2026
530 checks passed
@asdacap asdacap deleted the feature/beaconchain-core-prereqs branch June 18, 2026 07:51
flcl42 added a commit that referenced this pull request Jun 23, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants