1use std::marker::PhantomData;
4
5use p2panda_core::traits::{Digest, OperationId};
6use p2panda_store::Transaction;
7use p2panda_store::operations::OperationStore;
8use p2panda_store::orderer::OrdererStore;
9use thiserror::Error;
10use tokio::sync::{Mutex, Notify};
11
12use crate::orderer::{CausalOrderer, Ordering};
13use crate::processors::Processor;
14
15pub struct Orderer<T, ID, S> {
16 inner: Mutex<CausalOrderer<ID, S>>,
17 store: S,
18 notify: Notify,
19 _marker: PhantomData<T>,
20}
21
22impl<T, ID, S> Orderer<T, ID, S>
23where
24 ID: OperationId,
25 S: Clone + Transaction + OrdererStore<ID> + OperationStore<T, ID>,
26{
27 pub fn new(store: S) -> Self {
28 let inner = CausalOrderer::new(store.clone());
29
30 Self {
31 inner: Mutex::new(inner),
32 store,
33 notify: Notify::new(),
34 _marker: PhantomData,
35 }
36 }
37}
38
39impl<T, ID, S> Processor<T> for Orderer<T, ID, S>
40where
41 T: Digest<ID> + Ordering<ID>,
42 ID: OperationId,
43 S: Transaction + OrdererStore<ID> + OperationStore<T, ID>,
44{
45 type Output = T;
46
47 type Error = (Option<T>, OrdererError<T, ID, S>);
48
49 async fn process(&self, input: T) -> Result<(), Self::Error> {
50 let permit = match self.store.begin().await {
51 Ok(permit) => permit,
52 Err(err) => return Err((Some(input), OrdererError::Transaction(err))),
53 };
54
55 let inner = self.inner.lock().await;
56 if let Err(err) = inner.process(input.hash(), input.dependencies()).await {
57 return Err((Some(input), OrdererError::OrdererStore(err)));
58 };
59
60 self.store
61 .commit(permit)
62 .await
63 .map_err(|err| (Some(input), OrdererError::Transaction(err)))?;
64
65 self.notify.notify_one(); Ok(())
68 }
69
70 async fn next(&self) -> Result<Self::Output, Self::Error> {
71 loop {
72 let inner = self.inner.lock().await;
73
74 let permit = self
75 .store
76 .begin()
77 .await
78 .map_err(|err| (None, OrdererError::Transaction(err)))?;
79
80 if let Some(id) = inner
81 .next()
82 .await
83 .map_err(|err| (None, OrdererError::OrdererStore(err)))?
84 {
85 self.store
86 .commit(permit)
87 .await
88 .map_err(|err| (None, OrdererError::Transaction(err)))?;
89
90 return match self
91 .store
92 .get_operation(&id)
93 .await
94 .map_err(OrdererError::OperationStore)
95 {
96 Ok(Some(operation)) => Ok(operation),
97 Ok(None) => Err((None, OrdererError::StoreInconsistency(id))),
98 Err(err) => Err((None, err)),
99 };
100 }
101
102 self.notify.notified().await;
103 }
104 }
105}
106
107#[derive(Debug, Error)]
108pub enum OrdererError<T, ID, S>
109where
110 T: Ordering<ID>,
111 ID: OperationId,
112 S: Transaction + OrdererStore<ID> + OperationStore<T, ID>,
113{
114 #[error("could not find item with id {0} in operation store")]
115 StoreInconsistency(ID),
116
117 #[error("{0}")]
118 OrdererStore(<S as OrdererStore<ID>>::Error),
119
120 #[error("{0}")]
121 OperationStore(<S as OperationStore<T, ID>>::Error),
122
123 #[error("{0}")]
124 Transaction(<S as Transaction>::Error),
125}
126
127#[cfg(test)]
128mod tests {
129 use futures_util::stream;
130 use p2panda_core::{Body, Hash, Header, Operation, SigningKey, Topic};
131 use p2panda_store::operations::OperationStore;
132 use p2panda_store::{SqliteStore, tx_unwrap};
133 use serde::{Deserialize, Serialize};
134 use tokio::task;
135 use tokio_stream::StreamExt;
136
137 use crate::StreamLayerExt;
138
139 use super::{Orderer, Ordering};
140
141 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
142 struct TestExtension {
143 dependencies: Vec<Hash>,
144 }
145
146 impl Ordering<Hash> for Operation<TestExtension> {
147 fn dependencies(&self) -> &[Hash] {
148 &self.header.extensions.dependencies
149 }
150 }
151
152 #[tokio::test]
153 async fn out_of_order() {
154 let operation_panda = {
157 let signing_key = SigningKey::generate();
158 let body: Body = b"Hi, Icebear".to_vec().into();
159
160 let header = Header::builder().body(&body).build(
161 &signing_key,
162 TestExtension {
163 dependencies: vec![],
164 },
165 );
166
167 Operation::from_parts(header, Some(body))
168 };
169
170 let operation_icebear = {
171 let signing_key = SigningKey::generate();
172 let body: Body = b"Hello, Pandasan!".to_vec().into();
173
174 let header = Header::builder().body(&body).build(
175 &signing_key,
176 TestExtension {
177 dependencies: vec![operation_panda.hash],
178 },
179 );
180
181 Operation::from_parts(header, Some(body))
182 };
183
184 let local = task::LocalSet::new();
185
186 local
187 .run_until(async move {
188 let store = SqliteStore::temporary().await;
189
190 tx_unwrap!(store, {
192 let log_id = Topic::random();
193
194 store
195 .insert_operation(&operation_panda.hash, &operation_panda, &log_id)
196 .await
197 .unwrap();
198 store
199 .insert_operation(&operation_icebear.hash, &operation_icebear, &log_id)
200 .await
201 .unwrap();
202 });
203
204 let orderer = Orderer::new(store);
206
207 let mut stream = stream::iter(vec![
208 operation_icebear.clone(),
210 operation_panda.clone(),
212 ])
213 .layer(orderer);
214
215 let operation = stream.next().await.unwrap().unwrap();
216 assert_eq!(operation, operation_panda);
217
218 let operation = stream.next().await.unwrap().unwrap();
219 assert_eq!(operation, operation_icebear);
220 })
221 .await;
222 }
223}