miden_node_store/server/
mod.rs1use std::{
2 ops::Not,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use anyhow::Context;
8use miden_node_proto::generated::store::api_server;
9use miden_node_utils::tracing::grpc::store_trace_fn;
10use tokio::net::TcpListener;
11use tokio_stream::wrappers::TcpListenerStream;
12use tower_http::trace::TraceLayer;
13use tracing::{info, instrument};
14
15use crate::{
16 COMPONENT, DATABASE_MAINTENANCE_INTERVAL, GenesisState, blocks::BlockStore, db::Db,
17 server::db_maintenance::DbMaintenance, state::State,
18};
19
20mod api;
21mod db_maintenance;
22
23pub struct Store {
25 pub listener: TcpListener,
26 pub data_directory: PathBuf,
27}
28
29impl Store {
30 #[instrument(
32 target = COMPONENT,
33 name = "store.bootstrap",
34 skip_all,
35 err,
36 )]
37 pub fn bootstrap(genesis: GenesisState, data_directory: &Path) -> anyhow::Result<()> {
38 let genesis = genesis
39 .into_block()
40 .context("failed to convert genesis configuration into the genesis block")?;
41
42 let data_directory =
43 DataDirectory::load(data_directory.to_path_buf()).with_context(|| {
44 format!("failed to load data directory at {}", data_directory.display())
45 })?;
46 tracing::info!(target=COMPONENT, path=%data_directory.display(), "Data directory loaded");
47
48 let block_store = data_directory.block_store_dir();
49 let block_store =
50 BlockStore::bootstrap(block_store.clone(), &genesis).with_context(|| {
51 format!("failed to bootstrap block store at {}", block_store.display())
52 })?;
53 tracing::info!(target=COMPONENT, path=%block_store.display(), "Block store created");
54
55 let database_filepath = data_directory.database_path();
57 Db::bootstrap(database_filepath.clone(), &genesis).with_context(|| {
58 format!("failed to bootstrap database at {}", database_filepath.display())
59 })?;
60 tracing::info!(target=COMPONENT, path=%database_filepath.display(), "Database created");
61
62 Ok(())
63 }
64
65 pub async fn serve(self) -> anyhow::Result<()> {
69 info!(target: COMPONENT, endpoint=?self.listener, ?self.data_directory, "Loading database");
70
71 let data_directory =
72 DataDirectory::load(self.data_directory.clone()).with_context(|| {
73 format!("failed to load data directory at {}", self.data_directory.display())
74 })?;
75
76 let block_store =
77 Arc::new(BlockStore::load(data_directory.block_store_dir()).with_context(|| {
78 format!("failed to load block store at {}", self.data_directory.display())
79 })?);
80
81 let database_filepath = data_directory.database_path();
82 let db = Db::load(database_filepath.clone()).await.with_context(|| {
83 format!("failed to load database at {}", database_filepath.display())
84 })?;
85
86 let state = Arc::new(State::load(db, block_store).await.context("failed to load state")?);
87
88 let db_maintenance_service =
89 DbMaintenance::new(Arc::clone(&state), DATABASE_MAINTENANCE_INTERVAL);
90 let api_service = api_server::ApiServer::new(api::StoreApi { state });
91
92 info!(target: COMPONENT, "Database loaded");
93
94 tokio::spawn(db_maintenance_service.run());
95 tonic::transport::Server::builder()
97 .layer(TraceLayer::new_for_grpc().make_span_with(store_trace_fn))
98 .add_service(api_service)
99 .serve_with_incoming(TcpListenerStream::new(self.listener))
100 .await
101 .context("failed to serve store API")
102 }
103}
104
105#[derive(Clone)]
109pub struct DataDirectory(PathBuf);
110
111impl DataDirectory {
112 pub fn load(path: PathBuf) -> std::io::Result<Self> {
115 let meta = std::fs::metadata(&path)?;
116 if meta.is_dir().not() {
117 return Err(std::io::ErrorKind::NotConnected.into());
118 }
119
120 Ok(Self(path))
121 }
122
123 pub fn block_store_dir(&self) -> PathBuf {
124 self.0.join("blocks")
125 }
126
127 pub fn database_path(&self) -> PathBuf {
128 self.0.join("miden-store.sqlite3")
129 }
130
131 pub fn display(&self) -> std::path::Display<'_> {
132 self.0.display()
133 }
134}