//! Marshal variant and buffer traits. //! //! This module defines the core abstractions that allow the marshal actor to work //! with different block dissemination strategies: //! //! - [`Variant`]: Describes the types used by a marshal variant //! - [`Buffer`]: Abstracts over block dissemination strategies //! //! The [`Variant`] trait expects a 1:1 mapping between the [`Variant::Commitment`] and the //! block digest, with the commitment being a superset of the digest. The commitment may //! contain extra information that can be used for optimized retrieval or variant-specific //! mechanisms, though it is required that the digest can be extracted from the commitment //! for lookup purposes. use crate::{Block, simplex::scheme::Scheme, types::Round}; use commonware_codec::{Codec, Error as CodecError, Input, Read}; use commonware_cryptography::{Digest, Digested, Digestible, PublicKey}; use commonware_p2p::Recipients; use commonware_parallel::Strategy; use commonware_utils::channel::oneshot; use std::{future::Future, marker::PhantomData}; /// A block commitment and the evidence available when decoding it. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ExpectedCommitment { /// A locally certified or finalized block commitment, or an ancestor's. /// /// Decoding reuses its expensive components instead of recomputing them. Notarization /// alone is insufficient. Trusted(C), /// A commitment without certification evidence, whose components decoding recomputes. Untrusted(C), } /// A marker trait describing the types used by a variant of Marshal. /// /// Working blocks carry their identity. A [`Variant::Block`] reads its digest and commitment from /// what it was built with, and [`Variant::into_shared`] yields a [`Digested`] application block, /// so readers never compute identity themselves. A block's identity is established once, where /// its raw form enters marshal. The application digests a block it built, /// [`Variant::decode_block`] a received block, and [`Variant::restore`] or /// [`Variant::restore_across`] a stored block. pub trait Variant: Clone + Send + Sync + 'static { /// The working block type of marshal, whose digest and consensus commitment are ready. /// /// Cloning must share the block payload, and conversion to [`Self::StoredBlock`] /// must not copy it. type Block: Block::Digest> + Into; /// The application block type, as decoded from the wire. type ApplicationBlock: Block + Codec; /// The type of block stored in the archive, as decoded from storage. /// /// Decoding only parses, so a stored block's identity is reattached from the key it was /// stored under by [`Self::restore`], or computed by [`Self::restore_across`] when the key /// is unknown. type StoredBlock: Block::Digest> + Codec::Cfg>; /// The [`Digest`] type used by consensus. type Commitment: Digest; /// Returns the consensus commitment of a block. /// /// The commitment is what validators sign over during consensus. /// /// Together with [`Variant::commitment_to_inner`], implementations must satisfy: /// `commitment_to_inner(commitment(block)) == block.digest()`. fn commitment(block: &Self::Block) -> Self::Commitment; /// Extracts the block digest from a consensus commitment. /// /// For blocks/certificates accepted by marshal in this variant instance, the digest /// must uniquely determine the commitment. In other words, there should not be two accepted /// commitments with the same inner digest. fn commitment_to_inner(commitment: Self::Commitment) -> ::Digest; /// Returns the parent commitment referenced by `block`. fn parent_commitment(block: &Self::Block) -> Self::Commitment; /// Returns the parent commitment referenced by a stored block. /// /// The parent linkage is part of the stored bytes, so walking ancestry through storage does /// not restore each block's identity. fn stored_parent_commitment(block: &Self::StoredBlock) -> Self::Commitment; /// Validates a certificate payload against the active consensus scheme. fn check_payload(scheme: &S, payload: Self::Commitment) -> bool where S: Scheme; /// Decodes a [`Self::Block`] received over the wire, failing unless [`Self::commitment`] of /// the result equals the commitment in `expected`. fn decode_block( buf: impl Input, block_cfg: &::Cfg, expected: ExpectedCommitment, strategy: &impl Strategy, ) -> impl Future> + Send; /// Restores a working block from its stored form and `digest`, the key it was stored under. fn restore( stored: Self::StoredBlock, digest: ::Digest, ) -> Self::Block; /// Restores a working block from its stored form when the key it was stored under is /// unknown, computing any identity the stored form lacks as a job on `strategy`. fn restore_across( stored: Self::StoredBlock, strategy: &impl Strategy, ) -> impl Future + Send; /// Converts a working block to the application block without copying the payload. fn into_shared(block: Self::Block) -> Digested; /// Reconstructs a working block from an application block and the trusted consensus payload /// that commits to it. /// /// Panics if `payload` does not commit to the block's digest. fn from_application_block( block: Digested, payload: Self::Commitment, ) -> Self::Block; /// Returns the largest encoded size of a [`Self::Block`] a backfill response carries for an /// application block of at most `application` encoded bytes. fn max_backfill_block_size(application: usize) -> usize; /// Returns an upper bound on the payloads the [`Buffer`] implementation provided with the /// variant sends for application blocks of at most `application` encoded bytes and a committee /// of at most `participants`. A custom [`Buffer`] implementation sizes its own payloads. fn max_buffer_payload_size(participants: u32, application: usize) -> usize; } /// A buffer for block storage and retrieval, abstracting over different /// dissemination strategies. /// /// The trait is generic over a [`Variant`] which provides the block, commitment, /// and recipient types. /// /// Lookup operations come in two forms: /// - By digest: Simple lookup using only the block hash /// - By commitment: Lookup using the full consensus commitment, which may enable /// additional retrieval mechanisms depending on the variant pub trait Buffer: Clone + Send + Sync + 'static { /// The public key type used to identify peers. type PublicKey: PublicKey; /// Attempt to find a block by its digest. /// /// Returns `Some(block)` if the block is immediately available in the buffer, /// or `None` if it is not currently cached. /// /// This is a non-blocking lookup that does not trigger network fetches. fn find_by_digest( &self, digest: ::Digest, ) -> impl Future> + Send; /// Attempt to find a block by its commitment. /// /// Returns `Some(block)` if the block is immediately available in the buffer, /// or `None` if it is not currently cached. /// /// This is a non-blocking lookup that does not trigger network fetches. /// Having the full commitment may enable additional retrieval mechanisms /// depending on the variant implementation. fn find_by_commitment( &self, commitment: V::Commitment, ) -> impl Future> + Send; /// Subscribe to a block's availability by its digest. /// /// Returns a receiver that will resolve when the block becomes available. /// If the block is already cached, the receiver may resolve immediately. /// Returns `None` when the buffer cannot provide availability notifications. /// /// Keep the subscription open while the block may still arrive. Close it only when the buffer /// shuts down or can no longer obtain the block from any source. /// /// The returned receiver can be dropped to cancel the subscription. fn subscribe_by_digest( &self, digest: ::Digest, ) -> Option>; /// Subscribe to a block's availability by its commitment. /// /// Returns a receiver that will resolve when the block becomes available. /// If the block is already cached, the receiver may resolve immediately. /// Returns `None` when the buffer cannot provide availability notifications. /// /// Having the full commitment may enable additional retrieval mechanisms /// depending on the variant implementation. /// /// Keep the subscription open while the block may still arrive. Close it only when the buffer /// shuts down or can no longer obtain the block from any source. /// /// The returned receiver can be dropped to cancel the subscription. fn subscribe_by_commitment( &self, commitment: V::Commitment, ) -> Option>; /// Send a block to peers. fn send(&self, round: Round, block: V::Block, recipients: Recipients); } /// A buffer implementation that never stores, subscribes, or sends blocks. pub(super) struct NoBuffer

{ _public_key: PhantomData P>, } impl

NoBuffer

{ pub(super) const fn new() -> Self { Self { _public_key: PhantomData, } } } impl

Clone for NoBuffer

{ fn clone(&self) -> Self { *self } } impl

Copy for NoBuffer

{} impl Buffer for NoBuffer

where V: Variant, P: PublicKey, { type PublicKey = P; async fn find_by_digest(&self, _: ::Digest) -> Option { None } async fn find_by_commitment(&self, _: V::Commitment) -> Option { None } fn subscribe_by_digest( &self, _: ::Digest, ) -> Option> { None } fn subscribe_by_commitment(&self, _: V::Commitment) -> Option> { None } fn send(&self, _: Round, _: V::Block, _: Recipients) {} }