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, u64>,
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, u64>,
44{
45    type Output = T;
46
47    type Error = OrdererError<T, ID, S>;
48
49    async fn process(&self, input: T) -> Result<(), Self::Error> {
50        let permit = self
51            .store
52            .begin()
53            .await
54            .map_err(|err| OrdererError::Transaction(err))?;
55
56        let inner = self.inner.lock().await;
57        inner
58            .process(input.hash(), input.dependencies())
59            .await
60            .map_err(|err| OrdererError::OrdererStore(err))?;
61
62        self.store
63            .commit(permit)
64            .await
65            .map_err(|err| OrdererError::Transaction(err))?;
66
67        self.notify.notify_one(); // Wake up any pending next call
68        Ok(())
69    }
70
71    async fn next(&self) -> Result<Self::Output, Self::Error> {
72        loop {
73            let inner = self.inner.lock().await;
74
75            let permit = self
76                .store
77                .begin()
78                .await
79                .map_err(|err| OrdererError::Transaction(err))?;
80
81            if let Some(id) = inner.next().await.map_err(OrdererError::OrdererStore)? {
82                self.store
83                    .commit(permit)
84                    .await
85                    .map_err(|err| OrdererError::Transaction(err))?;
86
87                return match self
88                    .store
89                    .get_operation(&id)
90                    .await
91                    .map_err(OrdererError::OperationStore)
92                {
93                    Ok(Some(operation)) => Ok(operation),
94                    Ok(None) => Err(OrdererError::StoreInconsistency(id)),
95                    Err(err) => Err(err),
96                };
97            }
98
99            self.notify.notified().await;
100        }
101    }
102}
103
104#[derive(Debug, Error)]
105pub enum OrdererError<T, ID, S>
106where
107    T: Ordering<ID>,
108    ID: OperationId,
109    S: Transaction + OrdererStore<ID> + OperationStore<T, ID, u64>,
110{
111    #[error("could not find item with id {0} in operation store")]
112    StoreInconsistency(ID),
113
114    #[error("{0}")]
115    OrdererStore(<S as OrdererStore<ID>>::Error),
116
117    #[error("{0}")]
118    OperationStore(<S as OperationStore<T, ID, u64>>::Error),
119
120    #[error("{0}")]
121    Transaction(<S as Transaction>::Error),
122}
123
124#[cfg(test)]
125mod tests {
126    use futures_util::stream;
127    use p2panda_core::{Body, Hash, Header, Operation, SigningKey, Topic};
128    use p2panda_store::operations::OperationStore;
129    use p2panda_store::{SqliteStore, tx_unwrap};
130    use serde::{Deserialize, Serialize};
131    use tokio::task;
132    use tokio_stream::StreamExt;
133
134    use crate::StreamLayerExt;
135
136    use super::{Orderer, Ordering};
137
138    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
139    struct TestExtension {
140        dependencies: Vec<Hash>,
141    }
142
143    impl Ordering<Hash> for Operation<TestExtension> {
144        fn dependencies(&self) -> &[Hash] {
145            &self.header.extensions.dependencies
146        }
147    }
148
149    #[tokio::test]
150    async fn out_of_order() {
151        // Create two operations, one by Panda and one by Icebear. Panda's operation points at
152        // Icebear's.
153        let operation_panda = {
154            let signing_key = SigningKey::generate();
155            let verifying_key = signing_key.verifying_key();
156
157            let body: Body = b"Hi, Icebear".to_vec().into();
158
159            let mut header = Header {
160                verifying_key,
161                payload_size: body.size(),
162                payload_hash: Some(body.hash()),
163                extensions: TestExtension {
164                    dependencies: vec![],
165                },
166                ..Default::default()
167            };
168            header.sign(&signing_key);
169
170            Operation {
171                hash: header.hash(),
172                header,
173                body: Some(body),
174            }
175        };
176
177        let operation_icebear = {
178            let signing_key = SigningKey::generate();
179            let verifying_key = signing_key.verifying_key();
180
181            let body: Body = b"Hello, Pandasan!".to_vec().into();
182
183            let mut header = Header {
184                verifying_key,
185                payload_size: body.size(),
186                payload_hash: Some(body.hash()),
187                extensions: TestExtension {
188                    dependencies: vec![operation_panda.hash],
189                },
190                ..Default::default()
191            };
192            header.sign(&signing_key);
193
194            Operation {
195                hash: header.hash(),
196                header,
197                body: Some(body),
198            }
199        };
200
201        let local = task::LocalSet::new();
202
203        local
204            .run_until(async move {
205                let store = SqliteStore::temporary().await;
206
207                // Insert operations into store.
208                tx_unwrap!(store, {
209                    let log_id = Topic::random();
210
211                    store
212                        .insert_operation(&operation_panda.hash, &operation_panda, &log_id)
213                        .await
214                        .unwrap();
215                    store
216                        .insert_operation(&operation_icebear.hash, &operation_icebear, &log_id)
217                        .await
218                        .unwrap();
219                });
220
221                // Prepare processing pipeline for message ordering.
222                let orderer = Orderer::new(store);
223
224                let mut stream = stream::iter(vec![
225                    // Process Icebear's operation first. It will arrive "out of order".
226                    operation_icebear.clone(),
227                    // Process Pandas's operation next. It will "free" Icebear's operation.
228                    operation_panda.clone(),
229                ])
230                .layer(orderer);
231
232                let operation = stream.next().await.unwrap().unwrap();
233                assert_eq!(operation, operation_panda);
234
235                let operation = stream.next().await.unwrap().unwrap();
236                assert_eq!(operation, operation_icebear);
237            })
238            .await;
239    }
240}