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 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 pub vrf_value: near_crypto::vrf::Value,
64 pub vrf_proof: near_crypto::vrf::Proof,
65}
66
67#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
69pub struct BlockV3 {
70 pub header: BlockHeader,
71 pub body: BlockBodyV1,
72}
73
74#[derive(BorshSerialize, BorshDeserialize, Debug, Clone, Eq, PartialEq, ProtocolSchema)]
76pub struct BlockV4 {
77 pub header: BlockHeader,
78 pub body: BlockBody,
79}
80
81#[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 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 #[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 let mut prev_validator_proposals = vec![];
129 let mut gas_used = 0;
130 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 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 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 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 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 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 pub fn check_validity(&self) -> Result<(), BlockValidityError> {
461 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 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 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 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 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 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 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 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 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#[derive(
655 BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, serde::Serialize, ProtocolSchema,
656)]
657pub struct Tip {
658 pub height: BlockHeight,
660 pub last_block_hash: CryptoHash,
662 pub prev_block_hash: CryptoHash,
664 pub epoch_id: EpochId,
666 pub next_epoch_id: EpochId,
668}
669
670impl Tip {
671 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}