use crate::stateful::Application;
use commonware_actor::{
Feedback,
mailbox::{Overflow, Policy, Sender},
};
use commonware_consensus::{
Application as ConsensusApplication, Block, CertifiableBlock, Epochable, Reporter, Viewable,
marshal::{
Update,
ancestry::{Ancestry, BoxedAncestry},
},
};
use commonware_cryptography::Digestible;
use commonware_runtime::{Clock, Metrics, Spawner, telemetry::traces::TracedExt as _};
use commonware_utils::{
acknowledgement::Exact,
channel::{fallible::OneshotExt, oneshot},
sync::Mutex,
};
use rand_core::Rng;
use std::{
collections::VecDeque,
sync::{Arc, Weak},
};
use tracing::{Span, info_span};
type RetryMailbox<E, A> = Arc<dyn Fn(Message<E, A>) + Send + Sync>;
pub(in crate::stateful::actor) struct WeakAncestry<B: Block>(Weak<Mutex<BoxedAncestry<B>>>);
impl<B: Block> WeakAncestry<B> {
fn new(ancestry: impl Ancestry<B>) -> (Arc<Mutex<BoxedAncestry<B>>>, Self) {
let owner = Arc::new(Mutex::new(BoxedAncestry::new(ancestry)));
let reference = Self(Arc::downgrade(&owner));
(owner, reference)
}
pub(in crate::stateful::actor) fn upgrade(&self) -> Option<BoxedAncestry<B>> {
self.0.upgrade().map(|ancestry| ancestry.lock().clone())
}
}
pub(in crate::stateful::actor) struct Verification {
response: oneshot::Sender<bool>,
}
impl Verification {
pub(in crate::stateful::actor) async fn wait_for_cancellation(&mut self) {
self.response.closed().await;
}
pub(in crate::stateful::actor) fn is_cancelled(&self) -> bool {
self.response.is_closed()
}
pub(in crate::stateful::actor) fn respond(self, valid: bool) {
self.response.send_lossy(valid);
}
}
pub(super) enum Message<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
Propose {
span: Span,
context: (E, A::Context),
ancestry: BoxedAncestry<A::Block>,
upstream: A::Input,
response: oneshot::Sender<Option<A::Block>>,
},
Verify {
span: Span,
context: (E, A::Context),
ancestry: WeakAncestry<A::Block>,
verification: Verification,
},
Finalized {
span: Span,
block: Arc<A::Block>,
acknowledgement: Exact,
retry_mailbox: RetryMailbox<E, A>,
},
SubscribeDatabases {
response: oneshot::Sender<A::Databases>,
},
}
impl<E, A> Message<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
fn is_obsolete(&self) -> bool {
match self {
Self::Propose { response, .. } => response.is_closed(),
Self::Verify { verification, .. } => verification.is_cancelled(),
Self::SubscribeDatabases { response } => response.is_closed(),
Self::Finalized { .. } => false,
}
}
}
pub(super) struct Pending<E, A>(VecDeque<Message<E, A>>)
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>;
impl<E, A> Default for Pending<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
fn default() -> Self {
Self(VecDeque::new())
}
}
impl<E, A> Overflow<Message<E, A>> for Pending<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
fn is_empty(&self) -> bool {
self.0.is_empty()
}
fn drain<F>(&mut self, mut push: F)
where
F: FnMut(Message<E, A>) -> Option<Message<E, A>>,
{
while let Some(message) = self.0.pop_front() {
if message.is_obsolete() {
continue;
}
if let Some(message) = push(message) {
self.0.push_front(message);
break;
}
}
}
}
impl<E, A> Policy for Message<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
type Overflow = Pending<E, A>;
fn handle(overflow: &mut Self::Overflow, message: Self) {
if message.is_obsolete() {
return;
}
overflow.0.push_back(message);
}
}
pub struct Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
sender: Sender<Message<E, A>>,
retry_mailbox: RetryMailbox<E, A>,
}
impl<E, A> Clone for Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
retry_mailbox: self.retry_mailbox.clone(),
}
}
}
impl<E, A> Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
pub(super) fn new(sender: Sender<Message<E, A>>) -> Self {
let retry_sender = sender.clone();
let retry_mailbox = Arc::new(move |message| {
let _ = retry_sender.enqueue(message);
});
Self {
sender,
retry_mailbox,
}
}
}
impl<E, A> Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
pub async fn subscribe_databases(&self) -> A::Databases {
let (response, receiver) = oneshot::channel();
let _ = self
.sender
.enqueue(Message::SubscribeDatabases { response });
receiver
.await
.expect("stateful actor dropped during subscribe_databases")
}
}
impl<E, A> ConsensusApplication<E> for Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
type SigningScheme = A::SigningScheme;
type Context = A::Context;
type Block = A::Block;
type Input = A::Input;
async fn propose(
&mut self,
context: (E, Self::Context),
ancestry: impl Ancestry<Self::Block>,
upstream: Self::Input,
) -> Option<Self::Block> {
let (response, receiver) = oneshot::channel();
let span = info_span!(
"stateful.mailbox.propose",
epoch = context.1.epoch().traced(),
view = context.1.view().traced()
);
let _ = self.sender.enqueue(Message::Propose {
span,
context,
ancestry: BoxedAncestry::new(ancestry),
upstream,
response,
});
receiver.await.ok().flatten()
}
async fn verify(
&mut self,
context: (E, Self::Context),
ancestry: impl Ancestry<Self::Block>,
) -> bool {
let (response, receiver) = oneshot::channel();
let (ancestry_owner, ancestry) = WeakAncestry::new(ancestry);
let span = info_span!(
"stateful.mailbox.verify",
epoch = context.1.epoch().traced(),
view = context.1.view().traced()
);
let _ = self.sender.enqueue(Message::Verify {
span,
context,
ancestry,
verification: Verification { response },
});
let result = receiver
.await
.expect("stateful actor dropped during verify");
drop(ancestry_owner);
result
}
}
impl<E, A> Reporter for Mailbox<E, A>
where
E: Rng + Spawner + Metrics + Clock,
A: Application<E>,
{
type Activity = Update<A::Block>;
fn report(&mut self, activity: Self::Activity) -> Feedback {
let message = match activity {
Update::Tip(_, _, _) => return Feedback::Ok,
Update::Block(block, acknowledgement) => {
let context = block.context();
let span = info_span!(
"stateful.mailbox.finalized",
epoch = context.epoch().traced(),
view = context.view().traced(),
digest = %block.digest()
);
Message::Finalized {
span,
block,
acknowledgement,
retry_mailbox: self.retry_mailbox.clone(),
}
}
};
self.sender.enqueue(message)
}
}