Skip to main content

p2panda_stream/orderer/
processor.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use 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(); // Wake up any pending next call
66
67        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        // Create two operations, one by Panda and one by Icebear. Panda's operation points at
155        // Icebear's.
156        let operation_panda = {
157            let signing_key = SigningKey::generate();
158            let verifying_key = signing_key.verifying_key();
159
160            let body: Body = b"Hi, Icebear".to_vec().into();
161
162            let mut header = Header {
163                verifying_key,
164                payload_size: body.size(),
165                payload_hash: Some(body.hash()),
166                extensions: TestExtension {
167                    dependencies: vec![],
168                },
169                ..Default::default()
170            };
171            header.sign(&signing_key);
172
173            Operation {
174                hash: header.hash(),
175                header,
176                body: Some(body),
177            }
178        };
179
180        let operation_icebear = {
181            let signing_key = SigningKey::generate();
182            let verifying_key = signing_key.verifying_key();
183
184            let body: Body = b"Hello, Pandasan!".to_vec().into();
185
186            let mut header = Header {
187                verifying_key,
188                payload_size: body.size(),
189                payload_hash: Some(body.hash()),
190                extensions: TestExtension {
191                    dependencies: vec![operation_panda.hash],
192                },
193                ..Default::default()
194            };
195            header.sign(&signing_key);
196
197            Operation {
198                hash: header.hash(),
199                header,
200                body: Some(body),
201            }
202        };
203
204        let local = task::LocalSet::new();
205
206        local
207            .run_until(async move {
208                let store = SqliteStore::temporary().await;
209
210                // Insert operations into store.
211                tx_unwrap!(store, {
212                    let log_id = Topic::random();
213
214                    store
215                        .insert_operation(&operation_panda.hash, &operation_panda, &log_id)
216                        .await
217                        .unwrap();
218                    store
219                        .insert_operation(&operation_icebear.hash, &operation_icebear, &log_id)
220                        .await
221                        .unwrap();
222                });
223
224                // Prepare processing pipeline for message ordering.
225                let orderer = Orderer::new(store);
226
227                let mut stream = stream::iter(vec![
228                    // Process Icebear's operation first. It will arrive "out of order".
229                    operation_icebear.clone(),
230                    // Process Pandas's operation next. It will "free" Icebear's operation.
231                    operation_panda.clone(),
232                ])
233                .layer(orderer);
234
235                let operation = stream.next().await.unwrap().unwrap();
236                assert_eq!(operation, operation_panda);
237
238                let operation = stream.next().await.unwrap().unwrap();
239                assert_eq!(operation, operation_icebear);
240            })
241            .await;
242    }
243}