starknet_devnet_core/blocks/
mod.rs

1use std::collections::HashMap;
2
3use indexmap::IndexMap;
4use starknet_api::block::{BlockHeader, BlockHeaderWithoutHash, BlockNumber, BlockTimestamp};
5use starknet_api::block_hash::block_hash_calculator::BlockHeaderCommitments;
6use starknet_api::data_availability::L1DataAvailabilityMode;
7use starknet_api::felt;
8use starknet_rs_core::types::Felt;
9use starknet_types::contract_address::ContractAddress;
10use starknet_types::felt::{BlockHash, TransactionHash};
11use starknet_types::rpc::block::{BlockId, BlockStatus, BlockTag, ResourcePrice};
12use starknet_types::traits::HashProducer;
13use starknet_types_core::hash::{Poseidon, StarkHash};
14
15use crate::constants::{DEVNET_DEFAULT_STARTING_BLOCK_NUMBER, STARKNET_VERSION};
16use crate::error::{DevnetResult, Error};
17use crate::state::StarknetState;
18use crate::state::state_diff::StateDiff;
19use crate::traits::HashIdentified;
20
21pub(crate) struct StarknetBlocks {
22    pub(crate) num_to_hash: IndexMap<BlockNumber, BlockHash>,
23    pub(crate) hash_to_block: HashMap<BlockHash, StarknetBlock>,
24    pub(crate) pre_confirmed_block: StarknetBlock,
25    pub(crate) last_block_hash: Option<BlockHash>,
26    pub(crate) hash_to_state_diff: HashMap<BlockHash, StateDiff>,
27    pub(crate) hash_to_state: HashMap<BlockHash, StarknetState>,
28    pub(crate) aborted_blocks: Vec<Felt>,
29    pub(crate) starting_block_number: u64,
30    pub(crate) last_accepted_on_l1: Option<BlockHash>,
31}
32
33impl HashIdentified for StarknetBlocks {
34    type Element = StarknetBlock;
35    type Hash = BlockHash;
36
37    fn get_by_hash(&self, hash: Self::Hash) -> Option<&Self::Element> {
38        let block = self.hash_to_block.get(&hash)?;
39
40        Some(block)
41    }
42}
43
44impl Default for StarknetBlocks {
45    fn default() -> Self {
46        Self {
47            num_to_hash: IndexMap::new(),
48            hash_to_block: HashMap::new(),
49            pre_confirmed_block: StarknetBlock::create_pre_confirmed_block(),
50            last_block_hash: None,
51            hash_to_state_diff: HashMap::new(),
52            hash_to_state: HashMap::new(),
53            aborted_blocks: Vec::new(),
54            starting_block_number: DEVNET_DEFAULT_STARTING_BLOCK_NUMBER,
55            last_accepted_on_l1: None,
56        }
57    }
58}
59
60impl StarknetBlocks {
61    pub fn new(starting_block_number: u64, last_block_hash: Option<Felt>) -> Self {
62        let mut blocks = Self { starting_block_number, ..Default::default() };
63        blocks.pre_confirmed_block.set_block_number(starting_block_number);
64        blocks.last_block_hash = last_block_hash;
65        blocks
66    }
67
68    /// Inserts a block in the collection and modifies the block parent hash to match the last block
69    /// hash
70    pub fn insert(&mut self, mut block: StarknetBlock, state_diff: StateDiff) {
71        if let Some(last_block_hash) = self.last_block_hash {
72            block.header.block_header_without_hash.parent_hash =
73                starknet_api::block::BlockHash(last_block_hash);
74        }
75
76        let hash = block.block_hash();
77        let block_number = block.block_number();
78
79        self.num_to_hash.insert(block_number, hash);
80        self.hash_to_block.insert(hash, block);
81        self.hash_to_state_diff.insert(hash, state_diff);
82        self.last_block_hash = Some(hash);
83    }
84
85    fn get_by_num(&self, num: &BlockNumber) -> Option<&StarknetBlock> {
86        let block_hash = self.num_to_hash.get(num)?;
87        let block = self.hash_to_block.get(block_hash)?;
88
89        Some(block)
90    }
91
92    pub fn save_state_at(&mut self, block_hash: Felt, state: StarknetState) {
93        self.hash_to_state.insert(block_hash, state);
94    }
95
96    fn get_by_latest_hash(&self) -> Option<&StarknetBlock> {
97        if let Some(hash) = self.last_block_hash { self.get_by_hash(hash) } else { None }
98    }
99
100    pub fn get_by_block_id(&self, block_id: &BlockId) -> Option<&StarknetBlock> {
101        match block_id {
102            BlockId::Hash(hash) => self.get_by_hash(*hash),
103            BlockId::Number(block_number) => self.get_by_num(&BlockNumber(*block_number)),
104            BlockId::Tag(BlockTag::PreConfirmed) => Some(&self.pre_confirmed_block),
105            BlockId::Tag(BlockTag::Latest) => self.get_by_latest_hash(),
106            BlockId::Tag(BlockTag::L1Accepted) => {
107                self.last_accepted_on_l1.and_then(|h| self.get_by_hash(h))
108            }
109        }
110    }
111
112    /// Returns the block number from a block id, by finding the block by the block id
113    fn block_number_from_block_id(&self, block_id: &BlockId) -> Option<BlockNumber> {
114        self.get_by_block_id(block_id).map(|block| block.block_number())
115    }
116
117    /// Filter blocks based on from and to block ids and returns a collection of block's references
118    /// in ascending order
119    ///
120    /// # Arguments
121    /// * `from` - The block id from which to start the filtering
122    /// * `to` - The block id to which to end the filtering
123    pub fn get_blocks(
124        &self,
125        from: Option<BlockId>,
126        to: Option<BlockId>,
127    ) -> DevnetResult<Vec<&StarknetBlock>> {
128        // used IndexMap to keep elements in the order of the keys
129        let mut filtered_blocks: IndexMap<Felt, &StarknetBlock> = IndexMap::new();
130
131        let pre_confirmed_block_number = self.pre_confirmed_block.block_number();
132
133        let starting_block = if let Some(block_id) = from {
134            // If the value for block number provided is not correct it will return None
135            // So we have to return an error
136            let block_number = self.block_number_from_block_id(&block_id).ok_or(Error::NoBlock)?;
137            Some(block_number)
138        } else {
139            None
140        };
141
142        let ending_block = if let Some(block_id) = to {
143            // if the value for block number provided is not correct it will return None
144            // So we set the block number to the first possible block number which is 0
145            let block_number = self.block_number_from_block_id(&block_id).ok_or(Error::NoBlock)?;
146            Some(block_number)
147        } else {
148            None
149        };
150
151        fn is_block_number_in_range(
152            current_block_number: BlockNumber,
153            starting_block: Option<BlockNumber>,
154            ending_block: Option<BlockNumber>,
155        ) -> bool {
156            match (starting_block, ending_block) {
157                (None, None) => true,
158                (Some(start), None) => current_block_number >= start,
159                (None, Some(end)) => current_block_number <= end,
160                (Some(start), Some(end)) => {
161                    current_block_number >= start && current_block_number <= end
162                }
163            }
164        }
165
166        let mut insert_pre_confirmed_block_in_final_result = true;
167        // iterate over the blocks and apply the filter
168        // then insert the filtered blocks into the index map
169        self.num_to_hash
170            .iter()
171            .filter(|(current_block_number, _)| {
172                is_block_number_in_range(**current_block_number, starting_block, ending_block)
173            })
174            .for_each(|(block_number, block_hash)| {
175                if *block_number == pre_confirmed_block_number {
176                    insert_pre_confirmed_block_in_final_result = false;
177                }
178                filtered_blocks.insert(*block_hash, &self.hash_to_block[block_hash]);
179            });
180
181        let mut result: Vec<&StarknetBlock> = filtered_blocks.into_values().collect();
182
183        if is_block_number_in_range(pre_confirmed_block_number, starting_block, ending_block)
184            && insert_pre_confirmed_block_in_final_result
185        {
186            result.push(&self.pre_confirmed_block);
187        }
188
189        Ok(result)
190    }
191
192    pub(crate) fn remove(&mut self, block_hash: &BlockHash) -> Option<StarknetBlock> {
193        // Use question marks to early-return None.
194        self.hash_to_state.remove(block_hash)?;
195        self.hash_to_state_diff.remove(block_hash)?;
196        let block = self.hash_to_block.remove(block_hash)?;
197        self.num_to_hash.shift_remove(&block.block_number())?;
198        Some(block)
199    }
200
201    pub fn next_latest_block_number(&self) -> BlockNumber {
202        BlockNumber(self.pre_confirmed_block.block_number().0)
203    }
204}
205
206#[derive(Clone)]
207#[cfg_attr(test, derive(PartialEq, Eq))]
208pub struct StarknetBlock {
209    pub(crate) header: BlockHeader,
210    transaction_hashes: Vec<TransactionHash>,
211    pub(crate) status: BlockStatus,
212}
213
214impl From<&StarknetBlock> for starknet_types::rpc::block::PreConfirmedBlockHeader {
215    fn from(value: &StarknetBlock) -> Self {
216        Self {
217            block_number: value.block_number(),
218            sequencer_address: value.sequencer_address(),
219            timestamp: value.timestamp(),
220            starknet_version: STARKNET_VERSION.to_string(),
221            l1_gas_price: ResourcePrice {
222                price_in_fri: value
223                    .header
224                    .block_header_without_hash
225                    .l1_gas_price
226                    .price_in_fri
227                    .0
228                    .into(),
229                price_in_wei: value
230                    .header
231                    .block_header_without_hash
232                    .l1_gas_price
233                    .price_in_wei
234                    .0
235                    .into(),
236            },
237            l1_data_gas_price: ResourcePrice {
238                price_in_fri: value
239                    .header
240                    .block_header_without_hash
241                    .l1_data_gas_price
242                    .price_in_fri
243                    .0
244                    .into(),
245                price_in_wei: value
246                    .header
247                    .block_header_without_hash
248                    .l1_data_gas_price
249                    .price_in_wei
250                    .0
251                    .into(),
252            },
253            l1_da_mode: value.header.block_header_without_hash.l1_da_mode,
254            l2_gas_price: ResourcePrice {
255                price_in_fri: value
256                    .header
257                    .block_header_without_hash
258                    .l2_gas_price
259                    .price_in_fri
260                    .0
261                    .into(),
262                price_in_wei: value
263                    .header
264                    .block_header_without_hash
265                    .l2_gas_price
266                    .price_in_wei
267                    .0
268                    .into(),
269            },
270        }
271    }
272}
273
274impl From<&StarknetBlock> for starknet_types::rpc::block::BlockHeader {
275    fn from(value: &StarknetBlock) -> Self {
276        Self {
277            block_hash: value.block_hash(),
278            parent_hash: value.parent_hash(),
279            block_number: value.block_number(),
280            sequencer_address: value.sequencer_address(),
281            new_root: value.new_root(),
282            timestamp: value.timestamp(),
283            starknet_version: STARKNET_VERSION.to_string(),
284            l1_gas_price: ResourcePrice {
285                price_in_fri: value
286                    .header
287                    .block_header_without_hash
288                    .l1_gas_price
289                    .price_in_fri
290                    .0
291                    .into(),
292                price_in_wei: value
293                    .header
294                    .block_header_without_hash
295                    .l1_gas_price
296                    .price_in_wei
297                    .0
298                    .into(),
299            },
300            l1_data_gas_price: ResourcePrice {
301                price_in_fri: value
302                    .header
303                    .block_header_without_hash
304                    .l1_data_gas_price
305                    .price_in_fri
306                    .0
307                    .into(),
308                price_in_wei: value
309                    .header
310                    .block_header_without_hash
311                    .l1_data_gas_price
312                    .price_in_wei
313                    .0
314                    .into(),
315            },
316            l1_da_mode: value.header.block_header_without_hash.l1_da_mode,
317            l2_gas_price: ResourcePrice {
318                price_in_fri: value
319                    .header
320                    .block_header_without_hash
321                    .l2_gas_price
322                    .price_in_fri
323                    .0
324                    .into(),
325                price_in_wei: value
326                    .header
327                    .block_header_without_hash
328                    .l2_gas_price
329                    .price_in_wei
330                    .0
331                    .into(),
332            },
333            n_transactions: value.header.n_transactions as u64,
334            n_events: value.header.n_events as u64,
335            state_diff_length: value.header.state_diff_length.unwrap_or(0) as u64,
336            state_diff_commitment: value.header.state_diff_commitment.unwrap_or_default(),
337            transaction_commitment: value.header.transaction_commitment.unwrap_or_default(),
338            event_commitment: value.header.event_commitment.unwrap_or_default(),
339            receipt_commitment: value.header.receipt_commitment.unwrap_or_default(),
340        }
341    }
342}
343
344impl StarknetBlock {
345    pub(crate) fn add_transaction(&mut self, transaction_hash: TransactionHash) {
346        self.transaction_hashes.push(transaction_hash);
347    }
348
349    pub fn get_transactions(&self) -> &Vec<TransactionHash> {
350        &self.transaction_hashes
351    }
352
353    pub fn status(&self) -> &BlockStatus {
354        &self.status
355    }
356
357    pub fn block_hash(&self) -> BlockHash {
358        self.header.block_hash.0
359    }
360
361    pub fn parent_hash(&self) -> BlockHash {
362        self.header.block_header_without_hash.parent_hash.0
363    }
364
365    pub fn sequencer_address(&self) -> ContractAddress {
366        self.header.block_header_without_hash.sequencer.0.into()
367    }
368
369    pub fn timestamp(&self) -> BlockTimestamp {
370        self.header.block_header_without_hash.timestamp
371    }
372
373    pub fn new_root(&self) -> Felt {
374        self.header.block_header_without_hash.state_root.0
375    }
376
377    pub(crate) fn set_block_hash(&mut self, block_hash: BlockHash) {
378        self.header.block_hash = starknet_api::block::BlockHash(block_hash);
379    }
380
381    pub fn block_number(&self) -> BlockNumber {
382        self.header.block_header_without_hash.block_number
383    }
384
385    pub(crate) fn create_pre_confirmed_block() -> Self {
386        Self {
387            header: BlockHeader {
388                block_header_without_hash: BlockHeaderWithoutHash {
389                    l1_da_mode: L1DataAvailabilityMode::Blob,
390                    ..Default::default()
391                },
392                ..BlockHeader::default()
393            },
394            status: BlockStatus::PreConfirmed,
395            transaction_hashes: Vec::new(),
396        }
397    }
398
399    pub fn create_empty_accepted() -> Self {
400        Self {
401            header: BlockHeader::default(),
402            transaction_hashes: vec![],
403            status: BlockStatus::AcceptedOnL2,
404        }
405    }
406
407    pub(crate) fn set_block_number(&mut self, block_number: u64) {
408        self.header.block_header_without_hash.block_number = BlockNumber(block_number)
409    }
410
411    pub(crate) fn set_timestamp(&mut self, timestamp: BlockTimestamp) {
412        self.header.block_header_without_hash.timestamp = timestamp;
413    }
414
415    pub(crate) fn set_counts(
416        &mut self,
417        n_transactions: usize,
418        n_events: usize,
419        state_diff_length: usize,
420    ) {
421        self.header.n_transactions = n_transactions;
422        self.header.n_events = n_events;
423        self.header.state_diff_length = Some(state_diff_length);
424    }
425
426    pub(crate) fn set_commitments(&mut self, commitments: BlockHeaderCommitments) {
427        self.header.transaction_commitment = Some(commitments.transaction_commitment);
428        self.header.event_commitment = Some(commitments.event_commitment);
429        self.header.receipt_commitment = Some(commitments.receipt_commitment);
430        self.header.state_diff_commitment = Some(commitments.state_diff_commitment);
431    }
432}
433
434impl HashProducer for StarknetBlock {
435    type Error = Error;
436    fn generate_hash(&self) -> DevnetResult<BlockHash> {
437        let hash = Poseidon::hash_array(&[
438            felt!(self.header.block_header_without_hash.block_number.0), // block number
439            self.header.block_header_without_hash.state_root.0,          // global_state_root
440            *self.header.block_header_without_hash.sequencer.0.key(),    // sequencer_address
441            Felt::ZERO,                                                  /* block_timestamp;
442                                                                          * would normally be
443                                                                          * felt!(self.header.
444                                                                          * timestamp.0), but
445                                                                          * is modified to enable replicability
446                                                                          * in re-execution on
447                                                                          * loading on dump */
448            felt!(self.transaction_hashes.len() as u64), // transaction_count
449            Felt::ZERO,                                  // transaction_commitment
450            Felt::ZERO,                                  // event_count
451            Felt::ZERO,                                  // event_commitment
452            Felt::ZERO,                                  // protocol_version
453            Felt::ZERO,                                  // extra_data
454            self.header.block_header_without_hash.parent_hash.0, // parent_block_hash
455        ]);
456
457        Ok(hash)
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use starknet_api::block::{BlockHash, BlockHeader, BlockHeaderWithoutHash, BlockNumber};
464    use starknet_api::data_availability::L1DataAvailabilityMode;
465    use starknet_rs_core::types::Felt;
466    use starknet_types::rpc::block::{BlockId, BlockStatus, BlockTag};
467    use starknet_types::traits::HashProducer;
468
469    use super::{StarknetBlock, StarknetBlocks};
470    use crate::state::state_diff::StateDiff;
471    use crate::traits::HashIdentified;
472
473    #[test]
474    fn get_blocks_return_in_correct_order() {
475        let mut blocks = StarknetBlocks::default();
476        for block_number in 1..=10 {
477            let mut block_to_insert = StarknetBlock::create_pre_confirmed_block();
478            block_to_insert.header.block_header_without_hash.block_number =
479                BlockNumber(block_number);
480            block_to_insert.header.block_hash =
481                starknet_api::block::BlockHash(Felt::from(block_number as u128));
482            blocks.insert(block_to_insert, StateDiff::default());
483            blocks.pre_confirmed_block.header.block_header_without_hash.block_number =
484                BlockNumber(block_number).unchecked_next();
485        }
486
487        let block_numbers: Vec<u64> = blocks
488            .get_blocks(None, None)
489            .unwrap()
490            .iter()
491            .map(|block| block.block_number().0)
492            .collect();
493        assert_eq!(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], block_numbers);
494
495        let block_numbers: Vec<u64> = blocks
496            .get_blocks(Some(BlockId::Number(7)), None)
497            .unwrap()
498            .iter()
499            .map(|block| block.block_number().0)
500            .collect();
501        assert_eq!(vec![7, 8, 9, 10, 11], block_numbers);
502
503        let block_numbers: Vec<u64> = blocks
504            .get_blocks(Some(BlockId::Number(7)), Some(BlockId::Tag(BlockTag::Latest)))
505            .unwrap()
506            .iter()
507            .map(|block| block.block_number().0)
508            .collect();
509        assert_eq!(vec![7, 8, 9, 10], block_numbers);
510    }
511
512    #[test]
513    fn block_number_from_block_id_should_return_correct_result() {
514        let mut blocks = StarknetBlocks::new(0, None);
515        let mut block_to_insert = StarknetBlock::create_pre_confirmed_block();
516        blocks.pre_confirmed_block = block_to_insert.clone();
517
518        // latest block returns none, because collection is empty
519        assert!(blocks.block_number_from_block_id(&BlockId::Tag(BlockTag::Latest)).is_none());
520        // pre-confirmed block returns some
521        assert!(blocks.block_number_from_block_id(&BlockId::Tag(BlockTag::PreConfirmed)).is_some());
522
523        let block_hash = block_to_insert.generate_hash().unwrap();
524        block_to_insert.header.block_header_without_hash.block_number = BlockNumber(10);
525        block_to_insert.header.block_hash = starknet_api::block::BlockHash(block_hash);
526
527        blocks.insert(block_to_insert, StateDiff::default());
528
529        // returns block number, even if the block number is not present in the collection
530        assert!(blocks.block_number_from_block_id(&BlockId::Number(11)).is_none());
531        assert!(blocks.block_number_from_block_id(&BlockId::Number(10)).is_some());
532        // returns none because there is no block with the given hash
533        assert!(blocks.block_number_from_block_id(&BlockId::Hash(Felt::ONE)).is_none());
534        assert!(blocks.block_number_from_block_id(&BlockId::Tag(BlockTag::Latest)).is_some());
535        assert!(blocks.block_number_from_block_id(&BlockId::Tag(BlockTag::PreConfirmed)).is_some());
536        assert!(blocks.block_number_from_block_id(&BlockId::Hash(block_hash)).is_some());
537    }
538
539    #[test]
540    fn get_blocks_with_filter() {
541        let mut blocks = StarknetBlocks::default();
542
543        let last_block_number = 11;
544        for block_number in 2..=last_block_number {
545            let mut block_to_insert = StarknetBlock::create_pre_confirmed_block();
546            block_to_insert.header.block_header_without_hash.block_number =
547                BlockNumber(block_number);
548            block_to_insert.header.block_hash =
549                starknet_api::block::BlockHash(Felt::from(block_number as u128));
550            blocks.insert(block_to_insert.clone(), StateDiff::default());
551
552            // last block will be a pre_confirmed block
553            if block_number == last_block_number {
554                blocks.pre_confirmed_block = block_to_insert;
555            }
556        }
557
558        // check blocks len
559        assert!(blocks.hash_to_block.len() == 10);
560
561        // 1. None, None
562        // no filter
563        assert_eq!(blocks.get_blocks(None, None).unwrap().len(), 10);
564
565        // 2. Some, None
566        assert_eq!(blocks.get_blocks(Some(BlockId::Number(9)), None).unwrap().len(), 3);
567        // invalid from filter, should return err block not found
568        assert!(blocks.get_blocks(Some(BlockId::Number(12)), None).is_err());
569        // last block should be returned
570        assert_eq!(blocks.get_blocks(Some(BlockId::Number(11)), None).unwrap().len(), 1);
571        // from filter using hash
572        assert_eq!(blocks.get_blocks(Some(BlockId::Hash(Felt::from(9))), None).unwrap().len(), 3);
573        // from filter using tag
574        assert_eq!(blocks.get_blocks(Some(BlockId::Tag(BlockTag::Latest)), None).unwrap().len(), 1);
575        assert_eq!(
576            blocks.get_blocks(Some(BlockId::Tag(BlockTag::PreConfirmed)), None).unwrap().len(),
577            1
578        );
579
580        // 3. None, Some
581        // to filter using block number
582        assert_eq!(blocks.get_blocks(None, Some(BlockId::Number(9))).unwrap().len(), 8);
583        // to filter using invalid block number
584        assert!(blocks.get_blocks(None, Some(BlockId::Number(0))).is_err());
585        // to filter using hash
586        assert_eq!(blocks.get_blocks(None, Some(BlockId::Hash(Felt::from(9)))).unwrap().len(), 8);
587        // to filter using invalid hash
588        assert!(blocks.get_blocks(None, Some(BlockId::Hash(Felt::ZERO))).is_err());
589        // to filter using tag
590        assert_eq!(
591            blocks.get_blocks(None, Some(BlockId::Tag(BlockTag::Latest))).unwrap().len(),
592            10
593        );
594        assert_eq!(
595            blocks.get_blocks(None, Some(BlockId::Tag(BlockTag::PreConfirmed))).unwrap().len(),
596            10
597        );
598        // First block as to_block query param, should return empty collection
599        assert_eq!(blocks.get_blocks(None, Some(BlockId::Number(2))).unwrap().len(), 1);
600        // invalid to filter, should return err block not found
601        assert!(blocks.get_blocks(None, Some(BlockId::Number(1))).is_err());
602
603        // 4. Some, Some
604        // from block number to block number
605        assert_eq!(
606            blocks.get_blocks(Some(BlockId::Number(2)), Some(BlockId::Number(9))).unwrap().len(),
607            8
608        );
609        // from block number to block hash
610        assert_eq!(
611            blocks
612                .get_blocks(Some(BlockId::Number(2)), Some(BlockId::Hash(Felt::from(9))))
613                .unwrap()
614                .len(),
615            8
616        );
617        // from first block to latest/pre-confirmed, should return all blocks
618        assert_eq!(
619            blocks
620                .get_blocks(Some(BlockId::Number(2)), Some(BlockId::Tag(BlockTag::Latest)))
621                .unwrap()
622                .len(),
623            10
624        );
625        assert_eq!(
626            blocks
627                .get_blocks(Some(BlockId::Number(2)), Some(BlockId::Tag(BlockTag::PreConfirmed)))
628                .unwrap()
629                .len(),
630            10
631        );
632
633        // from last block to first block should return empty result
634        assert!(
635            blocks
636                .get_blocks(Some(BlockId::Number(10)), Some(BlockId::Number(2)))
637                .unwrap()
638                .is_empty()
639        );
640        // from last block to latest/pre-confirmed, should return 1 block
641        assert_eq!(
642            blocks
643                .get_blocks(Some(BlockId::Number(11)), Some(BlockId::Tag(BlockTag::Latest)))
644                .unwrap()
645                .len(),
646            1
647        );
648        assert_eq!(
649            blocks
650                .get_blocks(Some(BlockId::Number(11)), Some(BlockId::Tag(BlockTag::PreConfirmed)))
651                .unwrap()
652                .len(),
653            1
654        );
655
656        // bigger range than actual blocks in the collection, should return err
657        assert!(blocks.get_blocks(Some(BlockId::Number(0)), Some(BlockId::Number(1000))).is_err());
658
659        // from block hash to block_hash
660        assert_eq!(
661            blocks
662                .get_blocks(Some(BlockId::Hash(Felt::TWO)), Some(BlockId::Hash(Felt::from(9))))
663                .unwrap()
664                .len(),
665            8
666        );
667        assert!(
668            blocks
669                .get_blocks(Some(BlockId::Hash(Felt::TWO)), Some(BlockId::Hash(Felt::ZERO)))
670                .is_err()
671        );
672        assert!(
673            blocks
674                .get_blocks(Some(BlockId::Hash(Felt::from(10))), Some(BlockId::Hash(Felt::from(5))))
675                .unwrap()
676                .is_empty()
677        );
678        // from block hash to block number
679        assert_eq!(
680            blocks
681                .get_blocks(Some(BlockId::Hash(Felt::TWO)), Some(BlockId::Number(9)))
682                .unwrap()
683                .len(),
684            8
685        );
686        // from last block hash to latest/pre-confirmed
687        assert_eq!(
688            blocks
689                .get_blocks(
690                    Some(BlockId::Hash(Felt::from(11))),
691                    Some(BlockId::Tag(BlockTag::Latest))
692                )
693                .unwrap()
694                .len(),
695            1
696        );
697        assert_eq!(
698            blocks
699                .get_blocks(
700                    Some(BlockId::Hash(Felt::from(11))),
701                    Some(BlockId::Tag(BlockTag::PreConfirmed))
702                )
703                .unwrap()
704                .len(),
705            1
706        );
707
708        // from tag to tag
709        assert_eq!(
710            blocks
711                .get_blocks(
712                    Some(BlockId::Tag(BlockTag::Latest)),
713                    Some(BlockId::Tag(BlockTag::Latest))
714                )
715                .unwrap()
716                .len(),
717            1
718        );
719        assert_eq!(
720            blocks
721                .get_blocks(
722                    Some(BlockId::Tag(BlockTag::Latest)),
723                    Some(BlockId::Tag(BlockTag::PreConfirmed))
724                )
725                .unwrap()
726                .len(),
727            1
728        );
729        assert_eq!(
730            blocks
731                .get_blocks(
732                    Some(BlockId::Tag(BlockTag::PreConfirmed)),
733                    Some(BlockId::Tag(BlockTag::Latest))
734                )
735                .unwrap()
736                .len(),
737            1
738        );
739        assert_eq!(
740            blocks
741                .get_blocks(
742                    Some(BlockId::Tag(BlockTag::PreConfirmed)),
743                    Some(BlockId::Tag(BlockTag::PreConfirmed))
744                )
745                .unwrap()
746                .len(),
747            1
748        );
749
750        // from tag to block number/hash
751        assert_eq!(
752            blocks
753                .get_blocks(Some(BlockId::Tag(BlockTag::Latest)), Some(BlockId::Number(11)))
754                .unwrap()
755                .len(),
756            1
757        );
758        assert_eq!(
759            blocks
760                .get_blocks(
761                    Some(BlockId::Tag(BlockTag::Latest)),
762                    Some(BlockId::Hash(Felt::from(11)))
763                )
764                .unwrap()
765                .len(),
766            1
767        );
768        assert_eq!(
769            blocks
770                .get_blocks(Some(BlockId::Tag(BlockTag::PreConfirmed)), Some(BlockId::Number(11)))
771                .unwrap()
772                .len(),
773            1
774        );
775        assert_eq!(
776            blocks
777                .get_blocks(
778                    Some(BlockId::Tag(BlockTag::PreConfirmed)),
779                    Some(BlockId::Hash(Felt::from(11)))
780                )
781                .unwrap()
782                .len(),
783            1
784        );
785        assert!(
786            blocks
787                .get_blocks(Some(BlockId::Tag(BlockTag::Latest)), Some(BlockId::Number(2)))
788                .unwrap()
789                .is_empty()
790        );
791        assert!(
792            blocks
793                .get_blocks(Some(BlockId::Tag(BlockTag::Latest)), Some(BlockId::Hash(Felt::TWO)))
794                .unwrap()
795                .is_empty()
796        );
797    }
798
799    #[test]
800    fn get_by_block_id_is_correct() {
801        let mut blocks = StarknetBlocks::default();
802        let mut block_to_insert = StarknetBlock::create_pre_confirmed_block();
803        block_to_insert.header.block_hash =
804            starknet_api::block::BlockHash(block_to_insert.generate_hash().unwrap());
805        block_to_insert.header.block_header_without_hash.block_number = BlockNumber(10);
806        blocks.pre_confirmed_block = block_to_insert.clone();
807
808        blocks.insert(block_to_insert.clone(), StateDiff::default());
809
810        let extracted_block = blocks.get_by_block_id(&BlockId::Number(10)).unwrap();
811        assert!(block_to_insert == extracted_block.clone());
812
813        let extracted_block =
814            blocks.get_by_block_id(&BlockId::Hash(block_to_insert.block_hash())).unwrap();
815        assert!(block_to_insert == extracted_block.clone());
816
817        let extracted_block = blocks.get_by_block_id(&BlockId::Tag(BlockTag::Latest)).unwrap();
818        assert!(block_to_insert == extracted_block.clone());
819
820        let extracted_block =
821            blocks.get_by_block_id(&BlockId::Tag(BlockTag::PreConfirmed)).unwrap();
822        assert!(block_to_insert == extracted_block.clone());
823
824        match blocks.get_by_block_id(&BlockId::Number(11)) {
825            None => (),
826            _ => panic!("Expected none"),
827        }
828    }
829
830    #[test]
831    fn correct_block_linking_via_parent_hash() {
832        let mut blocks = StarknetBlocks::default();
833
834        for block_number in 0..3 {
835            let mut block = StarknetBlock::create_pre_confirmed_block();
836
837            block.status = BlockStatus::AcceptedOnL2;
838            block.header.block_header_without_hash.block_number = BlockNumber(block_number);
839            block.set_block_hash(block.generate_hash().unwrap());
840
841            blocks.insert(block, StateDiff::default());
842        }
843
844        assert!(
845            blocks
846                .get_by_num(&BlockNumber(0))
847                .unwrap()
848                .header
849                .block_header_without_hash
850                .parent_hash
851                == BlockHash::default()
852        );
853        assert!(
854            blocks.get_by_num(&BlockNumber(0)).unwrap().header.block_hash
855                == blocks
856                    .get_by_num(&BlockNumber(1))
857                    .unwrap()
858                    .header
859                    .block_header_without_hash
860                    .parent_hash
861        );
862        assert!(
863            blocks.get_by_num(&BlockNumber(1)).unwrap().header.block_hash
864                == blocks
865                    .get_by_num(&BlockNumber(2))
866                    .unwrap()
867                    .header
868                    .block_header_without_hash
869                    .parent_hash
870        );
871        assert!(
872            blocks
873                .get_by_num(&BlockNumber(1))
874                .unwrap()
875                .header
876                .block_header_without_hash
877                .parent_hash
878                != blocks
879                    .get_by_num(&BlockNumber(2))
880                    .unwrap()
881                    .header
882                    .block_header_without_hash
883                    .parent_hash
884        )
885    }
886
887    #[test]
888    fn get_by_hash_is_correct() {
889        let mut blocks = StarknetBlocks::default();
890        let mut block_to_insert = StarknetBlock::create_pre_confirmed_block();
891        block_to_insert.header.block_hash =
892            starknet_api::block::BlockHash(block_to_insert.generate_hash().unwrap());
893        block_to_insert.header.block_header_without_hash.block_number = BlockNumber(1);
894
895        blocks.insert(block_to_insert.clone(), StateDiff::default());
896
897        let extracted_block = blocks.get_by_hash(block_to_insert.block_hash()).unwrap();
898        assert!(block_to_insert == extracted_block.clone());
899    }
900
901    #[test]
902    fn check_pre_confirmed_block() {
903        let block = StarknetBlock::create_pre_confirmed_block();
904        assert!(block.status == BlockStatus::PreConfirmed);
905        assert!(block.transaction_hashes.is_empty());
906        assert_eq!(
907            block.header,
908            BlockHeader {
909                block_header_without_hash: BlockHeaderWithoutHash {
910                    l1_da_mode: L1DataAvailabilityMode::Blob,
911                    ..Default::default()
912                },
913                ..Default::default()
914            }
915        );
916    }
917}