1 P2P Protocol
Claude edited this page 2026-02-20 15:12:17 +00:00

P2P Protocol

The Lethean P2P network uses the Levin protocol, a binary TCP protocol inherited from the CryptoNote/epee library. All peer-to-peer communication (handshakes, block/tx propagation, chain sync) uses this wire format.

Levin Wire Format

Every message is prefixed with a 33-byte header (bucket_head2):

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

Total header size: 33 bytes (packed, no padding).

Constants

LEVIN_SIGNATURE        = 0x0101010101012101
LEVIN_PACKET_REQUEST   = 0x00000001
LEVIN_PACKET_RESPONSE  = 0x00000002
LEVIN_PROTOCOL_VER_0   = 0
LEVIN_PROTOCOL_VER_1   = 1

Packet Types

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

Header validation

On receiving a message, a node:

  1. Reads the first 8 bytes and verifies they match LEVIN_SIGNATURE.
  2. Reads the remaining 25 bytes of the header.
  3. Validates m_cb 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 are serialised using the epee portable storage binary format (key-value serialisation). This is a TLV-style format supporting nested objects, arrays, integers, strings, and binary blobs. It is distinct from the consensus binary serialisation used for blocks and transactions.

Command IDs

Commands are divided into two pools:

P2P Commands (base 1000)

Command ID Type Description
COMMAND_HANDSHAKE 1001 Request/Response Initial peer connection. Exchanges node data, sync payload, and peer lists
COMMAND_TIMED_SYNC 1002 Request/Response Periodic sync. Exchanges current chain state and updated peer lists
COMMAND_PING 1003 Request/Response Connectivity check. Verifies peer is reachable for inbound connections
COMMAND_REQUEST_STAT_INFO 1004 Request/Response Debug: request node statistics (requires proof of trust)

Blockchain Commands (base 2000)

Command ID Type Description
NOTIFY_NEW_BLOCK 2001 Notification Propagate a newly mined/staked block to peers
NOTIFY_OR_INVOKE_NEW_TRANSACTIONS 2002 Request/Response Propagate new transactions. Can be notification or invoke
NOTIFY_REQUEST_GET_OBJECTS 2003 Notification Request specific blocks and/or transactions by hash
NOTIFY_RESPONSE_GET_OBJECTS 2004 Notification Response with requested blocks and transactions
NOTIFY_REQUEST_CHAIN 2006 Notification Request chain skeleton (block IDs for sync)
NOTIFY_RESPONSE_CHAIN_ENTRY 2007 Notification Response with chain block IDs and heights

Note: Command IDs 2005 is unused (gap in the sequence).

Handshake Protocol

Connection Establishment

  1. Initiator connects via TCP and sends COMMAND_HANDSHAKE (1001) request.
  2. Responder validates the request and sends a response.
  3. Both sides begin periodic COMMAND_TIMED_SYNC (1002) exchanges.

Handshake Request

COMMAND_HANDSHAKE request {
    node_data {
        network_id:    uuid      // 16-byte network identifier (derived from CURRENCY_FORMATION_VERSION)
        peer_id:       uint64    // Random peer identifier
        local_time:    int64     // Node's local Unix timestamp
        my_port:       uint32    // Listening port for inbound connections
    }
    payload_data {
        current_height:          uint64    // Chain height (top block + 1)
        top_id:                  hash      // Hash of the top block
        last_checkpoint_height:  uint64    // Height of the last known checkpoint
        core_time:               uint64    // Core time
        client_version:          string    // Software version string
        non_pruning_mode_enabled: bool     // Whether full block data is available
    }
    maintrs_entry {
        maintainers_info_buff:   blob      // Signed maintainer info
        sign:                    signature // Ed25519 signature
    }
}

Handshake Response

COMMAND_HANDSHAKE response {
    node_data {
        network_id:    uuid
        peer_id:       uint64
        local_time:    int64
        my_port:       uint32
    }
    payload_data {
        current_height:          uint64
        top_id:                  hash
        last_checkpoint_height:  uint64
        core_time:               uint64
        client_version:          string
        non_pruning_mode_enabled: bool
    }
    local_peerlist:  []peerlist_entry   // Known peers (as binary blob)
    maintrs_entry {
        maintainers_info_buff:   blob
        sign:                    signature
    }
}

Handshake Validation

The responder rejects the handshake if:

  • network_id does not match (different chain or testnet/mainnet mismatch)
  • client_version is below minimum required version
  • Maintainer entry signature is invalid
  • The peer ID is already connected (duplicate connection)
  • The connection is not inbound (handshake must come from the connecting side)

Timed Sync

After handshake, peers exchange COMMAND_TIMED_SYNC (1002) every P2P_DEFAULT_HANDSHAKE_INTERVAL (60 seconds):

COMMAND_TIMED_SYNC request {
    payload_data { ... }     // Current chain state (same as handshake payload)
    maintrs_entry { ... }    // Maintainer entry
}

COMMAND_TIMED_SYNC response {
    local_time:     int64
    payload_data { ... }
    local_peerlist: []peerlist_entry
    maintrs_entry { ... }
}

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
        coinbase_global_outs: []uint64       // Global output indices for coinbase
        tx_global_outs:       [][]uint64     // Global output indices per tx
    }
    current_blockchain_height: uint64        // Sender's chain height
}

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

Transaction Propagation

New transactions are propagated via:

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

This command can function as both a notification and an invocation (request/response), as indicated by the m_have_to_return header flag. When invoked, the response contains a status code.

Up to CURRENCY_RELAY_TXS_MAX_COUNT (5) transactions are relayed 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    // First 10 sequential, then 2^n offsets, genesis last
}

The block ID list follows the CryptoNote sparse sync pattern:

  • 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             // Height of the first block in response
    total_height:  uint64             // Peer's total chain height
    m_block_ids:   []block_context_info  // Block hashes with context
}

Fetching Blocks and Transactions

After receiving the chain skeleton, the syncing node requests full 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                   // Transaction blobs
    blocks:                   []block_complete_entry    // Full blocks with txs
    missed_ids:               []hash                    // Hashes not found
    current_blockchain_height: uint64                   // Sender's height
}

Sync limits

Parameter Value Description
BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 2,000 Block IDs per chain request
BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 200 Blocks per download batch
BLOCKS_SYNCHRONIZING_DEFAULT_SIZE 2,000,000 Max bytes per sync packet (2 MB)
CURRENCY_PROTOCOL_MAX_BLOCKS_REQUEST_COUNT 500 Max blocks per get_objects
CURRENCY_PROTOCOL_MAX_TXS_REQUEST_COUNT 500 Max txs per get_objects

Peer Management

Peer Lists

Nodes maintain two peer lists:

List Max size Description
White list 1,000 (P2P_LOCAL_WHITE_PEERLIST_LIMIT) Verified peers that have completed a successful handshake and responded to a ping
Grey list 5,000 (P2P_LOCAL_GRAY_PEERLIST_LIMIT) Peers received from other nodes' peer lists but not yet verified

Peer List Entry

peerlist_entry {
    adr {
        ip:   uint32    // IPv4 address (network byte order)
        port: uint32    // Port number
    }
    id:        uint64   // Peer ID
    last_seen: int64    // Unix timestamp of last contact
}

Connection Strategy

Parameter Value Description
P2P_DEFAULT_CONNECTIONS_COUNT 8 Target outbound connections
P2P_DEFAULT_WHITELIST_CONNECTIONS_PERCENT 70% Fraction from white list
P2P_DEFAULT_PEERS_IN_HANDSHAKE 250 Max peers exchanged in handshake

When establishing outbound connections:

  • 70% of connections are made to white list peers (verified)
  • 30% of connections are made to grey list peers (unverified)
  • Each successful connection promotes the peer from grey to white list

Failure Handling

Parameter Value Description
P2P_IP_FAILS_BEFOR_BLOCK 10 Connection failures before blocking
P2P_IP_BLOCKTIME 86,400 seconds Block duration (24 hours)
P2P_FAILED_ADDR_FORGET_SECONDS 300 seconds Forget failed address after 5 minutes
P2P_IDLE_CONNECTION_KILL_INTERVAL 300 seconds Kill idle connections every 5 minutes

After 10 consecutive connection failures to the same IP address, that IP is blocked for 24 hours. Failed addresses are forgotten after 5 minutes, allowing retry. Idle connections with no traffic are terminated every 5 minutes.

Ping Verification

Before adding a peer to the white list, the node performs a ping check:

  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 the peer from grey to white list.

This prevents nodes from advertising unreachable addresses.

Network Identity

The network ID is a 16-byte UUID derived from CURRENCY_FORMATION_VERSION:

  • Mainnet: Formation version 84, network ID version 84 + 0 = 84
  • Testnet: Formation version 100, with P2P_NETWORK_ID_TESTNET_FLAG = 0

Nodes reject handshakes from peers with a different network ID, preventing mainnet/testnet cross-contamination.

Connection Timeouts

Parameter Value Description
P2P_DEFAULT_CONNECTION_TIMEOUT 5,000 ms TCP connection timeout
P2P_DEFAULT_PING_CONNECTION_TIMEOUT 2,000 ms Ping-specific timeout
P2P_DEFAULT_INVOKE_TIMEOUT 120,000 ms Command invoke timeout (2 min)
P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT 10,000 ms Handshake-specific timeout
P2P_DEFAULT_PACKET_MAX_SIZE 50,000,000 Maximum packet size (50 MB)