use crate::{
Application, Automaton, Block, CertifiableAutomaton, CertifiableBlock, Epochable, Handoff,
Heightable, Relay, Reporter,
marshal::{
ancestry::Ancestry,
application::{
build::{self, Resolved},
gates::{self, GateOutcome, Gates},
relay,
validation::{
Stage, is_block_in_expected_epoch, is_inferred_reproposal_at_certify,
is_valid_reproposal_at_verify,
},
},
coding::{
Coding, shards,
types::{CodedBlock, coding_config_for_participants, hash_context},
validation::{ProposalError, validate_block, validate_proposal},
},
core,
},
simplex::{
Plan,
scheme::Scheme,
types::{Activity, Context},
},
types::{Epoch, Epocher, Round, View, coding::Commitment},
};
use commonware_actor::Feedback;
use commonware_codec::Codec;
use commonware_coding::{Config as CodingConfig, Scheme as CodingScheme};
use commonware_cryptography::{
Committable, Digested, Digestible, Hasher,
certificate::{Provider, Scheme as _, Verifier},
};
use commonware_macros::select;
use commonware_parallel::Strategy;
use commonware_runtime::{
Clock, Metrics, Spawner,
telemetry::{
metrics::{
MetricsExt as _,
histogram::{Buckets, Timed},
},
traces::TracedExt as _,
},
};
use commonware_utils::channel::{fallible::OneshotExt, oneshot};
use futures::future::Either;
use std::{future::Future, sync::Arc};
use tracing::{Instrument as _, debug, info_span, warn};
type CodingResolved<B, C, H, A> =
Resolved<Commitment<B, C, H>, Arc<CodedBlock<B, C, H>>, A, CodingConfig>;
#[allow(clippy::type_complexity)]
pub struct MarshaledConfig<A, B, C, H, Z, S, ES>
where
B: CertifiableBlock<Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>>
+ Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
pub application: A,
pub marshal: core::Mailbox<Z::Scheme, Coding<B, C, H, <Z::Scheme as Verifier>::PublicKey>>,
pub shards: shards::Mailbox<B, C, H, <Z::Scheme as Verifier>::PublicKey>,
pub scheme_provider: Z,
pub strategy: S,
pub epocher: ES,
}
#[allow(clippy::type_complexity)]
pub struct Marshaled<E, A, B, C, H, Z, S, ES>
where
A: Application<E>,
B: CertifiableBlock<Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>>
+ Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
context: Arc<E>,
application: A,
marshal: core::Mailbox<Z::Scheme, Coding<B, C, H, <Z::Scheme as Verifier>::PublicKey>>,
shards: shards::Mailbox<B, C, H, <Z::Scheme as Verifier>::PublicKey>,
scheme_provider: Z,
epocher: ES,
strategy: S,
gates: Gates<Commitment<B, C, H>, Arc<CodedBlock<B, C, H>>>,
build_duration: Timed,
verify_duration: Timed,
proposal_parent_fetch_duration: Timed,
ancestor_fetch_duration: Timed,
erasure_encode_duration: Timed,
}
impl<E, A, B, C, H, Z, S, ES> Clone for Marshaled<E, A, B, C, H, Z, S, ES>
where
A: Application<E>,
B: CertifiableBlock<Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>>
+ Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
fn clone(&self) -> Self {
Self {
context: self.context.clone(),
application: self.application.clone(),
marshal: self.marshal.clone(),
shards: self.shards.clone(),
scheme_provider: self.scheme_provider.clone(),
epocher: self.epocher.clone(),
strategy: self.strategy.clone(),
gates: self.gates.clone(),
build_duration: self.build_duration.clone(),
verify_duration: self.verify_duration.clone(),
proposal_parent_fetch_duration: self.proposal_parent_fetch_duration.clone(),
ancestor_fetch_duration: self.ancestor_fetch_duration.clone(),
erasure_encode_duration: self.erasure_encode_duration.clone(),
}
}
}
impl<E, A, B, C, H, Z, S, ES> Marshaled<E, A, B, C, H, Z, S, ES>
where
E: Spawner + Metrics + Clock,
A: Application<
E,
Block = B,
SigningScheme = Z::Scheme,
Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
Input = (),
>,
B: CertifiableBlock<Context = <A as Application<E>>::Context> + Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
pub fn new(context: E, cfg: MarshaledConfig<A, B, C, H, Z, S, ES>) -> Self {
let MarshaledConfig {
application,
marshal,
shards,
scheme_provider,
strategy,
epocher,
} = cfg;
let build_histogram = context.histogram(
"build_duration",
"Histogram of time taken for the application to build a new block, in seconds",
Buckets::LOCAL,
);
let build_duration = Timed::new(build_histogram);
let verify_histogram = context.histogram(
"verify_duration",
"Histogram of time taken for the application to verify a block, in seconds",
Buckets::LOCAL,
);
let verify_duration = Timed::new(verify_histogram);
let parent_fetch_histogram = context.histogram(
"parent_fetch_duration",
"Histogram of time taken to fetch a parent block in proposal, in seconds",
Buckets::LOCAL,
);
let proposal_parent_fetch_duration = Timed::new(parent_fetch_histogram);
let ancestor_fetch_histogram = context.histogram(
"ancestor_fetch_duration",
"Histogram of time taken to fetch a block via the ancestry stream, in seconds",
Buckets::LOCAL,
);
let ancestor_fetch_duration = Timed::new(ancestor_fetch_histogram);
let erasure_histogram = context.histogram(
"erasure_encode_duration",
"Histogram of time taken to erasure encode a built block on the strategy, in seconds",
Buckets::LOCAL,
);
let erasure_encode_duration = Timed::new(erasure_histogram);
Self {
context: Arc::new(context),
application,
marshal,
shards,
scheme_provider,
strategy,
epocher,
gates: Gates::new(),
build_duration,
verify_duration,
proposal_parent_fetch_duration,
ancestor_fetch_duration,
erasure_encode_duration,
}
}
fn deferred_verify(
&self,
consensus_context: Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
commitment: Commitment<B, C, H>,
prefetched_block: Option<Arc<CodedBlock<B, C, H>>>,
stage: Stage,
) -> oneshot::Receiver<GateOutcome> {
let marshal = self.marshal.clone();
let mut application = self.application.clone();
let epocher = self.epocher.clone();
let verify_duration = self.verify_duration.clone();
let ancestor_fetch_duration = self.ancestor_fetch_duration.clone();
let candidate = prefetched_block.map_or_else(
|| {
Either::Right(
marshal.subscribe_by_commitment(commitment, core::CommitmentFallback::Wait),
)
},
Either::Left,
);
let (mut tx, rx) = oneshot::channel();
let context = self
.context
.child("deferred_verify")
.with_attribute("round", consensus_context.round);
let span = info_span!(
"marshal.coding.verify.deferred",
round = %consensus_context.round,
commitment = %commitment
);
context.spawn(move |runtime_context| {
async move {
let round = consensus_context.round;
let (parent_view, parent_commitment) = consensus_context.parent;
let parent_request = marshal.subscribe_by_commitment(
parent_commitment,
core::CommitmentFallback::FetchByRound {
round: Round::new(consensus_context.epoch(), parent_view),
},
);
let block = match candidate {
Either::Left(block) => block,
Either::Right(block_request) => select! {
_ = tx.closed() => {
debug!(
reason = "consensus dropped receiver",
"skipping verification"
);
return;
},
result = block_request => match result {
Ok(block) => block,
Err(_) => {
debug!(reason = "block unavailable", "skipping verification");
return;
}
},
},
};
let store = stage.store(&marshal, round, Arc::clone(&block));
let verify = async {
let parent = select! {
_ = tx.closed() => {
debug!(
reason = "consensus dropped receiver",
"skipping verification"
);
return None;
},
result = parent_request => match result {
Ok(parent) => parent,
Err(_) => {
debug!(reason = "failed to fetch parent", "skipping verification");
return None;
}
},
};
if let Err(err) = validate_block(
&epocher,
block.as_ref(),
parent.as_ref(),
&consensus_context,
commitment,
parent_commitment,
) {
debug!(
?err,
expected_commitment = %commitment,
block_commitment = %block.commitment(),
expected_parent_commitment = %parent_commitment,
parent_commitment = %parent.commitment(),
expected_parent = %parent.digest(),
block_parent = %block.parent(),
parent_height = %parent.height(),
block_height = %block.height(),
"block failed coded invariant validation"
);
return Some(false);
}
let ancestry_stream = marshal.ancestor_stream(
Arc::new(runtime_context.child("ancestor_stream")),
[block.inner_digested(), parent.inner_digested()],
ancestor_fetch_duration,
);
let validity_request = application
.verify(
(
runtime_context.child("app_verify"),
consensus_context.clone(),
),
ancestry_stream,
)
.instrument(info_span!(
"marshal.coding.application.verify",
round = %consensus_context.round,
commitment = %commitment,
parent_view = parent_view.traced(),
parent = %parent_commitment
));
let timer = verify_duration.timer(&runtime_context);
let result = select! {
_ = tx.closed() => {
debug!(
reason = "consensus dropped receiver",
"skipping verification"
);
None
},
is_valid = validity_request => Some(is_valid),
};
timer.observe(&runtime_context);
result
};
let (verdict, durable) = futures::join!(verify, store);
if let Some(application_valid) = gates::resolve(verdict, durable) {
tx.send_lossy(GateOutcome::Ready(application_valid));
}
}
.instrument(span)
});
rx
}
fn certify_from_embedded_context(
&self,
round: Round,
payload: Commitment<B, C, H>,
) -> oneshot::Receiver<bool> {
self.shards.notarized(payload, round);
debug!(
?round,
?payload,
"subscribing to block for certification using embedded context"
);
let block_rx = self
.marshal
.subscribe_by_commitment(payload, core::CommitmentFallback::FetchByRound { round });
let marshaled = self.clone();
let shards = self.shards.clone();
let (mut tx, rx) = oneshot::channel();
let context = self.context.child("certify").with_attribute("round", round);
context.spawn(move |_| {
async move {
let block = select! {
_ = tx.closed() => {
debug!(
reason = "consensus dropped receiver",
"skipping certification"
);
return;
},
result = block_rx => match result {
Ok(block) => block,
Err(_) => {
debug!(
?payload,
reason = "failed to fetch block for certification",
"skipping certification"
);
return;
}
},
};
let embedded_context = block.context();
let is_reproposal = is_inferred_reproposal_at_certify(
&marshaled.epocher,
block.height(),
embedded_context.round,
round,
);
if is_reproposal {
if !marshaled.marshal.certified(round, block).await {
return;
}
tx.send_lossy(true);
return;
}
shards.discovered(
payload,
embedded_context.leader.clone(),
embedded_context.round,
);
let verify_rx = marshaled.deferred_verify(
embedded_context,
payload,
Some(block),
Stage::Certified,
);
gates::forward(tx, verify_rx, |result| match result {
GateOutcome::Ready(result) => Some(result),
GateOutcome::Recover => None,
})
.await;
}
.instrument(info_span!(
"marshal.coding.certify.embedded",
round = %round,
commitment = %payload
))
});
rx
}
fn certify_from_existing_task(
&self,
round: Round,
payload: Commitment<B, C, H>,
task: oneshot::Receiver<GateOutcome>,
) -> oneshot::Receiver<bool> {
self.shards.notarized(payload, round);
self.marshal.hint_notarized(round, payload);
let marshaled = self.clone();
let (tx, rx) = oneshot::channel();
let context = self
.context
.child("certify_existing")
.with_attribute("round", round);
context.spawn(move |_| {
gates::drive(tx, task, round, payload, move || {
marshaled.certify_from_embedded_context(round, payload)
})
.instrument(info_span!(
"marshal.coding.certify.existing",
round = %round,
commitment = %payload
))
});
rx
}
fn checks(
&self,
consensus_context: Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
parent_fallback: core::CommitmentFallback,
) -> impl Future<Output = CodingResolved<B, C, H, impl Ancestry<B>>> + Send + 'static {
let scheme_provider = self.scheme_provider.clone();
let context = self.context.clone();
let marshal = self.marshal.clone();
let epocher = self.epocher.clone();
let proposal_parent_fetch_duration = self.proposal_parent_fetch_duration.clone();
let ancestor_fetch_duration = self.ancestor_fetch_duration.clone();
let build_duration = self.build_duration.clone();
async move {
let Some(scheme) = scheme_provider.scheme(consensus_context.epoch()) else {
debug!(round = %consensus_context.round, "no scheme for epoch, skipping proposal");
return Resolved::Skip;
};
let n_participants =
u16::try_from(scheme.participants().len()).expect("too many participants");
let coding_config = coding_config_for_participants(n_participants);
let (parent_view, parent_commitment) = consensus_context.parent;
let parent_request =
marshal.subscribe_by_commitment(parent_commitment, parent_fallback);
let parent_timer = proposal_parent_fetch_duration.timer(&*context);
let Ok(parent) = parent_request.await else {
debug!(
?parent_commitment,
reason = "failed to fetch parent block",
"skipping proposal"
);
return Resolved::Skip;
};
parent_timer.observe(&*context);
if parent_view != View::zero()
&& !is_block_in_expected_epoch(&epocher, parent.height(), consensus_context.epoch())
{
debug!(
?parent_commitment,
reason = "parent is outside the epoch",
"skipping proposal"
);
return Resolved::Skip;
}
let last_in_epoch = epocher
.last(consensus_context.epoch())
.expect("current epoch should exist");
if parent.height() == last_in_epoch {
return Resolved::Reuse(parent.commitment(), parent);
}
let ancestor_stream = marshal.ancestor_stream(
context.clone(),
[parent.inner_digested()],
ancestor_fetch_duration,
);
Resolved::Build(
ancestor_stream,
build_duration.timer(&*context),
coding_config,
)
}
}
}
impl<E, A, B, C, H, Z, S, ES> Automaton for Marshaled<E, A, B, C, H, Z, S, ES>
where
E: Spawner + Metrics + Clock,
A: Application<
E,
Block = B,
SigningScheme = Z::Scheme,
Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
Input = (),
>,
B: CertifiableBlock<Context = <A as Application<E>>::Context> + Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
type Digest = Commitment<B, C, H>;
type Context = Context<Self::Digest, <Z::Scheme as Verifier>::PublicKey>;
#[allow(clippy::async_yields_async)]
#[tracing::instrument(name = "marshal.coding.propose", level = "info", skip_all, fields(round = %consensus_context.round))]
async fn propose(
&mut self,
consensus_context: Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
) -> oneshot::Receiver<Self::Digest> {
let strategy = self.strategy.clone();
let erasure_encode_duration = self.erasure_encode_duration.clone();
let clock = self.context.clone();
let (parent_view, parent_commitment) = consensus_context.parent;
let checks = self.checks(
consensus_context.clone(),
core::CommitmentFallback::FetchByRound {
round: Round::new(consensus_context.epoch(), parent_view),
},
);
let span = info_span!(
"marshal.coding.propose.task",
round = %consensus_context.round
);
let round = consensus_context.round;
build::propose(
self.context.as_ref(),
&self.application,
self.gates.clone(),
consensus_context,
checks,
span,
move || {
info_span!(
"marshal.coding.application.propose",
round = %round,
parent_view = parent_view.traced(),
parent = %parent_commitment
)
},
move |block: Digested<B>, coding_config| async move {
let erasure_timer = erasure_encode_duration.timer(&*clock);
let coded = CodedBlock::encode(block, coding_config, &strategy).await;
erasure_timer.observe(&*clock);
(coded.commitment(), Arc::new(coded))
},
)
}
#[allow(clippy::async_yields_async)]
#[tracing::instrument(name = "marshal.coding.verify", level = "info", skip_all, fields(round = %consensus_context.round, commitment = %payload))]
async fn verify(
&mut self,
consensus_context: Context<Self::Digest, <Z::Scheme as Verifier>::PublicKey>,
payload: Self::Digest,
) -> oneshot::Receiver<bool> {
let Some(scheme) = self.scheme_provider.scheme(consensus_context.epoch()) else {
debug!(
round = %consensus_context.round,
"no scheme for epoch, skipping verify"
);
let (_, rx) = oneshot::channel();
return rx;
};
let n_participants =
u16::try_from(scheme.participants().len()).expect("too many participants");
let coding_config = coding_config_for_participants(n_participants);
let is_reproposal = payload == consensus_context.parent.1;
let proposal_context = (!is_reproposal).then_some(&consensus_context);
if let Err(err) = validate_proposal(payload, coding_config, proposal_context) {
match err {
ProposalError::CodingConfig => {
warn!(
round = %consensus_context.round,
got = ?payload.config(),
expected = ?coding_config,
"rejected proposal with unexpected coding configuration"
);
}
ProposalError::ContextDigest => {
let expected = hash_context::<H, _>(&consensus_context);
let got = payload.context();
warn!(
round = %consensus_context.round,
expected = ?expected,
got = ?got,
"rejected proposal with mismatched context digest"
);
}
}
let (tx, rx) = oneshot::channel();
tx.send_lossy(false);
return rx;
}
if is_reproposal {
let (parent_view, _) = consensus_context.parent;
let block_rx = self.marshal.subscribe_by_commitment(
payload,
core::CommitmentFallback::FetchByRound {
round: Round::new(consensus_context.epoch(), parent_view),
},
);
let marshal = self.marshal.clone();
let shards = self.shards.clone();
let epocher = self.epocher.clone();
let round = consensus_context.round;
let leader = consensus_context.leader;
let gates = self.gates.clone();
let (task_tx, task_rx) = oneshot::channel();
gates.insert(round, payload, task_rx);
let (mut tx, rx) = oneshot::channel();
let context = self
.context
.child("verify_reproposal")
.with_attribute("round", round);
context.spawn(move |_| {
async move {
let block = select! {
_ = tx.closed() => {
debug!(
reason = "consensus dropped receiver",
"skipping re-proposal verification"
);
return;
},
block = block_rx => match block {
Ok(block) => block,
Err(_) => {
debug!(
?payload,
reason = "failed to fetch block for re-proposal verification",
"skipping re-proposal verification"
);
return;
}
},
};
if !is_valid_reproposal_at_verify(&epocher, block.height(), round.epoch()) {
debug!(
height = %block.height(),
"re-proposal is not at epoch boundary"
);
task_tx.send_lossy(GateOutcome::Ready(false));
tx.send_lossy(false);
return;
}
shards.discovered(payload, leader, round);
let durable = marshal.verified(round, block).await;
if !durable {
return;
}
task_tx.send_lossy(GateOutcome::Ready(true));
tx.send_lossy(true);
}
.instrument(info_span!(
"marshal.coding.verify.reproposal",
round = %round,
commitment = %payload
))
});
return rx;
}
self.shards.discovered(
payload,
consensus_context.leader.clone(),
consensus_context.round,
);
let round = consensus_context.round;
let task = self.deferred_verify(consensus_context, payload, None, Stage::Verified);
self.gates.insert(round, payload, task);
match scheme.me().and_then(|m| m.signer()) {
Some(_) => {
let validity_rx = self.shards.subscribe_assigned_shard_verified(payload);
let (tx, rx) = oneshot::channel();
let context = self
.context
.child("shard_validity_wait")
.with_attribute("round", round);
context.spawn(move |_| {
async move {
gates::forward(tx, validity_rx, |()| Some(true)).await;
}
.instrument(info_span!(
"marshal.coding.verify.shard_validity",
round = %round,
commitment = %payload
))
});
rx
}
None => {
let (tx, rx) = oneshot::channel();
tx.send_lossy(true);
rx
}
}
}
}
impl<E, A, B, C, H, Z, S, ES> CertifiableAutomaton for Marshaled<E, A, B, C, H, Z, S, ES>
where
E: Spawner + Metrics + Clock,
A: Application<
E,
Block = B,
SigningScheme = Z::Scheme,
Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
Input = (),
>,
B: CertifiableBlock<Context = <A as Application<E>>::Context> + Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
#[allow(clippy::async_yields_async)]
#[tracing::instrument(name = "marshal.coding.prepare", level = "info", skip_all, fields(round = %consensus_context.round))]
async fn prepare(
&mut self,
consensus_context: Context<Self::Digest, <Z::Scheme as Verifier>::PublicKey>,
) -> oneshot::Receiver<Handoff<Self::Digest>> {
let strategy = self.strategy.clone();
let erasure_encode_duration = self.erasure_encode_duration.clone();
let clock = self.context.clone();
let (parent_view, parent_commitment) = consensus_context.parent;
let checks = self.checks(consensus_context.clone(), core::CommitmentFallback::Wait);
let span = info_span!(
"marshal.coding.prepare.task",
round = %consensus_context.round
);
let round = consensus_context.round;
build::prepare(
self.context.as_ref(),
&self.application,
self.gates.clone(),
consensus_context,
checks,
span,
move || {
info_span!(
"marshal.coding.application.prepare",
round = %round,
parent_view = parent_view.traced(),
parent = %parent_commitment
)
},
move |block: Digested<B>, coding_config| async move {
let erasure_timer = erasure_encode_duration.timer(&*clock);
let coded = CodedBlock::encode(block, coding_config, &strategy).await;
erasure_timer.observe(&*clock);
(coded.commitment(), Arc::new(coded))
},
)
}
#[allow(clippy::async_yields_async)]
#[tracing::instrument(name = "marshal.coding.certify", level = "info", skip_all, fields(round = %round, commitment = %payload))]
async fn certify(&mut self, round: Round, payload: Self::Digest) -> oneshot::Receiver<bool> {
let task = self.gates.claim(round, payload, |block, ack| {
self.marshal.verified_deferred(round, block, ack)
});
if let Some(task) = task {
return self.certify_from_existing_task(round, payload, task);
}
self.certify_from_embedded_context(round, payload)
}
}
impl<E, A, B, C, H, Z, S, ES> Relay for Marshaled<E, A, B, C, H, Z, S, ES>
where
E: Send + Sync + 'static,
A: Application<
E,
Block = B,
Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
>,
B: CertifiableBlock<Context = <A as Application<E>>::Context> + Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
type Digest = Commitment<B, C, H>;
type PublicKey = <Z::Scheme as Verifier>::PublicKey;
type Plan = Plan<Self::PublicKey>;
fn broadcast(&mut self, commitment: Self::Digest, plan: Self::Plan) -> Feedback {
if matches!(plan, Plan::Forward { .. }) {
return Feedback::Ok;
}
relay::broadcast(&self.gates, &self.marshal, commitment, plan)
}
}
impl<E, A, B, C, H, Z, S, ES> Reporter for Marshaled<E, A, B, C, H, Z, S, ES>
where
E: Send + Sync + 'static,
A: Application<
E,
Block = B,
Context = Context<Commitment<B, C, H>, <Z::Scheme as Verifier>::PublicKey>,
>,
B: CertifiableBlock<Context = <A as Application<E>>::Context> + Codec,
C: CodingScheme,
H: Hasher,
Z: Provider<Scope = Epoch, Scheme: Scheme<Commitment<B, C, H>>>,
S: Strategy,
ES: Epocher,
{
type Activity = Activity<Z::Scheme, Commitment<B, C, H>>;
fn report(&mut self, activity: Self::Activity) -> Feedback {
let retired = match &activity {
Activity::Finalization(finalization) => finalization.round(),
activity => Round::new(activity.epoch(), View::zero()),
};
self.gates.retain_after(retired);
self.marshal.report(activity)
}
}
#[cfg(test)]
mod tests {
use super::Marshaled;
use crate::{
marshal::{
application::gates::Gates,
coding::types::CodedBlock,
mocks::{
harness::{CodingB, P, S},
verifying::MockVerifyingApp,
},
},
types::{FixedEpocher, coding::Commitment},
};
use commonware_coding::ReedSolomon;
use commonware_cryptography::sha256::Sha256;
use commonware_parallel::Sequential;
use commonware_runtime::deterministic;
use std::sync::Arc;
type TestCommitment = Commitment<CodingB, ReedSolomon<Sha256>, Sha256>;
type TestCodedBlock = CodedBlock<CodingB, ReedSolomon<Sha256>, Sha256>;
impl
Marshaled<
deterministic::Context,
MockVerifyingApp<CodingB, S>,
CodingB,
ReedSolomon<Sha256>,
Sha256,
P,
Sequential,
FixedEpocher,
>
{
pub(in crate::marshal::coding) const fn gates(
&self,
) -> &Gates<TestCommitment, Arc<TestCodedBlock>> {
&self.gates
}
}
}