--- title: P2P Networking description: 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: ```go 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 ```go 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` | ```go 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: ```go 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 |