Skip to main content

commonware_consensus/marshal/core/
variant.rs

1//! Marshal variant and buffer traits.
2//!
3//! This module defines the core abstractions that allow the marshal actor to work
4//! with different block dissemination strategies:
5//!
6//! - [`Variant`]: Describes the types used by a marshal variant
7//! - [`Buffer`]: Abstracts over block dissemination strategies
8//!
9//! The [`Variant`] trait expects a 1:1 mapping between the [`Variant::Commitment`] and the
10//! block digest, with the commitment being a superset of the digest. The commitment may
11//! contain extra information that can be used for optimized retrieval or variant-specific
12//! mechanisms, though it is required that the digest can be extracted from the commitment
13//! for lookup purposes.
14
15use crate::{types::Round, Block};
16use commonware_codec::{Codec, Read};
17use commonware_cryptography::{Digest, Digestible, PublicKey};
18use commonware_p2p::Recipients;
19use commonware_utils::channel::oneshot;
20use std::{future::Future, sync::Arc};
21
22/// A marker trait describing the types used by a variant of Marshal.
23pub trait Variant: Clone + Send + Sync + 'static {
24    /// The working block type of marshal, supporting the consensus commitment.
25    ///
26    /// Must be convertible to `StoredBlock` via `Into` for archival.
27    type Block: Block<Digest = <Self::ApplicationBlock as Digestible>::Digest>
28        + Into<Self::StoredBlock>
29        + Clone;
30
31    /// The application block type.
32    type ApplicationBlock: Block + Clone;
33
34    /// The type of block stored in the archive.
35    ///
36    /// Must be convertible back to the working block type via `Into`.
37    type StoredBlock: Block<Digest = <Self::Block as Digestible>::Digest>
38        + Into<Self::Block>
39        + Clone
40        + Codec<Cfg = <Self::Block as Read>::Cfg>;
41
42    /// The [`Digest`] type used by consensus.
43    type Commitment: Digest;
44
45    /// Computes the consensus commitment for a block.
46    ///
47    /// The commitment is what validators sign over during consensus.
48    ///
49    /// Together with [`Variant::commitment_to_inner`], implementations must satisfy:
50    /// `commitment_to_inner(commitment(block)) == block.digest()`.
51    fn commitment(block: &Self::Block) -> Self::Commitment;
52
53    /// Extracts the block digest from a consensus commitment.
54    ///
55    /// For blocks/certificates accepted by marshal in this variant instance, the digest
56    /// must uniquely determine the commitment. In other words, there should not be two accepted
57    /// commitments with the same inner digest.
58    fn commitment_to_inner(commitment: Self::Commitment) -> <Self::Block as Digestible>::Digest;
59
60    /// Returns the parent commitment referenced by `block`.
61    fn parent_commitment(block: &Self::Block) -> Self::Commitment;
62
63    /// Converts a working block to an application block.
64    ///
65    /// This conversion cannot use `Into` due to orphan rules when `Block` wraps
66    /// `ApplicationBlock` (e.g., `CodedBlock<B, C, H> -> B`).
67    fn into_inner(block: Self::Block) -> Self::ApplicationBlock;
68}
69
70/// A buffer for block storage and retrieval, abstracting over different
71/// dissemination strategies.
72///
73/// The trait is generic over a [`Variant`] which provides the block, commitment,
74/// and recipient types.
75///
76/// Lookup operations come in two forms:
77/// - By digest: Simple lookup using only the block hash
78/// - By commitment: Lookup using the full consensus commitment, which may enable
79///   additional retrieval mechanisms depending on the variant
80pub trait Buffer<V: Variant>: Clone + Send + Sync + 'static {
81    /// The public key type used to identify peers.
82    type PublicKey: PublicKey;
83
84    /// The cached block type held internally by the buffer.
85    ///
86    /// This allows buffers to use efficient internal representations (e.g., `Arc<Block>`)
87    /// while providing conversion to the underlying block type via [`IntoBlock`].
88    type CachedBlock: IntoBlock<V::Block>;
89
90    /// Attempt to find a block by its digest.
91    ///
92    /// Returns `Some(block)` if the block is immediately available in the buffer,
93    /// or `None` if it is not currently cached.
94    ///
95    /// This is a non-blocking lookup that does not trigger network fetches.
96    fn find_by_digest(
97        &self,
98        digest: <V::Block as Digestible>::Digest,
99    ) -> impl Future<Output = Option<Self::CachedBlock>> + Send;
100
101    /// Attempt to find a block by its commitment.
102    ///
103    /// Returns `Some(block)` if the block is immediately available in the buffer,
104    /// or `None` if it is not currently cached.
105    ///
106    /// This is a non-blocking lookup that does not trigger network fetches.
107    /// Having the full commitment may enable additional retrieval mechanisms
108    /// depending on the variant implementation.
109    fn find_by_commitment(
110        &self,
111        commitment: V::Commitment,
112    ) -> impl Future<Output = Option<Self::CachedBlock>> + Send;
113
114    /// Subscribe to a block's availability by its digest.
115    ///
116    /// Returns a receiver that will resolve when the block becomes available.
117    /// If the block is already cached, the receiver may resolve immediately.
118    ///
119    /// The returned receiver can be dropped to cancel the subscription.
120    fn subscribe_by_digest(
121        &self,
122        digest: <V::Block as Digestible>::Digest,
123    ) -> impl Future<Output = oneshot::Receiver<Self::CachedBlock>> + Send;
124
125    /// Subscribe to a block's availability by its commitment.
126    ///
127    /// Returns a receiver that will resolve when the block becomes available.
128    /// If the block is already cached, the receiver may resolve immediately.
129    ///
130    /// Having the full commitment may enable additional retrieval mechanisms
131    /// depending on the variant implementation.
132    ///
133    /// The returned receiver can be dropped to cancel the subscription.
134    fn subscribe_by_commitment(
135        &self,
136        commitment: V::Commitment,
137    ) -> impl Future<Output = oneshot::Receiver<Self::CachedBlock>> + Send;
138
139    /// Notify the buffer that a block has been finalized.
140    ///
141    /// This allows the buffer to perform variant-specific cleanup operations.
142    fn finalized(&self, commitment: V::Commitment) -> impl Future<Output = ()> + Send;
143
144    /// Send a block to peers.
145    fn send(
146        &self,
147        round: Round,
148        block: V::Block,
149        recipients: Recipients<Self::PublicKey>,
150    ) -> impl Future<Output = ()> + Send;
151}
152
153/// A trait for cached block types that can be converted to the underlying block.
154///
155/// This trait allows buffer implementations to use efficient internal representations
156/// (e.g., `Arc<Block>`) while providing a uniform way to extract the block.
157pub trait IntoBlock<B>: Clone + Send {
158    /// Convert this cached block into the underlying block type.
159    fn into_block(self) -> B;
160}
161
162/// Blanket implementation for any cloneable block type.
163///
164/// This covers the standard variant where `CachedBlock = B`.
165impl<B: Clone + Send> IntoBlock<B> for B {
166    fn into_block(self) -> B {
167        self
168    }
169}
170
171/// Implementation for `Arc<B>` to support the coding variant.
172///
173/// Uses `Arc::unwrap_or_clone` to avoid cloning when the refcount is 1.
174impl<B: Clone + Send + Sync> IntoBlock<B> for Arc<B> {
175    fn into_block(self) -> B {
176        Self::unwrap_or_clone(self)
177    }
178}