near_primitives/
block.rs

1use crate::bandwidth_scheduler::BlockBandwidthRequests;
2use crate::block::BlockValidityError::{
3    InvalidChunkHeaderRoot, InvalidChunkMask, InvalidReceiptRoot, InvalidStateRoot,
4    InvalidTransactionRoot,
5};
6use crate::block_body::SpiceCoreStatement;
7use crate::block_body::{BlockBody, BlockBodyV1, ChunkEndorsementSignatures};
8pub use crate::block_header::*;
9use crate::challenge::Challenge;
10use crate::congestion_info::{BlockCongestionInfo, ExtendedCongestionInfo};
11use crate::hash::CryptoHash;
12use crate::merkle::{MerklePath, merklize, verify_path};
13use crate::num_rational::Rational32;
14#[cfg(feature = "clock")]
15use crate::optimistic_block::OptimisticBlock;
16use crate::sharding::{ChunkHashHeight, ShardChunkHeader, ShardChunkHeaderV1};
17use crate::types::{Balance, BlockHeight, EpochId, Gas};
18#[cfg(feature = "clock")]
19use crate::{
20    stateless_validation::chunk_endorsements_bitmap::ChunkEndorsementsBitmap,
21    utils::get_block_metadata,
22};
23use borsh::{BorshDeserialize, BorshSerialize};
24use itertools::Itertools;
25#[cfg(feature = "clock")]
26use near_primitives_core::types::ProtocolVersion;
27use near_schema_checker_lib::ProtocolSchema;
28use primitive_types::U256;
29use std::collections::BTreeMap;
30use std::ops::Deref;
31
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub enum BlockValidityError {
34    InvalidStateRoot,
35    InvalidReceiptRoot,
36    InvalidChunkHeaderRoot,
37    InvalidTransactionRoot,
38    InvalidChunkMask,
39}
40
41#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
42pub struct BlockV1 {
43    pub header: BlockHeader,
44    pub chunks: Vec<ShardChunkHeaderV1>,
45    #[deprecated]
46    pub challenges: Vec<Challenge>,
47
48    // Data to confirm the correctness of randomness beacon output
49    pub vrf_value: near_crypto::vrf::Value,
50    pub vrf_proof: near_crypto::vrf::Proof,
51}
52
53#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
54pub struct BlockV2 {
55    pub header: BlockHeader,
56    pub chunks: Vec<ShardChunkHeader>,
57    #[deprecated]
58    pub challenges: Vec<Challenge>,
59
60    // Data to confirm the correctness of randomness beacon output
61    pub vrf_value: near_crypto::vrf::Value,
62    pub vrf_proof: near_crypto::vrf::Proof,
63}
64
65/// V2 -> V3: added BlockBodyV1
66#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
67pub struct BlockV3 {
68    pub header: BlockHeader,
69    pub body: BlockBodyV1,
70}
71
72/// V3 -> V4: use versioned BlockBody
73#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
74pub struct BlockV4 {
75    pub header: BlockHeader,
76    pub body: BlockBody,
77}
78
79/// Versioned Block data structure.
80/// For each next version, document what are the changes between versions.
81#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
82#[borsh(use_discriminant = true)]
83#[repr(u8)]
84pub enum Block {
85    BlockV1(BlockV1) = 0,
86    BlockV2(BlockV2) = 1,
87    BlockV3(BlockV3) = 2,
88    BlockV4(BlockV4) = 3,
89}
90
91impl Block {
92    pub(crate) fn new_block(header: BlockHeader, body: BlockBody) -> Block {
93        // BlockV4 and BlockBodyV2 were introduced in the same protocol version `ChunkValidation`
94        // We should not expect BlockV4 to have BlockBodyV1
95        match body {
96            BlockBody::V1(_) => {
97                panic!("Attempted to include BlockBodyV1 in new protocol version")
98            }
99            _ => Block::BlockV4(BlockV4 { header, body }),
100        }
101    }
102
103    /// Produces new block from header of previous block, current state root and set of transactions.
104    #[cfg(feature = "clock")]
105    pub fn produce(
106        latest_protocol_version: ProtocolVersion,
107        prev: &BlockHeader,
108        height: BlockHeight,
109        block_ordinal: crate::types::NumBlocks,
110        chunks: Vec<ShardChunkHeader>,
111        chunk_endorsements: Vec<ChunkEndorsementSignatures>,
112        epoch_id: EpochId,
113        next_epoch_id: EpochId,
114        epoch_sync_data_hash: Option<CryptoHash>,
115        approvals: Vec<Option<Box<near_crypto::Signature>>>,
116        gas_price_adjustment_rate: Rational32,
117        min_gas_price: Balance,
118        max_gas_price: Balance,
119        minted_amount: Option<Balance>,
120        signer: &crate::validator_signer::ValidatorSigner,
121        next_bp_hash: CryptoHash,
122        block_merkle_root: CryptoHash,
123        clock: near_time::Clock,
124        sandbox_delta_time: Option<near_time::Duration>,
125        optimistic_block: Option<OptimisticBlock>,
126        // TODO(spice): Change type to Vec<SpiceCoreStatement> once spice feature is tied to
127        // protocol version. We use option for now to indicate whether spice feature is enabled to
128        // allow creating non-spice blocks for tests.
129        core_statements: Option<Vec<SpiceCoreStatement>>,
130    ) -> Self {
131        // Collect aggregate of validators and gas usage/limits from chunks.
132
133        let mut prev_validator_proposals = vec![];
134        let mut gas_used = Gas::ZERO;
135        // This computation of chunk_mask relies on the fact that chunks are ordered by shard_id.
136        let mut chunk_mask = vec![];
137        let mut balance_burnt = Balance::ZERO;
138        let mut gas_limit = Gas::ZERO;
139        for chunk in &chunks {
140            if chunk.height_included() == height {
141                prev_validator_proposals.extend(chunk.prev_validator_proposals());
142                gas_used = gas_used.checked_add(chunk.prev_gas_used()).unwrap();
143                gas_limit = gas_limit.checked_add(chunk.gas_limit()).unwrap();
144                balance_burnt = balance_burnt.checked_add(chunk.prev_balance_burnt()).unwrap();
145                chunk_mask.push(true);
146            } else {
147                chunk_mask.push(false);
148            }
149        }
150        let next_gas_price = Self::compute_next_gas_price(
151            prev.next_gas_price(),
152            gas_used,
153            gas_limit,
154            gas_price_adjustment_rate,
155            min_gas_price,
156            max_gas_price,
157        );
158
159        let new_total_supply = prev
160            .total_supply()
161            .checked_add(minted_amount.unwrap_or(Balance::ZERO))
162            .unwrap()
163            .checked_sub(balance_burnt)
164            .unwrap();
165
166        // Use the optimistic block data if available, otherwise compute it.
167        let (time, vrf_value, vrf_proof, random_value) = optimistic_block
168            .as_ref()
169            .map(|ob| {
170                tracing::debug!(target: "client", "Taking metadata from optimistic block");
171                (
172                    ob.inner.block_timestamp,
173                    ob.inner.vrf_value,
174                    ob.inner.vrf_proof,
175                    ob.inner.random_value,
176                )
177            })
178            .unwrap_or_else(|| {
179                let now = clock.now_utc().unix_timestamp_nanos() as u64;
180                get_block_metadata(prev, signer, now, sandbox_delta_time)
181            });
182
183        let last_ds_final_block =
184            if height == prev.height() + 1 { prev.hash() } else { prev.last_ds_final_block() };
185
186        let last_final_block =
187            if height == prev.height() + 1 && prev.last_ds_final_block() == prev.prev_hash() {
188                prev.prev_hash()
189            } else {
190                prev.last_final_block()
191            };
192
193        match prev {
194            BlockHeader::BlockHeaderV1(_) | BlockHeader::BlockHeaderV2(_) => {
195                debug_assert_eq!(prev.block_ordinal(), 0)
196            }
197            BlockHeader::BlockHeaderV3(_)
198            | BlockHeader::BlockHeaderV4(_)
199            | BlockHeader::BlockHeaderV5(_) => {
200                debug_assert_eq!(prev.block_ordinal() + 1, block_ordinal)
201            }
202        };
203
204        debug_assert_eq!(
205            chunk_endorsements.len(),
206            chunk_mask.len(),
207            "Chunk endorsements size is different from number of shards."
208        );
209        // Generate from the chunk endorsement signatures a bitmap with the same number of shards and validator assignments per shard,
210        // where `Option<Signature>` is mapped to `true` and `None` is mapped to `false`.
211        let chunk_endorsements_bitmap = Some(ChunkEndorsementsBitmap::from_endorsements(
212            chunk_endorsements
213                .iter()
214                .map(|endorsements_for_shard| {
215                    endorsements_for_shard.iter().map(|e| e.is_some()).collect_vec()
216                })
217                .collect_vec(),
218        ));
219
220        let chunks_wrapper = Chunks::from_chunk_headers(&chunks, height);
221        let prev_state_root = if cfg!(feature = "protocol_feature_spice") {
222            // TODO(spice): include state root from the relevant previous executed block.
223            CryptoHash::default()
224        } else {
225            chunks_wrapper.compute_state_root()
226        };
227        let prev_chunk_outgoing_receipts_root =
228            chunks_wrapper.compute_chunk_prev_outgoing_receipts_root();
229        let chunk_headers_root = chunks_wrapper.compute_chunk_headers_root();
230        let chunk_tx_root = chunks_wrapper.compute_chunk_tx_root();
231        let outcome_root = chunks_wrapper.compute_outcome_root();
232
233        let body = if let Some(core_statements) = core_statements {
234            BlockBody::new_for_spice(chunks, vrf_value, vrf_proof, core_statements)
235        } else {
236            BlockBody::new(chunks, vrf_value, vrf_proof, chunk_endorsements)
237        };
238
239        let header = BlockHeader::new(
240            latest_protocol_version,
241            height,
242            *prev.hash(),
243            body.compute_hash(),
244            prev_state_root,
245            prev_chunk_outgoing_receipts_root,
246            chunk_headers_root.0,
247            chunk_tx_root,
248            outcome_root,
249            time,
250            random_value,
251            prev_validator_proposals,
252            chunk_mask,
253            block_ordinal,
254            epoch_id,
255            next_epoch_id,
256            next_gas_price,
257            new_total_supply,
258            signer,
259            *last_final_block,
260            *last_ds_final_block,
261            epoch_sync_data_hash,
262            approvals,
263            next_bp_hash,
264            block_merkle_root,
265            prev.height(),
266            chunk_endorsements_bitmap,
267        );
268
269        Self::new_block(header, body)
270    }
271
272    pub fn verify_total_supply(
273        &self,
274        prev_total_supply: Balance,
275        minted_amount: Option<Balance>,
276    ) -> bool {
277        let mut balance_burnt = Balance::ZERO;
278
279        for chunk in self.chunks().iter_new() {
280            balance_burnt = balance_burnt.checked_add(chunk.prev_balance_burnt()).unwrap();
281        }
282
283        let new_total_supply = prev_total_supply
284            .checked_add(minted_amount.unwrap_or(Balance::ZERO))
285            .unwrap()
286            .checked_sub(balance_burnt)
287            .unwrap();
288        self.header().total_supply() == new_total_supply
289    }
290
291    pub fn verify_gas_price(
292        &self,
293        gas_price: Balance,
294        min_gas_price: Balance,
295        max_gas_price: Balance,
296        gas_price_adjustment_rate: Rational32,
297    ) -> bool {
298        let gas_used = self.chunks().compute_gas_used();
299        let gas_limit = self.chunks().compute_gas_limit();
300        let expected_price = Self::compute_next_gas_price(
301            gas_price,
302            gas_used,
303            gas_limit,
304            gas_price_adjustment_rate,
305            min_gas_price,
306            max_gas_price,
307        );
308        self.header().next_gas_price() == expected_price
309    }
310
311    /// Computes gas price for applying chunks in the next block according to the formula:
312    ///   next_gas_price = gas_price * (1 + (gas_used/gas_limit - 1/2) * adjustment_rate)
313    /// and clamped between min_gas_price and max_gas_price.
314    pub fn compute_next_gas_price(
315        gas_price: Balance,
316        gas_used: Gas,
317        gas_limit: Gas,
318        gas_price_adjustment_rate: Rational32,
319        min_gas_price: Balance,
320        max_gas_price: Balance,
321    ) -> Balance {
322        // If block was skipped, the price does not change.
323        if gas_limit == Gas::ZERO {
324            return gas_price;
325        }
326
327        let gas_used = u128::from(gas_used.as_gas());
328        let gas_limit = u128::from(gas_limit.as_gas());
329        let adjustment_rate_numer = *gas_price_adjustment_rate.numer() as u128;
330        let adjustment_rate_denom = *gas_price_adjustment_rate.denom() as u128;
331
332        // This number can never be negative as long as gas_used <= gas_limit and
333        // adjustment_rate_numer <= adjustment_rate_denom.
334        let numerator = 2 * adjustment_rate_denom * gas_limit
335            + 2 * adjustment_rate_numer * gas_used
336            - adjustment_rate_numer * gas_limit;
337        let denominator = 2 * adjustment_rate_denom * gas_limit;
338        let next_gas_price =
339            U256::from(gas_price.as_yoctonear()) * U256::from(numerator) / U256::from(denominator);
340
341        Balance::from_yoctonear(
342            next_gas_price
343                .clamp(
344                    U256::from(min_gas_price.as_yoctonear()),
345                    U256::from(max_gas_price.as_yoctonear()),
346                )
347                .as_u128(),
348        )
349    }
350
351    pub fn validate_chunk_header_proof(
352        chunk: &ShardChunkHeader,
353        chunk_root: &CryptoHash,
354        merkle_path: &MerklePath,
355    ) -> bool {
356        verify_path(
357            *chunk_root,
358            merkle_path,
359            &ChunkHashHeight(chunk.chunk_hash().clone(), chunk.height_included()),
360        )
361    }
362
363    #[inline]
364    pub fn header(&self) -> &BlockHeader {
365        match self {
366            Block::BlockV1(block) => &block.header,
367            Block::BlockV2(block) => &block.header,
368            Block::BlockV3(block) => &block.header,
369            Block::BlockV4(block) => &block.header,
370        }
371    }
372
373    pub fn chunks(&self) -> Chunks {
374        Chunks::new(&self)
375    }
376
377    #[inline]
378    pub fn vrf_value(&self) -> &near_crypto::vrf::Value {
379        match self {
380            Block::BlockV1(block) => &block.vrf_value,
381            Block::BlockV2(block) => &block.vrf_value,
382            Block::BlockV3(block) => &block.body.vrf_value,
383            Block::BlockV4(block) => &block.body.vrf_value(),
384        }
385    }
386
387    #[inline]
388    pub fn vrf_proof(&self) -> &near_crypto::vrf::Proof {
389        match self {
390            Block::BlockV1(block) => &block.vrf_proof,
391            Block::BlockV2(block) => &block.vrf_proof,
392            Block::BlockV3(block) => &block.body.vrf_proof,
393            Block::BlockV4(block) => &block.body.vrf_proof(),
394        }
395    }
396
397    #[inline]
398    pub fn chunk_endorsements(&self) -> &[ChunkEndorsementSignatures] {
399        match self {
400            Block::BlockV1(_) | Block::BlockV2(_) | Block::BlockV3(_) => &[],
401            Block::BlockV4(block) => block.body.chunk_endorsements(),
402        }
403    }
404
405    #[inline]
406    pub fn spice_core_statements(&self) -> &[SpiceCoreStatement] {
407        match self {
408            Block::BlockV1(_) | Block::BlockV2(_) | Block::BlockV3(_) => &[],
409            Block::BlockV4(block) => block.body.spice_core_statements(),
410        }
411    }
412
413    #[inline]
414    pub fn is_spice_block(&self) -> bool {
415        match self {
416            Block::BlockV1(_) | Block::BlockV2(_) | Block::BlockV3(_) => false,
417            Block::BlockV4(block) => block.body.is_spice_block(),
418        }
419    }
420
421    pub fn block_congestion_info(&self) -> BlockCongestionInfo {
422        self.chunks().block_congestion_info()
423    }
424
425    pub fn block_bandwidth_requests(&self) -> BlockBandwidthRequests {
426        self.chunks().block_bandwidth_requests()
427    }
428
429    pub fn hash(&self) -> &CryptoHash {
430        self.header().hash()
431    }
432
433    pub fn compute_block_body_hash(&self) -> Option<CryptoHash> {
434        match self {
435            Block::BlockV1(_) => None,
436            Block::BlockV2(_) => None,
437            Block::BlockV3(block) => Some(block.body.compute_hash()),
438            Block::BlockV4(block) => Some(block.body.compute_hash()),
439        }
440    }
441
442    /// Checks that block content matches block hash, with the possible exception of chunk signatures
443    pub fn check_validity(&self) -> Result<(), BlockValidityError> {
444        // Check that state root stored in the header matches the state root of the chunks
445        // With spice chunks wouldn't contain prev_state_roots.
446        // TODO(spice): check that block's state_root matches state_root corresponding to chunks of
447        // the appropriate executed block from the past.
448        if !cfg!(feature = "protocol_feature_spice") {
449            let state_root = self.chunks().compute_state_root();
450            if self.header().prev_state_root() != &state_root {
451                return Err(InvalidStateRoot);
452            }
453        }
454
455        // Check that chunk receipts root stored in the header matches the state root of the chunks
456        let chunk_receipts_root = self.chunks().compute_chunk_prev_outgoing_receipts_root();
457        if self.header().prev_chunk_outgoing_receipts_root() != &chunk_receipts_root {
458            return Err(InvalidReceiptRoot);
459        }
460
461        // Check that chunk headers root stored in the header matches the chunk headers root of the chunks
462        let chunk_headers_root = self.chunks().compute_chunk_headers_root().0;
463        if self.header().chunk_headers_root() != &chunk_headers_root {
464            return Err(InvalidChunkHeaderRoot);
465        }
466
467        // Check that chunk tx root stored in the header matches the tx root of the chunks
468        let chunk_tx_root = self.chunks().compute_chunk_tx_root();
469        if self.header().chunk_tx_root() != &chunk_tx_root {
470            return Err(InvalidTransactionRoot);
471        }
472
473        let outcome_root = self.chunks().compute_outcome_root();
474        if self.header().outcome_root() != &outcome_root {
475            return Err(InvalidTransactionRoot);
476        }
477
478        // Check that chunk included root stored in the header matches the chunk included root of the chunks
479        let chunk_mask: Vec<bool> =
480            self.chunks().iter().map(|chunk| chunk.is_new_chunk()).collect();
481        if self.header().chunk_mask() != &chunk_mask[..] {
482            return Err(InvalidChunkMask);
483        }
484
485        Ok(())
486    }
487}
488
489/// Distinguishes between new and old chunks.
490/// Note: Some of the data of the cold chunk may be incompatible with the current protocol version.
491/// Example in case of resharding, if the child shard chunk is missing in the first block.
492/// the shard_id of the old chunk may be different from the shard_id of the new chunk.
493#[derive(Clone, Debug)]
494pub enum ChunkType<'a> {
495    New(&'a ShardChunkHeader),
496    Old(&'a ShardChunkHeader),
497}
498
499/// Implements Deref for ChunkType to allow access to ShardChunkHeader methods directly.
500impl Deref for ChunkType<'_> {
501    type Target = ShardChunkHeader;
502
503    fn deref(&self) -> &Self::Target {
504        match self {
505            ChunkType::New(chunk) => chunk,
506            ChunkType::Old(chunk) => chunk,
507        }
508    }
509}
510
511impl ChunkType<'_> {
512    pub fn is_new_chunk(&self) -> bool {
513        matches!(self, ChunkType::New(_))
514    }
515}
516
517// For BlockV1, we store the chunks in a Vec, else we use a slice reference.
518enum ChunksCollection<'a> {
519    V1(Vec<ShardChunkHeader>),
520    V2(&'a [ShardChunkHeader]),
521}
522
523/// Implements Deref for ChunksCollection to allow access to [ShardChunkHeader] methods directly.
524impl Deref for ChunksCollection<'_> {
525    type Target = [ShardChunkHeader];
526
527    fn deref(&self) -> &Self::Target {
528        match self {
529            ChunksCollection::V1(chunks) => chunks.as_ref(),
530            ChunksCollection::V2(chunks) => chunks,
531        }
532    }
533}
534
535pub struct Chunks<'a> {
536    chunks: ChunksCollection<'a>,
537    block_height: BlockHeight,
538}
539
540/// Implements Deref for Chunks to allow access to [ShardChunkHeader] methods directly.
541impl Deref for Chunks<'_> {
542    type Target = [ShardChunkHeader];
543
544    fn deref(&self) -> &Self::Target {
545        &self.chunks
546    }
547}
548
549impl<'a> Chunks<'a> {
550    pub fn new(block: &'a Block) -> Self {
551        let chunks = match block {
552            Block::BlockV1(block) => ChunksCollection::V1(
553                block.chunks.iter().map(|h| ShardChunkHeader::V1(h.clone())).collect(),
554            ),
555            Block::BlockV2(block) => ChunksCollection::V2(&block.chunks),
556            Block::BlockV3(block) => ChunksCollection::V2(&block.body.chunks),
557            Block::BlockV4(block) => ChunksCollection::V2(&block.body.chunks()),
558        };
559
560        Self { chunks, block_height: block.header().height() }
561    }
562
563    pub fn from_chunk_headers(
564        chunk_headers: &'a [ShardChunkHeader],
565        block_height: BlockHeight,
566    ) -> Self {
567        Self { chunks: ChunksCollection::V2(chunk_headers), block_height }
568    }
569
570    /// Returns an iterator over all shard chunk headers, distinguishing between new and old chunks.
571    pub fn iter(&'a self) -> impl Iterator<Item = ChunkType<'a>> {
572        self.chunks.iter().map(|chunk| {
573            if chunk.is_new_chunk(self.block_height) {
574                ChunkType::New(chunk)
575            } else {
576                ChunkType::Old(chunk)
577            }
578        })
579    }
580
581    /// Returns an iterator over all shard chunk headers, regardless of whether they are new or old.
582    /// This doesn't have information about whether the chunk is new or old.
583    /// Use `iter` if you need to distinguish between new and old chunks.
584    pub fn iter_raw(&'a self) -> impl Iterator<Item = &'a ShardChunkHeader> {
585        self.chunks.iter()
586    }
587
588    /// Returns an iterator over the shard chunk headers that are old chunks.
589    pub fn iter_old(&'a self) -> impl Iterator<Item = &'a ShardChunkHeader> {
590        self.chunks.iter().filter(|chunk| !chunk.is_new_chunk(self.block_height))
591    }
592
593    /// Returns an iterator over the shard chunk headers that are new chunks.
594    pub fn iter_new(&'a self) -> impl Iterator<Item = &'a ShardChunkHeader> {
595        self.chunks.iter().filter(|chunk| chunk.is_new_chunk(self.block_height))
596    }
597
598    pub fn min_height_included(&self) -> Option<BlockHeight> {
599        self.iter().map(|chunk| chunk.height_included()).min()
600    }
601
602    pub fn block_congestion_info(&self) -> BlockCongestionInfo {
603        let mut result = BTreeMap::new();
604
605        for chunk in self.iter_raw() {
606            let shard_id = chunk.shard_id();
607
608            let congestion_info = chunk.congestion_info();
609            let height_included = chunk.height_included();
610            let height_current = self.block_height;
611            let missed_chunks_count = height_current.checked_sub(height_included);
612            let missed_chunks_count = missed_chunks_count
613                .expect("The chunk height included must be less or equal than block height!");
614
615            let extended_congestion_info =
616                ExtendedCongestionInfo::new(congestion_info, missed_chunks_count);
617            result.insert(shard_id, extended_congestion_info);
618        }
619        BlockCongestionInfo::new(result)
620    }
621
622    pub fn block_bandwidth_requests(&self) -> BlockBandwidthRequests {
623        let mut result = BTreeMap::new();
624
625        // It's okay to take bandwidth requests from a missing chunk,
626        // the chunk was missing so it didn't send anything and still
627        // wants to send out the same receipts.
628        for chunk in self.iter() {
629            let shard_id = chunk.shard_id();
630            if let Some(bandwidth_requests) = chunk.bandwidth_requests() {
631                result.insert(shard_id, bandwidth_requests.clone());
632            }
633        }
634
635        BlockBandwidthRequests { shards_bandwidth_requests: result }
636    }
637
638    // Instance methods that use self's iterator methods
639    pub fn compute_state_root(&self) -> CryptoHash {
640        merklize(&self.iter().map(|chunk| chunk.prev_state_root()).collect_vec()).0
641    }
642
643    pub fn compute_chunk_prev_outgoing_receipts_root(&self) -> CryptoHash {
644        merklize(&self.iter().map(|chunk| *chunk.prev_outgoing_receipts_root()).collect_vec()).0
645    }
646
647    pub fn compute_chunk_headers_root(&self) -> (CryptoHash, Vec<MerklePath>) {
648        merklize(
649            &self
650                .iter()
651                .map(|chunk| ChunkHashHeight(chunk.chunk_hash().clone(), chunk.height_included()))
652                .collect_vec(),
653        )
654    }
655
656    pub fn compute_chunk_tx_root(&self) -> CryptoHash {
657        merklize(&self.iter().map(|chunk| *chunk.tx_root()).collect_vec()).0
658    }
659
660    pub fn compute_outcome_root(&self) -> CryptoHash {
661        merklize(&self.iter().map(|chunk| *chunk.prev_outcome_root()).collect_vec()).0
662    }
663
664    pub fn compute_gas_used(&self) -> Gas {
665        self.iter_new()
666            .fold(Gas::ZERO, |acc, chunk| acc.checked_add(chunk.prev_gas_used()).unwrap())
667    }
668
669    pub fn compute_gas_limit(&self) -> Gas {
670        self.iter_new().fold(Gas::ZERO, |acc, chunk| acc.checked_add(chunk.gas_limit()).unwrap())
671    }
672}
673
674/// The tip of a fork. A handle to the fork ancestry from its leaf in the
675/// blockchain tree. References the max height and the latest and previous
676/// blocks for convenience
677#[derive(
678    BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize, ProtocolSchema,
679)]
680pub struct Tip {
681    /// Height of the tip (max height of the fork)
682    pub height: BlockHeight,
683    /// Last block pushed to the fork
684    pub last_block_hash: CryptoHash,
685    /// Previous block
686    pub prev_block_hash: CryptoHash,
687    /// Current epoch id. Used for getting validator info.
688    pub epoch_id: EpochId,
689    /// Next epoch id.
690    pub next_epoch_id: EpochId,
691}
692
693impl Tip {
694    /// Creates a new tip based on provided header.
695    pub fn from_header(header: &BlockHeader) -> Tip {
696        Tip {
697            height: header.height(),
698            last_block_hash: *header.hash(),
699            prev_block_hash: *header.prev_hash(),
700            epoch_id: *header.epoch_id(),
701            next_epoch_id: *header.next_epoch_id(),
702        }
703    }
704}