# How Neo's P2P LocalNode Establishes Connections with Remote Nodes and Routes Messages

> Discover how Neo's P2P LocalNode uses Akka.NET actors to establish remote node connections and route messages efficiently across the network via prioritized queues.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: internals
- Published: 2026-03-08

---

**Neo’s LocalNode uses Akka.NET actors to manage TCP connections, asynchronously resolving seed node DNS, executing version handshakes via RemoteNode instances, and routing messages through prioritized queues to broadcast inventories across the peer-to-peer network.**

The peer-to-peer networking layer in the Neo blockchain relies on a robust actor-based architecture to maintain decentralization. Understanding how Neo's P2P LocalNode establishes connections with remote nodes and handles message routing is essential for developers building on the Neo N3 platform or auditing its network consensus mechanisms.

## Actor-Based Architecture Overview

Neo’s networking stack treats every connection as an independent actor. The `LocalNode` class inherits from `Peer` and acts as the central coordinator, while individual `RemoteNode` actors represent connections to specific peers. When a `NeoSystem` initializes, it creates the `LocalNode` actor via `LocalNode.Props(this)` ([NeoSystem.cs L44-L51](https://github.com/neo-project/neo/blob/master-n3/src/Neo/NeoSystem.cs#L44-L51)).

## How LocalNode Establishes Remote Connections

### Actor Initialization and Seed List Resolution

The `LocalNode` constructor receives the owning `NeoSystem` and immediately stores the seed list from `system.Settings.SeedList` ([LocalNode.cs L91-L100](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L91-L100)). DNS resolution for each seed is performed asynchronously, allowing the node to begin accepting incoming connections while simultaneously resolving external peer addresses.

### Incoming TCP Connection Handling

When the underlying TCP listener accepts a new socket, `LocalNode.OnTcpConnected` creates a connection actor and immediately sends a `StartProtocol` message to trigger the version handshake ([LocalNode.cs L79-L82](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L79-L82)):

```csharp
protected override void OnTcpConnected(IActorRef connection, object remote, object local)
{
    connection.Tell(new RemoteNode.StartProtocol());
}

```

### RemoteNode Creation and Registration

The `LocalNode` overrides `ProtocolProps` to specify how `RemoteNode` actors are instantiated. It calls `RemoteNode.Props(system, this, connection, remote, local, Config)` to create the actor ([LocalNode.cs L94-L97](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L94-L97)). Upon construction, the `RemoteNode` registers itself in the parent’s concurrent dictionary via `localNode.RemoteNodes.TryAdd(Self, this)` ([RemoteNode.cs L80-L88](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/RemoteNode.cs#L80-L88)), enabling `LocalNode` to track active connections.

### Version Handshake and Connection Validation

Immediately after creation, the `RemoteNode` receives the `StartProtocol` message and constructs a `VersionPayload` containing the network ID, node nonce, and capability flags. It transmits this via `SendMessage(Message.Create(MessageCommand.Version, …))` ([RemoteNode.cs L3-L6](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/RemoteNode.cs#L3-L6)).

When the remote peer responds with its own `VersionPayload`, `LocalNode.AllowNewConnection` validates the nonce uniqueness, network ID compatibility, and checks for duplicate connections before adding the peer to `ConnectedPeers` ([LocalNode.cs L60-L73](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L60-L73)).

## Message Routing and Broadcast Mechanisms

### Broadcasting Messages to All Peers

Once validated, `LocalNode` serves as the message distribution hub. When any component sends a message to `LocalNode`, the actor re-broadcasts it to all attached `RemoteNode` instances via `BroadcastMessage(msg)`. This method iterates over `RemoteNodes.Keys` and dispatches the message to each peer actor ([LocalNode.cs L21-L28](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L21-L28)).

### RelayDirectly vs SendDirectly Inventory Propagation

Neo distinguishes between two inventory distribution strategies for blocks and transactions:

**RelayDirectly** filters inventory messages based on the remote peer's `LastBlockIndex`, only forwarding to nodes that haven't yet reached that height. This conserves bandwidth by avoiding redundant data transmission to synchronized peers ([LocalNode.cs L41-L53](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L41-L53)).

**SendDirectly** broadcasts inventories to all connected peers regardless of synchronization state via `SendToRemoteNodes(inventory)`, providing immediate propagation at the cost of potential redundancy ([LocalNode.cs L75-L78](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L75-L78)).

### Priority Queue Management in RemoteNode

Each `RemoteNode` maintains separate high-priority and low-priority message queues. Incoming messages are enqueued via `EnqueueMessage`, and the actor dequeues and transmits them only when both the TCP ACK flag and the protocol VerAck flag are true, as verified by `CheckMessageQueue` ([RemoteNode.cs L96-L111](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/RemoteNode.cs#L96-L111)).

The `RemoteNodeMailbox` implementation guarantees that protocol-critical messages—such as `Version`, `Verack`, and `Alert`—are processed before regular inventory traffic, ensuring handshake completion and network stability ([RemoteNodeMailbox.cs L48-L56](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/RemoteNode.cs#L48-L56)).

## Peer Discovery and Connection Maintenance

When the active peer count falls below the configured threshold, `LocalNode.NeedMorePeers` initiates discovery. If existing connections exist, the node broadcasts a `GetAddr` message to request additional peer addresses from the network. In the initial bootstrap case with zero peers, the node directly injects endpoints from the pre-resolved `SeedList` into the connection pool via `AddPeers(SeedList…)` ([LocalNode.cs L99-L119](https://github.com/neo-project/neo/blob/master-n3/src/Neo/Network/P2P/LocalNode.cs#L99-L119)).

## Practical Implementation Example

The following example demonstrates initializing the Neo P2P stack and broadcasting a transaction:

```csharp
// 1. Initialise the NEO system
var settings = ProtocolSettings.Load("protocol.neojson"); // load from config
using var system = new NeoSystem(settings);

// 2. Start the P2P node with default channels (TCP, UDP, etc.)
system.StartNode(new ChannelsConfig());

// 3. Send a transaction to all peers
var tx = ...; // IInventory implementation (Transaction)
system.LocalNode.Tell(new LocalNode.SendDirectly(tx));

// 4. Request more peers manually
system.LocalNode.Tell(new LocalNode.GetInstance()); // obtain reference if needed
system.LocalNode.Tell(new Message(MessageCommand.GetAddr));

```

## Key Source Files

| File | Role |
|------|------|
| **src/Neo/Network/P2P/LocalNode.cs** | Core actor that manages connections, peer discovery, and broadcast routing. |
| **src/Neo/Network/P2P/RemoteNode.cs** | Represents a single remote connection, handles inbound/outbound message queues and version handshake. |
| **src/Neo/Network/P2P/RemoteNodeMailbox.cs** | Akka mailbox that prioritises protocol‑critical messages for a `RemoteNode`. |
| **src/Neo/NeoSystem.cs** | Creates the `LocalNode` actor and exposes it via `NeoSystem.LocalNode`. |
| **src/Neo/Network/P2P/ChannelsConfig.cs** | Configuration object passed to start the local node (TCP ports, max connections, etc.). |
| **src/Neo/Network/P2P/TaskManager.cs** | Schedules periodic tasks such as peer‑request timers (used by `LocalNode`). |

## Summary

- **Actor-Based Design**: Neo’s P2P layer uses Akka.NET actors where `LocalNode` coordinates connections and `RemoteNode` instances manage individual peers.
- **Connection Establishment**: The process involves async DNS resolution of seed lists, TCP socket acceptance, `RemoteNode` actor creation, and a strict version handshake validated by `AllowNewConnection`.
- **Message Routing**: `LocalNode` broadcasts messages via `BroadcastMessage`, while `RelayDirectly` and `SendDirectly` provide filtered and unfiltered inventory propagation respectively.
- **Priority Handling**: `RemoteNode` uses high/low priority queues and a specialized `RemoteNodeMailbox` to ensure protocol messages are processed before regular traffic.
- **Peer Discovery**: `NeedMorePeers` triggers `GetAddr` broadcasts or seed list injection to maintain minimum connection counts.

## Frequently Asked Questions

### How does Neo's LocalNode handle incoming TCP connections?

When the TCP listener accepts a new socket, `LocalNode.OnTcpConnected` creates a connection actor and immediately dispatches a `StartProtocol` message to trigger the version handshake. This method then instantiates a `RemoteNode` actor via the overridden `ProtocolProps` method to manage the specific connection lifecycle and state.

### What is the difference between RelayDirectly and SendDirectly in Neo's P2P protocol?

`RelayDirectly` implements intelligent filtering by checking each remote peer's `LastBlockIndex` before forwarding blocks or transactions, ensuring bandwidth is not wasted on nodes that already possess the data. `SendDirectly` provides blanket propagation by forwarding inventory to all connected peers immediately, regardless of their synchronization status.

### How does Neo ensure critical protocol messages are processed before regular traffic?

Each `RemoteNode` utilizes a specialized `RemoteNodeMailbox` that prioritizes protocol-critical messages such as `Version`, `Verack`, and `Alert` over standard inventory messages. Additionally, the `RemoteNode` maintains separate high and low priority internal queues, processing messages only when both TCP ACK and protocol VerAck flags are verified.

### What triggers peer discovery in Neo's LocalNode?

When the active peer count falls below the configured minimum threshold, `LocalNode.NeedMorePeers` initiates the discovery process. If existing connections are available, the node broadcasts a `GetAddr` message to request additional peer addresses; otherwise, it directly injects pre-resolved seed endpoints from the `SeedList` into the connection pool.