Skip to main content

nox_core/
events.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4pub enum NoxEvent {
5    NodeStarted {
6        timestamp: u64,
7    },
8
9    PacketReceived {
10        packet_id: String,
11        data: Vec<u8>,
12        size_bytes: usize,
13    },
14
15    SendPacket {
16        next_hop_peer_id: String,
17        packet_id: String,
18        data: Vec<u8>,
19    },
20
21    PayloadDecrypted {
22        packet_id: String,
23        payload: Vec<u8>,
24    },
25
26    PeerConnected {
27        peer_id: String,
28    },
29
30    PeerDisconnected {
31        peer_id: String,
32    },
33
34    RelayerRegistered {
35        address: String,
36        sphinx_key: String,
37        url: String,
38        stake: String,
39        /// On-chain node role: 1=Relay, 2=Exit, 3=Full. Defaults to 3 for backward compatibility.
40        role: u8,
41        /// HTTPS ingress URL for client SDK packet submission (`None` for nodes without HTTP ingress).
42        ingress_url: Option<String>,
43        /// Extended metadata JSON URL (`None` if not set).
44        metadata_url: Option<String>,
45    },
46
47    RelayerRemoved {
48        address: String,
49    },
50
51    /// A node's Sphinx routing key was rotated on-chain.
52    RelayerKeyRotated {
53        address: String,
54        new_sphinx_key: String,
55    },
56
57    /// A node's role was updated on-chain (1=Relay, 2=Exit, 3=Full).
58    RelayerRoleUpdated {
59        address: String,
60        new_role: u8,
61    },
62
63    /// A node's URL was updated on-chain.
64    RelayerUrlUpdated {
65        address: String,
66        new_url: String,
67    },
68
69    /// A node was slashed on-chain.
70    RelayerSlashed {
71        address: String,
72        amount: String,
73        slasher: String,
74    },
75
76    /// `NoxRegistry` contract was paused.
77    RegistryPaused {
78        by: String,
79    },
80
81    /// `NoxRegistry` contract was unpaused.
82    RegistryUnpaused {
83        by: String,
84    },
85
86    PacketProcessed {
87        packet_id: String,
88        duration_ms: u64,
89    },
90
91    /// Per-hop Sphinx processing timing breakdown (only emitted with `hop-metrics` feature).
92    /// Nanosecond-precision timings for ECDH, key derivation, MAC, routing/body decryption, blinding.
93    HopTimingsRecorded {
94        packet_id: String,
95        ecdh_ns: u64,
96        key_derive_ns: u64,
97        mac_verify_ns: u64,
98        routing_decrypt_ns: u64,
99        body_decrypt_ns: u64,
100        blinding_ns: u64,
101        total_sphinx_ns: u64,
102    },
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    fn assert_json_roundtrip(event: &NoxEvent) {
110        let json = serde_json::to_string(event).expect("serialize");
111        let decoded: NoxEvent = serde_json::from_str(&json).expect("deserialize");
112        assert_eq!(*event, decoded);
113    }
114
115    fn assert_bincode_roundtrip(event: &NoxEvent) {
116        let bytes = bincode::serialize(event).expect("serialize");
117        let decoded: NoxEvent = bincode::deserialize(&bytes).expect("deserialize");
118        assert_eq!(*event, decoded);
119    }
120
121    #[test]
122    fn roundtrip_node_started() {
123        let event = NoxEvent::NodeStarted {
124            timestamp: 1709980000,
125        };
126        assert_json_roundtrip(&event);
127        assert_bincode_roundtrip(&event);
128    }
129
130    #[test]
131    fn roundtrip_packet_received() {
132        let event = NoxEvent::PacketReceived {
133            packet_id: "pkt-001".into(),
134            data: vec![0xDE, 0xAD, 0xBE, 0xEF],
135            size_bytes: 4,
136        };
137        assert_json_roundtrip(&event);
138        assert_bincode_roundtrip(&event);
139    }
140
141    #[test]
142    fn roundtrip_send_packet() {
143        let event = NoxEvent::SendPacket {
144            next_hop_peer_id: "peer-abc".into(),
145            packet_id: "pkt-002".into(),
146            data: vec![1, 2, 3],
147        };
148        assert_json_roundtrip(&event);
149        assert_bincode_roundtrip(&event);
150    }
151
152    #[test]
153    fn roundtrip_payload_decrypted() {
154        let event = NoxEvent::PayloadDecrypted {
155            packet_id: "pkt-003".into(),
156            payload: b"hello world".to_vec(),
157        };
158        assert_json_roundtrip(&event);
159        assert_bincode_roundtrip(&event);
160    }
161
162    #[test]
163    fn roundtrip_peer_connected() {
164        let event = NoxEvent::PeerConnected {
165            peer_id: "12D3KooW...".into(),
166        };
167        assert_json_roundtrip(&event);
168        assert_bincode_roundtrip(&event);
169    }
170
171    #[test]
172    fn roundtrip_peer_disconnected() {
173        let event = NoxEvent::PeerDisconnected {
174            peer_id: "12D3KooW...".into(),
175        };
176        assert_json_roundtrip(&event);
177        assert_bincode_roundtrip(&event);
178    }
179
180    #[test]
181    fn roundtrip_relayer_registered() {
182        let event = NoxEvent::RelayerRegistered {
183            address: "0x1234".into(),
184            sphinx_key: "0xabcd".into(),
185            url: "https://node.example.com".into(),
186            stake: "100000".into(),
187            role: 3,
188            ingress_url: Some("https://nox-1.hisoka.io".into()),
189            metadata_url: None,
190        };
191        assert_json_roundtrip(&event);
192        assert_bincode_roundtrip(&event);
193    }
194
195    #[test]
196    fn roundtrip_relayer_removed() {
197        let event = NoxEvent::RelayerRemoved {
198            address: "0x5678".into(),
199        };
200        assert_json_roundtrip(&event);
201        assert_bincode_roundtrip(&event);
202    }
203
204    #[test]
205    fn roundtrip_relayer_key_rotated() {
206        let event = NoxEvent::RelayerKeyRotated {
207            address: "0x1234".into(),
208            new_sphinx_key: "aabbccdd".into(),
209        };
210        assert_json_roundtrip(&event);
211        assert_bincode_roundtrip(&event);
212    }
213
214    #[test]
215    fn roundtrip_relayer_role_updated() {
216        let event = NoxEvent::RelayerRoleUpdated {
217            address: "0x1234".into(),
218            new_role: 2,
219        };
220        assert_json_roundtrip(&event);
221        assert_bincode_roundtrip(&event);
222    }
223
224    #[test]
225    fn roundtrip_relayer_url_updated() {
226        let event = NoxEvent::RelayerUrlUpdated {
227            address: "0x1234".into(),
228            new_url: "http://new-node.example.com:8080".into(),
229        };
230        assert_json_roundtrip(&event);
231        assert_bincode_roundtrip(&event);
232    }
233
234    #[test]
235    fn roundtrip_relayer_slashed() {
236        let event = NoxEvent::RelayerSlashed {
237            address: "0x1234".into(),
238            amount: "500000".into(),
239            slasher: "0x5678".into(),
240        };
241        assert_json_roundtrip(&event);
242        assert_bincode_roundtrip(&event);
243    }
244
245    #[test]
246    fn roundtrip_registry_paused() {
247        let event = NoxEvent::RegistryPaused {
248            by: "0xadmin".into(),
249        };
250        assert_json_roundtrip(&event);
251        assert_bincode_roundtrip(&event);
252    }
253
254    #[test]
255    fn roundtrip_registry_unpaused() {
256        let event = NoxEvent::RegistryUnpaused {
257            by: "0xadmin".into(),
258        };
259        assert_json_roundtrip(&event);
260        assert_bincode_roundtrip(&event);
261    }
262
263    #[test]
264    fn roundtrip_packet_processed() {
265        let event = NoxEvent::PacketProcessed {
266            packet_id: "pkt-004".into(),
267            duration_ms: 42,
268        };
269        assert_json_roundtrip(&event);
270        assert_bincode_roundtrip(&event);
271    }
272
273    #[test]
274    fn roundtrip_hop_timings_recorded() {
275        let event = NoxEvent::HopTimingsRecorded {
276            packet_id: "pkt-005".into(),
277            ecdh_ns: 100_000,
278            key_derive_ns: 50_000,
279            mac_verify_ns: 30_000,
280            routing_decrypt_ns: 20_000,
281            body_decrypt_ns: 40_000,
282            blinding_ns: 10_000,
283            total_sphinx_ns: 250_000,
284        };
285        assert_json_roundtrip(&event);
286        assert_bincode_roundtrip(&event);
287    }
288
289    #[test]
290    fn all_variants_covered() {
291        // Compile-time exhaustiveness check: if a variant is added to NoxEvent
292        // without a test, this match will fail to compile.
293        let events: Vec<NoxEvent> = vec![
294            NoxEvent::NodeStarted { timestamp: 0 },
295            NoxEvent::PacketReceived {
296                packet_id: String::new(),
297                data: vec![],
298                size_bytes: 0,
299            },
300            NoxEvent::SendPacket {
301                next_hop_peer_id: String::new(),
302                packet_id: String::new(),
303                data: vec![],
304            },
305            NoxEvent::PayloadDecrypted {
306                packet_id: String::new(),
307                payload: vec![],
308            },
309            NoxEvent::PeerConnected {
310                peer_id: String::new(),
311            },
312            NoxEvent::PeerDisconnected {
313                peer_id: String::new(),
314            },
315            NoxEvent::RelayerRegistered {
316                address: String::new(),
317                sphinx_key: String::new(),
318                url: String::new(),
319                stake: String::new(),
320                role: 0,
321                ingress_url: None,
322                metadata_url: None,
323            },
324            NoxEvent::RelayerRemoved {
325                address: String::new(),
326            },
327            NoxEvent::RelayerKeyRotated {
328                address: String::new(),
329                new_sphinx_key: String::new(),
330            },
331            NoxEvent::RelayerRoleUpdated {
332                address: String::new(),
333                new_role: 0,
334            },
335            NoxEvent::RelayerUrlUpdated {
336                address: String::new(),
337                new_url: String::new(),
338            },
339            NoxEvent::RelayerSlashed {
340                address: String::new(),
341                amount: String::new(),
342                slasher: String::new(),
343            },
344            NoxEvent::RegistryPaused { by: String::new() },
345            NoxEvent::RegistryUnpaused { by: String::new() },
346            NoxEvent::PacketProcessed {
347                packet_id: String::new(),
348                duration_ms: 0,
349            },
350            NoxEvent::HopTimingsRecorded {
351                packet_id: String::new(),
352                ecdh_ns: 0,
353                key_derive_ns: 0,
354                mac_verify_ns: 0,
355                routing_decrypt_ns: 0,
356                body_decrypt_ns: 0,
357                blinding_ns: 0,
358                total_sphinx_ns: 0,
359            },
360        ];
361        for event in &events {
362            assert_json_roundtrip(event);
363            assert_bincode_roundtrip(event);
364        }
365    }
366}