1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![deny(missing_docs)]
4#![no_std]
5
6#[cfg(feature = "alloc")]
7extern crate alloc;
8#[cfg(feature = "std")]
9extern crate std;
10
11use core::{time::Duration, future::Future};
12
13mod or;
14use or::*;
15
16mod timeout;
17use timeout::*;
18
19mod validators;
20pub use validators::*;
21
22mod commit;
23pub use commit::*;
24
25mod blockchain;
26pub use blockchain::*;
27
28mod message;
29pub use message::*;
30
31mod slash_reason;
32pub use slash_reason::*;
33
34#[cfg(feature = "std")]
35mod state;
36#[cfg(feature = "std")]
37use state::*;
38
39pub trait Network<V: Validator, S: Signature, A: AggregateSignature, B: Block> {
41 const LATENCY_TIME: Duration;
52
53 const BLOCK_DOWNLOADING_TIME: Duration;
58
59 const BLOCK_PROCESSING_TIME: Duration;
66
67 type Sleep: Future<Output = ()>;
69
70 fn sleep(duration: Duration) -> Self::Sleep;
74
75 fn broadcast(&mut self, message: Message<V, S, A, B>) -> impl Send + Future<Output = ()>;
86}
87
88#[cfg(feature = "std")]
89mod tendermint {
90 use core::{
91 sync::atomic::{Ordering, AtomicU64},
92 future::Future,
93 num::NonZero,
94 };
95 use alloc::sync::Arc;
96
97 use async_channel::{Sender, Receiver};
107
108 use borsh::{BorshSerialize, BorshDeserialize};
109 use serai_db::{Transaction as _, Db};
110
111 use crate::{
112 BlockNumber, ValidatorSet as _, SignatureScheme, Block, CommitFor, Blockchain, MessageFor,
113 MessageError, Signer, Network, State, Or,
114 };
115
116 pub struct Tendermint;
121
122 pub struct TendermintHandle<B: Blockchain> {
124 block_number: Arc<AtomicU64>,
125 observed_block_number: Arc<AtomicU64>,
126 sync: Sender<(B::Block, CommitFor<B>)>,
127 message: Sender<MessageFor<B>>,
128 }
129
130 struct TendermintProcess<B: Blockchain, S, D, N> {
131 sync: Receiver<(B::Block, CommitFor<B>)>,
132 message: Receiver<MessageFor<B>>,
133
134 state: State<B>,
135 blockchain: B,
136 signer: S,
137 db: D,
138 network: N,
139 }
140
141 impl<
142 B: Blockchain<
143 Validator: BorshSerialize + BorshDeserialize,
144 SignatureScheme: SignatureScheme<
145 Signature: BorshSerialize + BorshDeserialize,
146 AggregateSignature: BorshSerialize + BorshDeserialize,
147 >,
148 Block: BorshSerialize + BorshDeserialize + Block<Hash: BorshSerialize + BorshDeserialize>,
149 >,
150 S: Signer<
151 Validator = B::Validator,
152 Signature = <B::SignatureScheme as SignatureScheme>::Signature,
153 >,
154 D: Db,
155 N: Network<
156 B::Validator,
157 <B::SignatureScheme as SignatureScheme>::Signature,
158 <B::SignatureScheme as SignatureScheme>::AggregateSignature,
159 B::Block,
160 >,
161 > TendermintProcess<B, S, D, N>
162 {
163 async fn run(mut self) {
164 loop {
165 let tick = (Or {
172 f1: Or {
173 f1: self.sync.recv(),
175 f2: self.state.timeout::<N, _>(&self.blockchain, &self.signer),
182 },
183 f2: self.message.recv(),
185 })
186 .await;
187
188 match tick {
189 crate::or::Either::L(crate::or::Either::L(Ok((ref block, ref commit)))) => {
190 let block = block.clone();
192 let commit = commit.clone();
193 drop(tick);
194
195 if commit.block_number != self.state.block_number() {
196 continue;
197 }
198 if !commit.verify(
199 self.blockchain.validator_set(),
200 self.blockchain.signature_scheme(),
201 self.blockchain.genesis(),
202 block.hash(),
203 ) {
204 continue;
205 }
206
207 let mut txn = self.db.txn();
208 let messages = self
209 .state
210 .commit::<N>(&mut self.blockchain, &self.signer, &mut txn, block, commit)
211 .await;
212 txn.commit();
213
214 for message in messages {
215 self.network.broadcast(message).await;
216 }
217
218 continue;
219 }
220
221 crate::or::Either::L(crate::or::Either::R(timeout)) => {
227 let mut txn = self.db.txn();
228 let messages = timeout.respond::<N>(&mut txn).await;
229 txn.commit();
230
231 for message in messages {
232 self.network.broadcast(message).await;
233 }
234 }
235
236 crate::or::Either::R(Ok(ref message)) => {
237 let message = message.clone();
239 drop(tick);
240
241 let mut txn = self.db.txn();
242 let validator = message.validator;
243 match self.state.message::<N>(&self.blockchain, &self.signer, &mut txn, message).await {
244 Ok(messages) => {
245 txn.commit();
246 for message in messages {
247 self.network.broadcast(message).await;
248 }
249 }
250 Err(MessageError::Invalid(slash_reason)) => {
251 self.blockchain.slash(validator, slash_reason);
252 continue;
253 }
254 Err(
255 MessageError::Stale |
256 MessageError::Future |
257 MessageError::NotValidator |
258 MessageError::InvalidOuterSignature |
259 MessageError::AlreadyHandled,
260 ) => continue,
261 }
262 }
263 crate::or::Either::L(crate::or::Either::L(Err(async_channel::RecvError))) |
265 crate::or::Either::R(Err(async_channel::RecvError)) => return,
266 }
267
268 {
270 let mut txn = self.db.txn();
271 let messages =
272 self.state.attempt_commit::<N>(&mut self.blockchain, &self.signer, &mut txn).await;
273 txn.commit();
274
275 for message in messages {
276 self.network.broadcast(message).await;
277 }
278 }
279 }
280 }
281 }
282
283 #[derive(Debug)]
288 pub struct ProcessTerminated;
289
290 impl Tendermint {
291 async fn internal<
292 B: Blockchain<
293 Validator: BorshSerialize + BorshDeserialize,
294 SignatureScheme: SignatureScheme<
295 Signature: BorshSerialize + BorshDeserialize,
296 AggregateSignature: BorshSerialize + BorshDeserialize,
297 >,
298 Block: BorshSerialize + BorshDeserialize + Block<Hash: BorshSerialize + BorshDeserialize>,
299 >,
300 S: Signer<
301 Validator = B::Validator,
302 Signature = <B::SignatureScheme as SignatureScheme>::Signature,
303 >,
304 D: Db,
305 N: Network<
306 B::Validator,
307 <B::SignatureScheme as SignatureScheme>::Signature,
308 <B::SignatureScheme as SignatureScheme>::AggregateSignature,
309 B::Block,
310 >,
311 >(
312 blockchain: B,
313 signer: S,
314 mut db: D,
315 mut network: N,
316 proposal: B::Block,
317 ) -> (TendermintHandle<B>, TendermintProcess<B, S, D, N>) {
318 let (sync_send, sync_recv) = async_channel::bounded({
320 use core::time::Duration;
321 let time_per_block = N::BLOCK_DOWNLOADING_TIME
322 .saturating_add(N::BLOCK_PROCESSING_TIME)
323 .saturating_add(N::LATENCY_TIME.saturating_mul(3));
324 let blocks_per_minute =
325 Duration::from_mins(5).as_millis().div_ceil(time_per_block.as_millis().max(1));
326 usize::try_from(blocks_per_minute).unwrap_or(usize::MAX).clamp(1, 64)
328 });
329
330 let (message_send, message_recv) =
342 async_channel::bounded(2 * blockchain.validator_set().validators().into_iter().count());
343
344 let mut txn = db.txn();
345 let (state, messages) = State::new::<N>(&blockchain, &signer, &mut txn, proposal).await;
346 txn.commit();
347
348 for message in messages {
349 network.broadcast(message).await;
350 }
351
352 (
353 TendermintHandle {
354 block_number: state.block_number_ref(),
355 observed_block_number: state.observed_block_number_ref(),
356 sync: sync_send,
357 message: message_send,
358 },
359 (TendermintProcess {
360 sync: sync_recv,
361 message: message_recv,
362
363 state,
364 blockchain,
365 signer,
366 db,
367 network,
368 }),
369 )
370 }
371
372 pub fn process<
381 B: Send
382 + Sync
383 + Blockchain<
384 Validator: Send + Sync + BorshSerialize + BorshDeserialize,
385 ValidatorSet: Sync,
386 SignatureScheme: SignatureScheme<
387 Signature: Send + BorshSerialize + BorshDeserialize,
388 AggregateSignature: Send + BorshSerialize + BorshDeserialize,
389 >,
390 Genesis: Sync,
391 Block: Send
392 + Sync
393 + BorshSerialize
394 + BorshDeserialize
395 + Block<Hash: Send + BorshSerialize + BorshDeserialize>,
396 BlockProposal: Send,
397 >,
398 S: Send
399 + Sync
400 + Signer<
401 Validator = B::Validator,
402 Signature = <B::SignatureScheme as SignatureScheme>::Signature,
403 SignFuture: Send,
404 >,
405 D: Send + for<'db> Db<Transaction<'db>: Send>,
406 N: Send
407 + Network<
408 B::Validator,
409 <B::SignatureScheme as SignatureScheme>::Signature,
410 <B::SignatureScheme as SignatureScheme>::AggregateSignature,
411 B::Block,
412 Sleep: Send,
413 >,
414 >(
415 blockchain: B,
416 signer: S,
417 db: D,
418 network: N,
419 proposal: B::Block,
420 ) -> impl Send + Future<Output = (TendermintHandle<B>, impl Send + Future<Output = ()>)> {
421 async move {
422 let (handle, process) = Self::internal(blockchain, signer, db, network, proposal).await;
423 (handle, process.run())
424 }
425 }
426
427 #[expect(clippy::manual_async_fn)]
432 pub fn process_single_threaded<
433 B: Blockchain<
434 Validator: BorshSerialize + BorshDeserialize,
435 SignatureScheme: SignatureScheme<
436 Signature: BorshSerialize + BorshDeserialize,
437 AggregateSignature: BorshSerialize + BorshDeserialize,
438 >,
439 Block: BorshSerialize + BorshDeserialize + Block<Hash: BorshSerialize + BorshDeserialize>,
440 >,
441 S: Signer<
442 Validator = B::Validator,
443 Signature = <B::SignatureScheme as SignatureScheme>::Signature,
444 >,
445 D: Db,
446 N: Network<
447 B::Validator,
448 <B::SignatureScheme as SignatureScheme>::Signature,
449 <B::SignatureScheme as SignatureScheme>::AggregateSignature,
450 B::Block,
451 >,
452 >(
453 blockchain: B,
454 signer: S,
455 db: D,
456 network: N,
457 proposal: B::Block,
458 ) -> impl Future<Output = (TendermintHandle<B>, impl Future<Output = ()>)> {
459 async move {
460 let (handle, process) = Self::internal(blockchain, signer, db, network, proposal).await;
461 (handle, process.run())
462 }
463 }
464 }
465
466 impl<B: Blockchain> TendermintHandle<B> {
467 pub fn block_number(&self) -> BlockNumber {
472 BlockNumber(
473 NonZero::new(self.block_number.load(Ordering::Acquire))
474 .expect("block number was corrupted"),
475 )
476 }
477
478 pub fn observed_block_number(&self) -> BlockNumber {
491 BlockNumber(
492 NonZero::new(self.observed_block_number.load(Ordering::Acquire))
493 .expect("observed block number was corrupted"),
494 )
495 }
496
497 pub async fn sync(
505 &mut self,
506 block: B::Block,
507 commit: CommitFor<B>,
508 ) -> Result<(), ProcessTerminated> {
509 self.sync.send((block, commit)).await.map_err(|_| ProcessTerminated)
510 }
511
512 pub async fn message(&mut self, message: MessageFor<B>) -> Result<(), ProcessTerminated> {
520 self.message.send(message).await.map_err(|_| ProcessTerminated)
521 }
522 }
523}
524#[cfg(feature = "std")]
525pub use tendermint::*;