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}