Module IPC Protocol

Overview

The Module IPC (Inter-Process Communication) protocol enables secure communication between process-isolated modules and the base node. Modules run in separate processes and communicate via Unix domain sockets using a length-delimited binary message protocol.

Architecture

┌─────────────────────────────────────┐
│ blvm-node Process │
│ ┌───────────────────────────────┐ │
│ │ Module IPC Server │ │
│ │ (Unix Domain Socket) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
 │ IPC Protocol
 │ (Unix Domain Socket)
 │
┌─────────────┴─────────────────────┐
│ Module Process (Isolated) │
│ ┌───────────────────────────────┐ │
│ │ Module IPC Client │ │
│ │ (Unix Domain Socket) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘

Protocol Format

Message Encoding

Messages use length-delimited binary encoding:

[4-byte length][message payload]
  • Length: 4-byte little-endian integer (message size)
  • Payload: Binary-encoded message (bincode serialization)

Message Types

The protocol uses length-delimited ModuleMessage variants:

  1. Request: module → node (NodeAPI calls, handshake, RegisterModuleApi, …)
  2. Response: node → module
  3. Event: node → module (subscribed notifications)
  4. Log: module → node (forwarded to node logging)
  5. Invocation: node → module (CLI, RPC, or ModuleApi dispatch)
  6. InvocationResult: module → node (correlated reply)

Message Structure

Request Message

#![allow(unused)]
fn main() {
pub struct RequestMessage {
 pub correlation_id: CorrelationId,
 pub request_type: MessageType,
 pub payload: RequestPayload,
}
}

Request types (representative):

Reads and subscriptions include GetBlock, GetBlockHeader, GetTransaction, GetChainTip, GetBlockHeight, GetUTXO, SubscribeEvents, GetMempoolTransactions, GetNetworkStats, GetNetworkPeers, GetChainInfo, and many others (mining, storage, RPC, timers, …).

P2P serve policy & sync (module → node):

MessageTypeRole
MergeBlockServeDenylistAdd block hashes that must not receive full block on getdata (notfound instead).
GetBlockServeDenylistSnapshotBounded snapshot of the block denylist.
ClearBlockServeDenylist / ReplaceBlockServeDenylistClear or replace the full set.
MergeTxServeDenylistSame pattern for full tx on getdata.
GetTxServeDenylistSnapshotBounded snapshot of the tx denylist.
ClearTxServeDenylist / ReplaceTxServeDenylistClear or replace the tx set.
GetSyncStatusSync coordinator status (SyncStatus).
BanPeerBan peer by address; optional duration.
SetBlockServeMaintenanceModeRefuse all full-block getdata answers when enabled.

These affect relay/serving only, not consensus validation. See NodeAPI for the Rust surface.

Response Message

#![allow(unused)]
fn main() {
pub struct ResponseMessage {
 pub correlation_id: CorrelationId,
 pub payload: ResponsePayload,
}
}

Response payload variants carry typed data (blocks, templates, snapshots, booleans, errors, etc.); denylist merges return dedicated merged/snapshot payloads where applicable.

Event Message

#![allow(unused)]
fn main() {
pub struct EventMessage {
 pub event_type: EventType,
 pub payload: EventPayload,
}
}

Event types: The node defines many EventType values (chain, mempool, network, payments, mining, mesh, sync, modules, governance, maintenance, …). Modules subscribe to a subset via SubscribeEvents. See the EventType enum in traits.rs for the authoritative list: do not assume a fixed count in docs.

Log Message

#![allow(unused)]
fn main() {
pub struct LogMessage {
 pub level: LogLevel,
 pub message: String,
 pub module_id: String,
}
}

Log Levels: Error, Warn, Info, Debug, Trace

Communication Flow

Request-Response Pattern

  1. Module sends Request: Module sends request message with correlation ID
  2. Node processes Request: Node processes request and generates response
  3. Node sends Response: Node sends response with matching correlation ID
  4. Module receives Response: Module matches response to request using correlation ID

Invocation pattern (CLI, RPC, ModuleAPI)

The node sends Invocation messages to a connected module subprocess:

InvocationTypeUse
Clirunmodulecli / module CLI dispatch
RpcModule-registered RPC methods
ModuleApiInter-module call_module forwarded to the subprocess handler

The module replies with InvocationResult (same correlation_id). For ModuleApi, the payload is opaque bytes (InvocationResultPayload::ModuleApi).

Subprocess ModuleAPI registration

Spawned modules use blvm-sdk run_module_with_setup_and_api (not plain run_module!) when they register a ModuleAPI over IPC.

Spawned modules cannot pass Arc<dyn ModuleAPI> into the node process. Instead:

  1. Module sends RegisterModuleApi with method names and API version.
  2. Node installs IpcForwardingModuleAPI in the module registry.
  3. Other callers use call_module (or node RPC such as meshsendpacket) → node sends InvocationType::ModuleApi to the subprocess.
  4. On disconnect, the node unregisters the proxy.

Cross-task invocations use ModuleIpcHandle so callers do not lock the server accept loop.

Event Subscription Pattern

  1. Module subscribes: Module sends SubscribeEvents request with event types
  2. Node confirms: Node sends subscription confirmation
  3. Node publishes Events: Node sends event messages as they occur
  4. Module receives Events: Module processes events asynchronously

Connection Management

Handshake

On connection, the module sends a handshake as the first Request:

#![allow(unused)]
fn main() {
RequestPayload::Handshake {
 module_id,
 module_name,
 version,
}
}

The node replies with HandshakeAck (node version). Modules without a handshake receive a fallback connection id (legacy path).

Connection Lifecycle

  1. Connect: Module connects to Unix domain socket
  2. Handshake: Module sends handshake, node validates
  3. Active: Connection active, ready for requests/events
  4. Disconnect: Connection closed (graceful or error)

Security

Process Isolation

  • Modules run in separate processes with isolated memory
  • No shared memory between node and modules
  • Module crashes don't affect the base node

Permission System

Modules request capabilities that are validated before API access:

  • ReadBlockchain - Read-only blockchain access
  • ReadUTXO - Query UTXO set (read-only)
  • ReadChainState - Query chain state (height, tip)
  • SubscribeEvents - Subscribe to node events
  • SendTransactions - Submit transactions to mempool

Sandboxing

Modules run in sandboxed environments with:

  • Resource limits (CPU, memory, file descriptors)
  • Filesystem restrictions (module data dir)
  • Network: modules do not open arbitrary sockets; P2P and mesh sends go through NodeAPI with the network_access capability
  • Permission-based API access

Error Handling

Error Types

#![allow(unused)]
fn main() {
pub enum ModuleError {
 ConnectionError(String),
 ProtocolError(String),
 PermissionDenied(String),
 ResourceExhausted(String),
 Timeout(String),
}
}

Error Recovery

  • Connection Errors: Automatic reconnection with exponential backoff
  • Protocol Errors: Clear error messages, connection termination
  • Permission Errors: Detailed error messages, request rejection
  • Timeout Errors: Request timeout, connection remains active

Performance

Message Serialization

  • Format: bincode (binary encoding)
  • Size: Compact binary representation
  • Speed: Fast serialization/deserialization

Connection Pooling

  • Persistent Connections: Connections remain open for multiple requests
  • Concurrent Requests: Multiple requests can be in-flight simultaneously
  • Correlation IDs: Match responses to requests asynchronously

Implementation Details

IPC Server

The node-side IPC server:

  • Listens on Unix domain sockets under [modules].socket_dir (default data/modules/sockets, relative to the process unless configured in blvm.toml)
  • Accepts module connections (one socket per spawned module process)
  • Routes requests to NodeAPI implementation
  • Publishes events to subscribed modules

IPC Client

The module-side IPC client ( blvm-sdk runner):

  • Connects to the socket path passed in ModuleContext.socket_path at spawn
  • Sends requests and receives responses
  • Subscribes to events
  • Handles connection errors

Source

See Also