go-blockchain/docs/networking.md
Snider f7cd812263
Some checks failed
Security Scan / security (push) Successful in 7s
Test / Test (push) Failing after 18s
docs: add human-friendly documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:02:39 +00:00

9 KiB

title description
P2P Networking Levin wire protocol, peer discovery, block/transaction propagation, and chain synchronisation.

P2P Networking

The Lethean P2P network uses the Levin protocol, a binary TCP protocol inherited from the CryptoNote/epee library. The Go implementation spans two packages:

  • go-p2p/node/levin/ -- Wire format: 33-byte header, portable storage serialisation, framed TCP connections.
  • go-blockchain/p2p/ -- Application-level command semantics: handshake, sync, block/tx relay.

Levin Wire Format

Every message is prefixed with a 33-byte header:

Offset  Size   Field               Description
------  ----   -----               -----------
0       8      m_signature         Magic: 0x0101010101012101 (LE)
8       8      m_cb                Payload length in bytes (LE)
16      1      m_have_to_return    Boolean: expects a response
17      4      m_command           Command ID (LE uint32)
21      4      m_return_code       Return code (LE int32)
25      4      m_flags             Packet type flags (LE uint32)
29      4      m_protocol_version  Protocol version (LE uint32)

Packet Types

Flag Meaning
0x00000001 Request (expects response if m_have_to_return is set)
0x00000002 Response to a prior request

Header Validation

On receiving a message, a node:

  1. Reads 8 bytes and verifies they match 0x0101010101012101.
  2. Reads the remaining 25 bytes of the header.
  3. Validates payload length does not exceed P2P_DEFAULT_PACKET_MAX_SIZE (50 MB).
  4. Reads m_cb bytes of payload.
  5. Dispatches based on m_command.

Payload Serialisation

Payloads use the epee portable storage binary format -- a TLV-style key-value serialisation supporting nested objects, arrays, integers, strings, and binary blobs.

This is distinct from the consensus binary serialisation used for blocks and transactions in the wire/ package. Notably, portable storage varints use a 2-bit size mark in the low bits (1/2/4/8 byte encoding), not the 7-bit LEB128 varints used on the consensus wire.

Command IDs

Commands are divided into two pools, re-exported in the p2p/ package:

const (
    CommandHandshake       = 1001  // P2P handshake
    CommandTimedSync       = 1002  // Periodic state sync
    CommandPing            = 1003  // Connectivity check
    CommandNewBlock        = 2001  // Block propagation
    CommandNewTransactions = 2002  // Transaction propagation
    CommandRequestObjects  = 2003  // Request blocks/txs by hash
    CommandResponseObjects = 2004  // Response with blocks/txs
    CommandRequestChain    = 2006  // Request chain skeleton
    CommandResponseChain   = 2007  // Response with chain entry
)

Handshake Protocol

Go Types

type NodeData struct {
    NetworkID [16]byte
    PeerID    uint64
    LocalTime int64
    MyPort    uint32
}

type HandshakeRequest struct {
    NodeData    NodeData
    PayloadData CoreSyncData
}

type HandshakeResponse struct {
    NodeData     NodeData
    PayloadData  CoreSyncData
    PeerlistBlob []byte  // Packed 24-byte entries
}

Connection Flow

  1. Initiator connects via TCP and sends COMMAND_HANDSHAKE (1001) as a request.
  2. Responder validates the network ID, client version, and peer uniqueness.
  3. Responder replies with its own node data, sync state, and peer list.
  4. Both sides begin periodic COMMAND_TIMED_SYNC (1002) every 60 seconds.

Handshake Request Payload

node_data {
    network_id    [16]byte   // Derived from CURRENCY_FORMATION_VERSION
    peer_id       uint64     // Random peer identifier
    local_time    int64      // Node's Unix timestamp
    my_port       uint32     // Listening port for inbound connections
}
payload_data {
    current_height           uint64
    top_id                   [32]byte   // Top block hash
    last_checkpoint_height   uint64
    core_time                uint64
    client_version           string     // e.g. "6.0.1.2[go-blockchain]"
    non_pruning_mode_enabled bool
}

Handshake Rejection

The responder rejects the handshake if:

  • network_id does not match (different chain or testnet/mainnet mismatch)
  • client_version is below the minimum for the current hardfork era
  • The peer ID is already connected (duplicate connection)

Network Identity

Network Formation Version Network ID (byte 15)
Mainnet 84 0x54
Testnet 100 0x64
var NetworkIDMainnet = [16]byte{
    0x11, 0x10, 0x01, 0x11, 0x01, 0x01, 0x11, 0x01,
    0x10, 0x11, 0x01, 0x11, 0x01, 0x11, 0x21, 0x54,
}

Peer List Management

Peer Entry Format

Peer lists are exchanged as packed binary blobs, 24 bytes per entry:

type PeerlistEntry struct {
    IP       uint32   // IPv4 (network byte order)     [4 bytes]
    Port     uint32   // Port number                    [4 bytes]
    ID       uint64   // Peer ID                        [8 bytes]
    LastSeen int64    // Unix timestamp of last contact  [8 bytes]
}

entries := p2p.DecodePeerlist(blob)  // Splits packed blob into entries

Two-Tier Peer Lists

List Max Size Description
White list 1,000 Verified peers (successful handshake + ping)
Grey list 5,000 Unverified peers (received from other nodes)

Connection Strategy

Parameter Value
Target outbound connections 8
White list preference 70%
Peers exchanged per handshake 250

When establishing outbound connections, 70% are made to white list peers and 30% to grey list peers. Successful connections promote grey list peers to the white list.

Ping Verification

Before adding a peer to the white list:

  1. Connect to the peer's advertised IP:port.
  2. Send COMMAND_PING (1003).
  3. Verify the response contains status: "OK" and a matching peer_id.
  4. Only then promote from grey to white list.

Failure Handling

Parameter Value
Failures before blocking 10
Block duration 24 hours
Failed address forget time 5 minutes
Idle connection kill interval 5 minutes

Timed Sync

After handshake, peers exchange COMMAND_TIMED_SYNC (1002) every 60 seconds. This keeps peers informed of each other's chain state and propagates peer list updates.

Block Propagation

When a node mines or stakes a new block:

NOTIFY_NEW_BLOCK (2001) {
    b {
        block:                blob          // Serialised block
        txs:                  []blob        // Serialised transactions
    }
    current_blockchain_height: uint64
}

This is a notification (no response expected). The receiving node validates the block and relays it to its own peers.

Transaction Propagation

NOTIFY_OR_INVOKE_NEW_TRANSACTIONS (2002) {
    txs: []blob    // Serialised transaction blobs
}

Up to CURRENCY_RELAY_TXS_MAX_COUNT (5) transactions per message.

Chain Synchronisation

Requesting the Chain Skeleton

To sync, a node sends its known block IDs in a sparse pattern:

NOTIFY_REQUEST_CHAIN (2006) {
    block_ids: []hash    // Sparse: first 10 sequential, then 2^n offsets, genesis last
}

The Go implementation uses SparseChainHistory() which matches the C++ get_short_chain_history() algorithm:

  • The 10 most recent block hashes
  • Then every 2nd, 4th, 8th, 16th, 32nd, etc.
  • Always ending with the genesis block hash

Chain Response

NOTIFY_RESPONSE_CHAIN_ENTRY (2007) {
    start_height:  uint64
    total_height:  uint64
    m_block_ids:   []block_context_info
}

Fetching Blocks

NOTIFY_REQUEST_GET_OBJECTS (2003) {
    txs:    []hash    // Transaction hashes to fetch
    blocks: []hash    // Block hashes to fetch
}

NOTIFY_RESPONSE_GET_OBJECTS (2004) {
    txs:                      []blob
    blocks:                   []block_complete_entry
    missed_ids:               []hash
    current_blockchain_height: uint64
}

P2P Sync State Machine

The chain.P2PSync() function implements the full sync loop:

  1. Build sparse chain history from local chain state.
  2. Send REQUEST_CHAIN to the peer.
  3. Receive RESPONSE_CHAIN_ENTRY with block hashes.
  4. Fetch blocks in batches via REQUEST_GET_OBJECTS.
  5. Validate and store blocks through processBlockBlobs().
  6. Repeat until the peer has no more blocks.

The first block in each REQUEST_GET_OBJECTS response overlaps with the last known block (to confirm chain continuity) and is skipped during processing.

Sync Limits

Parameter Value
Block IDs per chain request 2,000
Blocks per download batch 200
Max bytes per sync packet 2 MB
Max blocks per get_objects 500
Max txs per get_objects 500

Connection Timeouts

Parameter Value
TCP connection 5,000 ms
Ping 2,000 ms
Command invoke 120,000 ms (2 min)
Handshake 10,000 ms
Max packet size 50 MB

Network Ports

Network P2P RPC Stratum
Mainnet 36942 36941 36940
Testnet 46942 46941 46940