use std::marker::PhantomData;
use p2panda_core::traits::{Digest, OperationId};
use p2panda_store::Transaction;
use p2panda_store::operations::OperationStore;
use p2panda_store::orderer::OrdererStore;
use thiserror::Error;
use tokio::sync::{Mutex, Notify};
use crate::orderer::{CausalOrderer, Ordering};
use crate::processors::Processor;
pub struct Orderer<T, ID, S> {
inner: Mutex<CausalOrderer<ID, S>>,
store: S,
notify: Notify,
_marker: PhantomData<T>,
}
impl<T, ID, S> Orderer<T, ID, S>
where
ID: OperationId,
S: Clone + Transaction + OrdererStore<ID> + OperationStore<T, ID, u64>,
{
pub fn new(store: S) -> Self {
let inner = CausalOrderer::new(store.clone());
Self {
inner: Mutex::new(inner),
store,
notify: Notify::new(),
_marker: PhantomData,
}
}
}
impl<T, ID, S> Processor<T> for Orderer<T, ID, S>
where
T: Digest<ID> + Ordering<ID>,
ID: OperationId,
S: Transaction + OrdererStore<ID> + OperationStore<T, ID, u64>,
{
type Output = T;
type Error = OrdererError<T, ID, S>;
async fn process(&self, input: T) -> Result<(), Self::Error> {
let permit = self
.store
.begin()
.await
.map_err(|err| OrdererError::Transaction(err))?;
let inner = self.inner.lock().await;
inner
.process(input.hash(), input.dependencies())
.await
.map_err(|err| OrdererError::OrdererStore(err))?;
self.store
.commit(permit)
.await
.map_err(|err| OrdererError::Transaction(err))?;
self.notify.notify_one(); Ok(())
}
async fn next(&self) -> Result<Self::Output, Self::Error> {
loop {
let inner = self.inner.lock().await;
let permit = self
.store
.begin()
.await
.map_err(|err| OrdererError::Transaction(err))?;
if let Some(id) = inner.next().await.map_err(OrdererError::OrdererStore)? {
self.store
.commit(permit)
.await
.map_err(|err| OrdererError::Transaction(err))?;
return match self
.store
.get_operation(&id)
.await
.map_err(OrdererError::OperationStore)
{
Ok(Some(operation)) => Ok(operation),
Ok(None) => Err(OrdererError::StoreInconsistency(id)),
Err(err) => Err(err),
};
}
self.notify.notified().await;
}
}
}
#[derive(Debug, Error)]
pub enum OrdererError<T, ID, S>
where
T: Ordering<ID>,
ID: OperationId,
S: Transaction + OrdererStore<ID> + OperationStore<T, ID, u64>,
{
#[error("could not find item with id {0} in operation store")]
StoreInconsistency(ID),
#[error("{0}")]
OrdererStore(<S as OrdererStore<ID>>::Error),
#[error("{0}")]
OperationStore(<S as OperationStore<T, ID, u64>>::Error),
#[error("{0}")]
Transaction(<S as Transaction>::Error),
}
#[cfg(test)]
mod tests {
use futures_util::stream;
use p2panda_core::{Body, Hash, Header, Operation, SigningKey, Topic};
use p2panda_store::operations::OperationStore;
use p2panda_store::{SqliteStore, tx_unwrap};
use serde::{Deserialize, Serialize};
use tokio::task;
use tokio_stream::StreamExt;
use crate::StreamLayerExt;
use super::{Orderer, Ordering};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
struct TestExtension {
dependencies: Vec<Hash>,
}
impl Ordering<Hash> for Operation<TestExtension> {
fn dependencies(&self) -> &[Hash] {
&self.header.extensions.dependencies
}
}
#[tokio::test]
async fn out_of_order() {
let operation_panda = {
let signing_key = SigningKey::generate();
let verifying_key = signing_key.verifying_key();
let body: Body = b"Hi, Icebear".to_vec().into();
let mut header = Header {
verifying_key,
payload_size: body.size(),
payload_hash: Some(body.hash()),
extensions: TestExtension {
dependencies: vec![],
},
..Default::default()
};
header.sign(&signing_key);
Operation {
hash: header.hash(),
header,
body: Some(body),
}
};
let operation_icebear = {
let signing_key = SigningKey::generate();
let verifying_key = signing_key.verifying_key();
let body: Body = b"Hello, Pandasan!".to_vec().into();
let mut header = Header {
verifying_key,
payload_size: body.size(),
payload_hash: Some(body.hash()),
extensions: TestExtension {
dependencies: vec![operation_panda.hash],
},
..Default::default()
};
header.sign(&signing_key);
Operation {
hash: header.hash(),
header,
body: Some(body),
}
};
let local = task::LocalSet::new();
local
.run_until(async move {
let store = SqliteStore::temporary().await;
tx_unwrap!(store, {
let log_id = Topic::random();
store
.insert_operation(&operation_panda.hash, &operation_panda, &log_id)
.await
.unwrap();
store
.insert_operation(&operation_icebear.hash, &operation_icebear, &log_id)
.await
.unwrap();
});
let orderer = Orderer::new(store);
let mut stream = stream::iter(vec![
operation_icebear.clone(),
operation_panda.clone(),
])
.layer(orderer);
let operation = stream.next().await.unwrap().unwrap();
assert_eq!(operation, operation_panda);
let operation = stream.next().await.unwrap().unwrap();
assert_eq!(operation, operation_icebear);
})
.await;
}
}