Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["crates/*"]
resolver = "2"

[workspace.package]
version = "0.6.3"
version = "0.6.4"
edition = "2024"
rust-version = "1.92"
authors = ["init4"]
Expand Down Expand Up @@ -35,13 +35,13 @@ incremental = false

[workspace.dependencies]
# internal
signet-hot = { version = "0.6.3", path = "./crates/hot" }
signet-hot-mdbx = { version = "0.6.3", path = "./crates/hot-mdbx" }
signet-cold = { version = "0.6.3", path = "./crates/cold" }
signet-cold-mdbx = { version = "0.6.3", path = "./crates/cold-mdbx" }
signet-cold-sql = { version = "0.6.3", path = "./crates/cold-sql" }
signet-storage = { version = "0.6.3", path = "./crates/storage" }
signet-storage-types = { version = "0.6.3", path = "./crates/types" }
signet-hot = { version = "0.6.4", path = "./crates/hot" }
signet-hot-mdbx = { version = "0.6.4", path = "./crates/hot-mdbx" }
signet-cold = { version = "0.6.4", path = "./crates/cold" }
signet-cold-mdbx = { version = "0.6.4", path = "./crates/cold-mdbx" }
signet-cold-sql = { version = "0.6.4", path = "./crates/cold-sql" }
signet-storage = { version = "0.6.4", path = "./crates/storage" }
signet-storage-types = { version = "0.6.4", path = "./crates/types" }

# External, in-house
signet-libmdbx = { version = "0.8.0" }
Expand All @@ -66,6 +66,7 @@ parking_lot = "0.12.5"
rand = "0.9.2"
rayon = "1.10"
serde = { version = "1.0.217", features = ["derive"] }
serial_test = "3.3"
tempfile = "3.20.0"
thiserror = "2.0.18"
tokio = { version = "1.45.0", features = ["full"] }
Expand Down
116 changes: 116 additions & 0 deletions crates/cold-mdbx/src/connector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! MDBX storage connector.
//!
//! Unified connector that can open both hot and cold MDBX databases.

use crate::{MdbxColdBackend, MdbxColdError};
use signet_cold::ColdConnect;
use signet_hot::HotConnect;
use signet_hot_mdbx::{DatabaseArguments, DatabaseEnv, MdbxError};
use std::path::PathBuf;

/// Errors that can occur when initializing MDBX connectors.
#[derive(Debug, thiserror::Error)]
pub enum MdbxConnectorError {
/// Missing environment variable.
#[error("missing environment variable: {0}")]
MissingEnvVar(&'static str),

/// Hot storage initialization failed.
#[error("hot storage initialization failed: {0}")]
HotInit(#[from] MdbxError),

/// Cold storage initialization failed.
#[error("cold storage initialization failed: {0}")]
ColdInit(#[from] MdbxColdError),
}

/// Connector for MDBX storage (both hot and cold).
///
/// This unified connector can open MDBX databases for both hot and cold storage.
/// It holds the path and database arguments, which can include custom geometry,
/// sync mode, max readers, and other MDBX-specific configuration.
///
/// # Example
///
/// ```ignore
/// use signet_hot_mdbx::{MdbxConnector, DatabaseArguments};
///
/// // Hot storage with custom args
/// let hot = MdbxConnector::new("/tmp/hot")
/// .with_db_args(DatabaseArguments::new().with_max_readers(1000));
///
/// // Cold storage with default args
/// let cold = MdbxConnector::new("/tmp/cold");
/// ```
#[derive(Debug, Clone)]
pub struct MdbxConnector {
path: PathBuf,
db_args: DatabaseArguments,
}

impl MdbxConnector {
/// Create a new MDBX connector with default database arguments.
pub fn new(path: impl Into<PathBuf>) -> Self {
Self { path: path.into(), db_args: DatabaseArguments::new() }
}

/// Set custom database arguments.
///
/// This allows configuring MDBX-specific settings like geometry, sync mode,
/// max readers, and exclusive mode.
#[must_use]
pub const fn with_db_args(mut self, db_args: DatabaseArguments) -> Self {
self.db_args = db_args;
self
}

/// Get a reference to the path.
pub fn path(&self) -> &std::path::Path {
&self.path
}

/// Get a reference to the database arguments.
pub const fn db_args(&self) -> &DatabaseArguments {
&self.db_args
}

/// Create a connector from environment variables.
///
/// Reads the path from the specified environment variable.
///
/// # Example
///
/// ```ignore
/// use signet_cold_mdbx::MdbxConnector;
///
/// let hot = MdbxConnector::from_env("SIGNET_HOT_PATH")?;
/// let cold = MdbxConnector::from_env("SIGNET_COLD_PATH")?;
/// ```
pub fn from_env(env_var: &'static str) -> Result<Self, MdbxConnectorError> {
let path: PathBuf =
std::env::var(env_var).map_err(|_| MdbxConnectorError::MissingEnvVar(env_var))?.into();
Ok(Self::new(path))
}
}

impl HotConnect for MdbxConnector {
type Hot = DatabaseEnv;
type Error = MdbxError;

fn connect(&self) -> Result<Self::Hot, Self::Error> {
self.db_args.clone().open_rw(&self.path)
}
}

impl ColdConnect for MdbxConnector {
type Cold = MdbxColdBackend;
type Error = MdbxColdError;

#[allow(clippy::manual_async_fn)]
fn connect(&self) -> impl std::future::Future<Output = Result<Self::Cold, Self::Error>> + Send {
// MDBX open is sync, but wrapped in async for trait consistency
// Opens read-write and creates tables
let path = self.path.clone();
async move { MdbxColdBackend::open_rw(&path) }
}
}
3 changes: 3 additions & 0 deletions crates/cold-mdbx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ pub use tables::{
mod backend;
pub use backend::MdbxColdBackend;

mod connector;
pub use connector::{MdbxConnector, MdbxConnectorError};

pub use signet_hot_mdbx::{DatabaseArguments, DatabaseEnvKind};
83 changes: 83 additions & 0 deletions crates/cold-sql/src/connector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//! SQL cold storage connector.

use crate::{SqlColdBackend, SqlColdError};
use signet_cold::ColdConnect;

/// Errors that can occur when initializing SQL connectors.
#[derive(Debug, thiserror::Error)]
pub enum SqlConnectorError {
/// Missing environment variable.
#[error("missing environment variable: {0}")]
MissingEnvVar(&'static str),

/// Cold storage initialization failed.
#[error("cold storage initialization failed: {0}")]
ColdInit(#[from] SqlColdError),
}

/// Connector for SQL cold storage (PostgreSQL or SQLite).
///
/// Automatically detects the database type from the URL:
/// - URLs starting with `postgres://` or `postgresql://` use PostgreSQL
/// - URLs starting with `sqlite:` use SQLite
///
/// # Example
///
/// ```ignore
/// use signet_cold_sql::SqlConnector;
///
/// // PostgreSQL
/// let pg = SqlConnector::new("postgres://localhost/signet");
/// let backend = pg.connect().await?;
///
/// // SQLite
/// let sqlite = SqlConnector::new("sqlite::memory:");
/// let backend = sqlite.connect().await?;
/// ```
#[cfg(any(feature = "sqlite", feature = "postgres"))]
#[derive(Debug, Clone)]
pub struct SqlConnector {
url: String,
}

#[cfg(any(feature = "sqlite", feature = "postgres"))]
impl SqlConnector {
/// Create a new SQL connector.
///
/// The database type is detected from the URL prefix.
pub fn new(url: impl Into<String>) -> Self {
Self { url: url.into() }
}

/// Get a reference to the connection URL.
pub fn url(&self) -> &str {
&self.url
}

/// Create a connector from environment variables.
///
/// Reads the SQL URL from the specified environment variable.
///
/// # Example
///
/// ```ignore
/// use signet_cold_sql::SqlConnector;
///
/// let cold = SqlConnector::from_env("SIGNET_COLD_SQL_URL")?;
/// ```
pub fn from_env(env_var: &'static str) -> Result<Self, SqlConnectorError> {
let url = std::env::var(env_var).map_err(|_| SqlConnectorError::MissingEnvVar(env_var))?;
Ok(Self::new(url))
}
}

#[cfg(any(feature = "sqlite", feature = "postgres"))]
impl ColdConnect for SqlConnector {
type Cold = SqlColdBackend;
type Error = SqlColdError;

fn connect(&self) -> impl std::future::Future<Output = Result<Self::Cold, Self::Error>> + Send {
let url = self.url.clone();
async move { SqlColdBackend::connect(&url).await }
}
}
5 changes: 5 additions & 0 deletions crates/cold-sql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ mod backend;
#[cfg(any(feature = "sqlite", feature = "postgres"))]
pub use backend::SqlColdBackend;

#[cfg(any(feature = "sqlite", feature = "postgres"))]
mod connector;
#[cfg(any(feature = "sqlite", feature = "postgres"))]
pub use connector::{SqlConnector, SqlConnectorError};

/// Backward-compatible alias for [`SqlColdBackend`] when using SQLite.
#[cfg(feature = "sqlite")]
pub type SqliteColdBackend = SqlColdBackend;
Expand Down
21 changes: 21 additions & 0 deletions crates/cold/src/connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Connection traits for cold storage backends.

use crate::ColdStorage;

/// Connector trait for cold storage backends.
///
/// Abstracts the connection/opening process for cold storage, allowing
/// different backends to implement their own initialization logic.
pub trait ColdConnect {
/// The cold storage type produced by this connector.
type Cold: ColdStorage;

/// The error type returned by connection attempts.
type Error: std::error::Error + Send + Sync + 'static;

/// Connect to the cold storage backend asynchronously.
///
/// Async to support backends that require async initialization
/// (like SQL connection pools).
fn connect(&self) -> impl std::future::Future<Output = Result<Self::Cold, Self::Error>> + Send;
}
3 changes: 3 additions & 0 deletions crates/cold/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ pub use stream::{StreamParams, produce_log_stream_default};
mod traits;
pub use traits::{BlockData, ColdStorage, LogStream};

pub mod connect;
pub use connect::ColdConnect;

/// Task module containing the storage task runner and handles.
pub mod task;
pub use task::{ColdStorageHandle, ColdStorageReadHandle, ColdStorageTask};
Expand Down
20 changes: 20 additions & 0 deletions crates/hot/src/connect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Connection traits for hot storage backends.

use crate::model::HotKv;

/// Connector trait for hot storage backends.
///
/// Abstracts the connection/opening process for hot storage, allowing
/// different backends to implement their own initialization logic.
pub trait HotConnect {
/// The hot storage type produced by this connector.
type Hot: HotKv;

/// The error type returned by connection attempts.
type Error: std::error::Error + Send + Sync + 'static;

/// Connect to the hot storage backend.
///
/// Synchronous since most hot storage backends use sync initialization.
fn connect(&self) -> Result<Self::Hot, Self::Error>;
}
3 changes: 3 additions & 0 deletions crates/hot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@
#[cfg(any(test, feature = "test-utils"))]
pub mod conformance;

pub mod connect;
pub use connect::HotConnect;

pub mod db;
pub use db::{HistoryError, HistoryRead, HistoryWrite};

Expand Down
6 changes: 6 additions & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
signet-cold.workspace = true
signet-cold-mdbx.workspace = true
signet-cold-sql = { workspace = true, optional = true }
signet-hot.workspace = true
signet-hot-mdbx.workspace = true
signet-storage-types.workspace = true

alloy.workspace = true
thiserror.workspace = true
tokio-util.workspace = true

[dev-dependencies]
serial_test.workspace = true
signet-storage = { path = ".", features = ["test-utils"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
tokio-util.workspace = true
Expand All @@ -33,3 +37,5 @@ trevm.workspace = true
[features]
default = []
test-utils = ["signet-hot/test-utils", "signet-cold/test-utils"]
postgres = ["signet-cold-sql/postgres"]
sqlite = ["signet-cold-sql/sqlite"]
Loading