Skip to main content

ab_core_primitives/
block.rs

1//! Block-related primitives
2
3pub mod body;
4pub mod header;
5#[cfg(feature = "alloc")]
6pub mod owned;
7
8use crate::block::body::{
9    BeaconChainBody, GenericBlockBody, IntermediateShardBody, LeafShardBlocksInfo, LeafShardBody,
10};
11use crate::block::header::{
12    BeaconChainHeader, GenericBlockHeader, IntermediateShardHeader, LeafShardHeader,
13};
14#[cfg(feature = "alloc")]
15use crate::block::owned::{
16    GenericOwnedBlock, OwnedBeaconChainBlock, OwnedBlock, OwnedIntermediateShardBlock,
17    OwnedLeafShardBlock,
18};
19use crate::hashes::Blake3Hash;
20use crate::shard::{RealShardKind, ShardIndex};
21use crate::solutions::SolutionRange;
22#[cfg(feature = "serde")]
23use ::serde::{Deserialize, Serialize};
24use ab_io_type::alignment::Alignment;
25use ab_io_type::trivial_type::TrivialType;
26use core::fmt;
27use core::iter::Step;
28use derive_more::{
29    Add, AddAssign, AsMut, AsRef, Deref, DerefMut, Display, From, Into, Sub, SubAssign,
30};
31#[cfg(feature = "scale-codec")]
32use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
33use transparent_wrapper::TransparentWrapper;
34
35/// Block number
36#[derive(
37    Debug,
38    Display,
39    Default,
40    Copy,
41    Clone,
42    Ord,
43    PartialOrd,
44    Eq,
45    PartialEq,
46    Hash,
47    Add,
48    AddAssign,
49    Sub,
50    SubAssign,
51    TrivialType,
52)]
53#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
54#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
55#[cfg_attr(feature = "serde", serde(transparent))]
56#[repr(C)]
57pub struct BlockNumber(u64);
58
59impl Step for BlockNumber {
60    #[inline(always)]
61    fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
62        u64::steps_between(&start.0, &end.0)
63    }
64
65    #[inline(always)]
66    fn forward_checked(start: Self, count: usize) -> Option<Self> {
67        u64::forward_checked(start.0, count).map(Self)
68    }
69
70    #[inline(always)]
71    fn forward_overflowing(start: Self, count: usize) -> (Self, bool) {
72        let (n, overflowing) = u64::forward_overflowing(start.0, count);
73        (Self(n), overflowing)
74    }
75
76    #[inline(always)]
77    fn backward_checked(start: Self, count: usize) -> Option<Self> {
78        u64::backward_checked(start.0, count).map(Self)
79    }
80
81    #[inline(always)]
82    fn backward_overflowing(start: Self, count: usize) -> (Self, bool) {
83        let (n, overflowing) = u64::backward_overflowing(start.0, count);
84        (Self(n), overflowing)
85    }
86}
87
88const impl From<u64> for BlockNumber {
89    #[inline(always)]
90    fn from(value: u64) -> Self {
91        Self(value)
92    }
93}
94
95const impl From<BlockNumber> for u64 {
96    #[inline(always)]
97    fn from(value: BlockNumber) -> Self {
98        value.0
99    }
100}
101
102impl BlockNumber {
103    /// Size in bytes
104    pub const SIZE: usize = size_of::<u64>();
105    /// Genesis block number
106    pub const ZERO: BlockNumber = BlockNumber(0);
107    /// First block number
108    pub const ONE: BlockNumber = BlockNumber(1);
109    /// Max block number
110    pub const MAX: BlockNumber = BlockNumber(u64::MAX);
111
112    /// Create block number from bytes
113    #[inline(always)]
114    pub const fn from_bytes(bytes: [u8; Self::SIZE]) -> Self {
115        Self(u64::from_le_bytes(bytes))
116    }
117
118    /// Convert block number to bytes
119    #[inline(always)]
120    pub const fn to_bytes(self) -> [u8; Self::SIZE] {
121        self.0.to_le_bytes()
122    }
123
124    /// Checked addition, returns `None` on overflow
125    #[inline(always)]
126    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
127        if let Some(n) = self.0.checked_add(rhs.0) {
128            Some(Self(n))
129        } else {
130            None
131        }
132    }
133
134    /// Saturating addition
135    #[inline(always)]
136    pub const fn saturating_add(self, rhs: Self) -> Self {
137        Self(self.0.saturating_add(rhs.0))
138    }
139
140    /// Checked subtraction, returns `None` on underflow
141    #[inline(always)]
142    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
143        if let Some(n) = self.0.checked_sub(rhs.0) {
144            Some(Self(n))
145        } else {
146            None
147        }
148    }
149
150    /// Saturating subtraction
151    #[inline(always)]
152    pub const fn saturating_sub(self, rhs: Self) -> Self {
153        Self(self.0.saturating_sub(rhs.0))
154    }
155}
156
157/// Block timestamp as Unix time in milliseconds
158#[derive(
159    Debug,
160    Display,
161    Default,
162    Copy,
163    Clone,
164    Ord,
165    PartialOrd,
166    Eq,
167    PartialEq,
168    Hash,
169    From,
170    Into,
171    Add,
172    AddAssign,
173    Sub,
174    SubAssign,
175    TrivialType,
176)]
177#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
178#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
179#[cfg_attr(feature = "serde", serde(transparent))]
180#[repr(C)]
181pub struct BlockTimestamp(u64);
182
183impl BlockTimestamp {
184    /// Size in bytes
185    pub const SIZE: usize = size_of::<u64>();
186
187    /// Create a new instance
188    #[inline(always)]
189    pub const fn from_millis(ms: u64) -> Self {
190        Self(ms)
191    }
192
193    /// Get internal representation
194    #[inline(always)]
195    pub const fn as_millis(self) -> u64 {
196        self.0
197    }
198
199    /// Checked addition, returns `None` on overflow
200    #[inline(always)]
201    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
202        if let Some(n) = self.0.checked_add(rhs.0) {
203            Some(Self(n))
204        } else {
205            None
206        }
207    }
208
209    /// Saturating addition
210    #[inline(always)]
211    pub const fn saturating_add(self, rhs: Self) -> Self {
212        Self(self.0.saturating_add(rhs.0))
213    }
214
215    /// Checked subtraction, returns `None` on underflow
216    #[inline(always)]
217    pub const fn checked_sub(self, rhs: Self) -> Option<Self> {
218        if let Some(n) = self.0.checked_sub(rhs.0) {
219            Some(Self(n))
220        } else {
221            None
222        }
223    }
224
225    /// Saturating subtraction
226    #[inline(always)]
227    pub const fn saturating_sub(self, rhs: Self) -> Self {
228        Self(self.0.saturating_sub(rhs.0))
229    }
230}
231
232/// Block root.
233///
234/// This is typically called block hash in other blockchains, but here it represents Merkle Tree
235/// root of the header rather than a single hash of its contents.
236#[derive(
237    Debug,
238    Display,
239    Default,
240    Copy,
241    Clone,
242    Eq,
243    PartialEq,
244    Ord,
245    PartialOrd,
246    Hash,
247    From,
248    Into,
249    AsRef,
250    AsMut,
251    Deref,
252    DerefMut,
253    TrivialType,
254    TransparentWrapper,
255)]
256#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
257#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
258#[cfg_attr(feature = "serde", serde(transparent))]
259#[repr(C)]
260pub struct BlockRoot(Blake3Hash);
261
262impl AsRef<[u8]> for BlockRoot {
263    #[inline(always)]
264    fn as_ref(&self) -> &[u8] {
265        self.0.as_ref()
266    }
267}
268
269impl AsMut<[u8]> for BlockRoot {
270    #[inline(always)]
271    fn as_mut(&mut self) -> &mut [u8] {
272        self.0.as_mut()
273    }
274}
275
276impl BlockRoot {
277    /// Size in bytes
278    pub const SIZE: usize = Blake3Hash::SIZE;
279
280    /// Create a new instance
281    #[inline(always)]
282    pub const fn new(hash: Blake3Hash) -> Self {
283        Self(hash)
284    }
285
286    /// Convenient conversion from slice of underlying representation for efficiency purposes
287    #[inline(always)]
288    pub const fn slice_from_repr(value: &[[u8; Self::SIZE]]) -> &[Self] {
289        Self::wrap_slice(Blake3Hash::slice_from_repr(value))
290    }
291
292    /// Convenient conversion to slice of underlying representation for efficiency purposes
293    #[inline(always)]
294    pub const fn repr_from_slice(value: &[Self]) -> &[[u8; Self::SIZE]] {
295        Blake3Hash::repr_from_slice(Self::peel_slice(value))
296    }
297}
298
299/// Generic block.
300///
301/// Can't be implemented outside of this module, so that code generic over blocks can rely on the
302/// header corresponding to the body, like with concrete blocks.
303pub impl(self) trait GenericBlock<'a>
304where
305    Self: Clone + fmt::Debug + Send + Sync,
306{
307    /// Shard kind
308    const SHARD_KIND: RealShardKind;
309
310    /// Block header type
311    type Header: GenericBlockHeader<'a>;
312    /// Block body type
313    type Body: GenericBlockBody<'a>;
314    /// Owned block
315    #[cfg(feature = "alloc")]
316    type Owned: GenericOwnedBlock<Block<'a> = Self>
317    where
318        Self: 'a;
319
320    /// Get block header
321    fn header(&self) -> &Self::Header;
322
323    /// Get block body
324    fn body(&self) -> &Self::Body;
325
326    /// Turn into an owned version
327    #[cfg(feature = "alloc")]
328    fn to_owned(self) -> Self::Owned;
329}
330
331/// Block that corresponds to the beacon chain.
332///
333/// Fields can't be modified after creation, so that the header always corresponds to the body,
334/// which constructors ensure.
335#[derive(Debug, Clone)]
336pub struct BeaconChainBlock<'a> {
337    /// Block header
338    pub mut(self) header: BeaconChainHeader<'a>,
339    /// Block body
340    pub mut(self) body: BeaconChainBody<'a>,
341}
342
343impl<'a> BeaconChainBlock<'a> {
344    /// Try to create a new instance from provided bytes for provided shard index.
345    ///
346    /// `bytes` should be 16-bytes aligned.
347    ///
348    /// Checks internal consistency of header, body, and block, but no consensus verification is
349    /// done.
350    ///
351    /// Returns an instance and remaining bytes on success, `None` if too few bytes were given,
352    /// bytes are not properly aligned or input is otherwise invalid.
353    #[inline]
354    pub fn try_from_bytes(bytes: &'a [u8]) -> Option<(Self, &'a [u8])> {
355        if !Alignment::MAX.is_slice_aligned(bytes) {
356            return None;
357        }
358
359        let (header, remainder) = BeaconChainHeader::try_from_bytes(bytes)?;
360        let remainder = align_to_and_ensure_zero_padding(remainder, Alignment::MAX)?;
361        let (body, remainder) = BeaconChainBody::try_from_bytes(remainder)?;
362
363        let block = Self { header, body };
364
365        // Check internal consistency
366        if !block.is_internally_consistent() {
367            return None;
368        }
369
370        Some((block, remainder))
371    }
372
373    /// Check that the header corresponds to the body.
374    ///
375    /// Internal consistency of the header and body is checked by their parsers.
376    #[inline]
377    fn is_internally_consistent(&self) -> bool {
378        // Header roots of intermediate shard blocks are expensive to compute, so they are only
379        // computed once for both the header field and the body root
380        let intermediate_shard_blocks_root = self.body.intermediate_shard_blocks().headers_root();
381
382        // Intermediate shard blocks are always children of the beacon chain: header parsing ensures
383        // that their shard indices are those of intermediate shards
384        self.body
385            .root_with_intermediate_shard_blocks_root(&intermediate_shard_blocks_root)
386            == self.header.result.body_root
387            && *self.header.intermediate_shard_blocks_root() == intermediate_shard_blocks_root
388    }
389
390    /// Create an owned version of this block
391    #[cfg(feature = "alloc")]
392    #[inline(always)]
393    pub fn to_owned(self) -> OwnedBeaconChainBlock {
394        OwnedBeaconChainBlock::from_block(self)
395    }
396}
397
398impl<'a> GenericBlock<'a> for BeaconChainBlock<'a> {
399    const SHARD_KIND: RealShardKind = RealShardKind::BeaconChain;
400
401    type Header = BeaconChainHeader<'a>;
402    type Body = BeaconChainBody<'a>;
403    #[cfg(feature = "alloc")]
404    type Owned = OwnedBeaconChainBlock;
405
406    #[inline(always)]
407    fn header(&self) -> &Self::Header {
408        &self.header
409    }
410
411    #[inline(always)]
412    fn body(&self) -> &Self::Body {
413        &self.body
414    }
415
416    #[cfg(feature = "alloc")]
417    #[inline(always)]
418    fn to_owned(self) -> Self::Owned {
419        self.to_owned()
420    }
421}
422
423/// Block that corresponds to an intermediate shard.
424///
425/// Fields can't be modified after creation, so that the header always corresponds to the body,
426/// which constructors ensure.
427#[derive(Debug, Clone)]
428pub struct IntermediateShardBlock<'a> {
429    /// Block header
430    pub mut(self) header: IntermediateShardHeader<'a>,
431    /// Block body
432    pub mut(self) body: IntermediateShardBody<'a>,
433}
434
435impl<'a> IntermediateShardBlock<'a> {
436    /// Try to create a new instance from provided bytes for provided shard index.
437    ///
438    /// `bytes` should be 16-bytes aligned.
439    ///
440    /// Checks internal consistency of header, body, and block, but no consensus verification is
441    /// done.
442    ///
443    /// Returns an instance and remaining bytes on success, `None` if too few bytes were given,
444    /// bytes are not properly aligned or input is otherwise invalid.
445    #[inline]
446    pub fn try_from_bytes(bytes: &'a [u8]) -> Option<(Self, &'a [u8])> {
447        if !Alignment::MAX.is_slice_aligned(bytes) {
448            return None;
449        }
450
451        let (header, remainder) = IntermediateShardHeader::try_from_bytes(bytes)?;
452        let remainder = align_to_and_ensure_zero_padding(remainder, Alignment::MAX)?;
453        let (body, remainder) = IntermediateShardBody::try_from_bytes(remainder)?;
454
455        let block = Self { header, body };
456
457        // Check internal consistency
458        if !block.is_internally_consistent() {
459            return None;
460        }
461
462        Some((block, remainder))
463    }
464
465    /// Check that the header corresponds to the body.
466    ///
467    /// Internal consistency of the header and body is checked by their parsers.
468    #[inline]
469    fn is_internally_consistent(&self) -> bool {
470        // Header roots of leaf shard blocks are expensive to compute, so they are only computed
471        // once for both the header field and the body root
472        let leaf_shard_blocks_root = self.body.leaf_shard_blocks().headers_root();
473
474        self.body
475            .root_with_leaf_shard_blocks_root(&leaf_shard_blocks_root)
476            == self.header.result.body_root
477            && *self.header.leaf_shard_blocks_root() == leaf_shard_blocks_root
478            && Self::find_leaf_shard_block_of_another_shard(
479                self.body.leaf_shard_blocks(),
480                self.header.prefix.shard_index,
481            )
482            .is_none()
483    }
484
485    /// Find a leaf shard block that is not a child of the shard with `shard_index`.
486    ///
487    /// Returns the shard index of the first such leaf shard block. The intermediate shard block
488    /// builder uses it too, so it accepts the same leaf shard blocks as the parser.
489    #[inline]
490    fn find_leaf_shard_block_of_another_shard(
491        leaf_shard_blocks: &LeafShardBlocksInfo<'_>,
492        shard_index: ShardIndex,
493    ) -> Option<ShardIndex> {
494        leaf_shard_blocks
495            .iter()
496            .map(|leaf_shard_block| leaf_shard_block.header.prefix.shard_index)
497            .find(|leaf_shard_index| !leaf_shard_index.is_child_of(shard_index))
498    }
499
500    /// Create an owned version of this block
501    #[cfg(feature = "alloc")]
502    #[inline(always)]
503    pub fn to_owned(self) -> OwnedIntermediateShardBlock {
504        OwnedIntermediateShardBlock::from_block(self)
505    }
506}
507
508impl<'a> GenericBlock<'a> for IntermediateShardBlock<'a> {
509    const SHARD_KIND: RealShardKind = RealShardKind::IntermediateShard;
510
511    type Header = IntermediateShardHeader<'a>;
512    type Body = IntermediateShardBody<'a>;
513    #[cfg(feature = "alloc")]
514    type Owned = OwnedIntermediateShardBlock;
515
516    #[inline(always)]
517    fn header(&self) -> &Self::Header {
518        &self.header
519    }
520
521    #[inline(always)]
522    fn body(&self) -> &Self::Body {
523        &self.body
524    }
525
526    #[cfg(feature = "alloc")]
527    #[inline(always)]
528    fn to_owned(self) -> Self::Owned {
529        self.to_owned()
530    }
531}
532
533/// Block that corresponds to a leaf shard.
534///
535/// Fields can't be modified after creation, so that the header always corresponds to the body,
536/// which constructors ensure.
537#[derive(Debug, Clone)]
538pub struct LeafShardBlock<'a> {
539    /// Block header
540    pub mut(self) header: LeafShardHeader<'a>,
541    /// Block body
542    pub mut(self) body: LeafShardBody<'a>,
543}
544
545impl<'a> LeafShardBlock<'a> {
546    /// Try to create a new instance from provided bytes for provided shard index.
547    ///
548    /// `bytes` should be 16-bytes aligned.
549    ///
550    /// Checks internal consistency of header, body, and block, but no consensus verification is
551    /// done.
552    ///
553    /// Returns an instance and remaining bytes on success, `None` if too few bytes were given,
554    /// bytes are not properly aligned or input is otherwise invalid.
555    #[inline]
556    pub fn try_from_bytes(bytes: &'a [u8]) -> Option<(Self, &'a [u8])> {
557        if !Alignment::MAX.is_slice_aligned(bytes) {
558            return None;
559        }
560
561        let (header, remainder) = LeafShardHeader::try_from_bytes(bytes)?;
562        let remainder = align_to_and_ensure_zero_padding(remainder, Alignment::MAX)?;
563        let (body, remainder) = LeafShardBody::try_from_bytes(remainder)?;
564
565        let block = Self { header, body };
566
567        // Check internal consistency
568        if !block.is_internally_consistent() {
569            return None;
570        }
571
572        Some((block, remainder))
573    }
574
575    /// Check that the header corresponds to the body.
576    ///
577    /// Internal consistency of the header and body is checked by their parsers.
578    #[inline]
579    fn is_internally_consistent(&self) -> bool {
580        self.body.root() == self.header.result.body_root
581    }
582
583    /// Create an owned version of this block
584    #[cfg(feature = "alloc")]
585    #[inline(always)]
586    pub fn to_owned(self) -> OwnedLeafShardBlock {
587        OwnedLeafShardBlock::from_block(self)
588    }
589}
590
591impl<'a> GenericBlock<'a> for LeafShardBlock<'a> {
592    const SHARD_KIND: RealShardKind = RealShardKind::LeafShard;
593
594    type Header = LeafShardHeader<'a>;
595    type Body = LeafShardBody<'a>;
596    #[cfg(feature = "alloc")]
597    type Owned = OwnedLeafShardBlock;
598
599    #[inline(always)]
600    fn header(&self) -> &Self::Header {
601        &self.header
602    }
603
604    #[inline(always)]
605    fn body(&self) -> &Self::Body {
606        &self.body
607    }
608
609    #[cfg(feature = "alloc")]
610    #[inline(always)]
611    fn to_owned(self) -> Self::Owned {
612        self.to_owned()
613    }
614}
615
616/// Block that contains [`BlockHeader`] and [`BlockBody`]
617///
618/// [`BlockHeader`]: crate::block::header::BlockHeader
619/// [`BlockBody`]: crate::block::body::BlockBody
620#[derive(Debug, Clone, From)]
621pub enum Block<'a> {
622    /// Block corresponds to the beacon chain
623    BeaconChain(BeaconChainBlock<'a>),
624    /// Block corresponds to an intermediate shard
625    IntermediateShard(IntermediateShardBlock<'a>),
626    /// Block corresponds to a leaf shard
627    LeafShard(LeafShardBlock<'a>),
628}
629
630impl<'a> Block<'a> {
631    /// Try to create a new instance from provided bytes.
632    ///
633    /// `bytes` should be 16-byte aligned.
634    ///
635    /// Checks internal consistency of header, body, and block, but no consensus verification is
636    /// done.
637    ///
638    /// Returns an instance and remaining bytes on success, `None` if too few bytes were given,
639    /// bytes are not properly aligned or input is otherwise invalid.
640    #[inline]
641    pub fn try_from_bytes(bytes: &'a [u8], shard_kind: RealShardKind) -> Option<(Self, &'a [u8])> {
642        match shard_kind {
643            RealShardKind::BeaconChain => {
644                let (block_header, remainder) = BeaconChainBlock::try_from_bytes(bytes)?;
645                Some((Self::BeaconChain(block_header), remainder))
646            }
647            RealShardKind::IntermediateShard => {
648                let (block_header, remainder) = IntermediateShardBlock::try_from_bytes(bytes)?;
649                Some((Self::IntermediateShard(block_header), remainder))
650            }
651            RealShardKind::LeafShard => {
652                let (block_header, remainder) = LeafShardBlock::try_from_bytes(bytes)?;
653                Some((Self::LeafShard(block_header), remainder))
654            }
655        }
656    }
657
658    /// Create an owned version of this block
659    #[cfg(feature = "alloc")]
660    #[inline(always)]
661    pub fn to_owned(self) -> OwnedBlock {
662        match self {
663            Self::BeaconChain(block) => block.to_owned().into(),
664            Self::IntermediateShard(block) => block.to_owned().into(),
665            Self::LeafShard(block) => block.to_owned().into(),
666        }
667    }
668}
669
670/// BlockWeight type for fork choice rule.
671///
672/// The smaller the solution range is, the heavier is the block.
673#[derive(
674    Debug,
675    Display,
676    Default,
677    Copy,
678    Clone,
679    Ord,
680    PartialOrd,
681    Eq,
682    PartialEq,
683    Hash,
684    Add,
685    AddAssign,
686    Sub,
687    SubAssign,
688)]
689#[cfg_attr(feature = "scale-codec", derive(Encode, Decode, MaxEncodedLen))]
690#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
691#[repr(C)]
692pub struct BlockWeight(u128);
693
694const impl From<u128> for BlockWeight {
695    #[inline(always)]
696    fn from(value: u128) -> Self {
697        Self(value)
698    }
699}
700
701const impl From<BlockWeight> for u128 {
702    #[inline(always)]
703    fn from(value: BlockWeight) -> Self {
704        value.0
705    }
706}
707
708impl BlockWeight {
709    /// Size in bytes
710    pub const SIZE: usize = size_of::<u128>();
711    /// Zero block weight
712    pub const ZERO: BlockWeight = BlockWeight(0);
713    /// Max block wright
714    pub const MAX: BlockWeight = BlockWeight(u128::MAX);
715
716    /// Derive block weight from provided solution range
717    pub const fn from_solution_range(solution_range: SolutionRange) -> Self {
718        Self::from(u128::from(
719            u64::from(SolutionRange::MAX) - u64::from(solution_range),
720        ))
721    }
722}
723
724/// Aligns bytes to `alignment` and ensures that all padding bytes (if any) are zero.
725///
726/// Returns `None` if there are fewer bytes than padding or padding is not zero.
727fn align_to_and_ensure_zero_padding(bytes: &[u8], alignment: Alignment) -> Option<&[u8]> {
728    let padding_len = bytes.as_ptr().align_offset(usize::from(alignment.get()));
729    let (padding, remainder) = bytes.split_at_checked(padding_len)?;
730
731    // Padding must be zero
732    if padding.iter().any(|&byte| byte != 0) {
733        return None;
734    }
735
736    Some(remainder)
737}