Module shared_socket

Source
Expand description

Shared Socket Service

This module provides a unified bidirectional Unix socket service for plugin communication. Instead of creating separate sockets for registration and API calls, all communication happens over a single shared socket, dramatically reducing overhead and complexity.

§Architecture

Single Shared Socket: All plugins connect to /tmp/relayer-plugin-shared.sock

Bidirectional Communication:

  • Plugins → Host: Register, ApiRequest, Trace, Shutdown
  • Host → Plugins: ApiResponse

Connection Tagging (Security): Each connection is “tagged” with an execution_id after the first Register message. All subsequent messages are validated against this tagged ID to prevent spoofing attacks (Plugin A cannot impersonate Plugin B).

§Message Protocol

All messages are JSON objects with a type field that discriminates the message type:

§Plugin → Host Messages

Register (first message, required):

{
  "type": "register",
  "execution_id": "abc-123"
}

ApiRequest (call Relayer API):

{
  "type": "api_request",
  "request_id": "req-1",
  "relayer_id": "relayer-1",
  "method": "sendTransaction",
  "payload": { "to": "0x...", "value": "100" }
}

Trace (observability event):

{
  "type": "trace",
  "trace": { "event": "processing", "timestamp": 1234567890 }
}

Shutdown (graceful close):

{
  "type": "shutdown"
}

§Host → Plugin Messages

ApiResponse (Relayer API result):

{
  "type": "api_response",
  "request_id": "req-1",
  "result": { "id": "tx-123", "status": "success" },
  "error": null
}

§Security Model

The connection tagging mechanism prevents execution_id spoofing:

  1. Plugin connects to shared socket
  2. Plugin sends Register message with execution_id
  3. Host “tags” the connection (file descriptor) with that execution_id
  4. All subsequent messages are validated against the tagged ID
  5. Attempts to change execution_id are rejected and connection is closed

This ensures Plugin A cannot send requests pretending to be Plugin B, even though they share the same socket file.

§Backward Compatibility

The handle_connection method maintains backward compatibility with the legacy Request/Response format from socket.rs. If a message doesn’t parse as PluginMessage, it attempts to parse as the legacy Request format and handles it accordingly.

§Performance Benefits vs Per-Execution Sockets

MetricShared SocketPer-Execution Socket
File descriptors1 per plugin2 per plugin
Syscalls~50% fewerBaseline
Connection setupReuse existingCreate new each time
Memory overheadO(active executions)O(active executions × 2)
DebuggingSingle streamTwo separate streams

§Example Usage

use openzeppelin_relayer::services::plugins::shared_socket::{
    get_shared_socket_service, ensure_shared_socket_started
};

// Get the global shared socket instance
let service = get_shared_socket_service()?;

// Register an execution (returns RAII guard)
let guard = service.register_execution("exec-123".to_string(), true).await;

// Plugin connects and sends messages over the shared socket...
// (handled automatically by the background listener)

// Collect traces when done (returns Some when emit_traces=true)
if let Some(mut traces_rx) = guard.into_receiver() {
    let traces = traces_rx.recv().await;
}

Structs§

ExecutionGuard
RAII guard for execution registration that auto-unregisters on drop
SharedSocketService
Shared socket service that handles multiple concurrent plugin executions

Enums§

PluginMessage
Unified message protocol for bidirectional communication

Functions§

ensure_shared_socket_started
Ensure the shared socket service is started
get_shared_socket_service
Get or create the global shared socket service Returns error if initialization fails instead of panicking