Skip to main content

mx_core/
ws_protocol.rs

1//! Shared WebSocket wire-format types for relayer ↔ stress communication.
2//!
3//! These types define the JSON contract between `mx-relayer` (serializer) and
4//! `mx-stress` (deserializer). Both crates MUST use these types instead of
5//! defining their own — this prevents silent field-name divergence (e.g.
6//! `camelCase` vs `snake_case`) that causes runtime parse failures.
7
8use serde::{Deserialize, Serialize};
9
10// ─────────────────────────────────────────────────────────────────────────────
11// Proto broadcast (binary WebSocket frame path)
12// ─────────────────────────────────────────────────────────────────────────────
13
14/// Response to a proto-encoded binary broadcast via WebSocket.
15///
16/// Sent by the relayer after processing a binary frame of protobuf-encoded
17/// transaction batches. Deserialized by stress tooling to track success/failure.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ProtoBroadcastResponse {
21    pub action: String,
22    pub status: String,
23    pub success_count: usize,
24    pub failure_count: usize,
25}
26
27impl ProtoBroadcastResponse {
28    /// Convenience constructor for the relayer (avoids `.into()` noise at call sites).
29    pub fn completed(success_count: usize, failure_count: usize) -> Self {
30        Self {
31            action: "broadcast_proto".into(),
32            status: "completed".into(),
33            success_count,
34            failure_count,
35        }
36    }
37}
38
39// ─────────────────────────────────────────────────────────────────────────────
40// JSON broadcast (text WebSocket frame path)
41// ─────────────────────────────────────────────────────────────────────────────
42
43/// Response to a JSON-encoded transaction broadcast via WebSocket.
44///
45/// Two-phase protocol:
46/// - Phase 1: `status: "accepted"` with `batch_size` (immediate ack)
47/// - Phase 2: `status: "completed"` | `"partial"` | `"failed"` with `hashes` and `failed`
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct BatchBroadcastResponse {
51    pub action: String,
52    pub status: String,
53    #[serde(default, skip_serializing_if = "Option::is_none")]
54    pub request_id: Option<String>,
55    /// Batch size (present in "accepted" ack)
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub batch_size: Option<usize>,
58    /// Transaction hashes (present in final response)
59    #[serde(default, skip_serializing_if = "Vec::is_empty")]
60    pub hashes: Vec<String>,
61    /// Failed transactions (present in final response)
62    #[serde(default, skip_serializing_if = "Vec::is_empty")]
63    pub failed: Vec<TxErrorResponse>,
64}
65
66/// Error details for a single failed transaction in a batch response.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct TxErrorResponse {
69    pub index: usize,
70    pub reason: String,
71}