1mod available_block_range;
2mod block_body;
3mod block_global;
4mod block_hash;
5mod block_hash_and_height;
6mod block_header;
7mod block_header_with_signatures;
8mod block_identifier;
9mod block_signatures;
10mod block_sync_status;
11mod block_v1;
12mod block_v2;
13mod block_with_signatures;
14mod chain_name_digest;
15mod era_end;
16mod finality_signature;
17mod finality_signature_id;
18mod json_compatibility;
19mod rewarded_signatures;
20mod rewards;
21#[cfg(any(all(feature = "std", feature = "testing"), test))]
22mod test_block_builder;
23
24use alloc::{boxed::Box, vec::Vec};
25use core::fmt::{self, Display, Formatter};
26use itertools::Either;
27#[cfg(feature = "json-schema")]
28use once_cell::sync::Lazy;
29#[cfg(feature = "std")]
30use std::error::Error as StdError;
31
32#[cfg(feature = "datasize")]
33use datasize::DataSize;
34#[cfg(feature = "std")]
35use num_rational::Ratio;
36
37#[cfg(feature = "json-schema")]
38use schemars::JsonSchema;
39
40#[cfg(feature = "std")]
41use crate::TransactionConfig;
42
43use crate::{
44 bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH},
45 transaction::TransactionHash,
46 Digest, EraId, ProtocolVersion, PublicKey, Timestamp,
47};
48pub use available_block_range::AvailableBlockRange;
49pub use block_body::{BlockBody, BlockBodyV1, BlockBodyV2};
50pub use block_global::{BlockGlobalAddr, BlockGlobalAddrTag};
51pub use block_hash::BlockHash;
52pub use block_hash_and_height::BlockHashAndHeight;
53pub use block_header::{BlockHeader, BlockHeaderV1, BlockHeaderV2};
54pub use block_header_with_signatures::{
55 BlockHeaderWithSignatures, BlockHeaderWithSignaturesValidationError,
56};
57pub use block_identifier::BlockIdentifier;
58pub use block_signatures::{
59 BlockSignatures, BlockSignaturesMergeError, BlockSignaturesV1, BlockSignaturesV2,
60};
61pub use block_sync_status::{BlockSyncStatus, BlockSynchronizerStatus};
62pub use block_v1::BlockV1;
63pub use block_v2::BlockV2;
64pub use block_with_signatures::BlockWithSignatures;
65pub use chain_name_digest::ChainNameDigest;
66pub use era_end::{EraEnd, EraEndV1, EraEndV2, EraReport};
67pub use finality_signature::{FinalitySignature, FinalitySignatureV1, FinalitySignatureV2};
68pub use finality_signature_id::FinalitySignatureId;
69#[cfg(all(feature = "std", feature = "json-schema"))]
70pub use json_compatibility::JsonBlockWithSignatures;
71pub use rewarded_signatures::{RewardedSignatures, SingleBlockRewardedSignatures};
72pub use rewards::Rewards;
73#[cfg(any(all(feature = "std", feature = "testing"), test))]
74pub use test_block_builder::{TestBlockBuilder, TestBlockV1Builder};
75
76#[cfg(feature = "json-schema")]
77static BLOCK: Lazy<Block> = Lazy::new(|| BlockV2::example().into());
78
79#[derive(Clone, Eq, PartialEq, Debug)]
81#[cfg_attr(any(feature = "std", test), derive(serde::Serialize))]
82#[cfg_attr(feature = "datasize", derive(DataSize))]
83#[non_exhaustive]
84pub enum BlockValidationError {
85 Bytesrepr(bytesrepr::Error),
87 UnexpectedBlockHash {
89 block: Box<Block>,
91 actual_block_hash: BlockHash,
93 },
94 UnexpectedBodyHash {
96 block: Box<Block>,
98 actual_block_body_hash: Digest,
100 },
101 IncompatibleVersions,
103}
104
105impl Display for BlockValidationError {
106 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
107 match self {
108 BlockValidationError::Bytesrepr(error) => {
109 write!(formatter, "error validating block: {}", error)
110 }
111 BlockValidationError::UnexpectedBlockHash {
112 block,
113 actual_block_hash,
114 } => {
115 write!(
116 formatter,
117 "block has incorrect block hash - actual block hash: {:?}, block: {:?}",
118 actual_block_hash, block
119 )
120 }
121 BlockValidationError::UnexpectedBodyHash {
122 block,
123 actual_block_body_hash,
124 } => {
125 write!(
126 formatter,
127 "block header has incorrect body hash - actual body hash: {:?}, block: {:?}",
128 actual_block_body_hash, block
129 )
130 }
131 BlockValidationError::IncompatibleVersions => {
132 write!(formatter, "block body and header versions do not match")
133 }
134 }
135 }
136}
137
138impl From<bytesrepr::Error> for BlockValidationError {
139 fn from(error: bytesrepr::Error) -> Self {
140 BlockValidationError::Bytesrepr(error)
141 }
142}
143
144#[cfg(feature = "std")]
145impl StdError for BlockValidationError {
146 fn source(&self) -> Option<&(dyn StdError + 'static)> {
147 match self {
148 BlockValidationError::Bytesrepr(error) => Some(error),
149 BlockValidationError::UnexpectedBlockHash { .. }
150 | BlockValidationError::UnexpectedBodyHash { .. }
151 | BlockValidationError::IncompatibleVersions => None,
152 }
153 }
154}
155
156#[derive(Clone, Copy, PartialEq, Eq, Debug)]
157pub enum BlockConversionError {
158 DifferentVersion { expected_version: u8 },
159}
160
161#[cfg(feature = "std")]
162impl Display for BlockConversionError {
163 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
164 match self {
165 BlockConversionError::DifferentVersion { expected_version } => {
166 write!(
167 f,
168 "Could not convert a block to the expected version {}",
169 expected_version
170 )
171 }
172 }
173 }
174}
175
176const TAG_LENGTH: usize = U8_SERIALIZED_LENGTH;
177
178const BLOCK_V1_TAG: u8 = 0;
180const BLOCK_V2_TAG: u8 = 1;
182
183#[cfg_attr(feature = "datasize", derive(DataSize))]
185#[cfg_attr(
186 any(feature = "std", feature = "json-schema", test),
187 derive(serde::Serialize, serde::Deserialize)
188)]
189#[derive(Clone, Debug, PartialEq, Eq)]
190#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
191pub enum Block {
192 #[cfg_attr(
194 any(feature = "std", feature = "json-schema", test),
195 serde(rename = "Version1")
196 )]
197 V1(BlockV1),
198 #[cfg_attr(
200 any(feature = "std", feature = "json-schema", test),
201 serde(rename = "Version2")
202 )]
203 V2(BlockV2),
204}
205
206impl Block {
207 #[doc(hidden)]
209 pub fn new_from_header_and_body(
210 block_header: BlockHeader,
211 block_body: BlockBody,
212 ) -> Result<Self, Box<BlockValidationError>> {
213 let hash = block_header.block_hash();
214 let block = match (block_body, block_header) {
215 (BlockBody::V1(body), BlockHeader::V1(header)) => {
216 Ok(Block::V1(BlockV1 { hash, header, body }))
217 }
218 (BlockBody::V2(body), BlockHeader::V2(header)) => {
219 Ok(Block::V2(BlockV2 { hash, header, body }))
220 }
221 _ => Err(BlockValidationError::IncompatibleVersions),
222 }?;
223
224 block.verify()?;
225 Ok(block)
226 }
227
228 pub fn clone_header(&self) -> BlockHeader {
230 match self {
231 Block::V1(v1) => BlockHeader::V1(v1.header().clone()),
232 Block::V2(v2) => BlockHeader::V2(v2.header().clone()),
233 }
234 }
235
236 pub fn take_header(self) -> BlockHeader {
238 match self {
239 Block::V1(v1) => BlockHeader::V1(v1.take_header()),
240 Block::V2(v2) => BlockHeader::V2(v2.take_header()),
241 }
242 }
243
244 pub fn timestamp(&self) -> Timestamp {
246 match self {
247 Block::V1(v1) => v1.header.timestamp(),
248 Block::V2(v2) => v2.header.timestamp(),
249 }
250 }
251
252 pub fn protocol_version(&self) -> ProtocolVersion {
254 match self {
255 Block::V1(v1) => v1.header.protocol_version(),
256 Block::V2(v2) => v2.header.protocol_version(),
257 }
258 }
259
260 pub fn hash(&self) -> &BlockHash {
262 match self {
263 Block::V1(v1) => v1.hash(),
264 Block::V2(v2) => v2.hash(),
265 }
266 }
267
268 pub fn body_hash(&self) -> &Digest {
270 match self {
271 Block::V1(v1) => v1.header().body_hash(),
272 Block::V2(v2) => v2.header().body_hash(),
273 }
274 }
275
276 pub fn random_bit(&self) -> bool {
278 match self {
279 Block::V1(v1) => v1.header().random_bit(),
280 Block::V2(v2) => v2.header().random_bit(),
281 }
282 }
283
284 pub fn accumulated_seed(&self) -> &Digest {
286 match self {
287 Block::V1(v1) => v1.accumulated_seed(),
288 Block::V2(v2) => v2.accumulated_seed(),
289 }
290 }
291
292 pub fn parent_hash(&self) -> &BlockHash {
294 match self {
295 Block::V1(v1) => v1.parent_hash(),
296 Block::V2(v2) => v2.parent_hash(),
297 }
298 }
299
300 pub fn proposer(&self) -> &PublicKey {
302 match self {
303 Block::V1(v1) => v1.proposer(),
304 Block::V2(v2) => v2.proposer(),
305 }
306 }
307
308 pub fn clone_body(&self) -> BlockBody {
310 match self {
311 Block::V1(v1) => BlockBody::V1(v1.body().clone()),
312 Block::V2(v2) => BlockBody::V2(v2.body().clone()),
313 }
314 }
315
316 pub fn take_body(self) -> BlockBody {
318 match self {
319 Block::V1(v1) => BlockBody::V1(v1.take_body()),
320 Block::V2(v2) => BlockBody::V2(v2.take_body()),
321 }
322 }
323
324 pub fn verify(&self) -> Result<(), BlockValidationError> {
326 match self {
327 Block::V1(v1) => v1.verify(),
328 Block::V2(v2) => v2.verify(),
329 }
330 }
331
332 pub fn height(&self) -> u64 {
334 match self {
335 Block::V1(v1) => v1.header.height(),
336 Block::V2(v2) => v2.header.height(),
337 }
338 }
339
340 pub fn era_id(&self) -> EraId {
342 match self {
343 Block::V1(v1) => v1.era_id(),
344 Block::V2(v2) => v2.era_id(),
345 }
346 }
347
348 pub fn clone_era_end(&self) -> Option<EraEnd> {
350 match self {
351 Block::V1(v1) => v1.header().era_end().cloned().map(EraEnd::V1),
352 Block::V2(v2) => v2.header().era_end().cloned().map(EraEnd::V2),
353 }
354 }
355
356 pub fn is_switch_block(&self) -> bool {
358 match self {
359 Block::V1(v1) => v1.header.is_switch_block(),
360 Block::V2(v2) => v2.header.is_switch_block(),
361 }
362 }
363
364 pub fn is_genesis(&self) -> bool {
366 match self {
367 Block::V1(v1) => v1.header.is_genesis(),
368 Block::V2(v2) => v2.header.is_genesis(),
369 }
370 }
371
372 pub fn state_root_hash(&self) -> &Digest {
374 match self {
375 Block::V1(v1) => v1.header.state_root_hash(),
376 Block::V2(v2) => v2.header.state_root_hash(),
377 }
378 }
379
380 pub fn rewarded_signatures(&self) -> &RewardedSignatures {
382 match self {
383 Block::V1(_v1) => &rewarded_signatures::EMPTY,
384 Block::V2(v2) => v2.body.rewarded_signatures(),
385 }
386 }
387
388 pub fn maybe_current_gas_price(&self) -> Option<u8> {
390 match self {
391 Block::V1(_) => None,
392 Block::V2(v2) => Some(v2.header().current_gas_price()),
393 }
394 }
395
396 pub fn transaction_count(&self) -> u64 {
398 match self {
399 Block::V1(block) => {
400 (block.body.deploy_hashes().len() + block.body.transfer_hashes().len()) as u64
401 }
402 Block::V2(block_v2) => block_v2.all_transactions().count() as u64,
403 }
404 }
405
406 pub fn all_transaction_hashes(&self) -> impl Iterator<Item = TransactionHash> + '_ {
408 match self {
409 Block::V1(block) => Either::Left(
410 block
411 .body
412 .deploy_and_transfer_hashes()
413 .map(TransactionHash::from),
414 ),
415 Block::V2(block_v2) => Either::Right(block_v2.all_transactions().copied()),
416 }
417 }
418
419 #[cfg(feature = "std")]
421 pub fn block_utilization(&self, transaction_config: TransactionConfig) -> u64 {
422 match self {
423 Block::V1(_) => {
424 0
426 }
427 Block::V2(block_v2) => {
428 let has_hit_slot_limt = self.has_hit_slot_capacity(transaction_config.clone());
429 let per_block_capacity = transaction_config
430 .transaction_v1_config
431 .get_max_block_count();
432
433 if has_hit_slot_limt {
434 100u64
435 } else {
436 let num = block_v2.all_transactions().count() as u64;
437 Ratio::new(num * 100, per_block_capacity).to_integer()
438 }
439 }
440 }
441 }
442
443 #[cfg(feature = "std")]
445 pub fn has_hit_slot_capacity(&self, transaction_config: TransactionConfig) -> bool {
446 match self {
447 Block::V1(_) => false,
448 Block::V2(block_v2) => {
449 let mint_count = block_v2.mint().count();
450 if mint_count as u64
451 >= transaction_config
452 .transaction_v1_config
453 .native_mint_lane
454 .max_transaction_count()
455 {
456 return true;
457 }
458
459 let auction_count = block_v2.auction().count();
460 if auction_count as u64
461 >= transaction_config
462 .transaction_v1_config
463 .native_auction_lane
464 .max_transaction_count()
465 {
466 return true;
467 }
468
469 let install_upgrade_count = block_v2.install_upgrade().count();
470 if install_upgrade_count as u64
471 >= transaction_config
472 .transaction_v1_config
473 .install_upgrade_lane
474 .max_transaction_count()
475 {
476 return true;
477 }
478
479 for (lane_id, transactions) in block_v2.body.transactions() {
480 let transaction_count = transactions.len();
481 if *lane_id < 2 {
482 continue;
483 };
484 let max_transaction_count = transaction_config
485 .transaction_v1_config
486 .get_max_transaction_count(*lane_id);
487
488 if transaction_count as u64 >= max_transaction_count {
489 return true;
490 }
491 }
492 false
493 }
494 }
495 }
496
497 #[doc(hidden)]
499 #[cfg(feature = "json-schema")]
500 pub fn example() -> &'static Self {
501 &BLOCK
502 }
503}
504
505impl Display for Block {
506 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
507 write!(
508 formatter,
509 "executed block #{}, {}, timestamp {}, {}, parent {}, post-state hash {}, body hash \
510 {}, random bit {}, protocol version: {}",
511 self.height(),
512 self.hash(),
513 self.timestamp(),
514 self.era_id(),
515 self.parent_hash().inner(),
516 self.state_root_hash(),
517 self.body_hash(),
518 self.random_bit(),
519 self.protocol_version()
520 )?;
521 if let Some(era_end) = self.clone_era_end() {
522 write!(formatter, ", era_end: {}", era_end)?;
523 }
524 Ok(())
525 }
526}
527
528impl ToBytes for Block {
529 fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
530 let mut buffer = bytesrepr::allocate_buffer(self)?;
531 match self {
532 Block::V1(v1) => {
533 buffer.insert(0, BLOCK_V1_TAG);
534 buffer.extend(v1.to_bytes()?);
535 }
536 Block::V2(v2) => {
537 buffer.insert(0, BLOCK_V2_TAG);
538 buffer.extend(v2.to_bytes()?);
539 }
540 }
541 Ok(buffer)
542 }
543
544 fn serialized_length(&self) -> usize {
545 TAG_LENGTH
546 + match self {
547 Block::V1(v1) => v1.serialized_length(),
548 Block::V2(v2) => v2.serialized_length(),
549 }
550 }
551}
552
553impl FromBytes for Block {
554 fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
555 let (tag, remainder) = u8::from_bytes(bytes)?;
556 match tag {
557 BLOCK_V1_TAG => {
558 let (body, remainder): (BlockV1, _) = FromBytes::from_bytes(remainder)?;
559 Ok((Self::V1(body), remainder))
560 }
561 BLOCK_V2_TAG => {
562 let (body, remainder): (BlockV2, _) = FromBytes::from_bytes(remainder)?;
563 Ok((Self::V2(body), remainder))
564 }
565 _ => Err(bytesrepr::Error::Formatting),
566 }
567 }
568}
569
570impl From<&BlockV2> for Block {
571 fn from(block: &BlockV2) -> Self {
572 Block::V2(block.clone())
573 }
574}
575
576impl From<BlockV2> for Block {
577 fn from(block: BlockV2) -> Self {
578 Block::V2(block)
579 }
580}
581
582impl From<&BlockV1> for Block {
583 fn from(block: &BlockV1) -> Self {
584 Block::V1(block.clone())
585 }
586}
587
588impl From<BlockV1> for Block {
589 fn from(block: BlockV1) -> Self {
590 Block::V1(block)
591 }
592}
593
594#[cfg(all(feature = "std", feature = "json-schema"))]
595impl From<JsonBlockWithSignatures> for Block {
596 fn from(block_with_signatures: JsonBlockWithSignatures) -> Self {
597 block_with_signatures.block
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use crate::{bytesrepr, testing::TestRng};
604
605 use super::*;
606
607 #[test]
608 fn bytesrepr_roundtrip() {
609 let rng = &mut TestRng::new();
610 let block_v1 = TestBlockV1Builder::new().build(rng);
611 let block = Block::V1(block_v1);
612 bytesrepr::test_serialization_roundtrip(&block);
613
614 let block_v2 = TestBlockBuilder::new().build(rng);
615 let block = Block::V2(block_v2);
616 bytesrepr::test_serialization_roundtrip(&block);
617 }
618}