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