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}