openzeppelin_monitor/models/blockchain/solana/
block.rs

1//! Solana block data structures.
2//!
3//! Note: These structures are based on the Solana RPC implementation:
4//! <https://solana.com/docs/rpc/http/getblock>
5
6use serde::{Deserialize, Serialize};
7use std::ops::Deref;
8
9use crate::models::SolanaTransaction;
10
11/// A confirmed block from the Solana blockchain.
12///
13/// This structure represents the response from the Solana RPC `getBlock` endpoint
14/// and matches the format defined in the Solana JSON-RPC specification.
15#[derive(Debug, Serialize, Deserialize, Clone, Default)]
16#[serde(rename_all = "camelCase")]
17pub struct ConfirmedBlock {
18	/// The slot number this block was produced in
19	pub slot: u64,
20
21	/// Hash of the block
22	pub blockhash: String,
23
24	/// Hash of the previous block
25	pub previous_blockhash: String,
26
27	/// The slot of the parent block
28	pub parent_slot: u64,
29
30	/// Timestamp when the block was produced (Unix timestamp in seconds)
31	pub block_time: Option<i64>,
32
33	/// Block height (may be None for older blocks)
34	pub block_height: Option<u64>,
35
36	/// Transactions in this block
37	#[serde(default)]
38	pub transactions: Vec<SolanaTransaction>,
39}
40
41/// Wrapper around ConfirmedBlock that implements additional functionality
42///
43/// This type provides a convenient interface for working with Solana block data
44/// while maintaining compatibility with the RPC response format.
45#[derive(Debug, Serialize, Deserialize, Clone, Default)]
46pub struct Block(pub ConfirmedBlock);
47
48impl Block {
49	/// Get the block number (slot)
50	pub fn number(&self) -> Option<u64> {
51		Some(self.0.slot)
52	}
53
54	/// Get the blockhash
55	pub fn blockhash(&self) -> &str {
56		&self.0.blockhash
57	}
58
59	/// Get the block timestamp
60	pub fn block_time(&self) -> Option<i64> {
61		self.0.block_time
62	}
63}
64
65impl From<ConfirmedBlock> for Block {
66	fn from(confirmed_block: ConfirmedBlock) -> Self {
67		Self(confirmed_block)
68	}
69}
70
71impl Deref for Block {
72	type Target = ConfirmedBlock;
73
74	fn deref(&self) -> &Self::Target {
75		&self.0
76	}
77}
78
79#[cfg(test)]
80mod tests {
81	use super::*;
82
83	#[test]
84	fn test_block_creation_and_number() {
85		let confirmed_block = ConfirmedBlock {
86			slot: 123456789,
87			blockhash: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
88			previous_blockhash: "3sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
89			parent_slot: 123456788,
90			block_time: Some(1234567890),
91			block_height: Some(100000000),
92			transactions: vec![],
93		};
94
95		let block = Block::from(confirmed_block.clone());
96
97		// Test number() method
98		assert_eq!(block.number(), Some(123456789));
99
100		// Test Deref implementation
101		assert_eq!(
102			block.blockhash,
103			"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn"
104		);
105		assert_eq!(block.slot, 123456789);
106		assert_eq!(block.block_time, Some(1234567890));
107	}
108
109	#[test]
110	fn test_default_implementation() {
111		let block = Block::default();
112
113		assert_eq!(block.slot, 0);
114		assert_eq!(block.blockhash, "");
115		assert_eq!(block.previous_blockhash, "");
116		assert_eq!(block.parent_slot, 0);
117		assert!(block.block_time.is_none());
118		assert!(block.block_height.is_none());
119		assert!(block.transactions.is_empty());
120	}
121
122	#[test]
123	fn test_serde_serialization() {
124		let confirmed_block = ConfirmedBlock {
125			slot: 123456789,
126			blockhash: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
127			previous_blockhash: "3sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
128			parent_slot: 123456788,
129			block_time: Some(1234567890),
130			block_height: Some(100000000),
131			transactions: vec![],
132		};
133
134		let block = Block(confirmed_block);
135
136		// Test serialization
137		let serialized = serde_json::to_string(&block).unwrap();
138
139		// Test deserialization
140		let deserialized: Block = serde_json::from_str(&serialized).unwrap();
141
142		assert_eq!(
143			deserialized.blockhash,
144			"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn"
145		);
146		assert_eq!(deserialized.slot, 123456789);
147		assert_eq!(deserialized.number(), Some(123456789));
148	}
149
150	#[test]
151	fn test_block_helpers() {
152		let confirmed_block = ConfirmedBlock {
153			slot: 123456789,
154			blockhash: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
155			previous_blockhash: "3sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn".to_string(),
156			parent_slot: 123456788,
157			block_time: Some(1234567890),
158			block_height: Some(100000000),
159			transactions: vec![],
160		};
161
162		let block = Block::from(confirmed_block);
163
164		assert_eq!(
165			block.blockhash(),
166			"4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZAMdL4VZHirAn"
167		);
168		assert_eq!(block.block_time(), Some(1234567890));
169	}
170}