1#[cfg(feature = "rocksdb")]
9use crate::storage::options::DbOptions;
10use crate::{
11 base_layer::{
12 mock,
13 rpc,
14 BaseLayer,
15 },
16 config::{
17 Config,
18 StorageConfig,
19 },
20 domain_agreement,
21 in_memory,
22 proving,
23 storage::{
24 Database,
25 DefaultStorage,
26 },
27 Domain,
28};
29use base_sdk::{
30 core::Shard,
31 crypto::ed25519,
32 vaults::{
33 Address,
34 Vault,
35 },
36};
37use domain_runtime::{
38 config::{
39 BaseLayerRetryConfig,
40 RuntimeConfig,
41 SdlRetentionConfig,
42 SignatureVerificationConfig,
43 },
44 storage::ColumnFamilies,
45};
46use http::{
47 uri::InvalidUri,
48 Uri,
49};
50use proving_clients::Proving;
51use snafu::{
52 ResultExt,
53 Snafu,
54};
55use std::{
56 collections::HashMap,
57 net::{
58 Ipv6Addr,
59 SocketAddr,
60 },
61 num::NonZero,
62};
63use storage::{
64 column_family::ColumnFamilies as _,
65 database::{
66 GenericDatabase,
67 StorageError,
68 },
69 key_value::KeyValueStorage,
70};
71
72#[derive(Debug, Snafu)]
74pub enum Error {
75 #[snafu(display("Base layer error: {source}"))]
77 BaseLayer {
78 source: Box<dyn std::error::Error + Send + Sync>,
80 },
81 #[snafu(display("Error while initializing storage: {source}"))]
83 Storage {
84 source: StorageError,
86 },
87}
88
89pub const ADMIN_API_DEFAULT_ADDR: SocketAddr =
91 SocketAddr::new(std::net::IpAddr::V6(Ipv6Addr::UNSPECIFIED), 4001);
92
93#[derive(Debug)]
103pub struct DomainBuilder<S> {
104 config: RuntimeConfig,
105 keypair: ed25519::PrivKey,
106 base_layer: BaseLayerOptions,
107 proving: Box<dyn Proving>,
108 storage: S,
109 admin_api_address: SocketAddr,
110}
111
112#[derive(Debug)]
114enum BaseLayerOptions {
115 Rpc { url: Result<Uri, Error> },
116 Mock { vaults: HashMap<Address, Vault> },
117}
118
119impl BaseLayerOptions {
120 fn mock() -> Self {
121 Self::Mock {
122 vaults: Default::default(),
123 }
124 }
125}
126
127impl Domain<DefaultStorage> {
128 pub fn from_config(config: Config) -> DomainBuilder<DbBuilderType> {
142 let builder = Self::builder(config.shard, config.keypair);
143
144 let builder = if let Some(retention) = config.sdl_retention {
145 builder.with_sdl_retention(retention)
146 } else {
147 builder
148 };
149
150 let builder = if let Some(url) = config.rpc_url {
151 builder.with_rpc(url)
152 } else {
153 builder
154 };
155
156 #[cfg(not(feature = "rocksdb"))]
157 if let Some(StorageConfig::Rocksdb { .. }) = config.storage {
158 panic!("The `rocksdb` feature is not enabled, but the configuration requests it.");
159 }
160
161 #[cfg(feature = "rocksdb")]
162 let builder = match config.storage {
163 Some(StorageConfig::InMemory) => {
164 panic!("The `rocksdb` feature is enabled, but the configuration requests in-memory storage.");
165 },
166 Some(StorageConfig::Rocksdb { path }) => {
167 builder.with_rocksdb(DbOptions::default().with_db_prefix_path(path))
168 },
169 None => builder,
170 };
171
172 #[cfg(not(feature = "admin-api"))]
173 if config.admin_api.is_some() {
174 panic!("The `admin-api` feature is not enabled, but the configuration requests it.");
175 }
176
177 #[cfg(feature = "admin-api")]
178 let builder = if let Some(addr) = config.admin_api {
179 builder.with_admin_api_ip(addr)
180 } else {
181 builder
182 };
183
184 if let Some(proving_config) = config.proving {
185 builder.with_proving_client(proving_config.into_proving_client())
186 } else {
187 builder
188 }
189 }
190
191 pub fn builder(
203 shard: NonZero<Shard>,
204 keypair: ed25519::PrivKey,
205 ) -> DomainBuilder<DbBuilderType> {
206 #[cfg(not(feature = "rocksdb"))]
207 let storage = Database::new(in_memory::Storage::new());
208 #[cfg(feature = "rocksdb")]
209 let storage = DbOptions::default();
210
211 DomainBuilder {
213 config: RuntimeConfig {
214 shard,
215 signature_verification: SignatureVerificationConfig::Enabled,
216 sdl_retention: SdlRetentionConfig::default(),
217 base_layer_retry: BaseLayerRetryConfig::OnEpochMismatch,
218 },
219 keypair,
220 storage,
221 base_layer: BaseLayerOptions::mock(),
222 proving: Box::new(proving::mock::Client::global_laws()),
223 admin_api_address: ADMIN_API_DEFAULT_ADDR,
224 }
225 }
226
227 pub fn in_mem_builder(
232 shard: NonZero<Shard>,
233 keypair: ed25519::PrivKey,
234 ) -> DomainBuilder<Database<in_memory::Storage>> {
235 Self::builder(shard, keypair).with_storage(in_memory::Storage::new())
236 }
237}
238
239impl<S> DomainBuilder<S> {
240 pub fn with_proving_client(self, proving: Box<dyn Proving>) -> Self {
258 Self {
259 config: self.config,
260 keypair: self.keypair,
261 base_layer: self.base_layer,
262 proving,
263 storage: self.storage,
264 admin_api_address: self.admin_api_address,
265 }
266 }
267
268 pub fn with_storage<S2>(self, storage: S2) -> DomainBuilder<Database<S2>>
274 where
275 S2: KeyValueStorage<ColumnFamilyIdentifier = ColumnFamilies, Error = StorageError>
276 + Send
277 + Sync
278 + 'static,
279 {
280 self.with_database(Database::new(storage))
281 }
282
283 pub fn with_database<S2>(self, database: Database<S2>) -> DomainBuilder<Database<S2>>
289 where
290 S2: KeyValueStorage<ColumnFamilyIdentifier = ColumnFamilies, Error = StorageError>
291 + Send
292 + Sync
293 + 'static,
294 {
295 DomainBuilder {
296 config: self.config,
297 keypair: self.keypair,
298 base_layer: self.base_layer,
299 proving: self.proving,
300 storage: database,
301 admin_api_address: self.admin_api_address,
302 }
303 }
304
305 pub const fn with_sdl_retention(mut self, sdl_retention: SdlRetentionConfig) -> Self {
307 self.config.sdl_retention = sdl_retention;
308 self
309 }
310
311 pub const fn with_base_layer_retry(mut self, config: BaseLayerRetryConfig) -> Self {
313 self.config.base_layer_retry = config;
314 self
315 }
316
317 pub fn with_rpc(mut self, url: impl TryInto<Uri, Error = InvalidUri> + Send) -> Self {
322 self.base_layer = BaseLayerOptions::Rpc {
323 url: url.try_into().boxed().context(BaseLayerSnafu),
324 };
325 self
326 }
327 pub fn with_mock_rpc(mut self, mock_vaults: HashMap<Address, Vault>) -> Self {
333 self.base_layer = BaseLayerOptions::Mock {
334 vaults: mock_vaults,
335 };
336 self
337 }
338
339 #[cfg(feature = "admin-api")]
344 pub fn with_admin_api(self, port: u16) -> Self {
345 self.with_admin_api_ip((ADMIN_API_DEFAULT_ADDR.ip(), port).into())
346 }
347
348 #[cfg(feature = "admin-api")]
353 pub const fn with_admin_api_ip(mut self, ip: SocketAddr) -> Self {
354 self.admin_api_address = ip;
355 self
356 }
357}
358
359#[cfg(not(feature = "rocksdb"))]
360type DbBuilderType = Database<in_memory::Storage>;
361#[cfg(feature = "rocksdb")]
362type DbBuilderType = DbOptions;
363
364#[cfg(feature = "rocksdb")]
365mod rocksdb {
366 use super::*;
367 use crate::storage::{
368 options::DbOptions,
369 rocksdb,
370 };
371 use storage::database::spec::DbSpecWithUserOptions;
372 use storage_rocksdb::RocksDb;
373
374 impl<S> DomainBuilder<S> {
376 pub fn with_rocksdb(self, db_options: DbOptions) -> DomainBuilder<DbOptions> {
378 DomainBuilder {
379 config: self.config,
380 keypair: self.keypair,
381 base_layer: self.base_layer,
382 proving: self.proving,
383 storage: db_options,
384 admin_api_address: self.admin_api_address,
385 }
386 }
387 }
388
389 impl DomainBuilder<DbOptions> {
390 pub async fn build(self) -> Result<Domain<rocksdb::Storage>, Error> {
392 let db_spec_with_options = DbSpecWithUserOptions::new(self.storage.clone().into());
393 let storage = RocksDb::new_static(db_spec_with_options).context(StorageSnafu)?;
394 self.with_storage(storage).build().await
395 }
396 }
397}
398
399impl<S> DomainBuilder<Database<S>>
400where
401 S: KeyValueStorage<ColumnFamilyIdentifier = ColumnFamilies, Error = StorageError>
402 + Send
403 + Sync
404 + 'static,
405{
406 pub async fn build(self) -> Result<Domain<S>, Error> {
408 let Self {
409 config,
410 keypair,
411 proving,
412 storage: db,
413 base_layer,
414 admin_api_address,
415 } = self;
416
417 let (base_layer, seed_vaults) = match base_layer {
418 BaseLayerOptions::Rpc { url } => {
419 let rpc_client = rpc::BaseLayer::new(url?.to_string(), keypair)
420 .await
421 .boxed()
422 .context(BaseLayerSnafu)?;
423
424 rpc_client
425 .check_valid_domain_agreement(config.shard)
426 .await
427 .or_else(|err| match err {
428 domain_agreement::Error::Rpc { source } => {
430 Err(source).context(BaseLayerSnafu)
431 },
432 domain_agreement::Error::NotFound
433 | domain_agreement::Error::WrongOperator { .. } => Ok(()),
434 })?;
435
436 let seed_vaults = if has_vaults(db.storage()) {
442 tracing::info!(
443 "Seed vaults already present in storage. Not fetching from RPC."
444 );
445 HashMap::new()
446 } else {
447 rpc_client
448 .get_all_vaults_of(config.shard)
449 .await
450 .boxed()
451 .context(BaseLayerSnafu)?
452 };
453
454 (BaseLayer::Rpc(rpc_client), seed_vaults)
455 },
456
457 BaseLayerOptions::Mock {
458 vaults: mock_vaults,
459 } => {
460 let seed_vaults = mock_vaults
462 .iter()
463 .filter(|(id, _)| id.shard() == config.shard.get())
464 .map(|(address, vault)| (address.owner(), vault.clone()))
465 .collect();
466
467 tracing::info!("Starting domain with a mock base layer RPC.");
468 (
469 BaseLayer::Mock(mock::BaseLayer::new(mock_vaults)),
470 seed_vaults,
471 )
472 },
473 };
474
475 tracing::info!(
476 "The domain will be started with {} seed vaults.",
477 seed_vaults.len()
478 );
479
480 Ok(Domain::new(
481 config,
482 proving,
483 db,
484 base_layer,
485 seed_vaults,
486 admin_api_address,
487 ))
488 }
489}
490
491fn has_vaults<S>(storage: &S) -> bool
492where
493 S: KeyValueStorage<ColumnFamilyIdentifier = ColumnFamilies>,
494{
495 matches!(
496 storage.iter(ColumnFamilies::FinalizedVaults.name()).next(),
497 Some(Ok(_))
498 )
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use base_sdk::crypto::ed25519::test_helpers::KeyDispenser as Ed25519KeyDispenser;
505 use crypto::test_helper::KeyDispenser as _;
506
507 #[cfg(not(feature = "rocksdb"))]
509 #[tokio::test]
510 async fn test_from_config_in_mem() {
511 let config = Config::with_defaults(
512 NonZero::new(1).unwrap(),
513 Ed25519KeyDispenser::default().dispense(),
514 );
515
516 assert!(Domain::from_config(config).build().await.is_ok())
517 }
518
519 #[cfg(feature = "rocksdb")]
522 #[test]
523 fn test_with_rocksdb() {
524 use std::path::PathBuf;
525
526 let db_options = DbOptions::default().with_db_prefix_path(PathBuf::from("/test"));
527 let builder = Domain::builder(
528 NonZero::new(1).unwrap(),
529 Ed25519KeyDispenser::default().dispense(),
530 )
531 .with_rocksdb(db_options.clone());
532
533 assert_eq!(builder.storage, db_options);
534 }
535}