reth_node_ethstats/
events.rs

1//! Types for ethstats event reporting.
2//! These structures define the data format used to report blockchain events to ethstats servers.
3
4use alloy_consensus::Header;
5use alloy_primitives::{Address, B256, U256};
6use serde::{Deserialize, Serialize};
7
8/// Collection of meta information about a node that is displayed on the monitoring page.
9/// This information is used to identify and display node details in the ethstats monitoring
10/// interface.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct NodeInfo {
13    /// The display name of the node in the monitoring interface
14    pub name: String,
15
16    /// The node's unique identifier
17    pub node: String,
18
19    /// The port number the node is listening on for P2P connections
20    pub port: u64,
21
22    /// The network ID the node is connected to (e.g. "1" for mainnet)
23    #[serde(rename = "net")]
24    pub network: String,
25
26    /// Comma-separated list of supported protocols and their versions
27    pub protocol: String,
28
29    /// API availability indicator ("Yes" or "No")
30    pub api: String,
31
32    /// Operating system the node is running on
33    pub os: String,
34
35    /// Operating system version/architecture
36    #[serde(rename = "os_v")]
37    pub os_ver: String,
38
39    /// Client software version
40    pub client: String,
41
42    /// Whether the node can provide historical block data
43    #[serde(rename = "canUpdateHistory")]
44    pub history: bool,
45}
46
47/// Authentication message used to login to the ethstats monitoring server.
48/// Contains node identification and authentication information.
49#[derive(Debug, Serialize, Deserialize)]
50pub struct AuthMsg {
51    /// The node's unique identifier
52    pub id: String,
53
54    /// Detailed information about the node
55    pub info: NodeInfo,
56
57    /// Secret password for authentication with the monitoring server
58    pub secret: String,
59}
60
61impl AuthMsg {
62    /// Generate a login message for the ethstats monitoring server.
63    pub fn generate_login_message(&self) -> String {
64        serde_json::json!({
65            "emit": ["hello", self]
66        })
67        .to_string()
68    }
69}
70
71/// Simplified transaction info, containing only the hash.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct TxStats {
74    /// Transaction hash
75    pub hash: B256,
76}
77
78/// Wrapper for uncle block headers.
79/// This ensures empty lists serialize as `[]` instead of `null`.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(transparent)]
82pub struct UncleStats(pub Vec<Header>);
83
84/// Information to report about individual blocks.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct BlockStats {
87    /// Block number (height in the chain).
88    pub number: U256,
89
90    /// Hash of this block.
91    pub hash: B256,
92
93    /// Hash of the parent block.
94    #[serde(rename = "parentHash")]
95    pub parent_hash: B256,
96
97    /// Timestamp of the block (Unix time).
98    pub timestamp: U256,
99
100    /// Address of the miner who produced this block.
101    pub miner: Address,
102
103    /// Total gas used by all transactions in the block.
104    #[serde(rename = "gasUsed")]
105    pub gas_used: u64,
106
107    /// Maximum gas allowed for this block.
108    #[serde(rename = "gasLimit")]
109    pub gas_limit: u64,
110
111    /// Difficulty for mining this block (as a decimal string).
112    #[serde(rename = "difficulty")]
113    pub diff: String,
114
115    /// Cumulative difficulty up to this block (as a decimal string).
116    #[serde(rename = "totalDifficulty")]
117    pub total_diff: String,
118
119    /// Simplified list of transactions in the block.
120    #[serde(rename = "transactions")]
121    pub txs: Vec<TxStats>,
122
123    /// Root hash of all transactions (Merkle root).
124    #[serde(rename = "transactionsRoot")]
125    pub tx_root: B256,
126
127    /// State root after applying this block.
128    #[serde(rename = "stateRoot")]
129    pub root: B256,
130
131    /// List of uncle block headers.
132    pub uncles: UncleStats,
133}
134
135/// Message containing a block to be reported to the ethstats monitoring server.
136#[derive(Debug, Serialize, Deserialize)]
137pub struct BlockMsg {
138    /// The node's unique identifier
139    pub id: String,
140
141    /// The block to report
142    pub block: BlockStats,
143}
144
145impl BlockMsg {
146    /// Generate a block message for the ethstats monitoring server.
147    pub fn generate_block_message(&self) -> String {
148        serde_json::json!({
149            "emit": ["block", self]
150        })
151        .to_string()
152    }
153}
154
155/// Message containing historical block data to be reported to the ethstats monitoring server.
156#[derive(Debug, Serialize, Deserialize)]
157pub struct HistoryMsg {
158    /// The node's unique identifier
159    pub id: String,
160
161    /// The historical block data to report
162    pub history: Vec<BlockStats>,
163}
164
165impl HistoryMsg {
166    /// Generate a history message for the ethstats monitoring server.
167    pub fn generate_history_message(&self) -> String {
168        serde_json::json!({
169            "emit": ["history", self]
170        })
171        .to_string()
172    }
173}
174
175/// Message containing pending transaction statistics to be reported to the ethstats monitoring
176/// server.
177#[derive(Debug, Serialize, Deserialize)]
178pub struct PendingStats {
179    /// Number of pending transactions
180    pub pending: u64,
181}
182
183/// Message containing pending transaction statistics to be reported to the ethstats monitoring
184/// server.
185#[derive(Debug, Serialize, Deserialize)]
186pub struct PendingMsg {
187    /// The node's unique identifier
188    pub id: String,
189
190    /// The pending transaction statistics to report
191    pub stats: PendingStats,
192}
193
194impl PendingMsg {
195    /// Generate a pending message for the ethstats monitoring server.
196    pub fn generate_pending_message(&self) -> String {
197        serde_json::json!({
198            "emit": ["pending", self]
199        })
200        .to_string()
201    }
202}
203
204/// Information reported about the local node.
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct NodeStats {
207    /// Whether the node is active
208    pub active: bool,
209
210    /// Whether the node is currently syncing
211    pub syncing: bool,
212
213    /// Number of connected peers
214    pub peers: u64,
215
216    /// Current gas price in wei
217    #[serde(rename = "gasPrice")]
218    pub gas_price: u64,
219
220    /// Node uptime percentage
221    pub uptime: u64,
222}
223
224/// Message containing node statistics to be reported to the ethstats monitoring server.
225#[derive(Debug, Serialize, Deserialize)]
226pub struct StatsMsg {
227    /// The node's unique identifier
228    pub id: String,
229
230    /// The stats to report
231    pub stats: NodeStats,
232}
233
234impl StatsMsg {
235    /// Generate a stats message for the ethstats monitoring server.
236    pub fn generate_stats_message(&self) -> String {
237        serde_json::json!({
238            "emit": ["stats", self]
239        })
240        .to_string()
241    }
242}
243
244/// Latency report message used to report network latency to the ethstats monitoring server.
245#[derive(Serialize, Deserialize, Debug)]
246pub struct LatencyMsg {
247    /// The node's unique identifier
248    pub id: String,
249
250    /// The latency to report in milliseconds
251    pub latency: u64,
252}
253
254impl LatencyMsg {
255    /// Generate a latency message for the ethstats monitoring server.
256    pub fn generate_latency_message(&self) -> String {
257        serde_json::json!({
258            "emit": ["latency", self]
259        })
260        .to_string()
261    }
262}
263
264/// Ping message sent to the ethstats monitoring server to initiate latency measurement.
265#[derive(Serialize, Deserialize, Debug)]
266pub struct PingMsg {
267    /// The node's unique identifier
268    pub id: String,
269
270    /// Client timestamp when the ping was sent
271    #[serde(rename = "clientTime")]
272    pub client_time: String,
273}
274
275impl PingMsg {
276    /// Generate a ping message for the ethstats monitoring server.
277    pub fn generate_ping_message(&self) -> String {
278        serde_json::json!({
279            "emit": ["node-ping", self]
280        })
281        .to_string()
282    }
283}
284
285/// Information reported about a new payload processing event.
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct PayloadStats {
288    /// Block number of the payload
289    pub number: U256,
290
291    /// Hash of the payload block
292    pub hash: B256,
293
294    /// Time taken to validate the payload in milliseconds
295    #[serde(rename = "processingTime")]
296    pub processing_time: u64,
297}
298
299/// Message containing new payload information to be reported to the ethstats monitoring server.
300#[derive(Debug, Serialize, Deserialize)]
301pub struct PayloadMsg {
302    /// The node's unique identifier
303    pub id: String,
304
305    /// The payload information to report
306    pub payload: PayloadStats,
307}
308
309impl PayloadMsg {
310    /// Generate a new payload message for the ethstats monitoring server.
311    pub fn generate_new_payload_message(&self) -> String {
312        serde_json::json!({
313            "emit": ["new-payload", self]
314        })
315        .to_string()
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use alloy_primitives::{B256, U256};
323
324    #[test]
325    fn test_payload_msg_generation() {
326        let payload_stats = PayloadStats {
327            number: U256::from(12345),
328            hash: B256::from_slice(&[1u8; 32]),
329            processing_time: 150,
330        };
331
332        let payload_msg = PayloadMsg { id: "test-node".to_string(), payload: payload_stats };
333
334        let message = payload_msg.generate_new_payload_message();
335        let parsed: serde_json::Value = serde_json::from_str(&message).expect("Valid JSON");
336
337        assert_eq!(parsed["emit"][0], "new-payload");
338        assert_eq!(parsed["emit"][1]["id"], "test-node");
339        assert_eq!(parsed["emit"][1]["payload"]["number"], "0x3039"); // 12345 in hex
340        assert_eq!(parsed["emit"][1]["payload"]["processingTime"], 150);
341
342        // Verify the structure contains all expected fields
343        assert!(parsed["emit"][1]["payload"]["hash"].is_string());
344        assert!(parsed["emit"][1]["payload"]["processingTime"].is_number());
345    }
346}