Skip to main content

ab_core_primitives/block/body/
owned.rs

1//! Data structures related to the owned version of [`BlockBody`]
2
3// Slow under Miri and these features don't change what the tests exercise, so under Miri they only
4// run with just `alloc` or with all features (like when testing the whole workspace)
5#[cfg(all(
6    test,
7    any(
8        not(miri),
9        not(any(
10            feature = "ed25519",
11            feature = "parallel",
12            feature = "scale-codec",
13            feature = "serde"
14        )),
15        all(
16            feature = "ed25519",
17            feature = "parallel",
18            feature = "scale-codec",
19            feature = "serde"
20        ),
21    ),
22))]
23mod tests;
24
25use crate::block::body::{
26    BeaconChainBody, BlockBody, GenericBlockBody, IntermediateShardBlockInfo,
27    IntermediateShardBody, LeafShardBlockInfo, LeafShardBody,
28};
29use crate::block::header::owned::{
30    OwnedIntermediateShardHeader, OwnedIntermediateShardHeaderError, OwnedLeafShardHeader,
31};
32use crate::pot::PotCheckpoints;
33use crate::segments::{LocalSegmentIndex, SegmentRoot};
34use crate::shard::RealShardKind;
35use crate::transaction::Transaction;
36use crate::transaction::owned::OwnedTransaction;
37use ab_aligned_buffer::alignment::Align16;
38use ab_aligned_buffer::{OwnedAlignedBuffer, SharedAlignedBuffer};
39use ab_io_type::alignment::Alignment;
40use ab_io_type::trivial_type::TrivialType;
41use core::fmt;
42use core::iter::{Step, TrustedLen};
43use derive_more::From;
44use triomphe::Arc;
45use yoke::Yoke;
46
47/// Generic owned block body
48pub trait GenericOwnedBlockBody:
49    Clone + fmt::Debug + Send + Sync + Into<OwnedBlockBody> + 'static
50{
51    /// Shard kind
52    const SHARD_KIND: RealShardKind;
53
54    /// Block body
55    type Body<'a>: GenericBlockBody<'a>
56    where
57        Self: 'a;
58
59    /// Create an owned body from a buffer
60    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer>;
61
62    /// Inner buffer with block body contents
63    fn buffer(&self) -> &SharedAlignedBuffer;
64
65    /// Number of clones in memory
66    fn ref_count(&self) -> usize;
67
68    /// Get a regular block body out of the owned version
69    fn body(&self) -> &Self::Body<'_>;
70}
71
72/// Transaction that can be written into the body.
73///
74/// Can't be implemented outside of this module, because block body builders rely on it appending
75/// exactly one correctly encoded transaction to the buffer or leaving it unchanged.
76pub impl(self) trait WritableBodyTransaction {
77    /// Write this transaction into the body.
78    ///
79    /// Returns [`OwnedLeafShardBodyError::BlockBodyIsTooLarge`] and leaves the buffer unchanged if
80    /// the body becomes too large.
81    fn write_into(&self, buffer: &mut OwnedAlignedBuffer) -> Result<(), OwnedLeafShardBodyError>;
82}
83
84impl WritableBodyTransaction for Transaction<'_> {
85    #[inline(always)]
86    fn write_into(&self, buffer: &mut OwnedAlignedBuffer) -> Result<(), OwnedLeafShardBodyError> {
87        if buffer.append(self.bytes()) {
88            Ok(())
89        } else {
90            Err(OwnedLeafShardBodyError::BlockBodyIsTooLarge)
91        }
92    }
93}
94
95impl WritableBodyTransaction for &OwnedTransaction {
96    #[inline(always)]
97    fn write_into(&self, buffer: &mut OwnedAlignedBuffer) -> Result<(), OwnedLeafShardBodyError> {
98        if buffer.append(self.buffer().as_slice()) {
99            Ok(())
100        } else {
101            Err(OwnedLeafShardBodyError::BlockBodyIsTooLarge)
102        }
103    }
104}
105
106#[derive(Debug, Clone)]
107struct TransactionBuilder {
108    num_transactions_offset: usize,
109    buffer: OwnedAlignedBuffer,
110}
111
112impl TransactionBuilder {
113    fn new(num_transactions_offset: usize, buffer: OwnedAlignedBuffer) -> Self {
114        Self {
115            num_transactions_offset,
116            buffer,
117        }
118    }
119
120    /// Add transaction to the body.
121    ///
122    /// Returns [`OwnedLeafShardBodyError::BlockBodyIsTooLarge`] and leaves the body unchanged if it
123    /// becomes too large.
124    fn add_transaction<T>(&mut self, transaction: T) -> Result<(), OwnedLeafShardBodyError>
125    where
126        T: WritableBodyTransaction,
127    {
128        // The buffer is already aligned, because padding follows the number of transactions and
129        // each transaction
130        self.inc_transaction_count();
131
132        let old_buffer_len = self.buffer.len();
133
134        if let Err(error) = transaction.write_into(&mut self.buffer) {
135            self.dec_transaction_count();
136            return Err(error);
137        }
138
139        if !align_to_16_bytes_with_padding(&mut self.buffer) {
140            self.dec_transaction_count();
141            // SAFETY: Length was obtained from the same buffer before the last write
142            unsafe {
143                self.buffer.set_len(old_buffer_len);
144            }
145            return Err(OwnedLeafShardBodyError::BlockBodyIsTooLarge);
146        }
147
148        Ok(())
149    }
150
151    /// Finish building block body
152    #[inline(always)]
153    fn finish(self) -> OwnedAlignedBuffer {
154        self.buffer
155    }
156
157    /// Increase the number of stored transactions
158    #[inline(always)]
159    fn inc_transaction_count(&mut self) {
160        // SAFETY: Constructor ensures the offset is valid and has space for `u32` (but not
161        // necessarily aligned)
162        unsafe {
163            let num_transactions_ptr = self
164                .buffer
165                .as_mut_ptr()
166                .byte_add(self.num_transactions_offset)
167                .cast::<u32>();
168            let num_transactions = num_transactions_ptr.read_unaligned();
169            let num_transactions = num_transactions.checked_add(1).expect(
170                "Each transaction takes at least `TransactionHeader::SIZE` bytes of the buffer, \
171                which can't be longer than `u32::MAX` bytes; qed",
172            );
173            num_transactions_ptr.write_unaligned(num_transactions);
174        }
175    }
176
177    /// Decrease the number of stored transactions
178    #[inline(always)]
179    fn dec_transaction_count(&mut self) {
180        // SAFETY: Constructor ensures the offset is valid and has space for `u32` (but not
181        // necessarily aligned)
182        unsafe {
183            let num_transactions_ptr = self
184                .buffer
185                .as_mut_ptr()
186                .byte_add(self.num_transactions_offset)
187                .cast::<u32>();
188            let num_transactions = num_transactions_ptr.read_unaligned();
189            let num_transactions = num_transactions.saturating_sub(1);
190            num_transactions_ptr.write_unaligned(num_transactions);
191        }
192    }
193}
194
195/// Errors for [`OwnedBeaconChainBody`]
196#[derive(Debug, thiserror::Error)]
197pub enum OwnedBeaconChainBodyError {
198    /// Too many PoT checkpoints
199    #[error("Too many PoT checkpoints: {actual}")]
200    TooManyPotCheckpoints {
201        /// Actual number of PoT checkpoints
202        actual: usize,
203    },
204    /// Too many own segment roots
205    #[error("Too many own segment roots: {actual}")]
206    TooManyOwnSegmentRoots {
207        /// Actual number of own segment roots
208        actual: usize,
209    },
210    /// Local segment index after the last own segment root doesn't fit into `u64`
211    #[error("Own segment index overflow, first local segment index: {first_local_segment_index}")]
212    OwnSegmentIndexOverflow {
213        /// Local segment index of the first own segment root
214        first_local_segment_index: LocalSegmentIndex,
215    },
216    /// Local segment indices of own segment roots are not consecutive
217    #[error(
218        "Own segment indices are not consecutive: {local_segment_index} after \
219        {previous_local_segment_index}"
220    )]
221    NonConsecutiveOwnSegmentIndices {
222        /// Local segment index of the previous own segment root
223        previous_local_segment_index: LocalSegmentIndex,
224        /// Local segment index that doesn't follow the previous one
225        local_segment_index: LocalSegmentIndex,
226    },
227    /// Too many intermediate shard blocks
228    #[error("Too many intermediate shard blocks: {actual}")]
229    TooManyIntermediateShardBlocks {
230        /// Actual number of intermediate shard blocks
231        actual: usize,
232    },
233    /// Block body is too large
234    #[error("Block body is too large")]
235    BlockBodyIsTooLarge,
236    /// Intermediate shard blocks are inconsistent, see
237    /// [`IntermediateShardBlocksInfo::try_from_bytes()`]
238    ///
239    /// [`IntermediateShardBlocksInfo::try_from_bytes()`]:
240    /// crate::block::body::IntermediateShardBlocksInfo::try_from_bytes
241    #[error("Intermediate shard blocks are inconsistent")]
242    InconsistentIntermediateShardBlocks,
243}
244
245/// An owned version of [`BeaconChainBody`].
246///
247/// It is correctly aligned in memory and well suited for sending and receiving over the network
248/// efficiently or storing in memory or on disk.
249#[derive(Debug, Clone)]
250pub struct OwnedBeaconChainBody {
251    inner: Arc<Yoke<BeaconChainBody<'static>, SharedAlignedBuffer>>,
252}
253
254impl GenericOwnedBlockBody for OwnedBeaconChainBody {
255    const SHARD_KIND: RealShardKind = RealShardKind::BeaconChain;
256
257    type Body<'a> = BeaconChainBody<'a>;
258
259    #[inline(always)]
260    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
261        Self::from_buffer(buffer)
262    }
263
264    #[inline(always)]
265    fn buffer(&self) -> &SharedAlignedBuffer {
266        self.buffer()
267    }
268
269    #[inline(always)]
270    fn ref_count(&self) -> usize {
271        self.ref_count()
272    }
273
274    #[inline(always)]
275    fn body(&self) -> &Self::Body<'_> {
276        self.body()
277    }
278}
279
280impl OwnedBeaconChainBody {
281    /// Create a new instance
282    pub fn new<'a, OS, ISB>(
283        own_segments: OS,
284        intermediate_shard_blocks: ISB,
285        pot_checkpoints: &[PotCheckpoints],
286    ) -> Result<Self, OwnedBeaconChainBodyError>
287    where
288        OS: TrustedLen<Item = (LocalSegmentIndex, SegmentRoot)>,
289        // TODO: This is probably the wrong type to use here, especially because it doesn't have a
290        //  public constructor right now
291        ISB: TrustedLen<Item = IntermediateShardBlockInfo<'a>> + 'a,
292    {
293        let num_pot_checkpoints = pot_checkpoints.len();
294        let num_pot_checkpoints = u32::try_from(num_pot_checkpoints).map_err(|_error| {
295            OwnedBeaconChainBodyError::TooManyPotCheckpoints {
296                actual: num_pot_checkpoints,
297            }
298        })?;
299        let num_own_segment_roots = own_segments.size_hint().0;
300        let num_own_segment_roots = u8::try_from(num_own_segment_roots).map_err(|_error| {
301            OwnedBeaconChainBodyError::TooManyOwnSegmentRoots {
302                actual: num_own_segment_roots,
303            }
304        })?;
305        let num_blocks = intermediate_shard_blocks.size_hint().0;
306        let num_blocks = u16::try_from(num_blocks).map_err(|_error| {
307            OwnedBeaconChainBodyError::TooManyIntermediateShardBlocks { actual: num_blocks }
308        })?;
309
310        // A body can't hold more than `u32::MAX` bytes of PoT checkpoints
311        let pot_checkpoints_size = num_pot_checkpoints
312            .checked_mul(PotCheckpoints::SIZE as u32)
313            .ok_or(OwnedBeaconChainBodyError::BlockBodyIsTooLarge)?;
314        let fixed_size = (
315            // Number of PoT checkpoints
316            u32::SIZE
317                // Number of own segment roots
318                + u8::SIZE
319                // Local segment index of the first own segment root
320                + if num_own_segment_roots > 0 { LocalSegmentIndex::SIZE } else { 0 }
321                // Own segment roots
322                + u32::from(num_own_segment_roots) * SegmentRoot::SIZE as u32
323                // Number of intermediate shard blocks
324                + u16::SIZE
325                // Numbers of own segment roots, leaf shard blocks and leaf shard blocks with
326                // segments of each block
327                + u32::from(num_blocks) * (u8::SIZE + 2 * u16::SIZE)
328        )
329            // Padding to 8-bytes boundary
330            .next_multiple_of(u32::from(Alignment::of::<u64>().get()));
331        // Everything except intermediate shard blocks is known upfront, so a body that doesn't fit
332        // into a buffer even without them is rejected before anything is allocated or copied
333        #[cfg_attr(
334            target_pointer_width = "64",
335            expect(
336                clippy::absurd_extreme_comparisons,
337                reason = "`MAX_CAPACITY` is `u32::MAX` where `usize` is 64-bit"
338            )
339        )]
340        let min_size = fixed_size
341            .checked_add(pot_checkpoints_size)
342            .filter(|&size| size <= OwnedAlignedBuffer::<Align16>::MAX_CAPACITY)
343            .ok_or(OwnedBeaconChainBodyError::BlockBodyIsTooLarge)?;
344        // This is only an estimate of intermediate shard blocks to get in the ballpark where
345        // reallocation should not be necessary in many cases
346        let intermediate_shard_blocks_size =
347            u32::from(num_blocks) * OwnedIntermediateShardHeader::MAX_ALLOCATION * 2;
348        // The estimate can make the capacity larger than any body can be
349        #[cfg_attr(
350            target_pointer_width = "64",
351            expect(
352                clippy::unnecessary_min_or_max,
353                reason = "`MAX_CAPACITY` is `u32::MAX` where `usize` is 64-bit"
354            )
355        )]
356        let mut buffer = OwnedAlignedBuffer::with_capacity(
357            min_size
358                .saturating_add(intermediate_shard_blocks_size)
359                .min(OwnedAlignedBuffer::<Align16>::MAX_CAPACITY),
360        );
361
362        let true = buffer.append(&num_pot_checkpoints.to_le_bytes()) else {
363            unreachable!("Fixed size data structures that are guaranteed to fit; qed");
364        };
365
366        let true = buffer.append(&[num_own_segment_roots]) else {
367            unreachable!("Fixed size data structures that are guaranteed to fit; qed");
368        };
369        let mut own_segments = own_segments.peekable();
370        if let Some(&(first_local_segment_index, _own_segment_root)) = own_segments.peek() {
371            // The parser rejects bodies where the index after the last segment root doesn't fit
372            // into `u64`
373            if LocalSegmentIndex::forward_checked(
374                first_local_segment_index,
375                usize::from(num_own_segment_roots),
376            )
377            .is_none()
378            {
379                return Err(OwnedBeaconChainBodyError::OwnSegmentIndexOverflow {
380                    first_local_segment_index,
381                });
382            }
383            let true = buffer.append(first_local_segment_index.as_bytes()) else {
384                unreachable!("Checked size above; qed");
385            };
386        }
387        let mut previous_local_segment_index = None;
388        for (local_segment_index, own_segment_root) in own_segments {
389            // Only the first local segment index is stored, the rest must follow it one by one
390            if let Some(previous_local_segment_index) = previous_local_segment_index
391                && LocalSegmentIndex::forward_checked(previous_local_segment_index, 1)
392                    != Some(local_segment_index)
393            {
394                return Err(OwnedBeaconChainBodyError::NonConsecutiveOwnSegmentIndices {
395                    previous_local_segment_index,
396                    local_segment_index,
397                });
398            }
399            previous_local_segment_index = Some(local_segment_index);
400
401            let true = buffer.append(own_segment_root.as_ref()) else {
402                unreachable!("Checked size above; qed");
403            };
404        }
405        // TODO: Would be nice for `IntermediateShardBlocksInfo` to have API to write this by itself
406        {
407            let true = buffer.append(&num_blocks.to_le_bytes()) else {
408                unreachable!("Fixed size data structures that are guaranteed to fit; qed");
409            };
410            let mut segments_roots_num_cursor = buffer.len() as usize;
411            for _ in 0..num_blocks {
412                // Placeholder values for the number of own segment roots, number of leaf shard
413                // blocks and number of leaf shard blocks with segments
414                let true = buffer.append(&[0; 5]) else {
415                    unreachable!("Checked size above; qed");
416                };
417            }
418            let true = align_to_8_with_padding(&mut buffer) else {
419                unreachable!("Checked size above; qed");
420            };
421            for intermediate_shard_block in intermediate_shard_blocks {
422                let num_own_segment_roots = intermediate_shard_block
423                    .own_segments
424                    .as_ref()
425                    .map_or_default(|own_segments| own_segments.num_segment_roots().get());
426                let num_leaf_shard_blocks_with_segments =
427                    intermediate_shard_block.num_leaf_shard_blocks_with_segments();
428
429                buffer.as_mut_slice()[segments_roots_num_cursor] = num_own_segment_roots;
430                buffer.as_mut_slice()[segments_roots_num_cursor + 1..][..2].copy_from_slice(
431                    &intermediate_shard_block
432                        .num_leaf_shard_blocks()
433                        .to_le_bytes(),
434                );
435                buffer.as_mut_slice()[segments_roots_num_cursor + 3..][..2]
436                    .copy_from_slice(&num_leaf_shard_blocks_with_segments.to_le_bytes());
437                segments_roots_num_cursor += 5;
438
439                OwnedIntermediateShardHeader::from_parts_into(
440                    intermediate_shard_block.header.prefix,
441                    intermediate_shard_block.header.result,
442                    intermediate_shard_block.header.consensus_info,
443                    intermediate_shard_block.header.beacon_chain_info(),
444                    intermediate_shard_block.header.leaf_shard_blocks_root(),
445                    &mut buffer,
446                )
447                .map_err(|error| match error {
448                    OwnedIntermediateShardHeaderError::HeaderTooLarge => {
449                        OwnedBeaconChainBodyError::BlockBodyIsTooLarge
450                    }
451                    OwnedIntermediateShardHeaderError::InvalidParts { error: _ } => {
452                        unreachable!("Parts of a parsed header are consistent; qed")
453                    }
454                })?
455                .with_seal(intermediate_shard_block.header.seal)
456                .expect("Seal of a parsed header matches its solution; qed");
457
458                if let Some(own_segments) = &intermediate_shard_block.own_segments {
459                    if !buffer.append(own_segments.first_local_segment_index.as_bytes()) {
460                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
461                    }
462                    if !buffer.append(SegmentRoot::slice_as_bytes(own_segments.segment_roots)) {
463                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
464                    }
465                }
466
467                for (_position, _shard_index, own_segments) in
468                    intermediate_shard_block.leaf_shards_segments()
469                {
470                    if !buffer.append(&[own_segments.num_segment_roots().get()]) {
471                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
472                    }
473                }
474                for (position, shard_index, own_segments) in
475                    intermediate_shard_block.leaf_shards_segments()
476                {
477                    if !buffer.append(&position.to_le_bytes()) {
478                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
479                    }
480                    if !buffer.append(shard_index.as_bytes()) {
481                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
482                    }
483                    if !buffer.append(own_segments.first_local_segment_index.as_bytes()) {
484                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
485                    }
486                    if !buffer.append(SegmentRoot::slice_as_bytes(own_segments.segment_roots)) {
487                        return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
488                    }
489                }
490
491                if !align_to_8_with_padding(&mut buffer) {
492                    return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
493                }
494            }
495        }
496
497        let true = buffer.append(PotCheckpoints::slice_as_bytes(pot_checkpoints)) else {
498            return Err(OwnedBeaconChainBodyError::BlockBodyIsTooLarge);
499        };
500
501        Self::from_buffer(buffer.into_shared()).map_err(|_buffer| {
502            // Everything else is written the way the parser reads it, only contents of
503            // intermediate shard blocks can be rejected
504            OwnedBeaconChainBodyError::InconsistentIntermediateShardBlocks
505        })
506    }
507
508    /// Create an owned body from a buffer
509    #[inline]
510    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
511        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
512        //  https://github.com/unicode-org/icu4x/issues/6665
513        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
514            let Some((body, extra_bytes)) = BeaconChainBody::try_from_bytes(buffer) else {
515                return Err(());
516            };
517            if !extra_bytes.is_empty() {
518                return Err(());
519            }
520
521            Ok(body)
522        })
523        .map_err(move |()| buffer)?;
524
525        Ok(Self {
526            inner: Arc::new(inner),
527        })
528    }
529
530    /// Inner buffer with block body contents
531    #[inline(always)]
532    pub fn buffer(&self) -> &SharedAlignedBuffer {
533        self.inner.backing_cart()
534    }
535
536    /// Number of clones in memory
537    #[inline(always)]
538    pub fn ref_count(&self) -> usize {
539        Arc::count(&self.inner)
540    }
541
542    /// Get [`BeaconChainBody`] out of [`OwnedBeaconChainBody`]
543    #[inline(always)]
544    pub fn body(&self) -> &BeaconChainBody<'_> {
545        self.inner.get()
546    }
547}
548
549/// Errors for [`OwnedIntermediateShardBody`]
550#[derive(Debug, thiserror::Error)]
551pub enum OwnedIntermediateShardBodyError {
552    /// Too many own segment roots
553    #[error("Too many own segment roots: {actual}")]
554    TooManyOwnSegmentRoots {
555        /// Actual number of own segment roots
556        actual: usize,
557    },
558    /// Local segment index after the last own segment root doesn't fit into `u64`
559    #[error("Own segment index overflow, first local segment index: {first_local_segment_index}")]
560    OwnSegmentIndexOverflow {
561        /// Local segment index of the first own segment root
562        first_local_segment_index: LocalSegmentIndex,
563    },
564    /// Local segment indices of own segment roots are not consecutive
565    #[error(
566        "Own segment indices are not consecutive: {local_segment_index} after \
567        {previous_local_segment_index}"
568    )]
569    NonConsecutiveOwnSegmentIndices {
570        /// Local segment index of the previous own segment root
571        previous_local_segment_index: LocalSegmentIndex,
572        /// Local segment index that doesn't follow the previous one
573        local_segment_index: LocalSegmentIndex,
574    },
575    /// Too many leaf shard blocks
576    #[error("Too many leaf shard blocks: {actual}")]
577    TooManyLeafShardBlocks {
578        /// Actual number of leaf shard blocks
579        actual: usize,
580    },
581    /// Leaf shard blocks are inconsistent, see [`LeafShardBlocksInfo::try_from_bytes()`]
582    ///
583    /// [`LeafShardBlocksInfo::try_from_bytes()`]:
584    /// crate::block::body::LeafShardBlocksInfo::try_from_bytes
585    #[error("Leaf shard blocks are inconsistent")]
586    InconsistentLeafShardBlocks,
587}
588
589/// An owned version of [`IntermediateShardBody`].
590///
591/// It is correctly aligned in memory and well suited for sending and receiving over the network
592/// efficiently or storing in memory or on disk.
593#[derive(Debug, Clone)]
594pub struct OwnedIntermediateShardBody {
595    inner: Arc<Yoke<IntermediateShardBody<'static>, SharedAlignedBuffer>>,
596}
597
598impl GenericOwnedBlockBody for OwnedIntermediateShardBody {
599    const SHARD_KIND: RealShardKind = RealShardKind::IntermediateShard;
600
601    type Body<'a> = IntermediateShardBody<'a>;
602
603    #[inline(always)]
604    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
605        Self::from_buffer(buffer)
606    }
607
608    #[inline(always)]
609    fn buffer(&self) -> &SharedAlignedBuffer {
610        self.buffer()
611    }
612
613    #[inline(always)]
614    fn ref_count(&self) -> usize {
615        self.ref_count()
616    }
617
618    #[inline(always)]
619    fn body(&self) -> &Self::Body<'_> {
620        self.body()
621    }
622}
623
624impl OwnedIntermediateShardBody {
625    /// Create a new instance
626    pub fn new<'a, OS, LSB>(
627        own_segments: OS,
628        leaf_shard_blocks: LSB,
629    ) -> Result<Self, OwnedIntermediateShardBodyError>
630    where
631        OS: TrustedLen<Item = (LocalSegmentIndex, SegmentRoot)>,
632        LSB: TrustedLen<Item = LeafShardBlockInfo<'a>> + 'a,
633    {
634        let num_own_segment_roots = own_segments.size_hint().0;
635        let num_own_segment_roots = u8::try_from(num_own_segment_roots).map_err(|_error| {
636            OwnedIntermediateShardBodyError::TooManyOwnSegmentRoots {
637                actual: num_own_segment_roots,
638            }
639        })?;
640        let num_blocks = leaf_shard_blocks.size_hint().0;
641        let num_blocks = u16::try_from(num_blocks).map_err(|_error| {
642            OwnedIntermediateShardBodyError::TooManyLeafShardBlocks { actual: num_blocks }
643        })?;
644
645        let mut buffer = OwnedAlignedBuffer::with_capacity(
646            (
647                // Number of own segment roots
648                u8::SIZE
649                    // Local segment index of the first own segment root
650                    + if num_own_segment_roots > 0 { LocalSegmentIndex::SIZE } else { 0 }
651                    // Own segment roots
652                    + u32::from(num_own_segment_roots) * SegmentRoot::SIZE as u32
653                    // Number of leaf shard blocks
654                    + u16::SIZE
655                    // Numbers of own segment roots of each block
656                    + u32::from(num_blocks) * u8::SIZE
657            )
658            // Padding to 8-bytes boundary
659            .next_multiple_of(u32::from(Alignment::of::<u64>().get()))
660                // This is only an estimate of leaf shard blocks to get in the ballpark where
661                // reallocation should not be necessary
662                + u32::from(num_blocks) * OwnedLeafShardHeader::MAX_ALLOCATION * 2,
663        );
664
665        let true = buffer.append(&[num_own_segment_roots]) else {
666            unreachable!("Fixed size data structures that are guaranteed to fit; qed");
667        };
668        let mut own_segments = own_segments.peekable();
669        if let Some(&(first_local_segment_index, _own_segment_root)) = own_segments.peek() {
670            // The parser rejects bodies where the index after the last segment root doesn't fit
671            // into `u64`
672            if LocalSegmentIndex::forward_checked(
673                first_local_segment_index,
674                usize::from(num_own_segment_roots),
675            )
676            .is_none()
677            {
678                return Err(OwnedIntermediateShardBodyError::OwnSegmentIndexOverflow {
679                    first_local_segment_index,
680                });
681            }
682            let true = buffer.append(first_local_segment_index.as_bytes()) else {
683                unreachable!("Checked size above; qed");
684            };
685        }
686        let mut previous_local_segment_index = None;
687        for (local_segment_index, own_segment_root) in own_segments {
688            // Only the first local segment index is stored, the rest must follow it one by one
689            if let Some(previous_local_segment_index) = previous_local_segment_index
690                && LocalSegmentIndex::forward_checked(previous_local_segment_index, 1)
691                    != Some(local_segment_index)
692            {
693                return Err(
694                    OwnedIntermediateShardBodyError::NonConsecutiveOwnSegmentIndices {
695                        previous_local_segment_index,
696                        local_segment_index,
697                    },
698                );
699            }
700            previous_local_segment_index = Some(local_segment_index);
701
702            let true = buffer.append(own_segment_root.as_ref()) else {
703                unreachable!("Checked size above; qed");
704            };
705        }
706        // TODO: Would be nice for `LeafShardBlocksInfo` to have API to write this by itself
707        {
708            let true = buffer.append(&num_blocks.to_le_bytes()) else {
709                unreachable!("Fixed size data structures that are guaranteed to fit; qed");
710            };
711            let mut own_segments_roots_num_cursor = buffer.len() as usize;
712            for _ in 0..num_blocks {
713                let true = buffer.append(&[0]) else {
714                    unreachable!("Checked size above; qed");
715                };
716            }
717            let true = align_to_8_with_padding(&mut buffer) else {
718                unreachable!("Checked size above; qed");
719            };
720            for leaf_shard_block in leaf_shard_blocks {
721                if let Some(own_segments) = &leaf_shard_block.own_segments {
722                    buffer.as_mut_slice()[own_segments_roots_num_cursor] =
723                        own_segments.num_segment_roots().get();
724                }
725                own_segments_roots_num_cursor += 1;
726
727                OwnedLeafShardHeader::from_parts_into(
728                    leaf_shard_block.header.prefix,
729                    leaf_shard_block.header.result,
730                    leaf_shard_block.header.consensus_info,
731                    leaf_shard_block.header.beacon_chain_info(),
732                    &mut buffer,
733                )
734                .expect(
735                    "Parts of a parsed header are consistent, and bodies with at most `u16::MAX` \
736                    leaf shard blocks with at most `u8::MAX` segment roots each are smaller than \
737                    1 GiB, which fits into a buffer even where `usize` is 32-bit; qed",
738                )
739                .with_seal(leaf_shard_block.header.seal)
740                .expect("Seal of a parsed header matches its solution; qed");
741                let true = align_to_8_with_padding(&mut buffer) else {
742                    unreachable!("Checked size above; qed");
743                };
744
745                let true = buffer.append(leaf_shard_block.transactions_root.as_bytes()) else {
746                    unreachable!("Checked size above; qed");
747                };
748                if let Some(own_segments) = &leaf_shard_block.own_segments {
749                    let true = buffer.append(own_segments.first_local_segment_index.as_bytes())
750                    else {
751                        unreachable!("Checked size above; qed");
752                    };
753                    let true =
754                        buffer.append(SegmentRoot::slice_as_bytes(own_segments.segment_roots))
755                    else {
756                        unreachable!("Checked size above; qed");
757                    };
758                }
759            }
760        }
761
762        Self::from_buffer(buffer.into_shared()).map_err(|_buffer| {
763            // Everything else is written the way the parser reads it, only contents of leaf shard
764            // blocks can be rejected
765            OwnedIntermediateShardBodyError::InconsistentLeafShardBlocks
766        })
767    }
768
769    /// Create an owned body from a buffer
770    #[inline]
771    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
772        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
773        //  https://github.com/unicode-org/icu4x/issues/6665
774        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
775            let Some((body, extra_bytes)) = IntermediateShardBody::try_from_bytes(buffer) else {
776                return Err(());
777            };
778            if !extra_bytes.is_empty() {
779                return Err(());
780            }
781
782            Ok(body)
783        })
784        .map_err(move |()| buffer)?;
785
786        Ok(Self {
787            inner: Arc::new(inner),
788        })
789    }
790
791    /// Inner buffer with block body contents
792    #[inline(always)]
793    pub fn buffer(&self) -> &SharedAlignedBuffer {
794        self.inner.backing_cart()
795    }
796
797    /// Number of clones in memory
798    #[inline(always)]
799    pub fn ref_count(&self) -> usize {
800        Arc::count(&self.inner)
801    }
802
803    /// Get [`IntermediateShardBody`] out of [`OwnedIntermediateShardBody`]
804    #[inline(always)]
805    pub fn body(&self) -> &IntermediateShardBody<'_> {
806        self.inner.get()
807    }
808}
809
810/// Errors for [`OwnedLeafShardBody`]
811#[derive(Debug, thiserror::Error)]
812pub enum OwnedLeafShardBodyError {
813    /// Too many own segment roots
814    #[error("Too many own segment roots: {actual}")]
815    TooManyOwnSegmentRoots {
816        /// Actual number of own segment roots
817        actual: usize,
818    },
819    /// Local segment index after the last own segment root doesn't fit into `u64`
820    #[error("Own segment index overflow, first local segment index: {first_local_segment_index}")]
821    OwnSegmentIndexOverflow {
822        /// Local segment index of the first own segment root
823        first_local_segment_index: LocalSegmentIndex,
824    },
825    /// Local segment indices of own segment roots are not consecutive
826    #[error(
827        "Own segment indices are not consecutive: {local_segment_index} after \
828        {previous_local_segment_index}"
829    )]
830    NonConsecutiveOwnSegmentIndices {
831        /// Local segment index of the previous own segment root
832        previous_local_segment_index: LocalSegmentIndex,
833        /// Local segment index that doesn't follow the previous one
834        local_segment_index: LocalSegmentIndex,
835    },
836    /// Block body is too large
837    #[error("Block body is too large")]
838    BlockBodyIsTooLarge,
839}
840
841/// An owned version of [`LeafShardBody`].
842///
843/// It is correctly aligned in memory and well suited for sending and receiving over the network
844/// efficiently or storing in memory or on disk.
845#[derive(Debug, Clone)]
846pub struct OwnedLeafShardBody {
847    inner: Arc<Yoke<LeafShardBody<'static>, SharedAlignedBuffer>>,
848}
849
850impl GenericOwnedBlockBody for OwnedLeafShardBody {
851    const SHARD_KIND: RealShardKind = RealShardKind::LeafShard;
852
853    type Body<'a> = LeafShardBody<'a>;
854
855    #[inline(always)]
856    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
857        Self::from_buffer(buffer)
858    }
859
860    #[inline(always)]
861    fn buffer(&self) -> &SharedAlignedBuffer {
862        self.buffer()
863    }
864
865    #[inline(always)]
866    fn ref_count(&self) -> usize {
867        self.ref_count()
868    }
869
870    #[inline(always)]
871    fn body(&self) -> &Self::Body<'_> {
872        self.body()
873    }
874}
875
876impl OwnedLeafShardBody {
877    /// Initialize building of [`OwnedLeafShardBody`]
878    pub fn init<OS>(
879        own_segments: OS,
880    ) -> Result<OwnedLeafShardBlockBodyBuilder, OwnedLeafShardBodyError>
881    where
882        OS: TrustedLen<Item = (LocalSegmentIndex, SegmentRoot)>,
883    {
884        let num_own_segment_roots = own_segments.size_hint().0;
885        let num_own_segment_roots = u8::try_from(num_own_segment_roots).map_err(|_error| {
886            OwnedLeafShardBodyError::TooManyOwnSegmentRoots {
887                actual: num_own_segment_roots,
888            }
889        })?;
890
891        // Transactions are not known yet, the buffer grows when they are added
892        let mut buffer = OwnedAlignedBuffer::with_capacity(
893            (
894                // Number of own segment roots
895                u8::SIZE
896                    // Local segment index of the first own segment root
897                    + if num_own_segment_roots > 0 {
898                        LocalSegmentIndex::SIZE
899                    } else {
900                        0
901                    }
902                    // Own segment roots
903                    + u32::from(num_own_segment_roots) * SegmentRoot::SIZE as u32
904                    // Number of transactions
905                    + u32::SIZE
906            )
907                // Padding to 16-bytes boundary
908                .next_multiple_of(u32::from(Alignment::MAX.get())),
909        );
910
911        let true = buffer.append(&[num_own_segment_roots]) else {
912            unreachable!("Fixed size data structures that are guaranteed to fit; qed");
913        };
914        let mut own_segments = own_segments.peekable();
915        if let Some(&(first_local_segment_index, _own_segment_root)) = own_segments.peek() {
916            // The parser rejects bodies where the index after the last segment root doesn't fit
917            // into `u64`
918            if LocalSegmentIndex::forward_checked(
919                first_local_segment_index,
920                usize::from(num_own_segment_roots),
921            )
922            .is_none()
923            {
924                return Err(OwnedLeafShardBodyError::OwnSegmentIndexOverflow {
925                    first_local_segment_index,
926                });
927            }
928            let true = buffer.append(first_local_segment_index.as_bytes()) else {
929                unreachable!("Checked size above; qed");
930            };
931        }
932        let mut previous_local_segment_index = None;
933        for (local_segment_index, own_segment_root) in own_segments {
934            // Only the first local segment index is stored, the rest must follow it one by one
935            if let Some(previous_local_segment_index) = previous_local_segment_index
936                && LocalSegmentIndex::forward_checked(previous_local_segment_index, 1)
937                    != Some(local_segment_index)
938            {
939                return Err(OwnedLeafShardBodyError::NonConsecutiveOwnSegmentIndices {
940                    previous_local_segment_index,
941                    local_segment_index,
942                });
943            }
944            previous_local_segment_index = Some(local_segment_index);
945
946            let true = buffer.append(own_segment_root.as_ref()) else {
947                unreachable!("Checked size above; qed");
948            };
949        }
950
951        let num_transactions_offset = buffer.len() as usize;
952        let true = buffer.append(&0u32.to_le_bytes()) else {
953            unreachable!("Checked size above; qed");
954        };
955        // The parser expects padding after the number of transactions even if there are none
956        let true = align_to_16_bytes_with_padding(&mut buffer) else {
957            unreachable!("Checked size above; qed");
958        };
959
960        Ok(OwnedLeafShardBlockBodyBuilder {
961            transaction_builder: TransactionBuilder::new(num_transactions_offset, buffer),
962        })
963    }
964
965    /// Create an owned body from a buffer
966    #[inline]
967    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
968        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
969        //  https://github.com/unicode-org/icu4x/issues/6665
970        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
971            let Some((body, extra_bytes)) = LeafShardBody::try_from_bytes(buffer) else {
972                return Err(());
973            };
974            if !extra_bytes.is_empty() {
975                return Err(());
976            }
977
978            Ok(body)
979        })
980        .map_err(move |()| buffer)?;
981
982        Ok(Self {
983            inner: Arc::new(inner),
984        })
985    }
986
987    /// Inner buffer with block body contents
988    #[inline(always)]
989    pub fn buffer(&self) -> &SharedAlignedBuffer {
990        self.inner.backing_cart()
991    }
992
993    /// Number of clones in memory
994    #[inline(always)]
995    pub fn ref_count(&self) -> usize {
996        Arc::count(&self.inner)
997    }
998
999    /// Get [`LeafShardBody`] out of [`OwnedLeafShardBody`]
1000    #[inline(always)]
1001    pub fn body(&self) -> &LeafShardBody<'_> {
1002        self.inner.get()
1003    }
1004}
1005
1006/// Builder for [`OwnedLeafShardBody`] that allows to add more transactions
1007#[derive(Debug, Clone)]
1008pub struct OwnedLeafShardBlockBodyBuilder {
1009    transaction_builder: TransactionBuilder,
1010}
1011
1012impl OwnedLeafShardBlockBodyBuilder {
1013    /// Add transaction to the body
1014    #[inline(always)]
1015    pub fn add_transaction<T>(&mut self, transaction: T) -> Result<(), OwnedLeafShardBodyError>
1016    where
1017        T: WritableBodyTransaction,
1018    {
1019        self.transaction_builder.add_transaction(transaction)?;
1020
1021        Ok(())
1022    }
1023
1024    /// Finish building block body
1025    pub fn finish(self) -> OwnedLeafShardBody {
1026        OwnedLeafShardBody::from_buffer(self.transaction_builder.finish().into_shared())
1027            .expect("Known to be created correctly; qed")
1028    }
1029}
1030
1031/// An owned version of [`BlockBody`].
1032///
1033/// It is correctly aligned in memory and well suited for sending and receiving over the network
1034/// efficiently or storing in memory or on disk.
1035#[derive(Debug, Clone, From)]
1036pub enum OwnedBlockBody {
1037    /// Block body corresponds to the beacon chain
1038    BeaconChain(OwnedBeaconChainBody),
1039    /// Block body corresponds to an intermediate shard
1040    IntermediateShard(OwnedIntermediateShardBody),
1041    /// Block body corresponds to a leaf shard
1042    LeafShard(OwnedLeafShardBody),
1043}
1044
1045impl OwnedBlockBody {
1046    /// Create an owned body from a buffer
1047    #[inline]
1048    pub fn from_buffer(
1049        buffer: SharedAlignedBuffer,
1050        shard_kind: RealShardKind,
1051    ) -> Result<Self, SharedAlignedBuffer> {
1052        Ok(match shard_kind {
1053            RealShardKind::BeaconChain => {
1054                Self::BeaconChain(OwnedBeaconChainBody::from_buffer(buffer)?)
1055            }
1056            RealShardKind::IntermediateShard => {
1057                Self::IntermediateShard(OwnedIntermediateShardBody::from_buffer(buffer)?)
1058            }
1059            RealShardKind::LeafShard => Self::LeafShard(OwnedLeafShardBody::from_buffer(buffer)?),
1060        })
1061    }
1062
1063    /// Inner buffer block body contents
1064    #[inline]
1065    pub fn buffer(&self) -> &SharedAlignedBuffer {
1066        match self {
1067            Self::BeaconChain(owned_body) => owned_body.buffer(),
1068            Self::IntermediateShard(owned_body) => owned_body.buffer(),
1069            Self::LeafShard(owned_body) => owned_body.buffer(),
1070        }
1071    }
1072
1073    /// Number of clones in memory
1074    #[inline(always)]
1075    pub fn ref_count(&self) -> usize {
1076        match self {
1077            Self::BeaconChain(owned_body) => owned_body.ref_count(),
1078            Self::IntermediateShard(owned_body) => owned_body.ref_count(),
1079            Self::LeafShard(owned_body) => owned_body.ref_count(),
1080        }
1081    }
1082
1083    /// Get [`BlockBody`] out of [`OwnedBlockBody`]
1084    #[inline]
1085    pub fn body(&self) -> BlockBody<'_> {
1086        match self {
1087            Self::BeaconChain(owned_body) => BlockBody::BeaconChain(*owned_body.body()),
1088            Self::IntermediateShard(owned_body) => BlockBody::IntermediateShard(*owned_body.body()),
1089            Self::LeafShard(owned_body) => BlockBody::LeafShard(*owned_body.body()),
1090        }
1091    }
1092}
1093
1094/// Aligns buffer to 8 bytes by adding necessary padding zero bytes.
1095///
1096/// Returns `false` if buffer becomes too long.
1097#[inline(always)]
1098#[must_use = "Check if buffer becomes too long"]
1099fn align_to_8_with_padding(buffer: &mut OwnedAlignedBuffer) -> bool {
1100    let alignment = usize::from(Alignment::of::<u64>().get());
1101    let unaligned_by = buffer.len() as usize % alignment;
1102    if unaligned_by > 0 {
1103        // SAFETY: Subtracted value is always smaller than alignment
1104        let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
1105
1106        if !buffer.append(&0u64.to_le_bytes()[..padding_bytes]) {
1107            return false;
1108        }
1109    }
1110
1111    true
1112}
1113
1114/// Aligns buffer to 16 bytes by adding necessary padding zero bytes.
1115///
1116/// Returns `false` if buffer becomes too long.
1117#[inline(always)]
1118#[must_use = "Check if buffer becomes too long"]
1119fn align_to_16_bytes_with_padding(buffer: &mut OwnedAlignedBuffer) -> bool {
1120    let alignment = usize::from(Alignment::MAX.get());
1121    let unaligned_by = buffer.len() as usize % alignment;
1122    if unaligned_by > 0 {
1123        // SAFETY: Subtracted value is always smaller than alignment
1124        let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
1125
1126        if !buffer.append(&0u128.to_le_bytes()[..padding_bytes]) {
1127            return false;
1128        }
1129    }
1130
1131    true
1132}