use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Debug;
use std::future::{Future, ready};
use std::mem;
use std::pin::Pin;
use fedimint_connectors::ServerResult;
use fedimint_connectors::error::ServerError;
use fedimint_core::task::{MaybeSend, MaybeSync};
use fedimint_core::{NumPeers, PeerId, maybe_add_send, maybe_add_send_sync};
pub trait QueryStrategy<IR, OR = IR> {
fn process(
&mut self,
peer_id: PeerId,
response: IR,
) -> impl Future<Output = QueryStep<OR>> + MaybeSend;
fn process_error(&mut self, _peer_id: PeerId, _error: &ServerError) -> QueryStep<OR> {
QueryStep::Continue
}
}
#[derive(Debug)]
pub enum QueryStep<R> {
Retry(BTreeSet<PeerId>),
Continue,
Success(R),
Failure(ServerError),
}
pub struct FilterMap<R, T> {
filter_map: Box<maybe_add_send_sync!(dyn Fn(R) -> ServerResult<T>)>,
}
impl<R, T> FilterMap<R, T> {
pub fn new(
filter_map: impl Fn(R) -> ServerResult<T> + MaybeSend + MaybeSync + 'static,
) -> Self {
Self {
filter_map: Box::new(filter_map),
}
}
}
impl<R, T: MaybeSend> QueryStrategy<R, T> for FilterMap<R, T> {
fn process(
&mut self,
_peer: PeerId,
response: R,
) -> impl Future<Output = QueryStep<T>> + MaybeSend {
ready(match (self.filter_map)(response) {
Ok(value) => QueryStep::Success(value),
Err(e) => QueryStep::Failure(e),
})
}
}
pub struct FilterMapThreshold<R, T> {
filter_map: Box<maybe_add_send_sync!(dyn Fn(PeerId, R) -> FilterMapFuture<T>)>,
filtered_responses: BTreeMap<PeerId, T>,
threshold: usize,
}
type FilterMapFuture<T> = Pin<Box<maybe_add_send!(dyn Future<Output = ServerResult<T>>)>>;
impl<R, T> FilterMapThreshold<R, T> {
pub fn new<Fut>(
verifier: impl Fn(PeerId, R) -> Fut + MaybeSend + MaybeSync + 'static,
num_peers: NumPeers,
) -> Self
where
Fut: Future<Output = ServerResult<T>> + MaybeSend + 'static,
{
Self {
filter_map: Box::new(move |peer, response| Box::pin(verifier(peer, response))),
filtered_responses: BTreeMap::new(),
threshold: num_peers.threshold(),
}
}
}
impl<R: MaybeSend, T: MaybeSend> QueryStrategy<R, BTreeMap<PeerId, T>>
for FilterMapThreshold<R, T>
{
async fn process(&mut self, peer: PeerId, response: R) -> QueryStep<BTreeMap<PeerId, T>> {
match (self.filter_map)(peer, response).await {
Ok(response) => {
self.filtered_responses.insert(peer, response);
if self.filtered_responses.len() == self.threshold {
QueryStep::Success(mem::take(&mut self.filtered_responses))
} else {
QueryStep::Continue
}
}
Err(e) => QueryStep::Failure(e),
}
}
}
pub struct ThresholdConsensus<R> {
responses: BTreeMap<PeerId, R>,
retry: BTreeSet<PeerId>,
threshold: usize,
}
impl<R> ThresholdConsensus<R> {
pub fn new(num_peers: NumPeers) -> Self {
Self {
responses: BTreeMap::new(),
retry: BTreeSet::new(),
threshold: num_peers.threshold(),
}
}
}
impl<R: Eq + Clone + MaybeSend> QueryStrategy<R> for ThresholdConsensus<R> {
fn process(
&mut self,
peer: PeerId,
response: R,
) -> impl Future<Output = QueryStep<R>> + MaybeSend {
self.responses.insert(peer, response.clone());
if self.responses.values().filter(|r| **r == response).count() == self.threshold {
return ready(QueryStep::Success(response));
}
assert!(self.retry.insert(peer));
ready(if self.retry.len() == self.threshold {
QueryStep::Retry(mem::take(&mut self.retry))
} else {
QueryStep::Continue
})
}
}
pub struct ThresholdAgreement<R> {
responses: BTreeMap<PeerId, R>,
errors: usize,
threshold: usize,
total: usize,
}
impl<R> ThresholdAgreement<R> {
pub fn new(num_peers: NumPeers) -> Self {
Self {
responses: BTreeMap::new(),
errors: 0,
threshold: num_peers.threshold(),
total: num_peers.total(),
}
}
fn diverged(&mut self) -> Option<QueryStep<Result<R, BTreeMap<PeerId, R>>>> {
if self.responses.len() + self.errors < self.total {
return None;
}
if self.responses.len() < self.threshold {
return None;
}
Some(QueryStep::Success(Err(mem::take(&mut self.responses))))
}
}
impl<R: Eq + Clone + MaybeSend> QueryStrategy<R, Result<R, BTreeMap<PeerId, R>>>
for ThresholdAgreement<R>
{
fn process(
&mut self,
peer: PeerId,
response: R,
) -> impl Future<Output = QueryStep<Result<R, BTreeMap<PeerId, R>>>> + MaybeSend {
self.responses.insert(peer, response.clone());
if self.responses.values().filter(|r| **r == response).count() == self.threshold {
return ready(QueryStep::Success(Ok(response)));
}
ready(self.diverged().unwrap_or(QueryStep::Continue))
}
fn process_error(
&mut self,
_peer: PeerId,
_error: &ServerError,
) -> QueryStep<Result<R, BTreeMap<PeerId, R>>> {
self.errors += 1;
self.diverged().unwrap_or(QueryStep::Continue)
}
}
#[cfg(test)]
mod tests;