//! Standard variant implementation for Marshal. //! //! The standard variant broadcasts complete blocks to all peers. Each validator //! receives the full block directly from the proposer or via gossip. use crate::{ Block, Heightable, marshal::{ ancestry::BlockProvider, core::{Buffer, CommitmentFallback, ExpectedCommitment, Mailbox, Variant}, }, simplex::scheme::Scheme as SimplexScheme, types::Round, }; use commonware_broadcast::{Broadcaster as _, buffered}; use commonware_codec::{Codec, Error as CodecError, Input, Read}; use commonware_cryptography::{Digested, Digestible, PublicKey, certificate::Scheme}; use commonware_p2p::Recipients; use commonware_parallel::Strategy; use commonware_utils::channel::oneshot; use std::{future::Future, sync::Arc}; /// The standard variant of Marshal, which broadcasts complete blocks. /// /// This variant sends the entire block to all peers. #[derive(Default, Clone, Copy)] pub struct Standard(std::marker::PhantomData); impl Variant for Standard where B: Block + Codec, { type ApplicationBlock = B; type Block = Digested; type StoredBlock = Arc; type Commitment = ::Digest; fn commitment(block: &Self::Block) -> Self::Commitment { // Standard variant commitment is exactly the block digest. block.digest() } fn commitment_to_inner(commitment: Self::Commitment) -> ::Digest { // Trivial left-inverse: digest == commitment in this variant. commitment } fn parent_commitment(block: &Self::Block) -> Self::Commitment { // In standard mode, commitments are digests, so parent commitment is parent digest. block.parent() } fn stored_parent_commitment(block: &Self::StoredBlock) -> Self::Commitment { block.parent() } fn check_payload(_scheme: &S, _payload: Self::Commitment) -> bool where S: SimplexScheme, { true } fn decode_block( buf: impl Input, block_cfg: &::Cfg, expected: ExpectedCommitment, strategy: &impl Strategy, ) -> impl Future> + Send { let (ExpectedCommitment::Trusted(digest) | ExpectedCommitment::Untrusted(digest)) = expected; let compute = B::decode_cfg(buf, block_cfg).map(|block| Digested::compute(block, strategy)); async move { let block = compute?.await; if block.digest() != digest { return Err(CodecError::Invalid("Block", "digest mismatch")); } Ok(block) } } fn restore( stored: Self::StoredBlock, digest: ::Digest, ) -> Self::Block { Digested::precomputed(stored, digest) } fn restore_across( stored: Self::StoredBlock, strategy: &impl Strategy, ) -> impl Future + Send { Digested::compute(stored, strategy) } fn into_shared(block: Self::Block) -> Digested { block } fn from_application_block( block: Digested, payload: Self::Commitment, ) -> Self::Block { assert_eq!(block.digest(), payload, "payload does not commit to block"); block } fn max_backfill_block_size(application: usize) -> usize { application } fn max_buffer_payload_size(_participants: u32, application: usize) -> usize { // The buffered broadcast sends each block whole. application } } impl Buffer> for buffered::Mailbox where B: Block + Codec, K: PublicKey, { type PublicKey = K; async fn find_by_digest(&self, digest: B::Digest) -> Option> { self.get(digest).await } async fn find_by_commitment(&self, commitment: B::Digest) -> Option> { self.find_by_digest(commitment).await } fn subscribe_by_digest(&self, digest: B::Digest) -> Option>> { Some(self.subscribe(digest)) } fn subscribe_by_commitment( &self, commitment: B::Digest, ) -> Option>> { self.subscribe_by_digest(commitment) } fn send(&self, _round: Round, block: Digested, recipients: Recipients) { self.broadcast(recipients, block); } } impl BlockProvider for Mailbox> where S: Scheme, B: Block + Codec, { type Block = B; fn subscribe_parent( &self, block: &Digested, ) -> impl Future>> + Send + 'static { let receiver = block.height().previous().map(|parent_height| { self.subscribe_by_commitment( block.parent(), CommitmentFallback::FetchByCommitment { height: parent_height, }, ) }); async move { receiver?.await.ok() } } } #[cfg(test)] mod tests { use super::*; use crate::{marshal::mocks::block::Block as MockBlock, types::Height}; use commonware_codec::Encode; use commonware_cryptography::{ Digest as _, Hasher as _, sha256::{Digest as Sha256Digest, Sha256}, }; use commonware_parallel::Sequential; use futures::executor::block_on; type TestBlock = MockBlock; type TestVariant = Standard; #[test] fn decode_block_binds_digest() { let block = TestBlock::new::(0, Sha256Digest::EMPTY, Height::new(1), 0); let encoded = block.encode(); for expected in [ ExpectedCommitment::Trusted(block.digest()), ExpectedCommitment::Untrusted(block.digest()), ] { let decoded = block_on(TestVariant::decode_block( encoded.clone(), &(), expected, &Sequential, )) .unwrap(); assert_eq!(decoded.inner(), &block); assert_eq!(decoded.digest(), block.digest()); } // A digest the bytes do not hash to is rejected, whether or not it is trusted. let other = Sha256::hash(&[b"other block"]); for expected in [ ExpectedCommitment::Trusted(other), ExpectedCommitment::Untrusted(other), ] { assert!( block_on(TestVariant::decode_block( encoded.clone(), &(), expected, &Sequential )) .is_err() ); } } }