starknet_rust_providers/sequencer/models/
block.rs

1use serde::Deserialize;
2use serde_with::serde_as;
3use starknet_rust_core::{
4    serde::unsigned_field_element::{UfeHex, UfeHexOption},
5    types::{Felt, L1DataAvailabilityMode, ResourcePrice},
6};
7
8use super::{ConfirmedTransactionReceipt, TransactionType};
9
10#[derive(Debug, Clone, Copy)]
11pub enum BlockId {
12    Hash(Felt),
13    Number(u64),
14    Pending,
15    Latest,
16}
17
18#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
19#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
20#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
21pub enum BlockStatus {
22    /// Block that is yet to be closed
23    Pending,
24    /// Block failed in the L2 pipeline
25    Aborted,
26    /// A reverted block (rejected on L1)
27    Reverted,
28    /// Block that was created on L2, in contrast to Pending, which is not yet closed
29    AcceptedOnL2,
30    /// Accepted on L1
31    AcceptedOnL1,
32}
33
34#[serde_as]
35#[derive(Debug, Deserialize)]
36#[cfg_attr(feature = "no_unknown_fields", serde(deny_unknown_fields))]
37pub struct Block {
38    #[serde(default)]
39    #[serde_as(as = "UfeHexOption")]
40    pub block_hash: Option<Felt>,
41    pub block_number: Option<u64>,
42    #[serde_as(as = "UfeHex")]
43    pub parent_block_hash: Felt,
44    pub timestamp: u64,
45    // Field marked optional as old blocks don't include it yet. Drop optional once resolved.
46    #[serde(default)]
47    #[serde_as(as = "UfeHexOption")]
48    pub sequencer_address: Option<Felt>,
49    #[serde(default)]
50    #[serde_as(as = "UfeHexOption")]
51    pub state_root: Option<Felt>,
52    #[serde(default)]
53    #[serde_as(as = "UfeHexOption")]
54    pub transaction_commitment: Option<Felt>,
55    #[serde(default)]
56    #[serde_as(as = "UfeHexOption")]
57    pub event_commitment: Option<Felt>,
58    pub status: BlockStatus,
59    pub l1_da_mode: L1DataAvailabilityMode,
60    pub l1_gas_price: ResourcePrice,
61    pub l2_gas_price: ResourcePrice,
62    pub l1_data_gas_price: ResourcePrice,
63    pub transactions: Vec<TransactionType>,
64    pub transaction_receipts: Vec<ConfirmedTransactionReceipt>,
65    // Field marked optional as old blocks don't include it yet. Drop optional once resolved.
66    pub starknet_version: Option<String>,
67}
68
69#[cfg(test)]
70mod tests {
71    use super::{super::transaction_receipt::TransactionExecutionStatus, *};
72
73    #[test]
74    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
75    fn test_block_deser_with_transactions() {
76        let raw = include_str!(
77            "../../../test-data/raw_gateway_responses/get_block/1_with_transactions.txt"
78        );
79
80        let block: Block = serde_json::from_str(raw).unwrap();
81
82        assert_eq!(block.block_number.unwrap(), 100);
83        assert_eq!(block.status, BlockStatus::AcceptedOnL1);
84        assert_eq!(
85            block.state_root.unwrap(),
86            Felt::from_hex("051098918fd96edda4e251f695181c063e21fb0666352e3469db507c7fd62b89")
87                .unwrap()
88        );
89        assert_eq!(
90            block.transaction_commitment.unwrap(),
91            Felt::from_hex("0576db32d35cf011694a73c6ce400d5d77f768cbd77ee7cf87d12902e0f9b4ec")
92                .unwrap()
93        );
94        assert_eq!(
95            block.event_commitment.unwrap(),
96            Felt::from_hex("01c972780140fd16dde94639226ca25818e4f24ecd5b5c3065cc1f5f5fc410f9")
97                .unwrap()
98        );
99        assert_eq!(block.transactions.len(), 4);
100        assert_eq!(block.transaction_receipts.len(), 4);
101
102        if let TransactionType::InvokeFunction(tx) = &block.transactions[0] {
103            assert_eq!(tx.calldata.len(), 16);
104        } else {
105            panic!("Did not deserialize Transaction::InvokeFunction properly");
106        }
107        let receipt = &block.transaction_receipts[0];
108        assert_eq!(receipt.execution_resources.as_ref().unwrap().n_steps, 10552);
109    }
110
111    #[test]
112    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
113    fn test_block_deser_with_messages() {
114        // has an L2 to L1 message
115        let raw =
116            include_str!("../../../test-data/raw_gateway_responses/get_block/2_with_messages.txt");
117
118        let block: Block = serde_json::from_str(raw).unwrap();
119
120        assert_eq!(block.block_number.unwrap(), 25);
121        assert_eq!(block.transaction_receipts.len(), 11);
122        let receipt = &block.transaction_receipts[10];
123        assert_eq!(receipt.l2_to_l1_messages.len(), 1);
124        assert_eq!(receipt.l2_to_l1_messages[0].payload.len(), 2);
125    }
126
127    #[test]
128    #[ignore = "block with the same criteria not found in alpha-sepolia yet"]
129    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
130    fn test_block_deser_with_messages_without_nonce() {
131        // has an L2 to L1 message
132        let raw = include_str!(
133            "../../../test-data/raw_gateway_responses/get_block/9_with_messages_without_nonce.txt"
134        );
135
136        let block: Block = serde_json::from_str(raw).unwrap();
137
138        assert_eq!(block.block_number.unwrap(), 1564);
139        assert_eq!(block.transaction_receipts.len(), 4);
140        let receipt = &block.transaction_receipts[1];
141        assert_eq!(receipt.l2_to_l1_messages.len(), 1);
142        assert_eq!(receipt.l2_to_l1_messages[0].payload.len(), 2);
143
144        let receipt = &block.transaction_receipts[2];
145        assert!(receipt.l1_to_l2_consumed_message.is_some());
146    }
147
148    #[test]
149    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
150    fn test_block_deser_with_events() {
151        // has events introduced with Starknet v0.7.0
152        let raw =
153            include_str!("../../../test-data/raw_gateway_responses/get_block/3_with_events.txt");
154
155        let block: Block = serde_json::from_str(raw).unwrap();
156
157        assert_eq!(block.block_number.unwrap(), 4);
158        assert_eq!(block.transaction_receipts.len(), 4);
159        let receipt = &block.transaction_receipts[3];
160        assert_eq!(receipt.events.len(), 1);
161        assert_eq!(receipt.events[0].keys.len(), 1);
162        assert_eq!(receipt.events[0].data.len(), 4);
163    }
164
165    #[test]
166    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
167    fn test_block_deser_pending() {
168        // pending blocks don't have `block_hash`, `block_number`, or `state_root`
169        let raw = include_str!("../../../test-data/raw_gateway_responses/get_block/4_pending.txt");
170
171        let block: Block = serde_json::from_str(raw).unwrap();
172
173        assert!(block.block_hash.is_none());
174        assert!(block.block_number.is_none());
175        assert!(block.state_root.is_none());
176        assert_eq!(block.status, BlockStatus::Pending);
177    }
178
179    #[test]
180    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
181    fn test_block_deser_new_attributes_0_8_2() {
182        // This block contains new fields introduced in Starknet v0.8.2
183        let new_block: Block = serde_json::from_str(include_str!(
184            "../../../test-data/raw_gateway_responses/get_block/6_with_sequencer_address.txt"
185        ))
186        .unwrap();
187        assert!(new_block.sequencer_address.is_some());
188    }
189
190    #[test]
191    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
192    fn test_block_deser_new_attributes_0_9_1() {
193        // This block contains new fields introduced in Starknet v0.9.1
194        let new_block: Block = serde_json::from_str(include_str!(
195            "../../../test-data/raw_gateway_responses/get_block/8_with_starknet_version.txt"
196        ))
197        .unwrap();
198        assert!(new_block.starknet_version.is_some());
199    }
200
201    #[test]
202    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
203    fn test_block_deser_with_declare_tx() {
204        let raw = include_str!(
205            "../../../test-data/raw_gateway_responses/get_block/7_with_declare_tx.txt"
206        );
207
208        let block: Block = serde_json::from_str(raw).unwrap();
209
210        let tx = match &block.transactions[2] {
211            TransactionType::Declare(tx) => tx,
212            _ => panic!("Unexpected tx type"),
213        };
214
215        assert_eq!(
216            tx.sender_address,
217            Felt::from_hex("0x68922eb87daed71fc3099031e178b6534fc39a570022342e8c166024da893f5")
218                .unwrap()
219        );
220    }
221
222    #[test]
223    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
224    fn test_block_deser_with_l1_handler() {
225        let raw = include_str!(
226            "../../../test-data/raw_gateway_responses/get_block/10_with_l1_handler.txt"
227        );
228
229        let block: Block = serde_json::from_str(raw).unwrap();
230
231        let tx = match &block.transactions[0] {
232            TransactionType::L1Handler(tx) => tx,
233            _ => panic!("Unexpected tx type"),
234        };
235
236        assert_eq!(
237            tx.contract_address,
238            Felt::from_hex("0x4c5772d1914fe6ce891b64eb35bf3522aeae1315647314aac58b01137607f3f")
239                .unwrap()
240        );
241    }
242
243    #[test]
244    #[ignore = "block with the same criteria not found in alpha-sepolia yet"]
245    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
246    fn test_block_deser_without_execution_resources() {
247        let raw = include_str!(
248            "../../../test-data/raw_gateway_responses/get_block/11_without_execution_resources.txt"
249        );
250
251        let block: Block = serde_json::from_str(raw).unwrap();
252
253        let receipt = &block.transaction_receipts[17];
254
255        assert!(receipt.execution_resources.is_none());
256    }
257
258    #[test]
259    #[ignore = "block with the same criteria not found in alpha-sepolia yet"]
260    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
261    fn test_block_deser_l1_handler_without_nonce() {
262        let raw = include_str!(
263            "../../../test-data/raw_gateway_responses/get_block/12_l1_handler_without_nonce.txt"
264        );
265
266        let block: Block = serde_json::from_str(raw).unwrap();
267
268        let tx = match &block.transactions[22] {
269            TransactionType::L1Handler(tx) => tx,
270            _ => panic!("Unexpected tx type"),
271        };
272
273        assert!(tx.nonce.is_none());
274    }
275
276    #[test]
277    #[ignore = "block with the same criteria not found in alpha-sepolia yet"]
278    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
279    fn test_block_deser_without_entry_point() {
280        let raw = include_str!(
281            "../../../test-data/raw_gateway_responses/get_block/13_without_entry_point.txt"
282        );
283
284        let block: Block = serde_json::from_str(raw).unwrap();
285
286        let tx = match &block.transactions[16] {
287            TransactionType::InvokeFunction(tx) => tx,
288            _ => panic!("Unexpected tx type"),
289        };
290
291        assert!(tx.entry_point_selector.is_none());
292    }
293
294    #[test]
295    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
296    fn test_block_deser_with_deploy_account() {
297        let raw = include_str!(
298            "../../../test-data/raw_gateway_responses/get_block/14_deploy_account.txt"
299        );
300
301        let block: Block = serde_json::from_str(raw).unwrap();
302
303        let tx = match &block.transactions[1] {
304            TransactionType::DeployAccount(tx) => tx,
305            _ => panic!("Unexpected tx type"),
306        };
307
308        assert_eq!(tx.signature.len(), 2);
309    }
310
311    #[test]
312    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
313    fn test_block_deser_with_declare_v2() {
314        let raw =
315            include_str!("../../../test-data/raw_gateway_responses/get_block/15_declare_v2.txt");
316
317        let block: Block = serde_json::from_str(raw).unwrap();
318
319        assert!(block
320            .transactions
321            .into_iter()
322            .any(|tx| matches!(tx, TransactionType::Declare(_))));
323    }
324
325    #[test]
326    #[ignore = "block with the same criteria not found in alpha-sepolia yet"]
327    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
328    fn test_block_deser_with_reverted_tx() {
329        let raw = include_str!(
330            "../../../test-data/raw_gateway_responses/get_block/16_with_reverted_tx.txt"
331        );
332
333        let block: Block = serde_json::from_str(raw).unwrap();
334
335        assert!(block.transaction_receipts.into_iter().any(|tx| matches!(
336            tx.execution_status,
337            Some(TransactionExecutionStatus::Reverted)
338        )));
339    }
340}