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:
- Reads the first 8 bytes and verifies they match
LEVIN_SIGNATURE. - Reads the remaining 25 bytes of the header.
- Validates
m_cbdoes not exceedP2P_DEFAULT_PACKET_MAX_SIZE(50 MB). - Reads
m_cbbytes of payload. - 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
- Initiator connects via TCP and sends
COMMAND_HANDSHAKE(1001) request. - Responder validates the request and sends a response.
- 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_iddoes not match (different chain or testnet/mainnet mismatch)client_versionis 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:
- Connect to the peer's advertised IP:port.
- Send
COMMAND_PING(1003). - Verify the response contains
status: "OK"and a matchingpeer_id. - 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) |