Skip to content

XDC Discovery#11527

Open
batrr wants to merge 4 commits intomasterfrom
feat/xdc-discovery
Open

XDC Discovery#11527
batrr wants to merge 4 commits intomasterfrom
feat/xdc-discovery

Conversation

@batrr
Copy link
Copy Markdown
Contributor

@batrr batrr commented May 8, 2026

Problem

Nethermind was failing to discover peers on the XDC network. XDC forked go-ethereum's discovery v4 protocol and changed the ping type byte from 1 (standard geth pingPacket) to 5 (pingXDC). XDC nodes only handle wire bytes {2, 3, 4, 5}, type 1 is rejected as unknown and dropped.

Nethermind was serializing pings with type byte 1, which XDC nodes silently drop. No pong was ever received, bonding never completed, and no peers were discovered.

https://github.com/XinFinOrg/XDPoSChain/blob/4ef1d4fdf597a15c3d2b28ae14763fe1b3bd2208/p2p/discover/udp.go#L61

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @batrr's task in 3m 48s —— View job


PR Review: XDC Discovery

  • Gather context and read changed files
  • Load coding rules and guidelines
  • Analyze each changed file
  • Post inline comments for specific issues
  • Write summary review

Summary

The approach is sound: XDC forked disc-v4 and remapped the ping type byte from 1 to 5, so Nethermind's standard pings were silently dropped. The fix correctly applies the Template Method pattern — virtual/override hooks on FromMsgTypeByte, CreateDiscoveryHandler, and MsgTypeByte — to let XdcModule slot in XDC-specific behaviour without touching the core discovery stack. The serialization analysis checks out: pong (2), FindNode (3), Neighbors (4) all use the same bytes in both protocols, so only the ping serializer and the inbound type-byte mapping needed changing.

Three issues to address before merge:

Severity Location Issue
High XdcModule.cs:138 DI registration relies on Autofac "last wins" ordering. If XdcModule loads before DiscoveryModule, XDC silently falls back to standard type byte 1. No compile error, no log warning.
Medium PingMsgSerializer.cs:16 protected byte MsgTypeByte is mutable. A protected virtual byte MsgTypeByte property is safer and idiomatic for this pattern.
Medium XdcNettyDiscoveryHandler.cs:22 The mapping silently excludes ENR (standard bytes 5/6) with no comment. A maintainer might later try to "fix" this.
Medium Missing tests CONTRIBUTING.md requires a regression test when fixing a bug. Nethermind.Xdc.Test has no coverage for the new handler, serializer, or byte mapping. A parameterised unit test on FromMsgTypeByte and a round-trip test for XdcPingMsgSerializer would be sufficient.
Low XdcDiscoveryApp.cs:18 Constructor duplicates parent's full parameter list. A comment explaining why (Autofac WithAttributeFiltering) would prevent future confusion.

The High and both Medium issues above mean this is not yet mergeable as-is. The DI ordering fragility in particular could silently re-introduce the exact bug being fixed.

Comment thread src/Nethermind/Nethermind.Xdc/XdcModule.cs
Comment thread src/Nethermind/Nethermind.Network.Discovery/Serializers/PingMsgSerializer.cs Outdated
Comment thread src/Nethermind/Nethermind.Xdc/Discovery/XdcNettyDiscoveryHandler.cs
Comment thread src/Nethermind/Nethermind.Xdc/Discovery/XdcDiscoveryApp.cs
@batrr
Copy link
Copy Markdown
Contributor Author

batrr commented May 8, 2026

@claude re review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @batrr's task in 5m 35s —— View job


PR Re-Review: XDC Discovery

  • Gather context
  • Read changed files
  • Load coding rules
  • Analyze changes
  • Write summary

What changed since the previous review

All four prior findings were addressed:

Previous finding Status
High — DI ordering fragility, no documentation ✅ Fixed — comment added at XdcModule.cs:138-139 correctly documenting the guaranteed ordering (plugins always after NethermindModule per NethermindRunnerModule)
Mediumprotected byte MsgTypeByte mutable field ✅ Fixed — changed to protected virtual byte MsgTypeByte => (byte)MsgType.Ping; in PingMsgSerializer.cs:16
Medium — ENR exclusion undocumented in XdcNettyDiscoveryHandler ✅ Fixed — comment added at lines 22-23
Low — constructor parameter list duplication unexplained ✅ Fixed — comment added at XdcDiscoveryApp.cs:18

Additionally, MessageSerializationService.cs was changed from TryAdd to dict assignment (=). This is correct and necessary: without it the standard PingMsgSerializer would win and XDC would silently keep using type byte 1. The = assignment makes plugin serializer overrides work correctly.


Remaining issue

Medium — Missing regression test

CONTRIBUTING.md requires a regression test when fixing a bug. The Nethermind XDC test project (Nethermind.Xdc.Test) has no coverage for the three new types. Two tests would suffice:

  1. XdcPingMsgSerializer round-trip — serialize a PingMsg and assert the type byte at position 97 is 5 (not 1). This directly guards against the regression of sending the wrong byte to XDC nodes.
  2. XdcNettyDiscoveryHandler.FromMsgTypeByte — parameterized test asserting: 5 → MsgType.Ping, 2 → Pong, 3 → FindNode, 4 → Neighbors, 1 → null, 6 → null.

Without these, the exact bug this PR fixes (wrong type byte sent to XDC nodes) has no automated guard and can regress silently.


Correctness confirmation

The fix is technically correct end-to-end:

  • Outbound path: XdcPingMsgSerializer.MsgTypeByte => 5PrepareBufferForSerialization writes 0x05 at offset 97. XDC nodes accept this as pingXDC. ✓
  • Inbound path: XdcNettyDiscoveryHandler.FromMsgTypeByte(5) → MsgType.Ping → dispatches to Deserialize<PingMsg>() which uses inherited PingMsgSerializer.Deserialize(). Byte layout is identical. ✓
  • DI ordering: NethermindRunnerModule adds NethermindModule at line 53, then iterates plugin modules in the foreach loop starting at line 82. XdcDiscoveryApp.As<DiscoveryApp>() always registers after DiscoveryApp.AsSelf(). ✓
  • Serializer override: MessageSerializationService now uses _zeroSerializers[...] = (dict assignment), so XdcPingMsgSerializer's SerializerInfo overwrites the standard one. CompositeDiscoveryApp resolves Func<DiscoveryApp> which returns XdcDiscoveryApp. ✓
  • XdcSubnetModule inherits from XdcModule and calls base.Load(), so subnet deployments also get XDC discovery. ✓

One Medium finding (missing regression test) blocks merge per repo policy.

@batrr
Copy link
Copy Markdown
Contributor Author

batrr commented May 8, 2026

@claude re review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @batrr's task in 3m 19s —— View job


PR Re-Review: XDC Discovery

  • Gather context
  • Read changed files
  • Load coding rules
  • Analyze changes
  • Write summary

What changed since the last review

The last remaining finding was a Medium — missing regression tests. Tests have now been added in Nethermind.Xdc.Test/Discovery/XdcDiscoveryTests.cs.

Previous finding Status
Medium — Missing regression tests ✅ Addressed — see analysis below

Test analysis

XdcPingMsgSerializer_WritesXdcTypeByte (lines 22–38)

Correct and sufficient as a regression guard. Constructs a full serializer with a real ECDSA + key generator, serializes a PingMsg, and asserts buffer.GetByte(97) == 5. The byte offset 97 matches MdcSigOffset = 32 + 64 + 1 in DiscoveryMsgSerializerBase, which is where PrepareBufferForSerialization writes the type byte. This directly guards against the original bug.

XdcNettyDiscoveryHandler_FromMsgTypeByte (lines 56–62)

Covers the two XDC-critical cases: 5 → Ping and 1 → null. The parameterized test intentionally omits bytes 2 (Pong), 3 (FindNode), 4 (Neighbors), and 6 (null), which are shared with standard disc-v4. Those paths are present in the XdcNettyDiscoveryHandler.FromMsgTypeByte switch but carry lower regression risk since they don't differ from standard behavior. A broader parameterized set would be ideal but is not blocking.


Full correctness check (all prior findings)

Item Status
Outbound type byte: XdcPingMsgSerializer.MsgTypeByte => 5 written at offset 97
Inbound: FromMsgTypeByte(5) → Ping, dispatches to PingMsgSerializer.Deserialize()
DI ordering comment at XdcModule.cs:138–139
protected virtual byte MsgTypeByte property (not mutable field)
ENR exclusion documented in XdcNettyDiscoveryHandler comment
Constructor duplication comment in XdcDiscoveryApp
MessageSerializationService uses dict = (not TryAdd) so XDC serializer wins
XdcSubnetModule inherits from XdcModule — subnet deployments also benefit

Remaining minor note (Low, non-blocking)

The XdcNettyDiscoveryHandler_FromMsgTypeByte test covers 2 of 6 mappings. Adding [TestCase((byte)2, MsgType.Pong)], [TestCase((byte)3, MsgType.FindNode)], [TestCase((byte)4, MsgType.Neighbors)], [TestCase((byte)6, null)] would complete the coverage. Not required for merge — the XDC-specific paths are already guarded.


Verdict

This PR is ready to merge. All prior Critical/High/Medium findings are resolved. The regression test for the core bug (wrong type byte sent to XDC nodes) is in place. The fix is technically correct end-to-end.

@batrr batrr marked this pull request as ready for review May 8, 2026 14:18
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 8, 2026

Claude finished @batrr's task in 2m 37s —— View job


PR Review: XDC Discovery

  • Gather context and read changed files
  • Load coding rules and guidelines
  • Analyze each changed file
  • Write summary review

Summary

This is the third re-review. All prior Critical/High/Medium findings have been addressed. The PR is in good shape.

What the PR does (end-to-end correctness confirmed):

Path What happens
Outbound Ping XdcPingMsgSerializer.MsgTypeByte => 5PrepareBufferForSerialization writes 0x05 at byte offset 97. XDC nodes accept this as pingXDC. ✓
Inbound Ping XdcNettyDiscoveryHandler.FromMsgTypeByte(5) → MsgType.PingDeserialize<PingMsg>()PingMsgSerializer.Deserialize(). Body layout is identical to standard disc-v4. ✓
DI ordering XdcModule registers after NetworkModule (per NethermindRunnerModule guarantee, documented at XdcModule.cs:138-139). XdcDiscoveryApp.As<DiscoveryApp>() wins. ✓
Serializer override MessageSerializationService uses dict = (last-wins), so XdcPingMsgSerializer registered by XdcModule overwrites the default PingMsgSerializer. ✓
XdcSubnetModule Inherits from XdcModule and calls base.Load() — subnet deployments also get XDC discovery. ✓

Previous findings — all resolved:

Finding Status
High — DI ordering fragility ✅ Comment at XdcModule.cs:138-139
Medium — mutable MsgTypeByte field ✅ Changed to protected virtual byte MsgTypeByte property
Medium — ENR exclusion undocumented ✅ Comment added at XdcNettyDiscoveryHandler.cs:22-23
Low — constructor duplication unexplained ✅ Comment added at XdcDiscoveryApp.cs:18
Medium — missing regression tests XdcDiscoveryTests.cs added

New findings

Low — MessageSerializationService semantics change (not blocking)

TryAdd[]= changes "first-registered-wins" to "last-registered-wins" for all serializer registrations, not just XDC. This is intentional and correct for the plugin-override pattern, and consistent with how Autofac handles duplicate registrations. No non-XDC code registers duplicate message types, so the behavioral change has no current side-effect. Worth being aware of if future modules also override serializers.

Low — FromMsgTypeByte test covers 2 of 6 mappings (not blocking)

[TestCase((byte)5, MsgType.Ping)] and [TestCase((byte)1, null)] are the XDC-critical paths. Bytes 2 (Pong), 3 (FindNode), 4 (Neighbors), and 6 (null) are tested implicitly by the parent class behaviour. Adding them would complete coverage but is not required for merge.


Verdict

Ready to merge. All prior High and Medium findings are resolved. The regression test directly guards against the original bug (wrong type byte sent to XDC nodes). No new High or Medium issues found.

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.

1 participant