Skip to main content

cuprated/
lib.rs

1//! `cuprated` library.
2//!
3//! Call [`Node::launch`] to initialize and run the node. Returns a [`Node`]
4//! with handles to node services.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use cuprated::{config::Config, Node};
10//!
11//! let config = Config::read_from_path("cuprated.toml")?;
12//! cuprated::logging::init_logging(&config);
13//!
14//! let mut node = Node::launch(config).await;
15//! let height = node.blockchain.context().chain_height;
16//! ```
17
18#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![allow(
21    unused_imports,
22    unreachable_pub,
23    unreachable_code,
24    unused_crate_dependencies,
25    dead_code,
26    unused_variables,
27    clippy::needless_pass_by_value,
28    clippy::unused_async,
29    clippy::diverging_sub_expression,
30    unused_mut,
31    clippy::let_unit_value,
32    clippy::needless_pass_by_ref_mut,
33    reason = "TODO: remove after v1.0.0"
34)]
35
36pub mod blockchain;
37pub mod config;
38pub mod constants;
39pub mod logging;
40pub mod monitor;
41pub mod version;
42
43mod p2p;
44mod rpc;
45mod tor;
46mod txpool;
47
48use std::sync::Arc;
49
50use anyhow::Context;
51use tokio::sync::{oneshot, RwLock};
52use tower::{Service, ServiceExt};
53use tracing::error;
54
55use cuprate_p2p::NetworkInterface;
56use cuprate_p2p_core::{ClearNet, Tor};
57use cuprate_txpool::service::TxpoolReadHandle;
58use cuprate_types::blockchain::BlockchainWriteRequest;
59
60use crate::{
61    blockchain::{BlockchainInterface, BlockchainManagerHandle, BlockchainSyncerHandle},
62    config::Config,
63    constants::DATABASE_CORRUPT_MSG,
64    monitor::TaskExecutor,
65    tor::initialize_tor_if_enabled,
66    txpool::IncomingTxHandler,
67};
68
69/// Captures the necessary context for launching the node.
70///
71/// A field belongs here if it is `Clone + Send + Sync`, used by
72/// multiple subsystems, and available before subsystem init begins.
73/// Write handles, single-consumer channels, `!Sync` types,
74/// and late-constructed services do _not_ belong here.
75#[derive(Clone)]
76pub(crate) struct LaunchContext {
77    /// The configuration this node was launched with.
78    pub config: Arc<Config>,
79
80    /// Reorg lock.
81    ///
82    /// A [`RwLock`] where a write lock is taken during a reorg and a read lock can be taken
83    /// for any operation which must complete without a reorg happening.
84    ///
85    /// Currently, the only operation that needs to take a read lock is adding txs to the tx-pool,
86    /// this can potentially be removed in the future, see: <https://github.com/Cuprate/cuprate/issues/305>
87    pub reorg_lock: Arc<RwLock<()>>,
88
89    /// Interface to the blockchain (database reads, cached state, mutations).
90    pub blockchain: BlockchainInterface,
91
92    /// Read handle to the transaction pool.
93    pub txpool_read: TxpoolReadHandle,
94
95    /// Task spawning and shutdown coordination.
96    pub task_executor: TaskExecutor,
97}
98
99/// An active `cuprated` node.
100///
101/// Returned by [`Node::launch`]. Use this to interact with the running node.
102#[must_use]
103pub struct Node {
104    /// Interface to the blockchain.
105    pub blockchain: BlockchainInterface,
106
107    /// Transaction pool queries.
108    pub txpool: TxpoolReadHandle,
109
110    /// Clearnet P2P interface.
111    pub clearnet: NetworkInterface<ClearNet>,
112
113    /// Tor P2P interface (available after sync).
114    pub tor: Option<oneshot::Receiver<NetworkInterface<Tor>>>,
115
116    /// The configuration this node was launched with.
117    pub config: Arc<Config>,
118
119    /// Task spawning and shutdown executor.
120    pub task_executor: TaskExecutor,
121}
122
123impl Drop for Node {
124    fn drop(&mut self) {
125        self.task_executor.trigger_shutdown();
126    }
127}
128
129impl Node {
130    /// Launch a new `cuprated` process.
131    ///
132    /// Sets up thread pools, databases, P2P networking, the blockchain manager,
133    /// and RPC servers.
134    ///
135    /// The caller should set up the following before calling this:
136    /// - Tracing/logging (the node emits tracing events during initialization)
137    /// - Global rayon thread pool (optional, uses rayon defaults if not set)
138    /// - Memory resolution (call [`resolve_max_memory`](crate::config::resolve_max_memory))
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if the database is corrupt, critical services fail to start,
143    /// or `target_max_memory` is unresolved.
144    pub async fn launch(config: impl Into<Arc<Config>>) -> Result<Self, anyhow::Error> {
145        let config: Arc<Config> = config.into();
146
147        // Initialize the database thread pool.
148        let db_thread_pool = Arc::new(
149            rayon::ThreadPoolBuilder::new()
150                .num_threads(config.storage.reader_threads)
151                .build()
152                .context("failed to build rayon database thread pool")?,
153        );
154
155        // Start the blockchain & tx-pool databases.
156        let fjall_db = fjall::Database::builder(config.fjall_directory())
157            .cache_size(config.fjall_cache_size())
158            .open()
159            .context(DATABASE_CORRUPT_MSG)?;
160
161        let (mut blockchain_read_handle, mut blockchain_write_handle, _) =
162            cuprate_blockchain::service::init_with_pool(
163                &config.blockchain_config(),
164                fjall_db.clone(),
165                Arc::clone(&db_thread_pool),
166            )
167            .context(DATABASE_CORRUPT_MSG)?;
168
169        let (txpool_read_handle, txpool_write_handle) =
170            cuprate_txpool::service::init_with_pool(fjall_db, db_thread_pool)
171                .context(DATABASE_CORRUPT_MSG)?;
172
173        // TODO: Add an argument/option for keeping alt blocks between restart.
174        blockchain_write_handle
175            .ready()
176            .await?
177            .call(BlockchainWriteRequest::FlushAltBlocks)
178            .await?;
179
180        // Check add the genesis block to the blockchain.
181        blockchain::check_add_genesis(
182            &mut blockchain_read_handle,
183            &mut blockchain_write_handle,
184            config.network(),
185        )
186        .await;
187
188        // Start the context service and the block/tx verifier.
189        let context_svc =
190            blockchain::init_consensus(blockchain_read_handle.clone(), config.context_config())
191                .await
192                .map_err(anyhow::Error::from_boxed)?;
193
194        // Create the blockchain syncer handle and synced signal sender.
195        let (blockchain_syncer_handle, synced_tx) = BlockchainSyncerHandle::new();
196
197        // Create the blockchain manager handle and command receiver.
198        let (blockchain_manager_handle, command_rx) = BlockchainManagerHandle::new();
199
200        // Create the blockchain interface.
201        let blockchain_interface = BlockchainInterface::new(
202            blockchain_read_handle,
203            context_svc,
204            blockchain_manager_handle,
205            blockchain_syncer_handle,
206        );
207
208        // Create the launch context.
209        let launch_ctx = LaunchContext {
210            config,
211            reorg_lock: Arc::new(RwLock::new(())),
212            blockchain: blockchain_interface,
213            txpool_read: txpool_read_handle,
214            task_executor: TaskExecutor::new(),
215        };
216
217        // Bootstrap or configure Tor if enabled.
218        let tor_enabled = !launch_ctx.config.offline && launch_ctx.config.p2p.tor_net.enabled;
219        let tor_context = initialize_tor_if_enabled(&launch_ctx).await;
220
221        // Start clearnet P2P zone
222        let (clearnet_interface, clearnet_tx_handler_subscriber) =
223            p2p::initialize_clearnet_p2p(&launch_ctx, &tor_context).await;
224
225        // Create Tor router delivery channel.
226        let (tor_router_tx, tor_router_rx) = tor_enabled.then(oneshot::channel).unzip();
227
228        // Create the incoming tx handler service.
229        let tx_handler = IncomingTxHandler::init(
230            &launch_ctx,
231            clearnet_interface.clone(),
232            tor_router_rx,
233            txpool_write_handle,
234        )
235        .await;
236
237        // Send tx handler sender to clearnet zone, offline zones have no subscriber.
238        if let Some(subscriber) = clearnet_tx_handler_subscriber {
239            if subscriber.send(tx_handler.clone()).is_err() {
240                unreachable!()
241            }
242        }
243
244        // Tor interface channel - populated when Tor starts after sync.
245        let (tor_tx, tor_rx) = oneshot::channel();
246
247        // Initialize the blockchain manager.
248        blockchain::init_blockchain_manager(
249            &launch_ctx,
250            clearnet_interface.clone(),
251            blockchain_write_handle,
252            tx_handler.txpool_manager.clone(),
253            synced_tx,
254            command_rx,
255        )
256        .await?;
257
258        // Initialize the RPC server(s).
259        rpc::init_rpc_servers(&launch_ctx, tx_handler.clone());
260
261        // Start Tor P2P zone after sync completes.
262        if tor_enabled {
263            p2p::initialize_tor_p2p(
264                launch_ctx.clone(),
265                tor_context,
266                tx_handler,
267                tor_tx,
268                tor_router_tx,
269            );
270        }
271
272        let LaunchContext {
273            blockchain,
274            txpool_read,
275            config,
276            task_executor,
277            ..
278        } = launch_ctx;
279
280        Ok(Self {
281            blockchain,
282            txpool: txpool_read,
283            clearnet: clearnet_interface,
284            tor: if tor_enabled { Some(tor_rx) } else { None },
285            config,
286            task_executor,
287        })
288    }
289
290    /// Trigger a graceful shutdown.
291    pub fn shutdown(&self) {
292        self.task_executor.trigger_shutdown();
293    }
294
295    /// Wait for shutdown to be triggered, then await all tracked tasks.
296    pub async fn wait_for_shutdown(&self) {
297        self.task_executor.wait_for_shutdown().await;
298    }
299}