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::DefaultStorage,
24 Domain,
25};
26use base_sdk::{
27 core::Shard,
28 crypto::ed25519,
29 vaults::{
30 Address,
31 Vault,
32 },
33};
34use domain_runtime::storage::ColumnFamilies;
35use http::{
36 uri::InvalidUri,
37 Uri,
38};
39use local_laws::LocalLaws;
40use proving_clients::Proving;
41use snafu::{
42 ResultExt,
43 Snafu,
44};
45#[cfg(feature = "admin-api")]
46use std::net::{
47 Ipv6Addr,
48 SocketAddr,
49};
50use std::{
51 collections::HashMap,
52 num::NonZero,
53};
54use storage::{
55 column_family::ColumnFamilies as _,
56 database::StorageError,
57 key_value::KeyValueStorageWithColumnFamilies,
58};
59
60#[derive(Debug, Snafu)]
62pub enum Error {
63 #[snafu(display("Base layer error: {source}"))]
65 BaseLayer {
66 source: Box<dyn std::error::Error + Send + Sync>,
68 },
69
70 #[snafu(display("Error while initializing storage: {source}"))]
72 Storage {
73 source: StorageError,
75 },
76}
77
78#[cfg(feature = "admin-api")]
80pub const ADMIN_API_DEFAULT_PORT: u16 = 4001;
81
82#[derive(Debug)]
92pub struct DomainBuilder<S, P> {
93 shard: NonZero<Shard>,
94 keypair: ed25519::PrivKey,
95 base_layer: BaseLayerOptions,
96 proving: P,
97 storage: S,
98
99 #[cfg(feature = "admin-api")]
100 admin_api_address: SocketAddr,
101}
102
103#[derive(Debug)]
105enum BaseLayerOptions {
106 Rpc { url: Result<Uri, Error> },
107 Mock { vaults: HashMap<Address, Vault> },
108}
109
110impl BaseLayerOptions {
111 fn mock() -> Self {
112 Self::Mock {
113 vaults: Default::default(),
114 }
115 }
116}
117
118impl Domain<proving::mock::Client, DefaultStorage> {
119 pub fn from_config(config: Config) -> DomainBuilder<DbBuilderType, proving::mock::Client> {
129 let builder = Self::builder(config.shard, config.keypair);
130 let builder = if let Some(url) = config.rpc_url {
131 builder.with_rpc(url)
132 } else {
133 builder
134 };
135
136 #[cfg(not(feature = "rocksdb"))]
137 if let Some(StorageConfig::RocksDb { .. }) = config.storage {
138 tracing::warn!("Domain configured to use rocksdb, but the feature is disabled");
139 }
140
141 #[cfg(feature = "rocksdb")]
142 let builder = if let Some(StorageConfig::RocksDb { path }) = config.storage {
143 builder.with_rocksdb(DbOptions::default().with_db_prefix_path(path))
144 } else {
145 builder
146 };
147
148 builder
149 }
150
151 pub fn builder(
163 shard: NonZero<Shard>,
164 keypair: ed25519::PrivKey,
165 ) -> DomainBuilder<DbBuilderType, proving::mock::Client> {
166 #[cfg(not(feature = "rocksdb"))]
167 let storage = in_memory::Storage::new();
168 #[cfg(feature = "rocksdb")]
169 let storage = DbOptions::default();
170
171 DomainBuilder {
172 shard,
173 keypair,
174 storage,
175 base_layer: BaseLayerOptions::mock(),
176 proving: proving::mock::Client::global_laws(),
177 #[cfg(feature = "admin-api")]
178 admin_api_address: (Ipv6Addr::UNSPECIFIED, ADMIN_API_DEFAULT_PORT).into(),
179 }
180 }
181
182 pub fn in_mem_builder(
185 shard: NonZero<Shard>,
186 keypair: ed25519::PrivKey,
187 ) -> DomainBuilder<in_memory::Storage, proving::mock::Client> {
188 Self::builder(shard, keypair).with_storage(in_memory::Storage::new())
189 }
190}
191
192impl<S, P> DomainBuilder<S, P> {
193 pub fn with_proving_client<P2>(self, proving: P2) -> DomainBuilder<S, P2>
211 where
212 P2: Proving,
213 {
214 DomainBuilder {
215 shard: self.shard,
216 keypair: self.keypair,
217 base_layer: self.base_layer,
218 proving,
219 storage: self.storage,
220 #[cfg(feature = "admin-api")]
221 admin_api_address: self.admin_api_address,
222 }
223 }
224
225 pub fn with_storage<S2>(self, storage: S2) -> DomainBuilder<S2, P>
233 where
234 S2: KeyValueStorageWithColumnFamilies<
235 ColumnFamilyIdentifier = ColumnFamilies,
236 Error = StorageError,
237 > + Send
238 + Sync
239 + 'static,
240 {
241 DomainBuilder {
242 shard: self.shard,
243 keypair: self.keypair,
244 base_layer: self.base_layer,
245 proving: self.proving,
246 storage,
247 #[cfg(feature = "admin-api")]
248 admin_api_address: self.admin_api_address,
249 }
250 }
251
252 pub fn with_rpc(mut self, url: impl TryInto<Uri, Error = InvalidUri> + Send) -> Self {
257 self.base_layer = BaseLayerOptions::Rpc {
258 url: url.try_into().boxed().context(BaseLayerSnafu),
259 };
260 self
261 }
262 pub fn with_mock_rpc(mut self, mock_vaults: HashMap<Address, Vault>) -> Self {
268 self.base_layer = BaseLayerOptions::Mock {
269 vaults: mock_vaults,
270 };
271 self
272 }
273
274 #[cfg(feature = "admin-api")]
279 pub fn with_admin_api(self, port: u16) -> Self {
280 self.with_admin_api_ip((Ipv6Addr::UNSPECIFIED, port).into())
281 }
282
283 #[cfg(feature = "admin-api")]
288 pub const fn with_admin_api_ip(mut self, ip: SocketAddr) -> Self {
289 self.admin_api_address = ip;
290 self
291 }
292}
293
294impl<S> DomainBuilder<S, proving::mock::Client> {
295 pub fn with_mock_local_laws<L: LocalLaws>(mut self) -> Self {
298 self.proving = self.proving.with_local_laws::<L>();
299 self
300 }
301}
302
303#[cfg(not(feature = "rocksdb"))]
304type DbBuilderType = in_memory::Storage;
305#[cfg(feature = "rocksdb")]
306type DbBuilderType = DbOptions;
307
308#[cfg(feature = "rocksdb")]
309mod rocksdb {
310 use super::*;
311 use crate::storage::{
312 options::DbOptions,
313 rocksdb,
314 };
315 use storage::database::spec::DbSpecWithUserOptions;
316 use storage_rocksdb::RocksDb;
317
318 impl<S, P> DomainBuilder<S, P> {
320 pub fn with_rocksdb(self, db_options: DbOptions) -> DomainBuilder<DbOptions, P> {
322 DomainBuilder {
323 shard: self.shard,
324 keypair: self.keypair,
325 base_layer: self.base_layer,
326 proving: self.proving,
327 storage: db_options,
328 #[cfg(feature = "admin-api")]
329 admin_api_address: self.admin_api_address,
330 }
331 }
332 }
333
334 impl<P> DomainBuilder<DbOptions, P>
335 where
336 P: Proving,
337 {
338 pub async fn build(self) -> Result<Domain<P, rocksdb::Storage>, Error> {
340 let db_spec_with_options = DbSpecWithUserOptions::new(self.storage.clone().into());
341 let storage = RocksDb::new_static(db_spec_with_options).context(StorageSnafu)?;
342 self.with_storage(storage).build().await
343 }
344 }
345}
346
347impl<P, S> DomainBuilder<S, P>
348where
349 P: Proving,
350 S: KeyValueStorageWithColumnFamilies<
351 ColumnFamilyIdentifier = ColumnFamilies,
352 Error = StorageError,
353 > + Send
354 + Sync
355 + 'static,
356{
357 pub async fn build(self) -> Result<Domain<P, S>, Error> {
359 let Self {
360 shard,
361 keypair,
362 proving,
363 storage,
364 base_layer,
365 #[cfg(feature = "admin-api")]
366 admin_api_address,
367 } = self;
368
369 let (base_layer, seed_vaults) = match base_layer {
370 BaseLayerOptions::Rpc { url } => {
371 let rpc_client = rpc::BaseLayer::new(url?.to_string(), keypair)
372 .await
373 .boxed()
374 .context(BaseLayerSnafu)?;
375
376 rpc_client
377 .check_valid_domain_agreement(shard)
378 .await
379 .or_else(|err| match err {
380 domain_agreement::Error::Rpc { source } => {
382 Err(source).context(BaseLayerSnafu)
383 },
384 domain_agreement::Error::NotFound
385 | domain_agreement::Error::WrongOperator { .. } => {
386 tracing::warn!("{err}");
387 Ok(())
388 },
389 })?;
390
391 let seed_vaults = if has_vaults(&storage) {
397 tracing::info!(
398 "Seed vaults already present in storage. Not fetching from RPC."
399 );
400 HashMap::new()
401 } else {
402 rpc_client
403 .get_all_vaults_of(shard)
404 .await
405 .boxed()
406 .context(BaseLayerSnafu)?
407 };
408
409 (BaseLayer::Rpc(rpc_client), seed_vaults)
410 },
411
412 BaseLayerOptions::Mock {
413 vaults: mock_vaults,
414 } => {
415 let seed_vaults = mock_vaults
417 .iter()
418 .filter(|(id, _)| id.shard() == shard.get())
419 .map(|(address, vault)| (address.owner(), vault.clone()))
420 .collect();
421
422 tracing::info!("Starting domain with a mock base layer RPC.");
423 (
424 BaseLayer::Mock(mock::BaseLayer::new(mock_vaults)),
425 seed_vaults,
426 )
427 },
428 };
429
430 tracing::info!(
431 "The domain will be started with {} seed vaults.",
432 seed_vaults.len()
433 );
434
435 Ok(Domain::new(
436 shard,
437 proving,
438 storage,
439 base_layer,
440 seed_vaults,
441 #[cfg(feature = "admin-api")]
442 admin_api_address,
443 ))
444 }
445}
446
447fn has_vaults<S>(storage: &S) -> bool
448where
449 S: KeyValueStorageWithColumnFamilies<ColumnFamilyIdentifier = ColumnFamilies>,
450{
451 matches!(
452 storage.iter(ColumnFamilies::FinalizedVaults.name()).next(),
453 Some(Ok(_))
454 )
455}
456
457#[cfg(test)]
458mod tests {
459 #[cfg(feature = "rocksdb")]
462 #[test]
463 fn test_with_rocksdb() {
464 use crate::{
465 storage::options::DbOptions,
466 Domain,
467 };
468 use base_sdk::crypto::ed25519::test_helpers::KeyDispenser as Ed25519KeyDispenser;
469 use crypto::test_helper::KeyDispenser as _;
470 use std::{
471 num::NonZero,
472 path::PathBuf,
473 };
474
475 let db_options = DbOptions::default().with_db_prefix_path(PathBuf::from("/test"));
476 let builder = Domain::builder(
477 NonZero::new(1).unwrap(),
478 Ed25519KeyDispenser::default().dispense(),
479 )
480 .with_rocksdb(db_options.clone());
481
482 assert_eq!(builder.storage, db_options);
483 }
484}