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