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