use crate::{
CertifiableBlock,
marshal::coding::types::CodedBlock,
types::{Round, coding::Commitment},
};
use commonware_actor::mailbox::{Overflow, Policy, Sender};
use commonware_coding::Scheme as CodingScheme;
use commonware_cryptography::{Hasher, PublicKey};
use commonware_utils::channel::oneshot;
use std::{collections::VecDeque, sync::Arc};
pub(crate) enum Message<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
Proposed {
block: Arc<CodedBlock<B, C, H>>,
round: Round,
},
Discovered {
commitment: Commitment<B, C, H>,
leader: P,
round: Round,
},
Notarized {
commitment: Commitment<B, C, H>,
round: Round,
},
GetByCommitment {
commitment: Commitment<B, C, H>,
response: oneshot::Sender<Option<Arc<CodedBlock<B, C, H>>>>,
},
GetByDigest {
digest: B::Digest,
response: oneshot::Sender<Option<Arc<CodedBlock<B, C, H>>>>,
},
SubscribeAssignedShardVerified {
commitment: Commitment<B, C, H>,
response: oneshot::Sender<()>,
},
SubscribeByCommitment {
commitment: Commitment<B, C, H>,
response: oneshot::Sender<Arc<CodedBlock<B, C, H>>>,
},
SubscribeByDigest {
digest: B::Digest,
response: oneshot::Sender<Arc<CodedBlock<B, C, H>>>,
},
}
impl<B, C, H, P> Message<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
pub(crate) fn response_closed(&self) -> bool {
match self {
Self::GetByCommitment { response, .. } | Self::GetByDigest { response, .. } => {
response.is_closed()
}
Self::SubscribeAssignedShardVerified { response, .. } => response.is_closed(),
Self::SubscribeByCommitment { response, .. }
| Self::SubscribeByDigest { response, .. } => response.is_closed(),
Self::Proposed { .. } | Self::Discovered { .. } | Self::Notarized { .. } => false,
}
}
}
pub(crate) struct Pending<B, C, H, P>(VecDeque<Message<B, C, H, P>>)
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey;
impl<B, C, H, P> Default for Pending<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
fn default() -> Self {
Self(VecDeque::new())
}
}
impl<B, C, H, P> Overflow<Message<B, C, H, P>> for Pending<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn drain<F>(&mut self, mut push: F)
where
F: FnMut(Message<B, C, H, P>) -> Option<Message<B, C, H, P>>,
{
while let Some(message) = self.0.pop_front() {
if message.response_closed() {
continue;
}
if let Some(message) = push(message) {
self.0.push_front(message);
break;
}
}
}
}
impl<B, C, H, P> Policy for Message<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
type Overflow = Pending<B, C, H, P>;
fn handle(overflow: &mut Self::Overflow, message: Self) {
if message.response_closed() {
return;
}
overflow.0.push_back(message);
}
}
pub struct Mailbox<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
pub(super) sender: Sender<Message<B, C, H, P>>,
}
impl<B, C, H, P> Clone for Mailbox<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
}
}
}
impl<B, C, H, P> Mailbox<B, C, H, P>
where
B: CertifiableBlock,
C: CodingScheme,
H: Hasher,
P: PublicKey,
{
pub(crate) const fn new(sender: Sender<Message<B, C, H, P>>) -> Self {
Self { sender }
}
pub fn proposed(&self, round: Round, block: CodedBlock<B, C, H>) {
self.proposed_shared(round, Arc::new(block));
}
pub(crate) fn proposed_shared(&self, round: Round, block: Arc<CodedBlock<B, C, H>>) {
let _ = self.sender.enqueue(Message::Proposed { block, round });
}
pub fn discovered(&self, commitment: Commitment<B, C, H>, leader: P, round: Round) {
let _ = self.sender.enqueue(Message::Discovered {
commitment,
leader,
round,
});
}
pub fn notarized(&self, commitment: Commitment<B, C, H>, round: Round) {
let _ = self
.sender
.enqueue(Message::Notarized { commitment, round });
}
pub async fn get(&self, commitment: Commitment<B, C, H>) -> Option<Arc<CodedBlock<B, C, H>>> {
let (response, receiver) = oneshot::channel();
let _ = self.sender.enqueue(Message::GetByCommitment {
commitment,
response,
});
receiver.await.ok().flatten()
}
pub async fn get_by_digest(&self, digest: B::Digest) -> Option<Arc<CodedBlock<B, C, H>>> {
let (response, receiver) = oneshot::channel();
let _ = self
.sender
.enqueue(Message::GetByDigest { digest, response });
receiver.await.ok().flatten()
}
pub fn subscribe_assigned_shard_verified(
&self,
commitment: Commitment<B, C, H>,
) -> oneshot::Receiver<()> {
let (responder, receiver) = oneshot::channel();
let _ = self
.sender
.enqueue(Message::SubscribeAssignedShardVerified {
commitment,
response: responder,
});
receiver
}
pub fn subscribe(
&self,
commitment: Commitment<B, C, H>,
) -> oneshot::Receiver<Arc<CodedBlock<B, C, H>>> {
let (responder, receiver) = oneshot::channel();
let _ = self.sender.enqueue(Message::SubscribeByCommitment {
commitment,
response: responder,
});
receiver
}
pub fn subscribe_by_digest(
&self,
digest: B::Digest,
) -> oneshot::Receiver<Arc<CodedBlock<B, C, H>>> {
let (responder, receiver) = oneshot::channel();
let _ = self.sender.enqueue(Message::SubscribeByDigest {
digest,
response: responder,
});
receiver
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
marshal::{
coding::types::coding_config_for_participants,
mocks::block::{EmptyBlock, encode_block},
},
types::{Epoch, Height, View},
};
use commonware_coding::ReedSolomon;
use commonware_cryptography::{
Committable, Digest as _, Sha256, ed25519, sha256::Digest as Sha256Digest,
};
type B = EmptyBlock<Sha256>;
type H = Sha256;
type C = ReedSolomon<H>;
type TestMessage = Message<B, C, H, ed25519::PublicKey>;
#[test]
fn policy_drains_fifo() {
let inner = B::new(Sha256Digest::EMPTY, Height::new(1), 1);
let block = encode_block::<B, C, H>(inner, coding_config_for_participants(4));
let commitment = block.commitment();
let round = Round::new(Epoch::zero(), View::new(1));
let mut overflow = Pending::default();
<TestMessage as Policy>::handle(&mut overflow, Message::Notarized { commitment, round });
let (response, _get_rx) = oneshot::channel();
<TestMessage as Policy>::handle(
&mut overflow,
Message::GetByCommitment {
commitment,
response,
},
);
let mut drained = Vec::new();
overflow.drain(|message| {
drained.push(message);
None
});
assert_eq!(drained.len(), 2);
assert!(matches!(&drained[0], TestMessage::Notarized { .. }));
assert!(matches!(&drained[1], TestMessage::GetByCommitment { .. }));
}
}