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