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:
- Plugin connects to shared socket
- Plugin sends Register message with execution_id
- Host “tags” the connection (file descriptor) with that execution_id
- All subsequent messages are validated against the tagged ID
- 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
| Metric | Shared Socket | Per-Execution Socket |
|---|---|---|
| File descriptors | 1 per plugin | 2 per plugin |
| Syscalls | ~50% fewer | Baseline |
| Connection setup | Reuse existing | Create new each time |
| Memory overhead | O(active executions) | O(active executions × 2) |
| Debugging | Single stream | Two 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§
- Execution
Guard - RAII guard for execution registration that auto-unregisters on drop
- Shared
Socket Service - Shared socket service that handles multiple concurrent plugin executions
Enums§
- Plugin
Message - 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