starknet_devnet_types/rpc/
block.rs1use serde::{Deserialize, Deserializer, Serialize};
2use starknet_api::block::{BlockNumber, BlockTimestamp};
3use starknet_api::core::{
4 EventCommitment, ReceiptCommitment, StateDiffCommitment, TransactionCommitment,
5};
6use starknet_api::data_availability::L1DataAvailabilityMode;
7use starknet_rs_core::types::Felt;
8
9use crate::contract_address::ContractAddress;
10use crate::felt::BlockHash;
11use crate::rpc::transactions::Transactions;
12pub type BlockRoot = Felt;
13
14#[derive(Clone, Debug, Copy, PartialEq, Eq)]
15pub enum BlockId {
16 Hash(Felt),
18 Number(u64),
20 Tag(BlockTag),
22}
23
24impl From<BlockId> for starknet_rs_core::types::BlockId {
25 fn from(block_id: BlockId) -> Self {
26 match block_id {
27 BlockId::Hash(felt) => Self::Hash(felt),
28 BlockId::Number(n) => Self::Number(n),
29 BlockId::Tag(tag) => Self::Tag(tag.into()),
30 }
31 }
32}
33
34impl<'de> Deserialize<'de> for BlockId {
35 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
36 where
37 D: Deserializer<'de>,
38 {
39 #[derive(Copy, Clone, Debug, Deserialize)]
40 enum BlockHashOrNumber {
41 #[serde(rename = "block_hash")]
42 Hash(Felt),
43 #[serde(rename = "block_number")]
44 Number(u64),
45 }
46
47 let value = serde_json::Value::deserialize(deserializer)?;
48 match value.as_str() {
49 Some("latest") => Ok(Self::Tag(BlockTag::Latest)),
50 Some("pre_confirmed") => Ok(Self::Tag(BlockTag::PreConfirmed)),
51 Some("l1_accepted") => Ok(Self::Tag(BlockTag::L1Accepted)),
52 _ => match serde_json::from_value::<BlockHashOrNumber>(value) {
53 Ok(BlockHashOrNumber::Hash(hash)) => Ok(Self::Hash(hash)),
54 Ok(BlockHashOrNumber::Number(n)) => Ok(Self::Number(n)),
55 Err(_) => Err(serde::de::Error::custom(
56 "Invalid block ID. Expected object with key (block_hash or block_number) or \
57 tag ('pre_confirmed' or 'latest' or 'l1_accepted').",
58 )),
59 },
60 }
61 }
62}
63
64#[derive(Debug, Clone)]
65#[allow(clippy::large_enum_variant)] pub enum BlockResult {
67 Block(Block),
68 PreConfirmedBlock(PreConfirmedBlock),
69}
70
71#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)]
72#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
73pub enum BlockStatus {
74 PreConfirmed,
76 AcceptedOnL2,
78 AcceptedOnL1,
80 Rejected,
82}
83
84#[derive(Debug, Clone, Serialize)]
85#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
86pub struct Block {
87 pub status: BlockStatus,
88 #[serde(flatten)]
89 pub header: BlockHeader,
90 pub transactions: Transactions,
91}
92
93#[derive(Debug, Clone, Serialize)]
94#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
95pub struct PreConfirmedBlock {
96 #[serde(flatten)]
97 pub header: PreConfirmedBlockHeader,
98 pub transactions: Transactions,
99}
100
101#[derive(Debug, Clone, Serialize)]
102#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
103pub struct BlockHeader {
104 pub block_hash: BlockHash,
105 pub parent_hash: BlockHash,
106 pub block_number: BlockNumber,
107 pub sequencer_address: ContractAddress,
108 pub new_root: BlockRoot,
109 pub timestamp: BlockTimestamp,
110 pub starknet_version: String,
111 pub l1_gas_price: ResourcePrice,
112 pub l2_gas_price: ResourcePrice,
113 pub l1_data_gas_price: ResourcePrice,
114 pub l1_da_mode: L1DataAvailabilityMode,
115 pub state_diff_commitment: StateDiffCommitment,
116 pub state_diff_length: u64,
117 pub transaction_commitment: TransactionCommitment,
118 pub event_commitment: EventCommitment,
119 #[serde(rename = "transaction_count")]
120 pub n_transactions: u64,
121 #[serde(rename = "event_count")]
122 pub n_events: u64,
123 pub receipt_commitment: ReceiptCommitment,
124}
125
126#[derive(Debug, Clone, Serialize)]
127#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
128pub struct PreConfirmedBlockHeader {
129 pub block_number: BlockNumber,
130 pub sequencer_address: ContractAddress,
131 pub timestamp: BlockTimestamp,
132 pub starknet_version: String,
133 pub l1_gas_price: ResourcePrice,
134 pub l2_gas_price: ResourcePrice,
135 pub l1_data_gas_price: ResourcePrice,
136 pub l1_da_mode: L1DataAvailabilityMode,
137}
138#[derive(Debug, Clone, Serialize)]
139#[cfg_attr(feature = "testing", derive(Deserialize), serde(deny_unknown_fields))]
140pub struct ResourcePrice {
141 pub price_in_fri: Felt,
144 pub price_in_wei: Felt,
145}
146
147impl From<starknet_rs_core::types::ResourcePrice> for ResourcePrice {
148 fn from(value: starknet_rs_core::types::ResourcePrice) -> Self {
149 Self { price_in_fri: value.price_in_fri, price_in_wei: value.price_in_wei }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154#[serde(deny_unknown_fields)]
155pub struct ReorgData {
157 pub starting_block_hash: BlockHash,
159 pub starting_block_number: BlockNumber,
161 pub ending_block_hash: BlockHash,
163 pub ending_block_number: BlockNumber,
165}
166
167#[derive(Debug, Clone)]
168pub enum SubscriptionBlockId {
169 Hash(Felt),
170 Number(u64),
171 Latest,
172 L1Accepted,
173}
174
175impl<'de> Deserialize<'de> for SubscriptionBlockId {
176 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177 where
178 D: Deserializer<'de>,
179 {
180 let block_id = BlockId::deserialize(deserializer)?;
181 match block_id {
182 BlockId::Hash(felt) => Ok(Self::Hash(felt)),
183 BlockId::Number(n) => Ok(Self::Number(n)),
184 BlockId::Tag(BlockTag::Latest) => Ok(Self::Latest),
185 BlockId::Tag(BlockTag::PreConfirmed) => {
186 Err(serde::de::Error::custom("Subscription block cannot be 'pre_confirmed'"))
187 }
188 BlockId::Tag(BlockTag::L1Accepted) => {
189 Err(serde::de::Error::custom("Subscription block cannot be 'l1_accepted'"))
190 }
191 }
192 }
193}
194
195impl From<SubscriptionBlockId> for BlockId {
196 fn from(block_id: SubscriptionBlockId) -> Self {
197 (&block_id).into()
198 }
199}
200
201impl From<&SubscriptionBlockId> for BlockId {
202 fn from(value: &SubscriptionBlockId) -> Self {
203 match value {
204 SubscriptionBlockId::Hash(hash) => Self::Hash(*hash),
205 SubscriptionBlockId::Number(n) => Self::Number(*n),
206 SubscriptionBlockId::Latest => Self::Tag(BlockTag::Latest),
207 SubscriptionBlockId::L1Accepted => Self::Tag(BlockTag::L1Accepted),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
213#[serde(rename_all = "snake_case")]
214pub enum BlockTag {
215 PreConfirmed,
216 Latest,
217 L1Accepted,
218}
219
220impl From<BlockTag> for starknet_rs_core::types::BlockTag {
221 fn from(tag: BlockTag) -> Self {
222 match tag {
223 BlockTag::PreConfirmed => Self::PreConfirmed,
224 BlockTag::Latest => Self::Latest,
225 BlockTag::L1Accepted => Self::L1Accepted,
226 }
227 }
228}
229
230#[cfg(test)]
231mod test_block_id {
232 use serde_json::json;
233 use starknet_rs_core::types::Felt;
234
235 use super::BlockTag;
236 use crate::rpc::block::BlockId;
237
238 #[test]
239 fn custom_block_id_deserialization() {
240 for (raw, expected) in [
241 (r#"{"block_hash": "0x1"}"#, BlockId::Hash(Felt::ONE)),
242 (r#"{"block_number": 123}"#, BlockId::Number(123)),
243 (r#""latest""#, BlockId::Tag(BlockTag::Latest)),
244 (r#""pre_confirmed""#, BlockId::Tag(BlockTag::PreConfirmed)),
245 (r#""l1_accepted""#, BlockId::Tag(BlockTag::L1Accepted)),
246 ] {
247 assert_eq!(serde_json::from_str::<BlockId>(raw).unwrap(), expected);
248 }
249 }
250
251 #[test]
252 fn custom_block_tag_deserialization() {
253 for (raw, expected) in [
254 ("latest", BlockTag::Latest),
255 ("pre_confirmed", BlockTag::PreConfirmed),
256 ("l1_accepted", BlockTag::L1Accepted),
257 ] {
258 assert_eq!(serde_json::from_value::<BlockTag>(json!(raw)).unwrap(), expected);
259 }
260 }
261}
262
263#[cfg(test)]
264mod test_subscription_block_id {
265 use serde_json::json;
266
267 use super::SubscriptionBlockId;
268
269 #[test]
270 fn accept_latest() {
271 serde_json::from_value::<SubscriptionBlockId>(json!("latest")).unwrap();
272 }
273
274 #[test]
275 fn reject_non_latest_subscription_block_tag() {
276 for tag in ["pending", "pre_confirmed", "l1_accepted"] {
277 serde_json::from_value::<SubscriptionBlockId>(json!(tag)).unwrap_err();
278 }
279 }
280
281 #[test]
282 fn reject_random_string() {
283 serde_json::from_value::<SubscriptionBlockId>(json!("random string")).unwrap_err();
284 }
285
286 #[test]
287 fn accept_valid_felt_as_block_hash() {
288 serde_json::from_value::<SubscriptionBlockId>(json!({ "block_hash": "0x1" })).unwrap();
289 }
290
291 #[test]
292 fn reject_invalid_felt_as_block_hash() {
293 serde_json::from_value::<SubscriptionBlockId>(json!({ "block_hash": "invalid" }))
294 .unwrap_err();
295 }
296
297 #[test]
298 fn reject_unwrapped_felt_as_block_hash() {
299 serde_json::from_value::<SubscriptionBlockId>(json!("0x123")).unwrap_err();
300 }
301
302 #[test]
303 fn accept_valid_number_as_block_number() {
304 serde_json::from_value::<SubscriptionBlockId>(json!({ "block_number": 123 })).unwrap();
305 }
306
307 #[test]
308 fn reject_unwrapped_number_as_block_number() {
309 serde_json::from_value::<SubscriptionBlockId>(json!(123)).unwrap_err();
310 }
311}