cuprate_blockchain/service/mod.rs
1//! [`tower::Service`] integeration + thread-pool.
2//!
3//! ## `service`
4//! The `service` module implements the [`tower`] integration,
5//! along with the reader/writer thread-pool system.
6//!
7//! The thread-pool allows outside crates to communicate with it by
8//! sending database [`Request`][req_r]s and receiving [`Response`][resp]s `async`hronously -
9//! without having to actually worry and handle the database themselves.
10//!
11//! The system is managed by this crate, and only requires [`init`] by the user.
12//!
13//! ## Handles
14//! The 2 handles to the database are:
15//! - [`BlockchainReadHandle`]
16//! - [`BlockchainWriteHandle`]
17//!
18//! The 1st allows any caller to send [`ReadRequest`][req_r]s.
19//!
20//! The 2nd allows any caller to send [`WriteRequest`][req_w]s.
21//!
22//! The [`BlockchainReadHandle`] can be shared as it is cheaply [`Clone`]able, however,
23//! the [`BlockchainWriteHandle`] cannot be cloned. There is only 1 place in Cuprate that
24//! writes, so it is passed there and used.
25//!
26//! ## Initialization
27//! The database & thread-pool system can be initialized with [`init()`].
28//!
29//! This causes the underlying database/threads to be setup
30//! and returns a read/write handle to that database.
31//!
32//! ## Shutdown
33//! Upon the above handles being dropped, the corresponding thread(s) will automatically exit, i.e:
34//! - The last [`BlockchainReadHandle`] is dropped => reader thread-pool exits
35//! - The last [`BlockchainWriteHandle`] is dropped => writer thread exits
36//!
37//! TODO: update this when `ConcreteEnv` is removed
38//!
39//! Upon dropping the [`cuprate_database::ConcreteEnv`]:
40//! - All un-processed database transactions are completed
41//! - All data gets flushed to disk (caused by [`Drop::drop`] impl on `ConcreteEnv`)
42//!
43//! ## Request and Response
44//! To interact with the database (whether reading or writing data),
45//! a `Request` can be sent using one of the above handles.
46//!
47//! Both the handles implement `tower::Service`, so they can be [`tower::Service::call`]ed.
48//!
49//! An `async`hronous channel will be returned from the call.
50//! This channel can be `.await`ed upon to (eventually) receive
51//! the corresponding `Response` to your `Request`.
52//!
53//! [req_r]: cuprate_types::blockchain::BlockchainReadRequest
54//!
55//! [req_w]: cuprate_types::blockchain::BlockchainWriteRequest
56//!
57//! [resp]: cuprate_types::blockchain::BlockchainResponse
58//!
59//! # Example
60//! Simple usage of `service`.
61//!
62//! ```rust
63//! use hex_literal::hex;
64//! use tower::{Service, ServiceExt};
65//!
66//! use cuprate_types::{blockchain::{BlockchainReadRequest, BlockchainWriteRequest, BlockchainResponse}, Chain};
67//! use cuprate_test_utils::data::BLOCK_V16_TX0;
68//!
69//! use cuprate_blockchain::{
70//! cuprate_database::Env,
71//! config::ConfigBuilder,
72//! };
73//!
74//! # #[tokio::main]
75//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
76//! // Create a configuration for the database environment.
77//! let tmp_dir = tempfile::tempdir()?;
78//! let db_dir = tmp_dir.path().to_owned();
79//! let config = ConfigBuilder::new()
80//! .data_directory(db_dir.into())
81//! .build();
82//!
83//! // Initialize the database thread-pool.
84//! let (mut read_handle, mut write_handle, _) = cuprate_blockchain::service::init(config)?;
85//!
86//! // Prepare a request to write block.
87//! let mut block = BLOCK_V16_TX0.clone();
88//! # block.height = 0_usize; // must be 0th height or panic in `add_block()`
89//! let request = BlockchainWriteRequest::WriteBlock(block);
90//!
91//! // Send the request.
92//! // We receive back an `async` channel that will
93//! // eventually yield the result when `service`
94//! // is done writing the block.
95//! let response_channel = write_handle.ready().await?.call(request);
96//!
97//! // Block write was OK.
98//! let response = response_channel.await?;
99//! assert_eq!(response, BlockchainResponse::Ok);
100//!
101//! // Now, let's try getting the block hash
102//! // of the block we just wrote.
103//! let request = BlockchainReadRequest::BlockHash(0, Chain::Main);
104//! let response_channel = read_handle.ready().await?.call(request);
105//! let response = response_channel.await?;
106//! assert_eq!(
107//! response,
108//! BlockchainResponse::BlockHash(
109//! hex!("43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428")
110//! )
111//! );
112//!
113//! // This causes the writer thread on the
114//! // other side of this handle to exit...
115//! drop(write_handle);
116//! // ...and this causes the reader thread-pool to exit.
117//! drop(read_handle);
118//! # Ok(()) }
119//! ```
120
121// needed for docs
122use tower as _;
123
124mod read;
125pub use read::{init_read_service, init_read_service_with_pool};
126
127mod write;
128pub use write::init_write_service;
129
130mod free;
131pub use free::{init, init_with_pool};
132mod types;
133pub use types::{BlockchainReadHandle, BlockchainWriteHandle};
134
135#[cfg(test)]
136mod tests;