1use crate::Transaction;
4use crate::types::responses::transactions::Hash;
5use serde::{Deserialize, Serialize};
6use std::fmt::{Display, Formatter, Result as FmtResult};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum CheckpointTransactions {
13 Full(Vec<Transaction>),
15 Hashes(Vec<Hash>),
17}
18
19impl Display for CheckpointTransactions {
20 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
21 match self {
22 CheckpointTransactions::Full(transactions) => {
23 write!(
24 f,
25 "Checkpoint with {} full transactions",
26 transactions.len()
27 )
28 }
29 CheckpointTransactions::Hashes(hashes) => {
30 write!(f, "Checkpoint with {} transaction hashes", hashes.len())
31 }
32 }
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Checkpoint {
40 pub hash: Hash,
42 pub parent_hash: Hash,
44 pub state_root: Hash,
46 pub transactions_root: Hash,
48 pub receipts_root: Hash,
50 pub number: u64,
52 pub timestamp: u64,
54 pub extra_data: String,
56 pub transactions: CheckpointTransactions,
58 pub size: Option<u64>,
60}
61
62impl Display for Checkpoint {
63 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
64 writeln!(f, "Checkpoint #{}:", self.number)?;
65 writeln!(f, " Hash: {}", self.hash.hash)?;
66 writeln!(f, " Parent Hash: {}", self.parent_hash.hash)?;
67 writeln!(f, " State Root: {}", self.state_root.hash)?;
68 writeln!(f, " Transactions Root: {}", self.transactions_root.hash)?;
69 writeln!(f, " Receipts Root: {}", self.receipts_root.hash)?;
70 writeln!(f, " Timestamp: {}", self.timestamp)?;
71 writeln!(f, " Extra Data: {}", self.extra_data)?;
72
73 if let Some(size) = self.size {
74 writeln!(f, " Size: {} bytes", size)?;
75 }
76
77 writeln!(f, " Transactions:")?;
78 match &self.transactions {
79 CheckpointTransactions::Full(transactions) => {
80 writeln!(
81 f,
82 " Count: {} (full transaction details)",
83 transactions.len()
84 )?;
85 for (i, tx) in transactions.iter().enumerate() {
86 writeln!(f, " Transaction {}:", i + 1)?;
87 writeln!(f, " Hash: {}", tx.hash)?;
88 writeln!(f, " From: {}", tx.from)?;
89 writeln!(f, " Nonce: {}", tx.nonce)?;
90 writeln!(f, " Epoch: {}", tx.epoch)?;
91 writeln!(f, " Checkpoint: {}", tx.checkpoint)?;
92 writeln!(f, " Chain ID: {}", tx.chain_id)?;
93
94 if let Some(checkpoint_hash) = &tx.checkpoint_hash {
95 writeln!(f, " Checkpoint Hash: {}", checkpoint_hash)?;
96 }
97 if let Some(checkpoint_number) = tx.checkpoint_number {
98 writeln!(f, " Checkpoint Number: {}", checkpoint_number)?;
99 }
100 if let Some(transaction_index) = tx.transaction_index {
101 writeln!(f, " Transaction Index: {}", transaction_index)?;
102 }
103
104 writeln!(f, " Signature:")?;
105 writeln!(f, " R: {}", tx.signature.r)?;
106 writeln!(f, " S: {}", tx.signature.s)?;
107 writeln!(f, " V: {}", tx.signature.v)?;
108
109 writeln!(f, " Payload: {:?}", tx.data)?;
110
111 if i < transactions.len() - 1 {
112 writeln!(f)?;
113 }
114 }
115 }
116 CheckpointTransactions::Hashes(hashes) => {
117 writeln!(f, " Count: {} (hashes only)", hashes.len())?;
118 for (i, hash) in hashes.iter().enumerate() {
119 writeln!(f, " {}: {}", i + 1, hash.hash)?;
120 }
121 }
122 }
123
124 Ok(())
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct CheckpointHeader {
132 pub hash: Hash,
134 pub parent_hash: Hash,
136 pub state_root: Hash,
138 pub transactions_root: Hash,
140 pub receipts_root: Hash,
142 pub number: u64,
144 pub timestamp: u64,
146 pub extra_data: String,
148}
149
150impl Display for CheckpointHeader {
151 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
152 write!(
153 f,
154 "Checkpoint #{}: {} (parent: {}, timestamp: {})",
155 self.number, self.hash, self.parent_hash, self.timestamp
156 )
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct CheckpointNumber {
163 pub number: u64,
165}
166
167impl Display for CheckpointNumber {
168 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
169 write!(f, "Checkpoint Number: {}", self.number)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use crate::types::responses::transactions::*;
177 use alloy_primitives::B256;
178 use std::str::FromStr;
179
180 fn create_hash(hex_str: &str) -> Hash {
182 let padded_hex = if hex_str.len() < 66 {
184 let without_prefix = hex_str.strip_prefix("0x").unwrap_or(hex_str);
185 format!("0x{:0<64}", without_prefix)
186 } else {
187 hex_str.to_string()
188 };
189
190 Hash {
191 hash: B256::from_str(&padded_hex).expect("Test data should be valid"),
192 }
193 }
194
195 #[test]
196 fn test_checkpoint_number_structure() {
197 let checkpoint_num = CheckpointNumber { number: 12345 };
198
199 let json = serde_json::to_string(&checkpoint_num).expect("Test data should be valid");
201 let deserialized: CheckpointNumber =
202 serde_json::from_str(&json).expect("Test data should be valid");
203
204 assert_eq!(checkpoint_num.number, deserialized.number);
205
206 let display_str = format!("{}", checkpoint_num);
208 assert_eq!(display_str, "Checkpoint Number: 12345");
209
210 let cloned_num = checkpoint_num.clone();
212 assert_eq!(checkpoint_num.number, cloned_num.number);
213
214 let debug_num_str = format!("{:?}", checkpoint_num);
216 assert!(debug_num_str.contains("CheckpointNumber"));
217 assert!(debug_num_str.contains("12345"));
218 }
219
220 #[test]
221 fn test_checkpoint_transactions_hashes() {
222 let hashes = vec![
223 create_hash("0x902006665c369834a0cf52eea2780f934a90b3c86a3918fb57371ac1fbbd7777"),
224 create_hash("0x20e081da293ae3b81e30f864f38f6911663d7f2cf98337fca38db3cf5bbe7a8f"),
225 create_hash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"),
226 ];
227
228 let single_hash_json =
230 serde_json::to_string(&hashes[0]).expect("Hash serialization should work");
231 assert!(single_hash_json.starts_with("\"0x"));
232 assert!(single_hash_json.ends_with("\""));
233 assert!(!single_hash_json.contains("{"));
234 assert!(!single_hash_json.contains("hash"));
235
236 let checkpoint_transactions = CheckpointTransactions::Hashes(hashes.clone());
237
238 let json =
240 serde_json::to_string(&checkpoint_transactions).expect("Test data should be valid");
241 let deserialized: CheckpointTransactions =
242 serde_json::from_str(&json).expect("Test data should be valid");
243
244 match deserialized {
245 CheckpointTransactions::Hashes(deserialized_hashes) => {
246 assert_eq!(deserialized_hashes.len(), 3);
247 assert_eq!(deserialized_hashes, hashes);
248 }
249 _ => panic!("Should be Hashes variant"),
250 }
251
252 let display_str = format!("{}", checkpoint_transactions);
254 assert!(display_str.contains("Checkpoint with 3 transaction hashes"));
255 }
256
257 #[test]
258 fn test_checkpoint_transactions_empty() {
259 let empty_full = CheckpointTransactions::Full(vec![]);
261 let display_str = format!("{}", empty_full);
262 assert!(display_str.contains("Checkpoint with 0 full transactions"));
263
264 let empty_hashes = CheckpointTransactions::Hashes(vec![]);
265 let display_str = format!("{}", empty_hashes);
266 assert!(display_str.contains("Checkpoint with 0 transaction hashes"));
267 }
268
269 #[test]
270 fn test_checkpoint_header_structure() {
271 let header = CheckpointHeader {
272 hash: create_hash("0x123abc456def789012345678901234567890123456789012345678901234567e"),
273 parent_hash: create_hash(
274 "0x000abc456def789012345678901234567890123456789012345678901234567e",
275 ),
276 state_root: create_hash(
277 "0xabc123456def789012345678901234567890123456789012345678901234567e",
278 ),
279 transactions_root: create_hash(
280 "0xdef456789012345678901234567890123456789012345678901234567890abc1",
281 ),
282 receipts_root: create_hash(
283 "0x789012345678901234567890123456789012345678901234567890abc123def4",
284 ),
285 number: 999,
286 timestamp: 1703097600,
287 extra_data: "0x1234".to_string(),
288 };
289
290 let json = serde_json::to_string(&header).expect("Test data should be valid");
292 let deserialized: CheckpointHeader =
293 serde_json::from_str(&json).expect("Test data should be valid");
294
295 assert_eq!(header.hash, deserialized.hash);
296 assert_eq!(header.parent_hash, deserialized.parent_hash);
297 assert_eq!(header.state_root, deserialized.state_root);
298 assert_eq!(header.transactions_root, deserialized.transactions_root);
299 assert_eq!(header.receipts_root, deserialized.receipts_root);
300 assert_eq!(header.number, deserialized.number);
301 assert_eq!(header.timestamp, deserialized.timestamp);
302 assert_eq!(header.extra_data, deserialized.extra_data);
303
304 let display_str = format!("{}", header);
306 assert!(display_str.contains("Checkpoint #999:"));
307 assert!(display_str.contains(&header.hash.hash.to_string()));
308 assert!(display_str.contains(&header.parent_hash.hash.to_string()));
309 assert!(display_str.contains("1703097600"));
310 }
311
312 #[test]
313 fn test_checkpoint_with_various_sizes() {
314 let checkpoint_with_size = Checkpoint {
315 hash: create_hash("0x123abc"),
316 parent_hash: create_hash("0x000abc"),
317 state_root: create_hash("0xabc123"),
318 transactions_root: create_hash("0xdef456"),
319 receipts_root: create_hash("0x789012"),
320 number: 100,
321 timestamp: 1703097600,
322 extra_data: "0x".to_string(),
323 transactions: CheckpointTransactions::Hashes(vec![]),
324 size: Some(2048),
325 };
326
327 let checkpoint_without_size = Checkpoint {
328 hash: create_hash("0x123abc"),
329 parent_hash: create_hash("0x000abc"),
330 state_root: create_hash("0xabc123"),
331 transactions_root: create_hash("0xdef456"),
332 receipts_root: create_hash("0x789012"),
333 number: 100,
334 timestamp: 1703097600,
335 extra_data: "0x".to_string(),
336 transactions: CheckpointTransactions::Hashes(vec![]),
337 size: None,
338 };
339
340 let json_with_size =
342 serde_json::to_string(&checkpoint_with_size).expect("Test data should be valid");
343 let deserialized_with_size: Checkpoint =
344 serde_json::from_str(&json_with_size).expect("Test data should be valid");
345 assert_eq!(checkpoint_with_size.size, deserialized_with_size.size);
346
347 let json_without_size =
349 serde_json::to_string(&checkpoint_without_size).expect("Test data should be valid");
350 let deserialized_without_size: Checkpoint =
351 serde_json::from_str(&json_without_size).expect("Test data should be valid");
352 assert_eq!(checkpoint_without_size.size, deserialized_without_size.size);
353 assert!(deserialized_without_size.size.is_none());
354 }
355
356 #[test]
357 fn test_checkpoint_with_hashes_only() {
358 let checkpoint = Checkpoint {
359 hash: create_hash("0x123abc456def789012345678901234567890123456789012345678901234567e"),
360 parent_hash: create_hash(
361 "0x000abc456def789012345678901234567890123456789012345678901234567e",
362 ),
363 state_root: create_hash(
364 "0xabc123456def789012345678901234567890123456789012345678901234567e",
365 ),
366 transactions_root: create_hash(
367 "0xdef456789012345678901234567890123456789012345678901234567890abc1",
368 ),
369 receipts_root: create_hash(
370 "0x789012345678901234567890123456789012345678901234567890abc123def4",
371 ),
372 number: 1500,
373 timestamp: 1703097600,
374 extra_data: "checkpoint_data".to_string(),
375 transactions: CheckpointTransactions::Hashes(vec![
376 create_hash("0x902006665c369834a0cf52eea2780f934a90b3c86a3918fb57371ac1fbbd7777"),
377 create_hash("0x20e081da293ae3b81e30f864f38f6911663d7f2cf98337fca38db3cf5bbe7a8f"),
378 ]),
379 size: None, };
381
382 let display_str = format!("{}", checkpoint);
384 assert!(display_str.contains("Checkpoint #1500:"));
385 assert!(display_str.contains("checkpoint_data"));
386 assert!(!display_str.contains("Size:")); assert!(display_str.contains("hashes only"));
388 assert!(
389 display_str
390 .contains("1: 0x902006665c369834a0cf52eea2780f934a90b3c86a3918fb57371ac1fbbd7777")
391 );
392 assert!(
393 display_str
394 .contains("2: 0x20e081da293ae3b81e30f864f38f6911663d7f2cf98337fca38db3cf5bbe7a8f")
395 );
396 }
397
398 #[test]
399 fn test_serde_untagged_enum() {
400 let hashes_json = r#"["0x902006665c369834a0cf52eea2780f934a90b3c86a3918fb57371ac1fbbd7777", "0x20e081da293ae3b81e30f864f38f6911663d7f2cf98337fca38db3cf5bbe7a8f"]"#;
404 let parsed: CheckpointTransactions =
405 serde_json::from_str(hashes_json).expect("Test data should be valid");
406
407 match parsed {
408 CheckpointTransactions::Hashes(hashes) => {
409 assert_eq!(hashes.len(), 2);
410 }
411 _ => panic!("Should parse as Hashes variant"),
412 }
413
414 }
418}