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}