1use crate::{
2 CertifiableBlock,
3 marshal::{
4 ancestry::BlockProvider,
5 coding::{
6 shards,
7 types::{CodedBlock, CodedBlockCfg, StoredCodedBlock, coding_config_for_participants},
8 },
9 core::{Buffer, CommitmentFallback, Mailbox, Retirement, Variant},
10 },
11 simplex::{scheme::Scheme as SimplexScheme, types::Context},
12 types::{Round, coding::Commitment},
13};
14use commonware_codec::Read;
15use commonware_coding::Scheme as CodingScheme;
16use commonware_cryptography::{Committable, Digestible, Hasher, PublicKey, certificate::Scheme};
17use commonware_p2p::Recipients;
18use commonware_utils::channel::oneshot;
19use std::{future::Future, sync::Arc};
20
21#[derive(Default)]
26pub struct Coding<B, C, H, P>(std::marker::PhantomData<(B, C, H, P)>)
27where
28 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
29 C: CodingScheme,
30 H: Hasher,
31 P: PublicKey;
32
33impl<B, C, H, P> Clone for Coding<B, C, H, P>
34where
35 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
36 C: CodingScheme,
37 H: Hasher,
38 P: PublicKey,
39{
40 fn clone(&self) -> Self {
41 *self
42 }
43}
44
45impl<B, C, H, P> Copy for Coding<B, C, H, P>
46where
47 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
48 C: CodingScheme,
49 H: Hasher,
50 P: PublicKey,
51{
52}
53
54impl<B, C, H, P> Variant for Coding<B, C, H, P>
55where
56 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
57 C: CodingScheme,
58 H: Hasher,
59 P: PublicKey,
60{
61 type ApplicationBlock = B;
62 type Block = CodedBlock<B, C, H>;
63 type StoredBlock = StoredCodedBlock<B, C, H>;
64 type Commitment = Commitment<B, C, H>;
65
66 fn commitment(block: &Self::Block) -> Self::Commitment {
67 block.commitment()
69 }
70
71 fn stored_commitment(block: &Self::StoredBlock) -> Self::Commitment {
72 block.commitment()
73 }
74
75 fn commitment_to_inner(commitment: Self::Commitment) -> <Self::Block as Digestible>::Digest {
76 commitment.block()
78 }
79
80 fn parent_commitment(block: &Self::Block) -> Self::Commitment {
81 block.context().parent.1
83 }
84
85 fn check_payload<S>(scheme: &S, payload: Self::Commitment) -> bool
86 where
87 S: SimplexScheme<Self::Commitment>,
88 {
89 let n_participants = u16::try_from(scheme.participants().len())
90 .expect("scheme must have at most 2^16-1 participants");
91 payload.config() == coding_config_for_participants(n_participants)
92 }
93
94 fn block_cfg(
95 block_cfg: &<Self::ApplicationBlock as Read>::Cfg,
96 expected: Self::Commitment,
97 ) -> <Self::Block as Read>::Cfg {
98 CodedBlockCfg {
99 inner: block_cfg.clone(),
100 expected,
101 }
102 }
103
104 fn into_inner(block: Self::Block) -> Self::ApplicationBlock {
105 block.into_inner()
106 }
107
108 fn into_inner_shared(block: Arc<Self::Block>) -> Arc<Self::ApplicationBlock> {
109 block.inner_shared()
110 }
111
112 fn owned_into_inner_shared(block: Self::Block) -> Arc<Self::ApplicationBlock> {
113 block.into_inner_shared()
114 }
115
116 fn from_application_block(
117 block: Self::ApplicationBlock,
118 payload: Self::Commitment,
119 ) -> Self::Block {
120 CodedBlock::new_trusted(block, payload)
121 }
122}
123
124impl<B, C, H, P> Buffer<Coding<B, C, H, P>> for shards::Mailbox<B, C, H, P>
125where
126 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
127 C: CodingScheme,
128 H: Hasher,
129 P: PublicKey,
130{
131 type PublicKey = P;
132
133 async fn find_by_digest(
134 &self,
135 digest: <CodedBlock<B, C, H> as Digestible>::Digest,
136 ) -> Option<Arc<CodedBlock<B, C, H>>> {
137 self.get_by_digest(digest).await
138 }
139
140 async fn find_by_commitment(
141 &self,
142 commitment: Commitment<B, C, H>,
143 ) -> Option<Arc<CodedBlock<B, C, H>>> {
144 self.get(commitment).await
145 }
146
147 fn subscribe_by_digest(
148 &self,
149 digest: <CodedBlock<B, C, H> as Digestible>::Digest,
150 ) -> Option<oneshot::Receiver<Arc<CodedBlock<B, C, H>>>> {
151 Some(self.subscribe_by_digest(digest))
152 }
153
154 fn subscribe_by_commitment(
155 &self,
156 commitment: Commitment<B, C, H>,
157 ) -> Option<oneshot::Receiver<Arc<CodedBlock<B, C, H>>>> {
158 Some(self.subscribe(commitment))
159 }
160
161 fn retire(&self, update: Retirement<Commitment<B, C, H>>) {
162 Self::retire(self, update);
163 }
164
165 fn send(&self, round: Round, block: Arc<CodedBlock<B, C, H>>, _recipients: Recipients<P>) {
166 self.proposed_shared(round, block);
168 }
169}
170
171impl<S, B, C, H, P> BlockProvider for Mailbox<S, Coding<B, C, H, P>>
172where
173 S: Scheme,
174 B: CertifiableBlock<Context = Context<Commitment<B, C, H>, P>>,
175 C: CodingScheme,
176 H: Hasher,
177 P: PublicKey,
178{
179 type Block = B;
180
181 fn subscribe_parent(
182 &self,
183 block: &Self::Block,
184 ) -> impl Future<Output = Option<Arc<Self::Block>>> + Send + 'static {
185 let receiver = block.height().previous().map(|parent_height| {
186 self.subscribe_by_commitment(
187 block.context().parent.1,
188 CommitmentFallback::FetchByCommitment {
189 height: parent_height,
190 },
191 )
192 });
193 async move { receiver?.await.ok().map(|block| block.inner_shared()) }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::{
201 marshal::{coding::types::StoredCodedBlock, mocks::block::Block},
202 types::{Epoch, Height, View},
203 };
204 use bytes::{Buf, BufMut};
205 use commonware_codec::{EncodeSize, Error, Read, Write};
206 use commonware_coding::{Config as CodingConfig, ReedSolomon};
207 use commonware_cryptography::{
208 Digest as _, Digestible, Signer as _,
209 ed25519::{PrivateKey, PublicKey},
210 sha256::{Digest as Sha256Digest, Sha256},
211 };
212 use commonware_math::algebra::Random;
213 use commonware_parallel::Sequential;
214 use commonware_utils::{NZU16, test_rng};
215
216 type TestCommitment = Commitment<NoCloneBlock, ReedSolomon<Sha256>, Sha256>;
217 type TestContext = Context<TestCommitment, PublicKey>;
218 type InnerBlock = Block<Sha256Digest, TestContext>;
219
220 struct NoCloneBlock {
221 inner: InnerBlock,
222 }
223
224 impl Clone for NoCloneBlock {
225 fn clone(&self) -> Self {
226 panic!("stored commitment lookup must not clone the inner block");
227 }
228 }
229
230 impl Write for NoCloneBlock {
231 fn write(&self, writer: &mut impl BufMut) {
232 self.inner.write(writer);
233 }
234 }
235
236 impl Read for NoCloneBlock {
237 type Cfg = ();
238
239 fn read_cfg(reader: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, Error> {
240 Ok(Self {
241 inner: InnerBlock::read_cfg(reader, cfg)?,
242 })
243 }
244 }
245
246 impl EncodeSize for NoCloneBlock {
247 fn encode_size(&self) -> usize {
248 self.inner.encode_size()
249 }
250 }
251
252 impl Digestible for NoCloneBlock {
253 type Digest = Sha256Digest;
254
255 fn digest(&self) -> Self::Digest {
256 self.inner.digest()
257 }
258 }
259
260 impl crate::Heightable for NoCloneBlock {
261 fn height(&self) -> Height {
262 self.inner.height
263 }
264 }
265
266 impl crate::Block for NoCloneBlock {
267 fn parent(&self) -> Self::Digest {
268 self.inner.parent
269 }
270 }
271
272 impl CertifiableBlock for NoCloneBlock {
273 type Context = TestContext;
274
275 fn context(&self) -> Self::Context {
276 self.inner.context.clone()
277 }
278 }
279
280 fn no_clone_block(config: CodingConfig) -> NoCloneBlock {
281 let mut rng = test_rng();
282 let leader = PrivateKey::random(&mut rng).public_key();
283 let parent_commitment = TestCommitment::from((
284 Sha256Digest::EMPTY,
285 Sha256Digest::EMPTY,
286 Sha256Digest::EMPTY,
287 config,
288 ));
289 let context = Context {
290 round: Round::new(Epoch::new(1), View::new(2)),
291 leader,
292 parent: (View::new(1), parent_commitment),
293 };
294 let inner = InnerBlock::new::<Sha256>(
295 context,
296 Sha256::hash(&[b"parent"]),
297 Height::new(7),
298 1_234_567,
299 );
300 NoCloneBlock { inner }
301 }
302
303 #[test]
304 fn stored_commitment_does_not_clone_coding_block() {
305 const CONFIG: CodingConfig = CodingConfig {
306 minimum_shards: NZU16!(1),
307 extra_shards: NZU16!(2),
308 };
309
310 type TestScheme = ReedSolomon<Sha256>;
311 type TestVariant = Coding<NoCloneBlock, TestScheme, Sha256, PublicKey>;
312
313 let block = no_clone_block(CONFIG);
314 let coded = CodedBlock::<NoCloneBlock, TestScheme, Sha256>::new(block, CONFIG, &Sequential);
315 let expected = coded.commitment();
316 let stored = StoredCodedBlock::new(coded);
317
318 assert_eq!(TestVariant::stored_commitment(&stored), expected);
319 }
320}