Skip to main content

ab_core_primitives/block/header/
owned.rs

1//! Data structures related to the owned version of [`BlockHeader`]
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::header::{
26    BeaconChainHeader, BlockHeader, BlockHeaderBeaconChainInfo, BlockHeaderConsensusInfo,
27    BlockHeaderConsensusParameters, BlockHeaderConsistencyError,
28    BlockHeaderFixedConsensusParameters, BlockHeaderPrefix, BlockHeaderResult, BlockHeaderSeal,
29    BlockHeaderSealType, GenericBlockHeader, IntermediateShardHeader, LeafShardHeader,
30};
31use crate::hashes::Blake3Hash;
32use crate::shard::{NumShardsUnchecked, RealShardKind};
33use ab_aligned_buffer::alignment::Align16;
34use ab_aligned_buffer::{OwnedAlignedBuffer, SharedAlignedBuffer};
35use ab_io_type::trivial_type::TrivialType;
36use core::fmt;
37use derive_more::From;
38use triomphe::Arc;
39use yoke::Yoke;
40
41/// Generic owned block header
42pub trait GenericOwnedBlockHeader:
43    Clone + fmt::Debug + Send + Sync + Into<OwnedBlockHeader> + 'static
44{
45    /// Shard kind
46    const SHARD_KIND: RealShardKind;
47
48    /// Block header
49    type Header<'a>: GenericBlockHeader<'a>
50    where
51        Self: 'a;
52
53    /// Create an owned header from a buffer
54    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer>;
55
56    /// Inner buffer with block header contents
57    fn buffer(&self) -> &SharedAlignedBuffer;
58
59    /// Number of clones in memory
60    fn ref_count(&self) -> usize;
61
62    /// Get a regular block header out of the owned version
63    fn header(&self) -> &Self::Header<'_>;
64}
65
66fn append_seal(buffer: &mut OwnedAlignedBuffer, seal: BlockHeaderSeal<'_>) {
67    match seal {
68        BlockHeaderSeal::Ed25519(seal) => {
69            let true = buffer.append(&[BlockHeaderSealType::Ed25519 as u8]) else {
70                unreachable!("Unsealed headers are only created when the largest seal fits; qed");
71            };
72            let true = buffer.append(seal.as_bytes()) else {
73                unreachable!("Unsealed headers are only created when the largest seal fits; qed");
74            };
75        }
76    }
77}
78
79/// Errors for [`OwnedBeaconChainHeader`]
80#[derive(Debug, thiserror::Error)]
81pub enum OwnedBeaconChainHeaderError {
82    /// Header with the largest possible consensus parameters and seal doesn't fit into the buffer
83    /// after the bytes already there
84    #[error("The header is too large")]
85    HeaderTooLarge,
86    /// Header parts are not consistent with each other or with the shard kind
87    #[error("Invalid header parts: {error}")]
88    InvalidParts {
89        /// Block header consistency error
90        #[from]
91        error: BlockHeaderConsistencyError,
92    },
93}
94
95/// An owned version of [`BeaconChainHeader`].
96///
97/// It is correctly aligned in memory and well suited for sending and receiving over the network
98/// efficiently or storing in memory or on disk.
99#[derive(Debug, Clone)]
100pub struct OwnedBeaconChainHeader {
101    inner: Arc<Yoke<BeaconChainHeader<'static>, SharedAlignedBuffer>>,
102}
103
104impl GenericOwnedBlockHeader for OwnedBeaconChainHeader {
105    const SHARD_KIND: RealShardKind = RealShardKind::BeaconChain;
106
107    type Header<'a> = BeaconChainHeader<'a>;
108
109    #[inline(always)]
110    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
111        Self::from_buffer(buffer)
112    }
113
114    #[inline(always)]
115    fn buffer(&self) -> &SharedAlignedBuffer {
116        self.buffer()
117    }
118
119    #[inline(always)]
120    fn ref_count(&self) -> usize {
121        self.ref_count()
122    }
123
124    #[inline(always)]
125    fn header(&self) -> &Self::Header<'_> {
126        self.header()
127    }
128}
129
130impl OwnedBeaconChainHeader {
131    /// Max allocation needed by this header
132    pub const MAX_ALLOCATION: u32 = BlockHeaderPrefix::SIZE
133        + BlockHeaderResult::SIZE
134        + BlockHeaderConsensusInfo::SIZE
135        + <Blake3Hash as TrivialType>::SIZE
136        + BlockHeaderConsensusParameters::MAX_SIZE
137        + BlockHeaderSeal::MAX_SIZE;
138
139    /// Create a new [`OwnedBeaconChainHeader`] from its parts.
140    ///
141    /// Returns an error if the parts are not consistent with each other or with the shard kind.
142    pub fn from_parts(
143        prefix: &BlockHeaderPrefix,
144        result: &BlockHeaderResult,
145        consensus_info: &BlockHeaderConsensusInfo,
146        intermediate_shard_blocks_root: &Blake3Hash,
147        consensus_parameters: &BlockHeaderConsensusParameters<'_>,
148    ) -> Result<OwnedBeaconChainHeaderUnsealed, BlockHeaderConsistencyError> {
149        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::BeaconChain)?;
150
151        let mut buffer = OwnedAlignedBuffer::with_capacity(Self::MAX_ALLOCATION);
152
153        Self::append_unsealed(
154            prefix,
155            result,
156            consensus_info,
157            intermediate_shard_blocks_root,
158            consensus_parameters,
159            &mut buffer,
160        )
161        .expect("The header with the largest seal fits into an empty buffer; qed");
162
163        Ok(OwnedBeaconChainHeaderUnsealed {
164            buffer,
165            public_key_hash: consensus_info.solution.public_key_hash,
166        })
167    }
168
169    /// Write a header from its parts into the provided buffer, after the bytes already there.
170    ///
171    /// The length of the buffer must be a multiple of 8 for the header to parse.
172    /// Returns an error if the parts are not consistent with each other or with the shard kind.
173    /// The seal is not attached, the header in the buffer is complete only after
174    /// [`OwnedBeaconChainHeaderUnsealedRef::with_seal()`] attaches it. Nothing is written when an
175    /// error is returned, otherwise the seal is guaranteed to fit.
176    pub fn from_parts_into<'a>(
177        prefix: &BlockHeaderPrefix,
178        result: &BlockHeaderResult,
179        consensus_info: &BlockHeaderConsensusInfo,
180        intermediate_shard_blocks_root: &Blake3Hash,
181        consensus_parameters: &BlockHeaderConsensusParameters<'_>,
182        buffer: &'a mut OwnedAlignedBuffer,
183    ) -> Result<OwnedBeaconChainHeaderUnsealedRef<'a>, OwnedBeaconChainHeaderError> {
184        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::BeaconChain)?;
185
186        let header_offset = buffer.len();
187
188        Self::append_unsealed(
189            prefix,
190            result,
191            consensus_info,
192            intermediate_shard_blocks_root,
193            consensus_parameters,
194            buffer,
195        )?;
196
197        Ok(OwnedBeaconChainHeaderUnsealedRef {
198            buffer,
199            header_offset,
200            public_key_hash: consensus_info.solution.public_key_hash,
201        })
202    }
203
204    /// Append everything except the seal to the buffer
205    fn append_unsealed(
206        prefix: &BlockHeaderPrefix,
207        result: &BlockHeaderResult,
208        consensus_info: &BlockHeaderConsensusInfo,
209        intermediate_shard_blocks_root: &Blake3Hash,
210        consensus_parameters: &BlockHeaderConsensusParameters<'_>,
211        buffer: &mut OwnedAlignedBuffer,
212    ) -> Result<(), OwnedBeaconChainHeaderError> {
213        let BlockHeaderConsensusParameters {
214            fixed_parameters,
215            super_segment_root,
216            next_solution_range,
217            pot_parameters_change,
218        } = consensus_parameters;
219        let BlockHeaderFixedConsensusParameters {
220            solution_range,
221            slot_iterations,
222            num_shards,
223        } = fixed_parameters;
224
225        // The header with the largest seal must fit after the bytes already in the buffer, so
226        // that neither the appends below nor the seal appended later can fail. The length of a
227        // buffer is never larger than its maximum capacity.
228        if OwnedAlignedBuffer::<Align16>::MAX_CAPACITY - buffer.len() < Self::MAX_ALLOCATION {
229            return Err(OwnedBeaconChainHeaderError::HeaderTooLarge);
230        }
231        let true = buffer.append(prefix.as_bytes()) else {
232            unreachable!("Checked size above; qed");
233        };
234        let true = buffer.append(result.as_bytes()) else {
235            unreachable!("Checked size above; qed");
236        };
237        let true = buffer.append(consensus_info.as_bytes()) else {
238            unreachable!("Checked size above; qed");
239        };
240        let true = buffer.append(intermediate_shard_blocks_root.as_bytes()) else {
241            unreachable!("Checked size above; qed");
242        };
243        // TODO: Would be nice for `BlockHeaderConsensusParameters` to have API to write this by
244        //  itself
245        {
246            let true = buffer.append(&solution_range.to_bytes()) else {
247                unreachable!("Checked size above; qed");
248            };
249            let true = buffer.append(&slot_iterations.get().to_le_bytes()) else {
250                unreachable!("Checked size above; qed");
251            };
252            let true = buffer.append(NumShardsUnchecked::from(*num_shards).as_bytes()) else {
253                unreachable!("Checked size above; qed");
254            };
255
256            let bitflags = {
257                let mut bitflags = 0u8;
258
259                if super_segment_root.is_some() {
260                    bitflags |= BlockHeaderConsensusParameters::SUPER_SEGMENT_ROOT_MASK;
261                }
262                if next_solution_range.is_some() {
263                    bitflags |= BlockHeaderConsensusParameters::NEXT_SOLUTION_RANGE_MASK;
264                }
265                if pot_parameters_change.is_some() {
266                    bitflags |= BlockHeaderConsensusParameters::POT_PARAMETERS_CHANGE_MASK;
267                }
268
269                bitflags
270            };
271
272            let true = buffer.append(&[bitflags]) else {
273                unreachable!("Checked size above; qed");
274            };
275
276            if let Some(super_segment_root) = super_segment_root {
277                let true = buffer.append(super_segment_root.as_ref()) else {
278                    unreachable!("Checked size above; qed");
279                };
280            }
281
282            if let Some(next_solution_range) = next_solution_range {
283                let true = buffer.append(&next_solution_range.to_bytes()) else {
284                    unreachable!("Checked size above; qed");
285                };
286            }
287
288            if let Some(pot_parameters_change) = pot_parameters_change {
289                let true = buffer.append(&pot_parameters_change.slot.to_bytes()) else {
290                    unreachable!("Checked size above; qed");
291                };
292                let true =
293                    buffer.append(&pot_parameters_change.slot_iterations.get().to_le_bytes())
294                else {
295                    unreachable!("Checked size above; qed");
296                };
297                let true = buffer.append(pot_parameters_change.entropy.as_ref()) else {
298                    unreachable!("Checked size above; qed");
299                };
300            }
301        }
302
303        Ok(())
304    }
305
306    /// Create an owned header from a buffer
307    #[inline]
308    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
309        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
310        //  https://github.com/unicode-org/icu4x/issues/6665
311        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
312            let Some((header, extra_bytes)) = BeaconChainHeader::try_from_bytes(buffer) else {
313                return Err(());
314            };
315            if !extra_bytes.is_empty() {
316                return Err(());
317            }
318
319            Ok(header)
320        })
321        .map_err(move |()| buffer)?;
322
323        Ok(Self {
324            inner: Arc::new(inner),
325        })
326    }
327
328    /// Inner buffer with block header contents
329    #[inline(always)]
330    pub fn buffer(&self) -> &SharedAlignedBuffer {
331        self.inner.backing_cart()
332    }
333
334    /// Number of clones in memory
335    #[inline(always)]
336    pub fn ref_count(&self) -> usize {
337        Arc::count(&self.inner)
338    }
339
340    /// Get [`BeaconChainHeader`] out of [`OwnedBeaconChainHeader`]
341    #[inline(always)]
342    pub fn header(&self) -> &BeaconChainHeader<'_> {
343        self.inner.get()
344    }
345}
346
347/// Owned beacon chain block header, which is not sealed yet
348#[derive(Debug, Clone)]
349pub struct OwnedBeaconChainHeaderUnsealed {
350    buffer: OwnedAlignedBuffer,
351    /// Public key hash of the solution, the seal must correspond to it
352    public_key_hash: Blake3Hash,
353}
354
355impl OwnedBeaconChainHeaderUnsealed {
356    /// Hash of the block before seal is applied to it
357    #[inline(always)]
358    pub fn pre_seal_hash(&self) -> Blake3Hash {
359        // TODO: Keyed hash with `block_header_seal` as a key
360        Blake3Hash::from(blake3::hash(self.buffer.as_slice()))
361    }
362
363    /// Add seal and return [`OwnedBeaconChainHeader`].
364    ///
365    /// Returns an error if the seal doesn't correspond to the public key hash of the solution.
366    pub fn with_seal(
367        self,
368        seal: BlockHeaderSeal<'_>,
369    ) -> Result<OwnedBeaconChainHeader, BlockHeaderConsistencyError> {
370        let Self {
371            mut buffer,
372            public_key_hash,
373        } = self;
374        BlockHeader::check_seal(&public_key_hash, seal)?;
375        append_seal(&mut buffer, seal);
376
377        let header = OwnedBeaconChainHeader::from_buffer(buffer.into_shared())
378            .expect("`from_parts()` checked the parts it wrote, the seal is checked above; qed");
379
380        Ok(header)
381    }
382}
383
384/// Beacon chain block header written into a buffer by
385/// [`OwnedBeaconChainHeader::from_parts_into()`], which is not sealed yet.
386///
387/// The header in the buffer is not complete until [`Self::with_seal()`] attaches the seal.
388#[derive(Debug)]
389#[must_use = "The header in the buffer is not complete until `with_seal()` attaches the seal"]
390pub struct OwnedBeaconChainHeaderUnsealedRef<'a> {
391    buffer: &'a mut OwnedAlignedBuffer,
392    header_offset: u32,
393    /// Public key hash of the solution, the seal must correspond to it
394    public_key_hash: Blake3Hash,
395}
396
397impl OwnedBeaconChainHeaderUnsealedRef<'_> {
398    /// Hash of the block before seal is applied to it
399    #[inline(always)]
400    pub fn pre_seal_hash(&self) -> Blake3Hash {
401        let header_bytes = self
402            .buffer
403            .as_slice()
404            .get(self.header_offset as usize..)
405            .expect("The buffer only grew since `header_offset` was its length; qed");
406        // TODO: Keyed hash with `block_header_seal` as a key
407        Blake3Hash::from(blake3::hash(header_bytes))
408    }
409
410    /// Attach the seal after the header in the buffer, which completes the header.
411    ///
412    /// Returns an error if the seal doesn't correspond to the public key hash of the solution. The
413    /// seal is not attached then, and the buffer ends with the unsealed header, which must not be
414    /// treated as a complete header.
415    #[inline(always)]
416    pub fn with_seal(self, seal: BlockHeaderSeal<'_>) -> Result<(), BlockHeaderConsistencyError> {
417        let Self {
418            buffer,
419            header_offset: _,
420            public_key_hash,
421        } = self;
422        BlockHeader::check_seal(&public_key_hash, seal)?;
423        append_seal(buffer, seal);
424
425        Ok(())
426    }
427}
428
429/// Errors for [`OwnedIntermediateShardHeader`]
430#[derive(Debug, thiserror::Error)]
431pub enum OwnedIntermediateShardHeaderError {
432    /// Header doesn't fit into the buffer after the bytes already there
433    #[error("The header is too large")]
434    HeaderTooLarge,
435    /// Header parts are not consistent with each other or with the shard kind
436    #[error("Invalid header parts: {error}")]
437    InvalidParts {
438        /// Block header consistency error
439        #[from]
440        error: BlockHeaderConsistencyError,
441    },
442}
443
444/// An owned version of [`IntermediateShardHeader`].
445///
446/// It is correctly aligned in memory and well suited for sending and receiving over the network
447/// efficiently or storing in memory or on disk.
448#[derive(Debug, Clone)]
449pub struct OwnedIntermediateShardHeader {
450    inner: Arc<Yoke<IntermediateShardHeader<'static>, SharedAlignedBuffer>>,
451}
452
453impl GenericOwnedBlockHeader for OwnedIntermediateShardHeader {
454    const SHARD_KIND: RealShardKind = RealShardKind::IntermediateShard;
455
456    type Header<'a> = IntermediateShardHeader<'a>;
457
458    #[inline(always)]
459    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
460        Self::from_buffer(buffer)
461    }
462
463    #[inline(always)]
464    fn buffer(&self) -> &SharedAlignedBuffer {
465        self.buffer()
466    }
467
468    #[inline(always)]
469    fn ref_count(&self) -> usize {
470        self.ref_count()
471    }
472
473    #[inline(always)]
474    fn header(&self) -> &Self::Header<'_> {
475        self.header()
476    }
477}
478
479impl OwnedIntermediateShardHeader {
480    /// Max allocation needed by this header
481    pub const MAX_ALLOCATION: u32 = BlockHeaderPrefix::SIZE
482        + BlockHeaderResult::SIZE
483        + BlockHeaderConsensusInfo::SIZE
484        + BlockHeaderBeaconChainInfo::SIZE
485        + <Blake3Hash as TrivialType>::SIZE
486        + BlockHeaderSeal::MAX_SIZE;
487
488    /// Create a new [`OwnedIntermediateShardHeader`] from its parts.
489    ///
490    /// Returns an error if the parts are not consistent with each other or with the shard kind.
491    pub fn from_parts(
492        prefix: &BlockHeaderPrefix,
493        result: &BlockHeaderResult,
494        consensus_info: &BlockHeaderConsensusInfo,
495        beacon_chain_info: &BlockHeaderBeaconChainInfo,
496        leaf_shard_blocks_root: &Blake3Hash,
497    ) -> Result<OwnedIntermediateShardHeaderUnsealed, BlockHeaderConsistencyError> {
498        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::IntermediateShard)?;
499
500        let mut buffer = OwnedAlignedBuffer::with_capacity(Self::MAX_ALLOCATION);
501
502        Self::append_unsealed(
503            prefix,
504            result,
505            consensus_info,
506            beacon_chain_info,
507            leaf_shard_blocks_root,
508            &mut buffer,
509        )
510        .expect("The header with the largest seal fits into an empty buffer; qed");
511
512        Ok(OwnedIntermediateShardHeaderUnsealed {
513            buffer,
514            public_key_hash: consensus_info.solution.public_key_hash,
515        })
516    }
517
518    /// Write a header from its parts into the provided buffer, after the bytes already there.
519    ///
520    /// The length of the buffer must be a multiple of 8 for the header to parse.
521    /// Returns an error if the parts are not consistent with each other or with the shard kind.
522    /// The seal is not attached, the header in the buffer is complete only after
523    /// [`OwnedIntermediateShardHeaderUnsealedRef::with_seal()`] attaches it. Nothing is written
524    /// when an error is returned, otherwise the seal is guaranteed to fit.
525    pub fn from_parts_into<'a>(
526        prefix: &BlockHeaderPrefix,
527        result: &BlockHeaderResult,
528        consensus_info: &BlockHeaderConsensusInfo,
529        beacon_chain_info: &BlockHeaderBeaconChainInfo,
530        leaf_shard_blocks_root: &Blake3Hash,
531        buffer: &'a mut OwnedAlignedBuffer,
532    ) -> Result<OwnedIntermediateShardHeaderUnsealedRef<'a>, OwnedIntermediateShardHeaderError>
533    {
534        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::IntermediateShard)?;
535
536        let header_offset = buffer.len();
537
538        Self::append_unsealed(
539            prefix,
540            result,
541            consensus_info,
542            beacon_chain_info,
543            leaf_shard_blocks_root,
544            buffer,
545        )?;
546
547        Ok(OwnedIntermediateShardHeaderUnsealedRef {
548            buffer,
549            header_offset,
550            public_key_hash: consensus_info.solution.public_key_hash,
551        })
552    }
553
554    /// Append everything except the seal to the buffer
555    fn append_unsealed(
556        prefix: &BlockHeaderPrefix,
557        result: &BlockHeaderResult,
558        consensus_info: &BlockHeaderConsensusInfo,
559        beacon_chain_info: &BlockHeaderBeaconChainInfo,
560        leaf_shard_blocks_root: &Blake3Hash,
561        buffer: &mut OwnedAlignedBuffer,
562    ) -> Result<(), OwnedIntermediateShardHeaderError> {
563        // The header with the largest seal must fit after the bytes already in the buffer, so
564        // that neither the appends below nor the seal appended later can fail. The length of a
565        // buffer is never larger than its maximum capacity.
566        if OwnedAlignedBuffer::<Align16>::MAX_CAPACITY - buffer.len() < Self::MAX_ALLOCATION {
567            return Err(OwnedIntermediateShardHeaderError::HeaderTooLarge);
568        }
569        let true = buffer.append(prefix.as_bytes()) else {
570            unreachable!("Checked size above; qed");
571        };
572        let true = buffer.append(result.as_bytes()) else {
573            unreachable!("Checked size above; qed");
574        };
575        let true = buffer.append(consensus_info.as_bytes()) else {
576            unreachable!("Checked size above; qed");
577        };
578        let true = buffer.append(beacon_chain_info.as_bytes()) else {
579            unreachable!("Checked size above; qed");
580        };
581        let true = buffer.append(leaf_shard_blocks_root.as_bytes()) else {
582            unreachable!("Checked size above; qed");
583        };
584
585        Ok(())
586    }
587
588    /// Create an owned header from a buffer
589    #[inline]
590    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
591        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
592        //  https://github.com/unicode-org/icu4x/issues/6665
593        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
594            let Some((header, extra_bytes)) = IntermediateShardHeader::try_from_bytes(buffer)
595            else {
596                return Err(());
597            };
598            if !extra_bytes.is_empty() {
599                return Err(());
600            }
601
602            Ok(header)
603        })
604        .map_err(move |()| buffer)?;
605
606        Ok(Self {
607            inner: Arc::new(inner),
608        })
609    }
610
611    /// Inner buffer with block header contents
612    #[inline(always)]
613    pub fn buffer(&self) -> &SharedAlignedBuffer {
614        self.inner.backing_cart()
615    }
616
617    /// Number of clones in memory
618    #[inline(always)]
619    pub fn ref_count(&self) -> usize {
620        Arc::count(&self.inner)
621    }
622
623    /// Get [`IntermediateShardHeader`] out of [`OwnedIntermediateShardHeader`]
624    #[inline(always)]
625    pub fn header(&self) -> &IntermediateShardHeader<'_> {
626        self.inner.get()
627    }
628}
629
630/// Owned intermediate shard block header, which is not sealed yet
631#[derive(Debug, Clone)]
632pub struct OwnedIntermediateShardHeaderUnsealed {
633    buffer: OwnedAlignedBuffer,
634    /// Public key hash of the solution, the seal must correspond to it
635    public_key_hash: Blake3Hash,
636}
637
638impl OwnedIntermediateShardHeaderUnsealed {
639    /// Hash of the block before seal is applied to it
640    #[inline(always)]
641    pub fn pre_seal_hash(&self) -> Blake3Hash {
642        // TODO: Keyed hash with `block_header_seal` as a key
643        Blake3Hash::from(blake3::hash(self.buffer.as_slice()))
644    }
645
646    /// Add seal and return [`OwnedIntermediateShardHeader`].
647    ///
648    /// Returns an error if the seal doesn't correspond to the public key hash of the solution.
649    pub fn with_seal(
650        self,
651        seal: BlockHeaderSeal<'_>,
652    ) -> Result<OwnedIntermediateShardHeader, BlockHeaderConsistencyError> {
653        let Self {
654            mut buffer,
655            public_key_hash,
656        } = self;
657        BlockHeader::check_seal(&public_key_hash, seal)?;
658        append_seal(&mut buffer, seal);
659
660        let header = OwnedIntermediateShardHeader::from_buffer(buffer.into_shared())
661            .expect("`from_parts()` checked the parts it wrote, the seal is checked above; qed");
662
663        Ok(header)
664    }
665}
666
667/// Intermediate shard block header written into a buffer by
668/// [`OwnedIntermediateShardHeader::from_parts_into()`], which is not sealed yet.
669///
670/// The header in the buffer is not complete until [`Self::with_seal()`] attaches the seal.
671#[derive(Debug)]
672#[must_use = "The header in the buffer is not complete until `with_seal()` attaches the seal"]
673pub struct OwnedIntermediateShardHeaderUnsealedRef<'a> {
674    buffer: &'a mut OwnedAlignedBuffer,
675    header_offset: u32,
676    /// Public key hash of the solution, the seal must correspond to it
677    public_key_hash: Blake3Hash,
678}
679
680impl OwnedIntermediateShardHeaderUnsealedRef<'_> {
681    /// Hash of the block before seal is applied to it
682    #[inline(always)]
683    pub fn pre_seal_hash(&self) -> Blake3Hash {
684        let header_bytes = self
685            .buffer
686            .as_slice()
687            .get(self.header_offset as usize..)
688            .expect("The buffer only grew since `header_offset` was its length; qed");
689        // TODO: Keyed hash with `block_header_seal` as a key
690        Blake3Hash::from(blake3::hash(header_bytes))
691    }
692
693    /// Attach the seal after the header in the buffer, which completes the header.
694    ///
695    /// Returns an error if the seal doesn't correspond to the public key hash of the solution. The
696    /// seal is not attached then, and the buffer ends with the unsealed header, which must not be
697    /// treated as a complete header.
698    #[inline(always)]
699    pub fn with_seal(self, seal: BlockHeaderSeal<'_>) -> Result<(), BlockHeaderConsistencyError> {
700        let Self {
701            buffer,
702            header_offset: _,
703            public_key_hash,
704        } = self;
705        BlockHeader::check_seal(&public_key_hash, seal)?;
706        append_seal(buffer, seal);
707
708        Ok(())
709    }
710}
711
712/// Errors for [`OwnedLeafShardHeader`]
713#[derive(Debug, thiserror::Error)]
714pub enum OwnedLeafShardHeaderError {
715    /// Header doesn't fit into the buffer after the bytes already there
716    #[error("The header is too large")]
717    HeaderTooLarge,
718    /// Header parts are not consistent with each other or with the shard kind
719    #[error("Invalid header parts: {error}")]
720    InvalidParts {
721        /// Block header consistency error
722        #[from]
723        error: BlockHeaderConsistencyError,
724    },
725}
726
727/// An owned version of [`LeafShardHeader`].
728///
729/// It is correctly aligned in memory and well suited for sending and receiving over the network
730/// efficiently or storing in memory or on disk.
731#[derive(Debug, Clone)]
732pub struct OwnedLeafShardHeader {
733    inner: Arc<Yoke<LeafShardHeader<'static>, SharedAlignedBuffer>>,
734}
735
736impl GenericOwnedBlockHeader for OwnedLeafShardHeader {
737    const SHARD_KIND: RealShardKind = RealShardKind::LeafShard;
738
739    type Header<'a> = LeafShardHeader<'a>;
740
741    #[inline(always)]
742    fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
743        Self::from_buffer(buffer)
744    }
745
746    #[inline(always)]
747    fn buffer(&self) -> &SharedAlignedBuffer {
748        self.buffer()
749    }
750
751    #[inline(always)]
752    fn ref_count(&self) -> usize {
753        self.ref_count()
754    }
755
756    #[inline(always)]
757    fn header(&self) -> &Self::Header<'_> {
758        self.header()
759    }
760}
761
762impl OwnedLeafShardHeader {
763    /// Max allocation needed by this header
764    pub const MAX_ALLOCATION: u32 = BlockHeaderPrefix::SIZE
765        + BlockHeaderResult::SIZE
766        + BlockHeaderConsensusInfo::SIZE
767        + BlockHeaderBeaconChainInfo::SIZE
768        + BlockHeaderSeal::MAX_SIZE;
769
770    /// Create a new [`OwnedLeafShardHeader`] from its parts.
771    ///
772    /// Returns an error if the parts are not consistent with each other or with the shard kind.
773    pub fn from_parts(
774        prefix: &BlockHeaderPrefix,
775        result: &BlockHeaderResult,
776        consensus_info: &BlockHeaderConsensusInfo,
777        beacon_chain_info: &BlockHeaderBeaconChainInfo,
778    ) -> Result<OwnedLeafShardHeaderUnsealed, BlockHeaderConsistencyError> {
779        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::LeafShard)?;
780
781        let mut buffer = OwnedAlignedBuffer::with_capacity(Self::MAX_ALLOCATION);
782
783        Self::append_unsealed(
784            prefix,
785            result,
786            consensus_info,
787            beacon_chain_info,
788            &mut buffer,
789        )
790        .expect("The header with the largest seal fits into an empty buffer; qed");
791
792        Ok(OwnedLeafShardHeaderUnsealed {
793            buffer,
794            public_key_hash: consensus_info.solution.public_key_hash,
795        })
796    }
797
798    /// Write a header from its parts into the provided buffer, after the bytes already there.
799    ///
800    /// The length of the buffer must be a multiple of 8 for the header to parse.
801    /// Returns an error if the parts are not consistent with each other or with the shard kind.
802    /// The seal is not attached, the header in the buffer is complete only after
803    /// [`OwnedLeafShardHeaderUnsealedRef::with_seal()`] attaches it. Nothing is written when an
804    /// error is returned, otherwise the seal is guaranteed to fit.
805    pub fn from_parts_into<'a>(
806        prefix: &BlockHeaderPrefix,
807        result: &BlockHeaderResult,
808        consensus_info: &BlockHeaderConsensusInfo,
809        beacon_chain_info: &BlockHeaderBeaconChainInfo,
810        buffer: &'a mut OwnedAlignedBuffer,
811    ) -> Result<OwnedLeafShardHeaderUnsealedRef<'a>, OwnedLeafShardHeaderError> {
812        BlockHeader::check_shared_parts(prefix, consensus_info, RealShardKind::LeafShard)?;
813
814        let header_offset = buffer.len();
815
816        Self::append_unsealed(prefix, result, consensus_info, beacon_chain_info, buffer)?;
817
818        Ok(OwnedLeafShardHeaderUnsealedRef {
819            buffer,
820            header_offset,
821            public_key_hash: consensus_info.solution.public_key_hash,
822        })
823    }
824
825    /// Append everything except the seal to the buffer
826    fn append_unsealed(
827        prefix: &BlockHeaderPrefix,
828        result: &BlockHeaderResult,
829        consensus_info: &BlockHeaderConsensusInfo,
830        beacon_chain_info: &BlockHeaderBeaconChainInfo,
831        buffer: &mut OwnedAlignedBuffer,
832    ) -> Result<(), OwnedLeafShardHeaderError> {
833        // The header with the largest seal must fit after the bytes already in the buffer, so
834        // that neither the appends below nor the seal appended later can fail. The length of a
835        // buffer is never larger than its maximum capacity.
836        if OwnedAlignedBuffer::<Align16>::MAX_CAPACITY - buffer.len() < Self::MAX_ALLOCATION {
837            return Err(OwnedLeafShardHeaderError::HeaderTooLarge);
838        }
839        let true = buffer.append(prefix.as_bytes()) else {
840            unreachable!("Checked size above; qed");
841        };
842        let true = buffer.append(result.as_bytes()) else {
843            unreachable!("Checked size above; qed");
844        };
845        let true = buffer.append(consensus_info.as_bytes()) else {
846            unreachable!("Checked size above; qed");
847        };
848        let true = buffer.append(beacon_chain_info.as_bytes()) else {
849            unreachable!("Checked size above; qed");
850        };
851
852        Ok(())
853    }
854
855    /// Create an owned header from a buffer
856    #[inline]
857    pub fn from_buffer(buffer: SharedAlignedBuffer) -> Result<Self, SharedAlignedBuffer> {
858        // TODO: Cloning is cheap, but will not be necessary if/when this is resolved:
859        //  https://github.com/unicode-org/icu4x/issues/6665
860        let inner = Yoke::try_attach_to_cart(buffer.clone(), |buffer| {
861            let Some((header, extra_bytes)) = LeafShardHeader::try_from_bytes(buffer) else {
862                return Err(());
863            };
864            if !extra_bytes.is_empty() {
865                return Err(());
866            }
867
868            Ok(header)
869        })
870        .map_err(move |()| buffer)?;
871
872        Ok(Self {
873            inner: Arc::new(inner),
874        })
875    }
876
877    /// Inner buffer with block header contents
878    #[inline(always)]
879    pub fn buffer(&self) -> &SharedAlignedBuffer {
880        self.inner.backing_cart()
881    }
882
883    /// Number of clones in memory
884    #[inline(always)]
885    pub fn ref_count(&self) -> usize {
886        Arc::count(&self.inner)
887    }
888
889    /// Get [`LeafShardHeader`] out of [`OwnedLeafShardHeader`]
890    #[inline(always)]
891    pub fn header(&self) -> &LeafShardHeader<'_> {
892        self.inner.get()
893    }
894}
895
896/// Owned leaf shard block header, which is not sealed yet
897#[derive(Debug, Clone)]
898pub struct OwnedLeafShardHeaderUnsealed {
899    buffer: OwnedAlignedBuffer,
900    /// Public key hash of the solution, the seal must correspond to it
901    public_key_hash: Blake3Hash,
902}
903
904impl OwnedLeafShardHeaderUnsealed {
905    /// Hash of the block before seal is applied to it
906    #[inline(always)]
907    pub fn pre_seal_hash(&self) -> Blake3Hash {
908        // TODO: Keyed hash with `block_header_seal` as a key
909        Blake3Hash::from(blake3::hash(self.buffer.as_slice()))
910    }
911
912    /// Add seal and return [`OwnedLeafShardHeader`].
913    ///
914    /// Returns an error if the seal doesn't correspond to the public key hash of the solution.
915    pub fn with_seal(
916        self,
917        seal: BlockHeaderSeal<'_>,
918    ) -> Result<OwnedLeafShardHeader, BlockHeaderConsistencyError> {
919        let Self {
920            mut buffer,
921            public_key_hash,
922        } = self;
923        BlockHeader::check_seal(&public_key_hash, seal)?;
924        append_seal(&mut buffer, seal);
925
926        let header = OwnedLeafShardHeader::from_buffer(buffer.into_shared())
927            .expect("`from_parts()` checked the parts it wrote, the seal is checked above; qed");
928
929        Ok(header)
930    }
931}
932
933/// Leaf shard block header written into a buffer by [`OwnedLeafShardHeader::from_parts_into()`],
934/// which is not sealed yet.
935///
936/// The header in the buffer is not complete until [`Self::with_seal()`] attaches the seal.
937#[derive(Debug)]
938#[must_use = "The header in the buffer is not complete until `with_seal()` attaches the seal"]
939pub struct OwnedLeafShardHeaderUnsealedRef<'a> {
940    buffer: &'a mut OwnedAlignedBuffer,
941    header_offset: u32,
942    /// Public key hash of the solution, the seal must correspond to it
943    public_key_hash: Blake3Hash,
944}
945
946impl OwnedLeafShardHeaderUnsealedRef<'_> {
947    /// Hash of the block before seal is applied to it
948    #[inline(always)]
949    pub fn pre_seal_hash(&self) -> Blake3Hash {
950        let header_bytes = self
951            .buffer
952            .as_slice()
953            .get(self.header_offset as usize..)
954            .expect("The buffer only grew since `header_offset` was its length; qed");
955        // TODO: Keyed hash with `block_header_seal` as a key
956        Blake3Hash::from(blake3::hash(header_bytes))
957    }
958
959    /// Attach the seal after the header in the buffer, which completes the header.
960    ///
961    /// Returns an error if the seal doesn't correspond to the public key hash of the solution. The
962    /// seal is not attached then, and the buffer ends with the unsealed header, which must not be
963    /// treated as a complete header.
964    #[inline(always)]
965    pub fn with_seal(self, seal: BlockHeaderSeal<'_>) -> Result<(), BlockHeaderConsistencyError> {
966        let Self {
967            buffer,
968            header_offset: _,
969            public_key_hash,
970        } = self;
971        BlockHeader::check_seal(&public_key_hash, seal)?;
972        append_seal(buffer, seal);
973
974        Ok(())
975    }
976}
977
978/// An owned version of [`BlockHeader`].
979///
980/// It is correctly aligned in memory and well suited for sending and receiving over the network
981/// efficiently or storing in memory or on disk.
982#[derive(Debug, Clone, From)]
983pub enum OwnedBlockHeader {
984    /// Block header corresponds to the beacon chain
985    BeaconChain(OwnedBeaconChainHeader),
986    /// Block header corresponds to an intermediate shard
987    IntermediateShard(OwnedIntermediateShardHeader),
988    /// Block header corresponds to a leaf shard
989    LeafShard(OwnedLeafShardHeader),
990}
991
992impl OwnedBlockHeader {
993    /// Create an owned header from a buffer
994    #[inline]
995    pub fn from_buffer(
996        buffer: SharedAlignedBuffer,
997        shard_kind: RealShardKind,
998    ) -> Result<Self, SharedAlignedBuffer> {
999        Ok(match shard_kind {
1000            RealShardKind::BeaconChain => {
1001                Self::BeaconChain(OwnedBeaconChainHeader::from_buffer(buffer)?)
1002            }
1003            RealShardKind::IntermediateShard => {
1004                Self::IntermediateShard(OwnedIntermediateShardHeader::from_buffer(buffer)?)
1005            }
1006            RealShardKind::LeafShard => Self::LeafShard(OwnedLeafShardHeader::from_buffer(buffer)?),
1007        })
1008    }
1009
1010    /// Inner buffer block header contents
1011    #[inline]
1012    pub fn buffer(&self) -> &SharedAlignedBuffer {
1013        match self {
1014            Self::BeaconChain(owned_header) => owned_header.buffer(),
1015            Self::IntermediateShard(owned_header) => owned_header.buffer(),
1016            Self::LeafShard(owned_header) => owned_header.buffer(),
1017        }
1018    }
1019
1020    /// Number of clones in memory
1021    #[inline]
1022    pub fn ref_count(&self) -> usize {
1023        match self {
1024            Self::BeaconChain(owned_header) => owned_header.ref_count(),
1025            Self::IntermediateShard(owned_header) => owned_header.ref_count(),
1026            Self::LeafShard(owned_header) => owned_header.ref_count(),
1027        }
1028    }
1029
1030    /// Get [`BlockHeader`] out of [`OwnedBlockHeader`]
1031    #[inline]
1032    pub fn header(&self) -> BlockHeader<'_> {
1033        match self {
1034            Self::BeaconChain(owned_header) => {
1035                BlockHeader::BeaconChain(owned_header.header().clone())
1036            }
1037            Self::IntermediateShard(owned_header) => {
1038                BlockHeader::IntermediateShard(owned_header.header().clone())
1039            }
1040            Self::LeafShard(owned_header) => BlockHeader::LeafShard(owned_header.header().clone()),
1041        }
1042    }
1043}