unc_primitives/
block.rs

1use crate::block::BlockValidityError::{
2    InvalidChallengeRoot, InvalidChunkHeaderRoot, InvalidChunkMask, InvalidReceiptRoot,
3    InvalidStateRoot, InvalidTransactionRoot,
4};
5pub use crate::block_header::*;
6use crate::challenge::{Challenges, ChallengesResult};
7use crate::checked_feature;
8use crate::hash::{hash, CryptoHash};
9use crate::merkle::{merklize, verify_path, MerklePath};
10use crate::num_rational::Rational32;
11use crate::sharding::{
12    ChunkHashHeight, EncodedShardChunk, ReedSolomonWrapper, ShardChunk, ShardChunkHeader,
13    ShardChunkHeaderV1,
14};
15use crate::static_clock::StaticClock;
16use crate::types::{Balance, BlockHeight, EpochId, Gas, NumBlocks, StateRoot};
17use crate::utils::to_timestamp;
18use crate::validator_signer::{EmptyValidatorSigner, ValidatorSigner};
19use crate::version::{ProtocolVersion, SHARD_CHUNK_HEADER_UPGRADE_VERSION};
20use borsh::{BorshDeserialize, BorshSerialize};
21use chrono::{DateTime, Utc};
22use primitive_types::U256;
23use std::ops::Index;
24use std::sync::Arc;
25use unc_crypto::Signature;
26use unc_primitives_core::types::ShardId;
27
28#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq, Default)]
29pub struct GenesisId {
30    /// Chain Id
31    pub chain_id: String,
32    /// Hash of genesis block
33    pub hash: CryptoHash,
34}
35
36#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)]
37pub enum BlockValidityError {
38    InvalidStateRoot,
39    InvalidReceiptRoot,
40    InvalidChunkHeaderRoot,
41    InvalidTransactionRoot,
42    InvalidChunkMask,
43    InvalidChallengeRoot,
44}
45
46#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
47pub struct BlockV1 {
48    pub header: BlockHeader,
49    pub chunks: Vec<ShardChunkHeaderV1>,
50    pub challenges: Challenges,
51
52    // Data to confirm the correctness of randomness beacon output
53    pub vrf_value: unc_crypto::vrf::Value,
54    pub vrf_proof: unc_crypto::vrf::Proof,
55}
56
57#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
58pub struct BlockV2 {
59    pub header: BlockHeader,
60    pub chunks: Vec<ShardChunkHeader>,
61    pub challenges: Challenges,
62
63    // Data to confirm the correctness of randomness beacon output
64    pub vrf_value: unc_crypto::vrf::Value,
65    pub vrf_proof: unc_crypto::vrf::Proof,
66}
67
68/// V2 -> V3: added BlockBody
69#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
70pub struct BlockV3 {
71    pub header: BlockHeader,
72    pub body: BlockBody,
73}
74
75#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
76pub struct BlockBody {
77    pub chunks: Vec<ShardChunkHeader>,
78    pub challenges: Challenges,
79
80    // Data to confirm the correctness of randomness beacon output
81    pub vrf_value: unc_crypto::vrf::Value,
82    pub vrf_proof: unc_crypto::vrf::Proof,
83}
84
85/// Versioned Block data structure.
86/// For each next version, document what are the changes between versions.
87#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq)]
88pub enum Block {
89    BlockV1(Arc<BlockV1>),
90    BlockV2(Arc<BlockV2>),
91    BlockV3(Arc<BlockV3>),
92}
93
94pub fn genesis_chunks(
95    state_roots: Vec<StateRoot>,
96    shard_ids: &[ShardId],
97    initial_gas_limit: Gas,
98    genesis_height: BlockHeight,
99    genesis_protocol_version: ProtocolVersion,
100) -> Vec<ShardChunk> {
101    let mut rs = ReedSolomonWrapper::new(1, 2);
102    let state_roots = if state_roots.len() == shard_ids.len() {
103        state_roots
104    } else {
105        assert_eq!(state_roots.len(), 1);
106        std::iter::repeat(state_roots[0]).take(shard_ids.len()).collect()
107    };
108
109    shard_ids
110        .into_iter()
111        .zip(state_roots)
112        .map(|(&shard_id, state_root)| {
113            let (encoded_chunk, _) = EncodedShardChunk::new(
114                CryptoHash::default(),
115                state_root,
116                CryptoHash::default(),
117                genesis_height,
118                shard_id,
119                &mut rs,
120                0,
121                initial_gas_limit,
122                0,
123                CryptoHash::default(),
124                vec![],
125                vec![],
126                vec![],
127                &[],
128                CryptoHash::default(),
129                &EmptyValidatorSigner::default(),
130                genesis_protocol_version,
131            )
132            .expect("Failed to decode genesis chunk");
133            let mut chunk = encoded_chunk.decode_chunk(1).expect("Failed to decode genesis chunk");
134            chunk.set_height_included(genesis_height);
135            chunk
136        })
137        .collect()
138}
139
140impl Block {
141    fn block_from_protocol_version(
142        this_epoch_protocol_version: ProtocolVersion,
143        next_epoch_protocol_version: ProtocolVersion,
144        header: BlockHeader,
145        body: BlockBody,
146    ) -> Block {
147        if next_epoch_protocol_version < SHARD_CHUNK_HEADER_UPGRADE_VERSION {
148            let legacy_chunks = body
149                .chunks
150                .into_iter()
151                .map(|chunk| match chunk {
152                    ShardChunkHeader::V1(header) => header,
153                    ShardChunkHeader::V2(_) => panic!(
154                        "Attempted to include VersionedShardChunkHeaderV2 in old protocol version"
155                    ),
156                    ShardChunkHeader::V3(_) => panic!(
157                        "Attempted to include VersionedShardChunkHeaderV3 in old protocol version"
158                    ),
159                })
160                .collect();
161
162            Block::BlockV1(Arc::new(BlockV1 {
163                header,
164                chunks: legacy_chunks,
165                challenges: body.challenges,
166                vrf_value: body.vrf_value,
167                vrf_proof: body.vrf_proof,
168            }))
169        } else if !checked_feature!("stable", BlockHeaderV4, this_epoch_protocol_version) {
170            Block::BlockV2(Arc::new(BlockV2 {
171                header,
172                chunks: body.chunks,
173                challenges: body.challenges,
174                vrf_value: body.vrf_value,
175                vrf_proof: body.vrf_proof,
176            }))
177        } else {
178            Block::BlockV3(Arc::new(BlockV3 { header, body }))
179        }
180    }
181
182    /// Returns genesis block for given genesis date and state root.
183    pub fn genesis(
184        genesis_protocol_version: ProtocolVersion,
185        chunks: Vec<ShardChunkHeader>,
186        timestamp: DateTime<Utc>,
187        height: BlockHeight,
188        initial_gas_price: Balance,
189        initial_total_supply: Balance,
190        next_bp_hash: CryptoHash,
191    ) -> Self {
192        let challenges = vec![];
193        for chunk in &chunks {
194            assert_eq!(chunk.height_included(), height);
195        }
196        let vrf_value = unc_crypto::vrf::Value([0; 32]);
197        let vrf_proof = unc_crypto::vrf::Proof([0; 64]);
198        let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
199        let header = BlockHeader::genesis(
200            genesis_protocol_version,
201            height,
202            Block::compute_state_root(&body.chunks),
203            Block::compute_block_body_hash_impl(&body),
204            Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
205            Block::compute_chunk_headers_root(&body.chunks).0,
206            Block::compute_chunk_tx_root(&body.chunks),
207            body.chunks.len() as u64,
208            Block::compute_challenges_root(&body.challenges),
209            timestamp,
210            initial_gas_price,
211            initial_total_supply,
212            next_bp_hash,
213        );
214
215        Self::block_from_protocol_version(
216            genesis_protocol_version,
217            genesis_protocol_version,
218            header,
219            body,
220        )
221    }
222
223    /// Produces new block from header of previous block, current state root and set of transactions.
224    pub fn produce(
225        this_epoch_protocol_version: ProtocolVersion,
226        next_epoch_protocol_version: ProtocolVersion,
227        prev: &BlockHeader,
228        height: BlockHeight,
229        block_ordinal: NumBlocks,
230        chunks: Vec<ShardChunkHeader>,
231        epoch_id: EpochId,
232        next_epoch_id: EpochId,
233        epoch_sync_data_hash: Option<CryptoHash>,
234        approvals: Vec<Option<Box<Signature>>>,
235        gas_price_adjustment_rate: Rational32,
236        min_gas_price: Balance,
237        max_gas_price: Balance,
238        minted_amount: Option<Balance>,
239        challenges_result: ChallengesResult,
240        challenges: Challenges,
241        signer: &dyn ValidatorSigner,
242        next_bp_hash: CryptoHash,
243        block_merkle_root: CryptoHash,
244        timestamp_override: Option<DateTime<chrono::Utc>>,
245    ) -> Self {
246        // Collect aggregate of validators and gas usage/limits from chunks.
247        let mut prev_validator_power_proposals = vec![];
248        let mut prev_validator_pledge_proposals = vec![];
249        let mut gas_used = 0;
250        // This computation of chunk_mask relies on the fact that chunks are ordered by shard_id.
251        let mut chunk_mask = vec![];
252        let mut balance_burnt = 0;
253        let mut gas_limit = 0;
254        for chunk in chunks.iter() {
255            if chunk.height_included() == height {
256                prev_validator_power_proposals.extend(chunk.prev_validator_power_proposals());
257                prev_validator_pledge_proposals.extend(chunk.prev_validator_pledge_proposals());
258                gas_used += chunk.prev_gas_used();
259                gas_limit += chunk.gas_limit();
260                balance_burnt += chunk.prev_balance_burnt();
261                chunk_mask.push(true);
262            } else {
263                chunk_mask.push(false);
264            }
265        }
266        let next_gas_price = Self::compute_next_gas_price(
267            prev.next_gas_price(),
268            gas_used,
269            gas_limit,
270            gas_price_adjustment_rate,
271            min_gas_price,
272            max_gas_price,
273        );
274
275        let new_total_supply = prev.total_supply() + minted_amount.unwrap_or(0) - balance_burnt;
276        let now = to_timestamp(timestamp_override.unwrap_or_else(StaticClock::utc));
277        let time = if now <= prev.raw_timestamp() { prev.raw_timestamp() + 1 } else { now };
278
279        let (vrf_value, vrf_proof) = signer.compute_vrf_with_proof(prev.random_value().as_ref());
280        let random_value = hash(vrf_value.0.as_ref());
281
282        let last_ds_final_block =
283            if height == prev.height() + 1 { prev.hash() } else { prev.last_ds_final_block() };
284
285        let last_final_block =
286            if height == prev.height() + 1 && prev.last_ds_final_block() == prev.prev_hash() {
287                prev.prev_hash()
288            } else {
289                prev.last_final_block()
290            };
291
292        match prev {
293            BlockHeader::BlockHeaderV1(_) => debug_assert_eq!(prev.block_ordinal(), 0),
294            BlockHeader::BlockHeaderV2(_) => debug_assert_eq!(prev.block_ordinal(), 0),
295            BlockHeader::BlockHeaderV3(_) => {
296                debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
297            }
298            BlockHeader::BlockHeaderV4(_) => {
299                debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
300            }
301        };
302
303        let body = BlockBody { chunks, challenges, vrf_value, vrf_proof };
304        let header = BlockHeader::new(
305            this_epoch_protocol_version,
306            next_epoch_protocol_version,
307            height,
308            *prev.hash(),
309            Block::compute_block_body_hash_impl(&body),
310            Block::compute_state_root(&body.chunks),
311            Block::compute_chunk_prev_outgoing_receipts_root(&body.chunks),
312            Block::compute_chunk_headers_root(&body.chunks).0,
313            Block::compute_chunk_tx_root(&body.chunks),
314            Block::compute_outcome_root(&body.chunks),
315            time,
316            Block::compute_challenges_root(&body.challenges),
317            random_value,
318            prev_validator_power_proposals,
319            prev_validator_pledge_proposals,
320            chunk_mask,
321            block_ordinal,
322            epoch_id,
323            next_epoch_id,
324            next_gas_price,
325            new_total_supply,
326            challenges_result,
327            signer,
328            *last_final_block,
329            *last_ds_final_block,
330            epoch_sync_data_hash,
331            approvals,
332            next_bp_hash,
333            block_merkle_root,
334            prev.height(),
335        );
336
337        Self::block_from_protocol_version(
338            this_epoch_protocol_version,
339            next_epoch_protocol_version,
340            header,
341            body,
342        )
343    }
344
345    pub fn verify_total_supply(
346        &self,
347        prev_total_supply: Balance,
348        minted_amount: Option<Balance>,
349    ) -> bool {
350        let mut balance_burnt = 0;
351
352        for chunk in self.chunks().iter() {
353            if chunk.height_included() == self.header().height() {
354                balance_burnt += chunk.prev_balance_burnt();
355            }
356        }
357
358        let new_total_supply = prev_total_supply + minted_amount.unwrap_or(0) - balance_burnt;
359        self.header().total_supply() == new_total_supply
360    }
361
362    pub fn verify_gas_price(
363        &self,
364        gas_price: Balance,
365        min_gas_price: Balance,
366        max_gas_price: Balance,
367        gas_price_adjustment_rate: Rational32,
368    ) -> bool {
369        let gas_used = Self::compute_gas_used(self.chunks().iter(), self.header().height());
370        let gas_limit = Self::compute_gas_limit(self.chunks().iter(), self.header().height());
371        let expected_price = Self::compute_next_gas_price(
372            gas_price,
373            gas_used,
374            gas_limit,
375            gas_price_adjustment_rate,
376            min_gas_price,
377            max_gas_price,
378        );
379        self.header().next_gas_price() == expected_price
380    }
381
382    /// Computes gas price for applying chunks in the next block according to the formula:
383    ///   next_gas_price = gas_price * (1 + (gas_used/gas_limit - 1/2) * adjustment_rate)
384    /// and clamped between min_gas_price and max_gas_price.
385    pub fn compute_next_gas_price(
386        gas_price: Balance,
387        gas_used: Gas,
388        gas_limit: Gas,
389        gas_price_adjustment_rate: Rational32,
390        min_gas_price: Balance,
391        max_gas_price: Balance,
392    ) -> Balance {
393        // If block was skipped, the price does not change.
394        if gas_limit == 0 {
395            return gas_price;
396        }
397
398        let gas_used = u128::from(gas_used);
399        let gas_limit = u128::from(gas_limit);
400        let adjustment_rate_numer = *gas_price_adjustment_rate.numer() as u128;
401        let adjustment_rate_denom = *gas_price_adjustment_rate.denom() as u128;
402
403        // This number can never be negative as long as gas_used <= gas_limit and
404        // adjustment_rate_numer <= adjustment_rate_denom.
405        let numerator = 2 * adjustment_rate_denom * gas_limit
406            + 2 * adjustment_rate_numer * gas_used
407            - adjustment_rate_numer * gas_limit;
408        let denominator = 2 * adjustment_rate_denom * gas_limit;
409        let next_gas_price =
410            U256::from(gas_price) * U256::from(numerator) / U256::from(denominator);
411
412        next_gas_price.clamp(U256::from(min_gas_price), U256::from(max_gas_price)).as_u128()
413    }
414
415    pub fn compute_state_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
416        chunks: T,
417    ) -> CryptoHash {
418        merklize(
419            &chunks.into_iter().map(|chunk| chunk.prev_state_root()).collect::<Vec<CryptoHash>>(),
420        )
421        .0
422    }
423
424    pub fn compute_block_body_hash_impl(body: &BlockBody) -> CryptoHash {
425        CryptoHash::hash_borsh(body)
426    }
427
428    pub fn compute_chunk_prev_outgoing_receipts_root<
429        'a,
430        T: IntoIterator<Item = &'a ShardChunkHeader>,
431    >(
432        chunks: T,
433    ) -> CryptoHash {
434        merklize(
435            &chunks
436                .into_iter()
437                .map(|chunk| chunk.prev_outgoing_receipts_root())
438                .collect::<Vec<CryptoHash>>(),
439        )
440        .0
441    }
442
443    pub fn compute_chunk_headers_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
444        chunks: T,
445    ) -> (CryptoHash, Vec<MerklePath>) {
446        merklize(
447            &chunks
448                .into_iter()
449                .map(|chunk| ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()))
450                .collect::<Vec<ChunkHashHeight>>(),
451        )
452    }
453
454    pub fn compute_chunk_tx_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
455        chunks: T,
456    ) -> CryptoHash {
457        merklize(&chunks.into_iter().map(|chunk| chunk.tx_root()).collect::<Vec<CryptoHash>>()).0
458    }
459
460    pub fn compute_outcome_root<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
461        chunks: T,
462    ) -> CryptoHash {
463        merklize(
464            &chunks.into_iter().map(|chunk| chunk.prev_outcome_root()).collect::<Vec<CryptoHash>>(),
465        )
466        .0
467    }
468
469    pub fn compute_challenges_root(challenges: &Challenges) -> CryptoHash {
470        merklize(&challenges.iter().map(|challenge| challenge.hash).collect::<Vec<CryptoHash>>()).0
471    }
472
473    pub fn compute_gas_used<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
474        chunks: T,
475        height: BlockHeight,
476    ) -> Gas {
477        chunks.into_iter().fold(0, |acc, chunk| {
478            if chunk.height_included() == height {
479                acc + chunk.prev_gas_used()
480            } else {
481                acc
482            }
483        })
484    }
485
486    pub fn compute_gas_limit<'a, T: IntoIterator<Item = &'a ShardChunkHeader>>(
487        chunks: T,
488        height: BlockHeight,
489    ) -> Gas {
490        chunks.into_iter().fold(0, |acc, chunk| {
491            if chunk.height_included() == height {
492                acc + chunk.gas_limit()
493            } else {
494                acc
495            }
496        })
497    }
498
499    pub fn validate_chunk_header_proof(
500        chunk: &ShardChunkHeader,
501        chunk_root: &CryptoHash,
502        merkle_path: &MerklePath,
503    ) -> bool {
504        verify_path(
505            *chunk_root,
506            merkle_path,
507            &ChunkHashHeight(chunk.chunk_hash(), chunk.height_included()),
508        )
509    }
510
511    #[inline]
512    pub fn header(&self) -> &BlockHeader {
513        match self {
514            Block::BlockV1(block) => &block.header,
515            Block::BlockV2(block) => &block.header,
516            Block::BlockV3(block) => &block.header,
517        }
518    }
519
520    pub fn chunks(&self) -> ChunksCollection {
521        match self {
522            Block::BlockV1(block) => ChunksCollection::V1(
523                block.chunks.iter().map(|h| ShardChunkHeader::V1(h.clone())).collect(),
524            ),
525            Block::BlockV2(block) => ChunksCollection::V2(&block.chunks),
526            Block::BlockV3(block) => ChunksCollection::V2(&block.body.chunks),
527        }
528    }
529
530    #[inline]
531    pub fn challenges(&self) -> &Challenges {
532        match self {
533            Block::BlockV1(block) => &block.challenges,
534            Block::BlockV2(block) => &block.challenges,
535            Block::BlockV3(block) => &block.body.challenges,
536        }
537    }
538
539    #[inline]
540    pub fn vrf_value(&self) -> &unc_crypto::vrf::Value {
541        match self {
542            Block::BlockV1(block) => &block.vrf_value,
543            Block::BlockV2(block) => &block.vrf_value,
544            Block::BlockV3(block) => &block.body.vrf_value,
545        }
546    }
547
548    #[inline]
549    pub fn vrf_proof(&self) -> &unc_crypto::vrf::Proof {
550        match self {
551            Block::BlockV1(block) => &block.vrf_proof,
552            Block::BlockV2(block) => &block.vrf_proof,
553            Block::BlockV3(block) => &block.body.vrf_proof,
554        }
555    }
556
557    pub fn hash(&self) -> &CryptoHash {
558        self.header().hash()
559    }
560
561    pub fn compute_block_body_hash(&self) -> Option<CryptoHash> {
562        match self {
563            Block::BlockV1(_) => None,
564            Block::BlockV2(_) => None,
565            Block::BlockV3(block) => Some(Self::compute_block_body_hash_impl(&block.body)),
566        }
567    }
568
569    /// Checks that block content matches block hash, with the possible exception of chunk signatures
570    pub fn check_validity(&self) -> Result<(), BlockValidityError> {
571        // Check that state root stored in the header matches the state root of the chunks
572        let state_root = Block::compute_state_root(self.chunks().iter());
573        if self.header().prev_state_root() != &state_root {
574            return Err(InvalidStateRoot);
575        }
576
577        // Check that chunk receipts root stored in the header matches the state root of the chunks
578        let chunk_receipts_root =
579            Block::compute_chunk_prev_outgoing_receipts_root(self.chunks().iter());
580        if self.header().prev_chunk_outgoing_receipts_root() != &chunk_receipts_root {
581            return Err(InvalidReceiptRoot);
582        }
583
584        // Check that chunk headers root stored in the header matches the chunk headers root of the chunks
585        let chunk_headers_root = Block::compute_chunk_headers_root(self.chunks().iter()).0;
586        if self.header().chunk_headers_root() != &chunk_headers_root {
587            return Err(InvalidChunkHeaderRoot);
588        }
589
590        // Check that chunk tx root stored in the header matches the tx root of the chunks
591        let chunk_tx_root = Block::compute_chunk_tx_root(self.chunks().iter());
592        if self.header().chunk_tx_root() != &chunk_tx_root {
593            return Err(InvalidTransactionRoot);
594        }
595
596        let outcome_root = Block::compute_outcome_root(self.chunks().iter());
597        if self.header().outcome_root() != &outcome_root {
598            return Err(InvalidTransactionRoot);
599        }
600
601        // Check that chunk included root stored in the header matches the chunk included root of the chunks
602        let chunk_mask: Vec<bool> = self
603            .chunks()
604            .iter()
605            .map(|chunk| chunk.height_included() == self.header().height())
606            .collect();
607        if self.header().chunk_mask() != &chunk_mask[..] {
608            return Err(InvalidChunkMask);
609        }
610
611        // Check that challenges root stored in the header matches the challenges root of the challenges
612        let challenges_root = Block::compute_challenges_root(self.challenges());
613        if self.header().challenges_root() != &challenges_root {
614            return Err(InvalidChallengeRoot);
615        }
616
617        Ok(())
618    }
619}
620
621pub enum ChunksCollection<'a> {
622    V1(Vec<ShardChunkHeader>),
623    V2(&'a [ShardChunkHeader]),
624}
625
626pub struct VersionedChunksIter<'a> {
627    chunks: &'a [ShardChunkHeader],
628    curr_index: usize,
629    len: usize,
630}
631
632impl<'a> VersionedChunksIter<'a> {
633    fn new(chunks: &'a [ShardChunkHeader]) -> Self {
634        Self { chunks, curr_index: 0, len: chunks.len() }
635    }
636}
637
638impl<'a> Iterator for VersionedChunksIter<'a> {
639    type Item = &'a ShardChunkHeader;
640
641    fn next(&mut self) -> Option<Self::Item> {
642        if self.curr_index < self.len {
643            let item = &self.chunks[self.curr_index];
644            self.curr_index += 1;
645            Some(item)
646        } else {
647            None
648        }
649    }
650}
651
652impl<'a> ExactSizeIterator for VersionedChunksIter<'a> {
653    fn len(&self) -> usize {
654        self.len - self.curr_index
655    }
656}
657
658impl<'a> Index<usize> for ChunksCollection<'a> {
659    type Output = ShardChunkHeader;
660
661    fn index(&self, index: usize) -> &Self::Output {
662        match self {
663            ChunksCollection::V1(chunks) => &chunks[index],
664            ChunksCollection::V2(chunks) => &chunks[index],
665        }
666    }
667}
668
669impl<'a> ChunksCollection<'a> {
670    pub fn len(&self) -> usize {
671        match self {
672            ChunksCollection::V1(chunks) => chunks.len(),
673            ChunksCollection::V2(chunks) => chunks.len(),
674        }
675    }
676
677    pub fn iter(&'a self) -> VersionedChunksIter<'a> {
678        match self {
679            ChunksCollection::V1(chunks) => VersionedChunksIter::new(chunks),
680            ChunksCollection::V2(chunks) => VersionedChunksIter::new(chunks),
681        }
682    }
683
684    pub fn get(&self, index: usize) -> Option<&ShardChunkHeader> {
685        match self {
686            ChunksCollection::V1(chunks) => chunks.get(index),
687            ChunksCollection::V2(chunks) => chunks.get(index),
688        }
689    }
690}
691
692/// The tip of a fork. A handle to the fork ancestry from its leaf in the
693/// blockchain tree. References the max height and the latest and previous
694/// blocks for convenience
695#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize)]
696pub struct Tip {
697    /// Height of the tip (max height of the fork)
698    pub height: BlockHeight,
699    /// Last block pushed to the fork
700    pub last_block_hash: CryptoHash,
701    /// Previous block
702    pub prev_block_hash: CryptoHash,
703    /// Current epoch id. Used for getting validator info.
704    pub epoch_id: EpochId,
705    /// Next epoch id.
706    pub next_epoch_id: EpochId,
707}
708
709impl Tip {
710    /// Creates a new tip based on provided header.
711    pub fn from_header(header: &BlockHeader) -> Tip {
712        Tip {
713            height: header.height(),
714            last_block_hash: *header.hash(),
715            prev_block_hash: *header.prev_hash(),
716            epoch_id: header.epoch_id().clone(),
717            next_epoch_id: header.next_epoch_id().clone(),
718        }
719    }
720}