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 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 pub vrf_value: near_crypto::vrf::Value,
62 pub vrf_proof: near_crypto::vrf::Proof,
63}
64
65#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
67pub struct BlockV3 {
68 pub header: BlockHeader,
69 pub body: BlockBodyV1,
70}
71
72#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
74pub struct BlockV4 {
75 pub header: BlockHeader,
76 pub body: BlockBody,
77}
78
79#[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 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 #[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 core_statements: Option<Vec<SpiceCoreStatement>>,
130 ) -> Self {
131 let mut prev_validator_proposals = vec![];
134 let mut gas_used = Gas::ZERO;
135 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 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 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 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 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 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 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 pub fn check_validity(&self) -> Result<(), BlockValidityError> {
444 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 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 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 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 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#[derive(Clone, Debug)]
494pub enum ChunkType<'a> {
495 New(&'a ShardChunkHeader),
496 Old(&'a ShardChunkHeader),
497}
498
499impl 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
517enum ChunksCollection<'a> {
519 V1(Vec<ShardChunkHeader>),
520 V2(&'a [ShardChunkHeader]),
521}
522
523impl 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
540impl 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 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 pub fn iter_raw(&'a self) -> impl Iterator<Item = &'a ShardChunkHeader> {
585 self.chunks.iter()
586 }
587
588 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 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 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 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#[derive(
678 BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize, ProtocolSchema,
679)]
680pub struct Tip {
681 pub height: BlockHeight,
683 pub last_block_hash: CryptoHash,
685 pub prev_block_hash: CryptoHash,
687 pub epoch_id: EpochId,
689 pub next_epoch_id: EpochId,
691}
692
693impl Tip {
694 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}