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