1use std::num::NonZeroUsize;
4use std::path::Path;
5use std::sync::Arc;
6use std::sync::atomic::AtomicUsize;
7
8use arc_swap::ArcSwap;
9use miden_node_utils::ErrorReport;
10use miden_node_utils::clap::StorageOptions;
11use miden_node_utils::shutdown::CancellationToken;
12use miden_node_utils::spawn::spawn_blocking_in_current_span;
13use miden_node_utils::tracing::miden_instrument;
14use tokio::sync::{mpsc, watch};
15use tokio::task::JoinHandle;
16use tracing::Instrument;
17
18use crate::account_state_forest::AccountStateForestBackend;
19use crate::accounts::AccountTreeWithHistory;
20use crate::blocks::BlockStore;
21use crate::db::Db;
22use crate::errors::StateInitializationError;
23use crate::proven_tip::ProvenTipWriter;
24use crate::state::loader::{
25 ACCOUNT_STATE_FOREST_STORAGE_DIR,
26 ACCOUNT_TREE_STORAGE_DIR,
27 AccountForestLoader,
28 NULLIFIER_TREE_STORAGE_DIR,
29 TreeStorage,
30 TreeStorageLoader,
31 load_mmr,
32 verify_account_state_forest_consistency,
33 verify_tree_consistency,
34};
35use crate::state::writer::{WriteRequest, WriteWorker, WriterTask};
36use crate::state::{
37 BlockCache,
38 BlockWriter,
39 ProofCache,
40 ProofWriter,
41 SnapshotGuard,
42 State,
43 StateSnapshot,
44};
45use crate::{COMPONENT, DataDirectory, DatabaseOptions};
46
47async fn join_load_task<T>(
52 handle: JoinHandle<Result<T, StateInitializationError>>,
53) -> Result<T, StateInitializationError> {
54 match handle.await {
55 Ok(result) => result,
56 Err(err) => std::panic::resume_unwind(err.into_panic()),
57 }
58}
59
60const BLOCK_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(512).unwrap();
62
63const PROOF_CACHE_CAPACITY: NonZeroUsize = NonZeroUsize::new(512).unwrap();
65
66#[must_use = "call `start` to spawn the write worker and obtain the state"]
75pub struct LoadedState {
76 state: State,
77 writer: WriteWorker,
78 write_tx: mpsc::Sender<WriteRequest>,
79}
80
81impl LoadedState {
82 pub fn start(
100 self,
101 shutdown: CancellationToken,
102 ) -> (Arc<State>, BlockWriter, ProofWriter, WriterTask) {
103 let writer_task = tokio::spawn(self.writer.run(shutdown));
104 let state = Arc::new(self.state);
105 let block_writer = BlockWriter {
106 block_store: Arc::clone(&state.block_store),
107 write_tx: self.write_tx,
108 };
109 let proof_writer = ProofWriter { state: Arc::clone(&state) };
110 (state, block_writer, proof_writer, WriterTask(writer_task))
111 }
112}
113
114impl State {
118 #[miden_instrument(
124 target = COMPONENT,
125 )]
126 pub async fn load(
127 data_path: &Path,
128 storage_options: StorageOptions,
129 ) -> Result<LoadedState, StateInitializationError> {
130 Self::load_with_database_options(data_path, storage_options, DatabaseOptions::default())
131 .await
132 }
133
134 #[miden_instrument(
140 target = COMPONENT,
141 )]
142 pub async fn load_with_database_options(
143 data_path: &Path,
144 storage_options: StorageOptions,
145 database_options: DatabaseOptions,
146 ) -> Result<LoadedState, StateInitializationError> {
147 let data_directory = DataDirectory::load(data_path.to_path_buf())
148 .map_err(StateInitializationError::DataDirectoryLoadError)?;
149
150 let block_store = Arc::new(
151 BlockStore::load(data_directory.block_store_dir())
152 .map_err(StateInitializationError::BlockStoreLoadError)?,
153 );
154
155 let database_filepath = data_directory.database_path();
156 let db = Arc::new(
157 Db::load_with_pool_size(
158 database_filepath.clone(),
159 database_options.connection_pool_size,
160 )
161 .await
162 .map_err(StateInitializationError::DatabaseLoadError)?,
163 );
164
165 let latest_block_num = db
168 .select_block_header_by_block_num(None)
169 .await?
170 .ok_or(StateInitializationError::GenesisBlockMissing)?
171 .block_num();
172
173 let apply_block_thread_priority = storage_options.apply_block_thread_priority;
174
175 #[cfg(feature = "rocksdb")]
176 let (account_storage_config, nullifier_storage_config, forest_storage_config) = (
177 storage_options.account_tree.into(),
178 storage_options.nullifier_tree.into(),
179 storage_options.account_state_forest.into(),
180 );
181 #[cfg(not(feature = "rocksdb"))]
182 let (account_storage_config, nullifier_storage_config, forest_storage_config) =
183 ((), (), ());
184
185 let mmr_task = tokio::spawn(
191 {
192 let db = Arc::clone(&db);
193 async move { load_mmr(&db).await }
194 }
195 .in_current_span(),
196 );
197 let account_tree_task = tokio::spawn(
198 {
199 let (db, path) = (Arc::clone(&db), data_path.to_path_buf());
200 async move {
201 join_load_task(spawn_blocking_in_current_span(move || {
202 TreeStorage::create(
203 &path,
204 &account_storage_config,
205 ACCOUNT_TREE_STORAGE_DIR,
206 )
207 }))
208 .await?
209 .load_account_tree(&db)
210 .await
211 }
212 }
213 .in_current_span(),
214 );
215 let nullifier_tree_task = tokio::spawn(
216 {
217 let (db, path) = (Arc::clone(&db), data_path.to_path_buf());
218 async move {
219 join_load_task(spawn_blocking_in_current_span(move || {
220 TreeStorage::create(
221 &path,
222 &nullifier_storage_config,
223 NULLIFIER_TREE_STORAGE_DIR,
224 )
225 }))
226 .await?
227 .load_nullifier_tree(&db)
228 .await
229 }
230 }
231 .in_current_span(),
232 );
233 let forest_task = tokio::spawn(
234 {
235 let (db, path) = (Arc::clone(&db), data_path.to_path_buf());
236 async move {
237 let forest = join_load_task(spawn_blocking_in_current_span(move || {
238 AccountStateForestBackend::create(
239 &path,
240 &forest_storage_config,
241 ACCOUNT_STATE_FOREST_STORAGE_DIR,
242 )
243 }))
244 .await?
245 .load_account_state_forest(&db, latest_block_num)
246 .await?;
247 verify_account_state_forest_consistency(&forest, &db).await?;
248 Ok(forest)
249 }
250 }
251 .in_current_span(),
252 );
253 let (blockchain, account_tree, nullifier_tree, forest) = tokio::try_join!(
254 join_load_task(mmr_task),
255 join_load_task(account_tree_task),
256 join_load_task(nullifier_tree_task),
257 join_load_task(forest_task),
258 )?;
259
260 verify_tree_consistency(account_tree.root(), nullifier_tree.root(), &db).await?;
264
265 let account_tree = AccountTreeWithHistory::new(account_tree, latest_block_num);
266
267 let proven_tip_init = block_store
269 .load_proven_tip()
270 .map_err(StateInitializationError::ProvenTipLoadError)?;
271 let (proven_tip, _rx) = ProvenTipWriter::new(proven_tip_init);
272
273 let (committed_tip_tx, _rx) = watch::channel(latest_block_num);
275 let committed_tip_tx = Arc::new(committed_tip_tx);
276
277 let block_cache = BlockCache::new(BLOCK_CACHE_CAPACITY);
278 let proof_cache = ProofCache::new(PROOF_CACHE_CAPACITY);
279
280 let snapshots_live = Arc::new(AtomicUsize::new(0));
282
283 let initial_snapshot = Arc::new(StateSnapshot::new(
285 nullifier_tree
286 .reader()
287 .map_err(|e| StateInitializationError::NullifierTreeIoError(e.as_report()))?,
288 blockchain.clone(),
289 account_tree.reader(),
290 forest
291 .reader()
292 .map_err(|e| StateInitializationError::AccountStateForestIoError(e.as_report()))?,
293 SnapshotGuard::new(Arc::clone(&snapshots_live), latest_block_num),
294 ));
295 let latest_snapshot = Arc::new(ArcSwap::from(initial_snapshot));
296
297 let (write_tx, write_rx) = mpsc::channel(1);
302 let block_writer = WriteWorker::new(
303 Arc::clone(&db),
304 Arc::clone(&block_store),
305 Arc::clone(&latest_snapshot),
306 Arc::clone(&committed_tip_tx),
307 block_cache.clone(),
308 write_rx,
309 nullifier_tree,
310 account_tree,
311 blockchain,
312 forest,
313 snapshots_live,
314 apply_block_thread_priority,
315 );
316 let state = Self {
317 data_directory: data_path.to_path_buf(),
318 db,
319 block_store,
320 latest_snapshot,
321 proven_tip,
322 committed_tip_tx,
323 block_cache,
324 proof_cache,
325 };
326
327 Ok(LoadedState { state, writer: block_writer, write_tx })
328 }
329
330 #[doc(hidden)]
341 pub async fn for_tests(data_path: &Path) -> (Arc<Self>, BlockWriter, ProofWriter) {
342 let (state, block_writer, proof_writer, _writer_task) =
343 Self::load(data_path, StorageOptions::default())
344 .await
345 .expect("state should load")
346 .start(CancellationToken::new());
347 (state, block_writer, proof_writer)
348 }
349}