Skip to content

[Add] Neo N3 P2P peer session: framing, handshake, connect, and keep-alive - #32

Merged
cschuchardt88 merged 11 commits into
Rapid-Loop:masterfrom
cschuchardt88:neo-hosting/add/service
Jul 23, 2026
Merged

cschuchardt88 merged 11 commits into
Rapid-Loop:masterfrom
cschuchardt88:neo-hosting/add/service

Conversation

@cschuchardt88

Copy link
Copy Markdown
Member

Summary

Adds a Neo N3–compatible P2P session layer so this platform can speak the same TCP wire protocol as neo-project/neo Network/P2P: message framing, node capabilities, Version/Verack handshake, listen + outbound connect, and Ping/Pong with idle timeouts.

Also wires store/backup config defaults into the hosting environment provider and adds RandomNumberFactory as a public SDK utility (used for nonces and available to consumers).

Motivation

Node hosting needs real peer connectivity. This PR lands the connection + handshake foundation (aligned with Neo’s Message, RemoteNode handshake, and capability layout) without implementing full chain sync yet.

What’s included

1. Protocol framing (ProtocolMessage)

Wire layout matches Neo N3:

[Flags:1][Command:1][VarSize length][payload]
API Behavior
TryRead Returns 0 if incomplete, byte count if complete, throws if payload > PayloadMaxSize (0x02000000)
Create Builds outbound frames; LZ4 compress only for Neo-allowed commands when savings ≥ 64 bytes and payload > 128
ToArray(allowCompression) Can force uncompressed send (pre-handshake / remote disable-compression)

2. Version & capabilities

  • VersionMessage — network, version, timestamp, nonce, user agent, capabilities
  • Capability hierarchy with single type-byte ownership (DeserializeFrom + DeserializeWithoutType / SerializeWithoutType), matching Neo
  • Types: TcpServer (port), FullNode (start height), ArchivalNode, DisableCompression
  • Duplicate capability check by type; max 32 capabilities
  • Fix: array GetSerializedSize now sums sizes correctly (no length × sum)

3. Peer connection (NodeConnection)

  • Socket → Pipe → frame parse loop (TryReadFrame)
  • ConnectAsync outbound factory
  • WaitForHandshakeAsync
  • SendAsync with optional compression
  • Events: HandshakeCompleted, MessageReceived (post-handshake), Disconnected
  • LocalBlockIndex / RemoteLastBlockIndex for Ping/Pong height

4. Handshake (NodeHandshake)

Pure state machine (testable without I/O):

WaitingForVersion → (Version OK) → send Verack → WaitingForVerack → Ready

Rejects: wrong command order, network mismatch, self-nonce, handshake messages after Ready.

5. Listener (NodeServerListener)

  • TCP accept with ReuseAddress, default backlog 128
  • No named mutex (OS bind exclusivity only)
  • Shared node nonce + local capabilities (FullNode + TcpServer)
  • Tracks inbound and outbound peers; removes on Disconnected
  • ConnectAsync for outbound dial using this node’s identity

6. Keep-alive (Neo-style)

Timer Value Action
Handshake 10s Close if not Ready
Receive idle 60s Close
Keep-alive tick 30s Evaluate timeouts / ping
Send idle 1 min Send Ping
  • Auto Pong on Ping (same nonce, local height)
  • PingMessage payload: LastBlockIndex, Timestamp, Nonce

7. Hosting / config

  • BlockchainStoreNames config keys
  • Env defaults for store path, create-if-missing, backup path/max backups
  • RandomNumberFactory public SDK surface

8. Tests

Area Coverage
UT_ProtocolMessage Incomplete frames, trailing bytes, VarSize, Create/compress round-trip
UT_NodeCapabilityMessage Per-capability serialize/deserialize
UT_VersionMessage Capabilities, duplicates, Version inside ProtocolMessage
UT_NodeHandshake Happy path + protocol violations
UT_NodeConnection Frame consumption on sequences
UT_NodeConnectionConnect / UT_NodeServerListener Loopback accept, outbound connect, client cleanup, Ping/Pong
UT_PingMessage Payload + frame round-trip
TestUtilities GetFreeTcpPort, WaitForAsync

Commits

  1. Added environment variables
  2. Added Version Network Message Class
  3. Fixed Version Message tests
  4. Added Node Server and Node Connection Classes
  5. Add Neo N3 P2P framing, handshake, and keep-alive for node connections

Out of scope (follow-ups)

  • Peer discovery (GetAddr / Addr, seed list)
  • Sync messages (GetHeaders, Headers, Inv, GetData, Block, Tx, …)
  • Max peers / connect queue / per-IP limits
  • Relay, mempool, bloom filters
  • Live smoke test against a public Neo seed

Test plan

  • dotnet test tests/Neo.Core.Tests/Neo.Core.Tests.csproj --filter "FullyQualifiedName~Net"
  • Framing incomplete/complete/oversize behavior
  • Capability type byte not double-read
  • Listen + accept + disconnect tracking
  • Outbound connect
  • Version/Verack
  • Ping/Pong + idle timeouts
  • Full protocol / chain sync (future PRs)

Compatibility notes

  • Intended to interoperate with Neo N3 P2P framing + handshake + ping
  • Compression policy matches Neo’s command list and thresholds
  • Until remote Version is known (or peer disables compression), sends uncompressed frames

Implement protocol-compatible wire framing (TryRead/Create), fix capability
serialize/deserialize, and complete the peer session path: listen, outbound
connect, Version/Verack handshake, Ping/Pong, and idle timeouts. Include
coverage for messages, handshake, and loopback accept/connect.
Use OS-assigned port 0, BoundEndPoint, exclusive bind on Windows, and disconnect-safe WaitForHandshakeAsync so socket tests do not race under MethodLevel parallelization.
@cschuchardt88

Copy link
Copy Markdown
Member Author

Reopening to resync head after CI flake-fix push (fork tip 17b69f9; PR was stuck on 52e5aa8).

@cschuchardt88
cschuchardt88 merged commit 32e1c69 into Rapid-Loop:master Jul 23, 2026
4 checks passed
@cschuchardt88
cschuchardt88 deleted the neo-hosting/add/service branch July 23, 2026 09:14
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