starknet_devnet_types/rpc/
block.rs

1use 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    /// Block hash.
17    Hash(Felt),
18    /// Block number (height).
19    Number(u64),
20    /// Block tag
21    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)] // Block is most dominant variant, doesn't matter if it is larger
66pub 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    /// Almost like pre-confirmed.
75    PreConfirmed,
76    /// A block that was created on L2.
77    AcceptedOnL2,
78    /// A block that was accepted on L1.
79    AcceptedOnL1,
80    /// A block rejected on L1.
81    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    // for now this will be always 0, this field is introduced in 0.5.0
142    // but current version of blockifier/starknet_api doesn't return this value
143    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)]
155/// Data about reorganized blocks, starting and ending block number and hash
156pub struct ReorgData {
157    /// Hash of the first known block of the orphaned chain
158    pub starting_block_hash: BlockHash,
159    /// Number of the first known block of the orphaned chain
160    pub starting_block_number: BlockNumber,
161    /// The last known block of the orphaned chain
162    pub ending_block_hash: BlockHash,
163    /// Number of the last known block of the orphaned chain
164    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}