From 27e8a15c12b8bd04e2a99a0ab4dc1fd4debc77dd Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 18 Jul 2025 10:05:51 +0200 Subject: [PATCH 1/5] feat(storage): adding sled, making rocksdb optional --- Cargo.lock | 213 +++++++- Cargo.toml | 11 +- crates/cli/Cargo.toml | 1 + crates/cli/src/apply_args/database.rs | 8 +- crates/cli/src/cfg.rs | 416 +++++++++++++++ crates/cli/src/tests/mod.rs | 2 + crates/storage/Cargo.toml | 10 +- crates/storage/src/database.rs | 9 + crates/storage/src/factory.rs | 14 +- crates/storage/src/lib.rs | 3 + crates/storage/src/rocksdb.rs | 66 --- crates/storage/src/sled.rs | 286 ++++++++++ crates/storage/src/tests/mod.rs | 144 ++++- crates/tests/src/lib.rs | 6 +- crates/tree/Cargo.toml | 11 +- crates/tree/benches/database_comparison.rs | 588 +++++++++++++++++++++ crates/tree/src/tests/mod.rs | 26 +- 17 files changed, 1694 insertions(+), 120 deletions(-) create mode 100644 crates/cli/src/cfg.rs create mode 100644 crates/storage/src/sled.rs create mode 100644 crates/tree/benches/database_comparison.rs diff --git a/Cargo.lock b/Cargo.lock index 382fa268..f0415b5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,6 +512,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -1953,6 +1959,12 @@ dependencies = [ "thiserror 2.0.12", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbindgen" version = "0.27.0" @@ -2117,6 +2129,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cid" version = "0.11.1" @@ -2452,6 +2491,42 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -2620,7 +2695,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.11", ] [[package]] @@ -2634,7 +2709,7 @@ dependencies = [ "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.11", ] [[package]] @@ -3475,6 +3550,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -3635,6 +3720,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gcd" version = "2.3.0" @@ -3823,6 +3917,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "halo2" version = "0.1.0-beta.2" @@ -3972,7 +4076,7 @@ dependencies = [ "ipconfig", "lru-cache", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "rand 0.8.5", "resolv-conf", "smallvec", @@ -4650,7 +4754,7 @@ dependencies = [ "mirai-annotations", "num-derive", "num-traits", - "parking_lot", + "parking_lot 0.12.4", "serde", "sha2 0.10.8", "thiserror 1.0.69", @@ -5062,7 +5166,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "quick-protobuf", "rand 0.8.5", @@ -5090,7 +5194,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "quick-protobuf", "rand 0.8.5", @@ -5114,7 +5218,7 @@ dependencies = [ "hickory-resolver", "libp2p-core 0.42.0", "libp2p-identity", - "parking_lot", + "parking_lot 0.12.4", "smallvec", "tracing", ] @@ -5317,7 +5421,7 @@ dependencies = [ "libp2p-core 0.42.0", "libp2p-identity", "libp2p-tls", - "parking_lot", + "parking_lot 0.12.4", "quinn", "rand 0.8.5", "ring 0.17.14", @@ -5449,7 +5553,7 @@ dependencies = [ "futures-rustls", "libp2p-core 0.42.0", "libp2p-identity", - "parking_lot", + "parking_lot 0.12.4", "pin-project-lite", "rw-stream-sink", "soketto", @@ -5469,7 +5573,7 @@ dependencies = [ "futures", "js-sys", "libp2p-core 0.41.3", - "parking_lot", + "parking_lot 0.12.4", "send_wrapper 0.6.0", "thiserror 1.0.69", "tracing", @@ -5487,7 +5591,7 @@ dependencies = [ "futures", "js-sys", "libp2p-core 0.42.0", - "parking_lot", + "parking_lot 0.12.4", "send_wrapper 0.6.0", "thiserror 1.0.69", "tracing", @@ -6378,6 +6482,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -6849,6 +6959,17 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.4" @@ -6856,7 +6977,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.11", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -6867,7 +7002,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] @@ -7203,7 +7338,7 @@ dependencies = [ "log", "nix 0.27.1", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "smallvec", "symbolic-demangle", "tempfile", @@ -7518,6 +7653,7 @@ dependencies = [ "auto_impl", "jmt", "mockall", + "paste", "prism-common", "prism-da", "prism-errors", @@ -7525,6 +7661,7 @@ dependencies = [ "prism-serde", "rocksdb", "serde", + "sled", "tempfile", "tokio", "tracing", @@ -7556,7 +7693,7 @@ dependencies = [ "lazy_static", "opentelemetry", "opentelemetry_sdk", - "parking_lot", + "parking_lot 0.12.4", "prism-telemetry", "thiserror 2.0.12", "tracing", @@ -7587,6 +7724,7 @@ name = "prism-tree" version = "0.1.0" dependencies = [ "anyhow", + "criterion", "jmt", "paste", "prism-common", @@ -7696,7 +7834,7 @@ checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", - "parking_lot", + "parking_lot 0.12.4", "prometheus-client-derive-encode", ] @@ -8136,6 +8274,15 @@ dependencies = [ "libc", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.13" @@ -9135,7 +9282,7 @@ dependencies = [ "futures", "log", "once_cell", - "parking_lot", + "parking_lot 0.12.4", "scc", "serial_test_derive", ] @@ -9263,6 +9410,22 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +[[package]] +name = "sled" +version = "0.34.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" +dependencies = [ + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -10276,6 +10439,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.9.0" @@ -10302,7 +10475,7 @@ dependencies = [ "io-uring", "libc", "mio", - "parking_lot", + "parking_lot 0.12.4", "pin-project-lite", "signal-hook-registry", "slab", @@ -11789,7 +11962,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "rand 0.8.5", "static_assertions", @@ -11804,7 +11977,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot", + "parking_lot 0.12.4", "pin-project", "rand 0.8.5", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index 1fe83377..b9735a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,9 @@ authors = [ ] edition = "2024" description = "prism is the first trust-minimized key-transparency solution, allowing for automatic verification of service providers via light clients. Powered by Celestia." -homepage = "https://prism.deltadevs.xyz" +homepage = "https://prism.rs" repository = "https://github.com/deltadevsde/prism" -license = "MIT" +license = "AGPL-3.0" keywords = ["crypto", "key-transparency"] readme = "README.md" @@ -84,8 +84,8 @@ reqwest = { version = "0.12", features = ["json"] } url = { version = "2.5" } # database -rocksdb = { version = "0.21.0", features = ["multi-threaded-cf"] } redb = "2.6.0" +sled = "0.34.7" # async async-trait = "0.1.86" @@ -282,3 +282,8 @@ unexpected_cfgs = { level = "warn", check-cfg = [ # https://davidlattimore.github.io/posts/2024/02/04/speeding-up-the-rust-edit-build-run-cycle.html debug = "line-tables-only" split-debuginfo = "unpacked" + +[profile.bench] +opt-level = 3 +debug = false +lto = true diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 00b80ef1..0fb10ada 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -12,6 +12,7 @@ path = "src/main.rs" [features] default = [] +rocksdb = ["prism-storage/rocksdb"] test_utils = [] [dependencies] diff --git a/crates/cli/src/apply_args/database.rs b/crates/cli/src/apply_args/database.rs index 35d5b326..85429616 100644 --- a/crates/cli/src/apply_args/database.rs +++ b/crates/cli/src/apply_args/database.rs @@ -1,5 +1,8 @@ use anyhow::Result; -use prism_storage::{DatabaseConfig, rocksdb::RocksDBConfig}; +use prism_storage::DatabaseConfig; + +#[cfg(feature = "rocksdb")] +use prism_storage::rocksdb::RocksDBConfig; use crate::cli_args::{CliDatabaseArgs, CliDatabaseType}; @@ -9,6 +12,7 @@ pub fn apply_database_args(config: &mut DatabaseConfig, args: &CliDatabaseArgs) // No cli arg specified, do not modify config Ok(()) } + #[cfg(feature = "rocksdb")] (DatabaseConfig::RocksDB(rocksdb_config), Some(CliDatabaseType::RocksDB)) => { apply_rocksdb_args(rocksdb_config, args) } @@ -20,6 +24,7 @@ pub fn apply_database_args(config: &mut DatabaseConfig, args: &CliDatabaseArgs) } } +#[cfg(feature = "rocksdb")] fn apply_rocksdb_args(config: &mut RocksDBConfig, args: &CliDatabaseArgs) -> Result<()> { if let Some(path) = &args.rocksdb_path { config.path = path.clone(); @@ -28,6 +33,7 @@ fn apply_rocksdb_args(config: &mut RocksDBConfig, args: &CliDatabaseArgs) -> Res } #[cfg_attr(coverage_nightly, coverage(off))] +#[cfg(feature = "rocksdb")] #[cfg(test)] mod tests { use anyhow::Result; diff --git a/crates/cli/src/cfg.rs b/crates/cli/src/cfg.rs new file mode 100644 index 00000000..a367af75 --- /dev/null +++ b/crates/cli/src/cfg.rs @@ -0,0 +1,416 @@ +use anyhow::{Context, Result, anyhow}; +use clap::{Args, Parser, Subcommand, ValueEnum}; +use config::{ConfigBuilder, File, builder::DefaultState}; +use dirs::home_dir; +use dotenvy::dotenv; +use prism_errors::{DataAvailabilityError, GeneralError}; +use prism_keys::VerifyingKey; +use prism_prover::{prover::DEFAULT_MAX_EPOCHLESS_GAP, webserver::WebServerConfig}; +use prism_serde::base64::FromBase64; + +#[cfg(feature = "rocksdb")] +use prism_storage::rocksdb::{RocksDBConfig, RocksDBConnection}; +use prism_storage::{ + Database, + database::StorageBackend, + inmemory::InMemoryDatabase, + sled::{SledConfig, SledConnection}, +}; + +use prism_telemetry::config::{TelemetryConfig, get_default_telemetry_config}; +use serde::{Deserialize, Serialize}; +use std::{fs, path::Path, str::FromStr, sync::Arc, time::Duration}; +use tracing::{error, info}; + +use prism_da::{ + DataAvailabilityLayer, LightDataAvailabilityLayer, + celestia::{ + full_node::CelestiaConnection, + light_client::LightClientConnection, + utils::{CelestiaConfig, Network, NetworkConfig}, + }, + consts::{DA_RETRY_COUNT, DA_RETRY_INTERVAL}, + memory::InMemoryDataAvailabilityLayer, +}; + +#[derive(Clone, Debug, Subcommand, Deserialize)] +pub enum Commands { + LightClient(CommandArgs), + FullNode(CommandArgs), + Prover(CommandArgs), +} + +#[derive(Args, Deserialize, Clone, Debug)] +pub struct CommandArgs { + #[arg(short = 'n', long, default_value = "local")] + network_name: Option, + + #[arg(long)] + /// Prover's verifying key, used to verify epoch signatures. Expected to be a base64-encoded + /// string. + verifying_key: Option, + + #[arg(long)] + home_path: Option, + + #[command(flatten)] + database: DatabaseArgs, + + /// The type of keystore to use. + /// + /// Can be one of: `keychain`, `file`. + #[arg(long, default_value = "keychain")] + keystore_type: Option, + + /// The path to the keystore. + /// + /// This is only used if the keystore type is `file`. + #[arg(long, default_value = "~/.prism/keystore.json")] + keystore_path: Option, + + #[command(flatten)] + celestia: CelestiaArgs, + + #[command(flatten)] + webserver: WebserverArgs, +} + +#[derive(Parser, Clone, Debug, Deserialize)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Args, Deserialize, Clone, Debug)] +#[group(required = false, multiple = true)] +struct CelestiaArgs { + /// Celestia Client websocket URL + #[arg(short = 'c', long)] + celestia_client: Option, + + /// Celestia Snark Namespace ID + #[arg(long)] + snark_namespace_id: Option, + + /// Celestia Transaction Namespace ID + #[arg(long)] + operation_namespace_id: Option, + + /// Height to start searching the DA layer for SNARKs on + #[arg(short = 's', long)] + celestia_start_height: Option, +} + +#[derive(Args, Deserialize, Clone, Debug)] +#[group(required = false, multiple = true)] +struct WebserverArgs { + #[arg(long)] + webserver_active: Option, + + /// IP address for the webserver to listen on + #[arg(long, requires = "webserver_active", default_value = "127.0.0.1")] + host: Option, + + /// Port number for the webserver to listen on + #[arg(short, long, requires = "webserver_active")] + port: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Config { + #[serde(skip_serializing_if = "Option::is_none")] + pub webserver: Option, + pub network: NetworkConfig, + pub keystore_type: Option, + pub keystore_path: Option, + pub da_layer: DALayerOption, + pub db: StorageBackend, + pub telemetry: Option, + /// Maximum number of DA heights the prover will wait before posting a gapfiller proof + pub max_epochless_gap: u64, +} + +impl Config { + fn initialize(path: &str, network_name: &str) -> Self { + Config { + webserver: Some(WebServerConfig::default()), + keystore_type: Some("keychain".to_string()), + keystore_path: Some(format!("{}keystore.json", path)), + network: Network::from_str(network_name).unwrap().config(), + da_layer: DALayerOption::default(), + db: StorageBackend::Sled(SledConfig::new(&format!("{}data", path))), + telemetry: Some(get_default_telemetry_config()), + max_epochless_gap: DEFAULT_MAX_EPOCHLESS_GAP, + } + } +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum DALayerOption { + #[default] + Celestia, + InMemory, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, ValueEnum)] +pub enum DBValues { + #[default] + Sled, + #[cfg(feature = "rocksdb")] + RocksDB, + InMemory, +} + +#[derive(Args, Deserialize, Clone, Debug)] +pub struct DatabaseArgs { + #[arg(long, value_enum, default_value_t = DBValues::Sled)] + /// Storage backend to use. Default: `sled` + db_type: DBValues, + + /// Path to the database, used when `db_type` is `rocks-db` or `sled` + #[arg(long)] + db_path: Option, +} + +pub fn load_config(args: CommandArgs) -> Result { + dotenv().ok(); + + let home_path = get_prism_home(&args).context("Failed to determine prism home path")?; + + ensure_config_file_exists( + &home_path, + &args.clone().network_name.unwrap_or("custom".to_string()), + ) + .context("Failed to ensure config file exists")?; + + if let Some(rocksdb_path) = &args.database.db_path { + fs::create_dir_all(rocksdb_path).context("Failed to create RocksDB directory")?; + } + + let config_source = ConfigBuilder::::default() + .add_source(File::with_name(&format!("{}/config.toml", home_path))) + .build() + .context("Failed to build config")?; + + info!("Config file contents: {:?}", config_source); + + let loaded_config: Config = + config_source.try_deserialize().context("Failed to deserialize config file")?; + + let final_config = apply_command_line_args(loaded_config, args); + + info!("Final config: {:?}", final_config); + + Ok(final_config) +} + +fn get_prism_home(args: &CommandArgs) -> Result { + let network_name = args.network_name.clone().unwrap_or_else(|| "custom".to_string()); + args.home_path + .clone() + .or_else(|| { + home_dir().map(|path| format!("{}/.prism/{}/", path.to_string_lossy(), network_name)) + }) + .ok_or_else(|| { + GeneralError::MissingArgumentError("could not determine config path".to_string()).into() + }) +} + +fn ensure_config_file_exists(home_path: &str, network_name: &str) -> Result<()> { + let config_path = &format!("{}/config.toml", home_path); + if !Path::new(config_path).exists() { + if let Some(parent) = Path::new(config_path).parent() { + fs::create_dir_all(parent).context("Failed to create config directory")?; + } + + let default_config = Config::initialize(home_path, network_name); + let config_toml = + toml::to_string(&default_config).context("Failed to serialize default config")?; + + fs::write(config_path, config_toml).context("Failed to write default config to disk")?; + } + Ok(()) +} + +fn apply_command_line_args(config: Config, args: CommandArgs) -> Config { + let webserver_config = &config.webserver.unwrap_or_default(); + let network_config = &config.network.network.config(); + let prism_home = get_prism_home(&args.clone()).unwrap(); + + let default_celestia_config = CelestiaConfig::default(); + let celestia_config = match config.da_layer { + DALayerOption::Celestia => { + let existing_config = config.network.celestia_config.clone().unwrap_or_default(); + + Some(CelestiaConfig { + connection_string: args + .celestia + .celestia_client + .or(Some(existing_config.connection_string)) + .unwrap_or(default_celestia_config.connection_string), + + start_height: args + .celestia + .celestia_start_height + .or(Some(existing_config.start_height)) + .unwrap_or(default_celestia_config.start_height), + + snark_namespace_id: args + .celestia + .snark_namespace_id + .or(Some(existing_config.snark_namespace_id)) + .unwrap_or(default_celestia_config.snark_namespace_id), + + operation_namespace_id: args + .celestia + .operation_namespace_id + .or(Some(existing_config.operation_namespace_id)) + .unwrap_or(default_celestia_config.operation_namespace_id), + + pruning_delay: existing_config.pruning_delay, + sampling_window: existing_config.sampling_window, + fetch_timeout: existing_config.fetch_timeout, + fetch_max_retries: existing_config.fetch_max_retries, + }) + } + DALayerOption::InMemory => None, + }; + + Config { + webserver: Some(WebServerConfig { + enabled: args.webserver.webserver_active.unwrap_or(webserver_config.enabled), + host: args.webserver.host.unwrap_or(webserver_config.host.clone()), + port: args.webserver.port.unwrap_or(webserver_config.port), + }), + db: match args.database.db_type { + #[cfg(feature = "rocksdb")] + DBValues::RocksDB => StorageBackend::RocksDB(RocksDBConfig { + path: args.database.rocksdb_path.unwrap_or_else(|| format!("{}/data", prism_home)), + }), + DBValues::Sled => StorageBackend::Sled(SledConfig { + path: args.database.db_path.unwrap_or_else(|| format!("{}/data", prism_home)), + ..SledConfig::default() + }), + DBValues::InMemory => StorageBackend::InMemory, + }, + network: NetworkConfig { + network: Network::from_str(&args.network_name.unwrap_or_default()).unwrap(), + celestia_network: network_config.celestia_network.clone(), + verifying_key: args + .verifying_key + .and_then(|x| VerifyingKey::from_base64(x).ok()) + .unwrap_or(network_config.verifying_key.clone()), + celestia_config, + }, + keystore_type: args.keystore_type.or(config.keystore_type), + keystore_path: args.keystore_path.or(config.keystore_path), + da_layer: config.da_layer, + telemetry: config.telemetry, + max_epochless_gap: config.max_epochless_gap, + } +} + +pub fn initialize_db(cfg: &Config) -> Result>> { + match &cfg.db { + #[cfg(feature = "rocksdb")] + StorageBackend::RocksDB(cfg) => { + let db = RocksDBConnection::new(cfg) + .map_err(|e| GeneralError::InitializationError(e.to_string())) + .context("Failed to initialize RocksDB")?; + + Ok(Arc::new(Box::new(db) as Box)) + } + StorageBackend::Sled(cfg) => { + let db = SledConnection::new(cfg) + .map_err(|e| GeneralError::InitializationError(e.to_string())) + .context("Failed to initialize Sled")?; + + Ok(Arc::new(Box::new(db) as Box)) + } + + StorageBackend::InMemory => Ok(Arc::new( + Box::new(InMemoryDatabase::new()) as Box + )), + } +} + +pub async fn initialize_da_layer( + config: &Config, +) -> Result> { + match config.da_layer { + DALayerOption::Celestia => { + let celestia_conf = config + .network + .celestia_config + .clone() + .context("Celestia configuration not found")?; + + for attempt in 1..=DA_RETRY_COUNT { + match CelestiaConnection::new(&celestia_conf, None).await { + Ok(da) => return Ok(Arc::new(da) as Arc), + Err(e) => { + if attempt == DA_RETRY_COUNT { + return Err(DataAvailabilityError::NetworkError(format!( + "failed to connect to celestia node after {} attempts: {}", + DA_RETRY_COUNT, e + )) + .into()); + } + error!( + "Attempt {} to connect to celestia node failed: {}. Retrying in {} seconds...", + attempt, + e, + DA_RETRY_INTERVAL.as_secs() + ); + tokio::time::sleep(DA_RETRY_INTERVAL).await; + } + } + } + unreachable!() // This line should never be reached due to the return in the last iteration + } + DALayerOption::InMemory => { + let (da_layer, _height_rx, _block_rx) = + InMemoryDataAvailabilityLayer::new(Duration::from_secs(10)); + Ok(Arc::new(da_layer) as Arc) + } + } +} + +pub async fn initialize_light_da_layer( + config: &Config, +) -> Result> { + match config.da_layer { + DALayerOption::Celestia => { + info!("Initializing light client connection..."); + info!("Network config: {:?}", config.network); + let connection = match LightClientConnection::new(&config.network).await { + Ok(conn) => conn, + Err(e) => { + error!("Failed to initialize light client connection: {:?}", e); + error!("Network config: {:?}", config.network); + if let Some(celestia_config) = &config.network.celestia_config { + error!( + "Celestia connection string: {}", + celestia_config.connection_string + ); + error!("Start height: {}", celestia_config.start_height); + } + return Err(anyhow!( + "Failed to initialize light client connection: {}", + e + )); + } + }; + Ok(Arc::new(connection) + as Arc< + dyn LightDataAvailabilityLayer + Send + Sync + 'static, + >) + } + DALayerOption::InMemory => { + let (da_layer, _height_rx, _block_rx) = + InMemoryDataAvailabilityLayer::new(Duration::from_secs(10)); + Ok(Arc::new(da_layer)) + } + } +} diff --git a/crates/cli/src/tests/mod.rs b/crates/cli/src/tests/mod.rs index 1da18983..0c5cb391 100644 --- a/crates/cli/src/tests/mod.rs +++ b/crates/cli/src/tests/mod.rs @@ -179,6 +179,7 @@ path = "/config/path" assert_eq!(config.full_node.webserver.port, 3000); // Check database config + #[cfg(feature = "rocksdb")] if let prism_storage::DatabaseConfig::RocksDB(rocksdb_config) = &config.db { assert_eq!(rocksdb_config.path, "/cli/path"); } else { @@ -282,6 +283,7 @@ path = "/config/db" assert!(config.prover.webserver.enabled); assert_eq!(config.prover.webserver.port, 4000); + #[cfg(feature = "rocksdb")] if let prism_storage::DatabaseConfig::RocksDB(rocksdb_config) = &config.db { assert_eq!(rocksdb_config.path, "/cli/db"); } else { diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 33765ab0..2d395632 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -10,8 +10,13 @@ license.workspace = true keywords.workspace = true readme.workspace = true +[features] +default = [] +rocksdb = ["dep:rocksdb"] + [dependencies] serde = { workspace = true } +sled = { workspace = true } tracing = { workspace = true } anyhow = { workspace = true } jmt = { workspace = true } @@ -22,9 +27,12 @@ prism-common = { workspace = true } prism-presets = { workspace = true } prism-serde = { workspace = true } auto_impl = { workspace = true } -rocksdb = { workspace = true } +rocksdb = { version = "0.21.0", features = [ + "multi-threaded-cf", +], optional = true } [dev-dependencies] +paste = { workspace = true } tempfile.workspace = true tokio = { workspace = true, default-features = false, features = [ "macros", diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index c4eda465..992a60ff 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -3,6 +3,15 @@ use auto_impl::auto_impl; use jmt::storage::{TreeReader, TreeWriter}; use prism_common::digest::Digest; use prism_da::FinalizedEpoch; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum StorageBackend { + #[cfg(feature = "rocksdb")] + RocksDB(crate::rocksdb::RocksDBConfig), + InMemory, + Sled(crate::sled::SledConfig), +} #[auto_impl(&, Box, Arc)] pub trait Database: Send + Sync + TreeReader + TreeWriter { diff --git a/crates/storage/src/factory.rs b/crates/storage/src/factory.rs index 64ee70fc..6c59df4e 100644 --- a/crates/storage/src/factory.rs +++ b/crates/storage/src/factory.rs @@ -6,11 +6,10 @@ use prism_presets::{ApplyPreset, FullNodePreset, PresetError, ProverPreset}; use serde::{Deserialize, Serialize}; use tracing::info; -use crate::{ - Database, - inmemory::InMemoryDatabase, - rocksdb::{RocksDBConfig, RocksDBConnection}, -}; +use crate::{Database, inmemory::InMemoryDatabase}; + +#[cfg(feature = "rocksdb")] +use crate::rocksdb::{RocksDBConfig, RocksDBConnection}; /// Configuration for the storage layer used by Prism nodes. /// @@ -26,6 +25,7 @@ pub enum DatabaseConfig { /// RocksDB storage backend for production deployments. /// Provides persistent, crash-resistant storage with LSM-tree architecture. + #[cfg(feature = "rocksdb")] RocksDB(RocksDBConfig), } @@ -65,6 +65,7 @@ pub async fn create_storage( info!("Initializing storage layer..."); match config { DatabaseConfig::InMemory => Ok(Arc::new(Box::new(InMemoryDatabase::new()))), + #[cfg(feature = "rocksdb")] DatabaseConfig::RocksDB(config) => { let db = RocksDBConnection::new(config)?; Ok(Arc::new(Box::new(db))) @@ -85,6 +86,7 @@ mod tests { } #[test] + #[cfg(feature = "rocksdb")] fn test_database_config_apply_full_node_development_preset() { let mut config = DatabaseConfig::RocksDB(RocksDBConfig::new("/test/data")); let result = config.apply_preset(&FullNodePreset::Development); @@ -104,6 +106,7 @@ mod tests { } #[test] + #[cfg(feature = "rocksdb")] fn test_database_config_apply_prover_development_preset() { let mut config = DatabaseConfig::RocksDB(RocksDBConfig::new("/test/data")); let result = config.apply_preset(&ProverPreset::Development); @@ -132,6 +135,7 @@ mod tests { } #[tokio::test] + #[cfg(feature = "rocksdb")] async fn test_create_storage_rocksdb() { use tempfile::TempDir; diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 785a4a37..4a5829f7 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -87,6 +87,9 @@ mod database; mod factory; pub mod inmemory; +pub mod sled; + +#[cfg(feature = "rocksdb")] pub mod rocksdb; #[cfg(test)] diff --git a/crates/storage/src/rocksdb.rs b/crates/storage/src/rocksdb.rs index 7f05937b..b2bd3001 100644 --- a/crates/storage/src/rocksdb.rs +++ b/crates/storage/src/rocksdb.rs @@ -308,69 +308,3 @@ impl TreeWriter for RocksDBConnection { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use jmt::{KeyHash, OwnedValue, Version}; - use tempfile::TempDir; - - fn setup_db() -> (TempDir, RocksDBConnection) { - let temp_dir = TempDir::new().unwrap(); - let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); - let db = RocksDBConnection::new(&cfg).unwrap(); - (temp_dir, db) - } - - #[test] - fn test_rw_commitment() { - let (_temp_dir, db) = setup_db(); - - let epoch = 1; - let commitment = Digest([1; 32]); - - db.set_commitment(&epoch, &commitment).unwrap(); - let read_commitment = db.get_commitment(&epoch).unwrap(); - - assert_eq!(read_commitment, commitment); - } - - #[test] - fn test_write_and_read_value() { - let (_temp_dir, db) = setup_db(); - - let key_hash = KeyHash([1; 32]); - let value: OwnedValue = vec![4, 5, 6]; - let version: Version = 1; - - let mut batch = NodeBatch::default(); - batch.insert_value(version, key_hash, value.clone()); - - db.write_node_batch(&batch).unwrap(); - - let read_value = db.get_value_option(version, key_hash).unwrap(); - assert_eq!(read_value, Some(value)); - } - - #[test] - fn test_get_value_option_with_multiple_versions() { - let (_temp_dir, db) = setup_db(); - - let key_hash = KeyHash([2; 32]); - let value1: OwnedValue = vec![1, 1, 1]; - let value2: OwnedValue = vec![2, 2, 2]; - - let mut batch = NodeBatch::default(); - batch.insert_value(1, key_hash, value1.clone()); - batch.insert_value(2, key_hash, value2.clone()); - - db.write_node_batch(&batch).unwrap(); - - assert_eq!(db.get_value_option(1, key_hash).unwrap(), Some(value1)); - assert_eq!( - db.get_value_option(2, key_hash).unwrap(), - Some(value2.clone()) - ); - assert_eq!(db.get_value_option(3, key_hash).unwrap(), Some(value2)); - } -} diff --git a/crates/storage/src/sled.rs b/crates/storage/src/sled.rs new file mode 100644 index 00000000..f59b392d --- /dev/null +++ b/crates/storage/src/sled.rs @@ -0,0 +1,286 @@ +use std::sync::Arc; + +use crate::Database; +use anyhow::{Result, anyhow}; +use jmt::{ + KeyHash, OwnedValue, Version, + storage::{LeafNode, Node, NodeBatch, NodeKey, TreeReader, TreeWriter}, +}; +use prism_common::digest::Digest; +use prism_errors::DatabaseError; +use prism_serde::binary::{FromBinary, ToBinary}; +use serde::{Deserialize, Serialize}; +use sled::{Db, Transactional, Tree}; + +const KEY_PREFIX_NODE: &str = "node:"; +const KEY_PREFIX_VALUE_HISTORY: &str = "value_history:"; + +const KEY_PREFIX_EPOCHS: &str = "epochs:height_"; +const KEY_PREFIX_COMMITMENTS: &str = "commitments:epoch_"; + +const KEY_SYNC_HEIGHT: &str = "app_state:sync_height"; +const KEY_LATEST_EPOCH_HEIGHT: &str = "app_state:latest_epoch_height"; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] +pub struct SledConfig { + pub path: String, + pub cache_capacity: Option, + pub flush_every_ms: Option, +} + +impl SledConfig { + pub fn new(path: &str) -> Self { + Self { + path: path.to_string(), + cache_capacity: None, + flush_every_ms: None, + } + } + + const fn with_cache_capacity(mut self, capacity: u64) -> Self { + self.cache_capacity = Some(capacity); + self + } + + const fn with_flush_every_ms(mut self, ms: u64) -> Self { + self.flush_every_ms = Some(ms); + self + } +} + +#[derive(Clone)] +pub struct SledConnection { + connection: Arc, + node_tree: Tree, + value_tree: Tree, + path: String, +} + +impl SledConnection { + pub fn new(cfg: &SledConfig) -> Result { + let mut config = sled::Config::new().path(&cfg.path); + + if let Some(capacity) = cfg.cache_capacity { + config = config.cache_capacity(capacity); + } + + if let Some(flush_ms) = cfg.flush_every_ms { + config = config.flush_every_ms(Some(flush_ms)); + } + + let db = config.open()?; + let node_tree = db.open_tree(KEY_PREFIX_NODE)?; + let value_tree = db.open_tree(KEY_PREFIX_VALUE_HISTORY)?; + + Ok(Self { + connection: Arc::new(db), + node_tree, + value_tree, + path: cfg.path.clone(), + }) + } +} + +impl Database for SledConnection { + fn get_commitment(&self, epoch: &u64) -> anyhow::Result { + let key = format!("{KEY_PREFIX_COMMITMENTS}{}", epoch); + let raw_bytes = self.connection.get(key.as_bytes())?.ok_or_else(|| { + DatabaseError::NotFoundError(format!("commitment from epoch_{}", epoch)) + })?; + + let value: [u8; 32] = raw_bytes.as_ref().try_into().map_err(|_| { + anyhow!( + "commitment digest should always be 32 bytes, got {} bytes", + raw_bytes.len() + ) + })?; + + Ok(Digest(value)) + } + + fn set_commitment(&self, epoch: &u64, commitment: &Digest) -> anyhow::Result<()> { + let key = format!("{KEY_PREFIX_COMMITMENTS}{}", epoch); + self.connection.insert(key.as_bytes(), &commitment.0[..])?; + Ok(()) + } + + fn get_last_synced_height(&self) -> anyhow::Result { + let res = self + .connection + .get(KEY_SYNC_HEIGHT)? + .ok_or_else(|| DatabaseError::NotFoundError("current sync height".to_string()))?; + + Ok(u64::from_be_bytes(res.as_ref().try_into().map_err( + |_| { + anyhow!( + "failed byte conversion from BigEndian to u64: expected 8 bytes, got {}", + res.len() + ) + }, + )?)) + } + + fn set_last_synced_height(&self, height: &u64) -> anyhow::Result<()> { + self.connection.insert(KEY_SYNC_HEIGHT, &height.to_be_bytes())?; + Ok(()) + } + + fn get_epoch(&self, height: &u64) -> anyhow::Result { + let key = format!("{}{}", KEY_PREFIX_EPOCHS, height); + let epoch_data = self + .connection + .get(key.as_bytes())? + .ok_or_else(|| DatabaseError::NotFoundError(format!("epoch at height {}", height)))?; + + prism_da::FinalizedEpoch::decode_from_bytes(epoch_data.as_ref()).map_err(|e| { + anyhow!(DatabaseError::ParsingError(format!( + "Failed to decode epoch at height {}: {}", + height, e + ))) + }) + } + + fn add_epoch(&self, epoch: &prism_da::FinalizedEpoch) -> anyhow::Result<()> { + // Get the latest height to check for sequential ordering + let latest_height = self.get_latest_epoch_height().ok(); + + if let Some(latest) = latest_height { + if latest as usize + 1 != epoch.height as usize { + return Err(anyhow!(DatabaseError::WriteError(format!( + "epoch height mismatch: expected {}, got {}", + latest + 1, + epoch.height + )))); + } + } else if epoch.height != 0 { + // If there's no latest height, we expect the first epoch to have height 0 + return Err(anyhow!(DatabaseError::WriteError(format!( + "first epoch must have height 0, got {}", + epoch.height + )))); + } + + // Encode the epoch to bytes + let epoch_data = epoch.encode_to_bytes().map_err(|e| { + anyhow!(DatabaseError::ParsingError(format!( + "Failed to encode epoch at height {}: {}", + epoch.height, e + ))) + })?; + + // Use a transaction to atomically store the epoch and update the latest height + let epoch_key = format!("{}{}", KEY_PREFIX_EPOCHS, epoch.height); + let height_bytes = epoch.height.to_be_bytes(); + + self.connection + .transaction(|tx| { + tx.insert(epoch_key.as_bytes(), epoch_data.as_slice())?; + tx.insert(KEY_LATEST_EPOCH_HEIGHT, &height_bytes)?; + Ok::<(), sled::transaction::ConflictableTransactionError>(()) + }) + .unwrap(); + + Ok(()) + } + + fn get_latest_epoch_height(&self) -> anyhow::Result { + let res = self + .connection + .get(KEY_LATEST_EPOCH_HEIGHT)? + .ok_or_else(|| DatabaseError::NotFoundError("latest epoch height".to_string()))?; + + Ok(u64::from_be_bytes(res.as_ref().try_into().map_err( + |_| { + anyhow!( + "failed byte conversion from BigEndian to u64: expected 8 bytes, got {}", + res.len() + ) + }, + )?)) + } + + fn get_latest_epoch(&self) -> anyhow::Result { + let height = self.get_latest_epoch_height()?; + self.get_epoch(&height) + } + + fn flush_database(&self) -> Result<()> { + // sled doesn't have a destroy method, so we need to drop the connection and remove the + // directory + drop(self.connection.clone()); + std::fs::remove_dir_all(&self.path)?; + Ok(()) + } +} + +impl TreeReader for SledConnection { + fn get_node_option(&self, node_key: &NodeKey) -> Result> { + let value = self.node_tree.get(node_key.encode_to_bytes()?)?; + + match value { + Some(data) => Ok(Some(Node::decode_from_bytes(data.as_ref())?)), + None => Ok(None), + } + } + + fn get_value_option( + &self, + max_version: Version, + key_hash: KeyHash, + ) -> Result> { + let value_key = key_hash.0; + let max_version_bytes = max_version.to_be_bytes(); + let mut max_key = Vec::with_capacity(32 + max_version_bytes.len()); + max_key.extend_from_slice(&value_key); + max_key.extend_from_slice(&max_version_bytes); + + // Use reverse iteration to find the highest version <= max_version + let mut iter = self.value_tree.range(..=max_key).rev(); + + while let Some(Ok((key, value))) = iter.next() { + if key.starts_with(&value_key) { + return Ok(Some(OwnedValue::decode_from_bytes(value.as_ref())?)); + } + } + + Ok(None) + } + + fn get_rightmost_leaf(&self) -> Result> { + unimplemented!("JMT Restoration from snapshot is unimplemented."); + } +} + +impl TreeWriter for SledConnection { + fn write_node_batch(&self, node_batch: &NodeBatch) -> Result<()> { + (&self.node_tree, &self.value_tree) + .transaction(|(tx_node, tx_values)| { + for (node_key, node) in node_batch.nodes() { + let key = node_key.encode_to_bytes().unwrap(); + let value = node.encode_to_bytes().unwrap(); + tx_node.insert(key, value.as_slice()).unwrap(); + } + + for ((version, key_hash), value) in node_batch.values() { + let value_key = key_hash.0; + let encoded_value = value + .as_ref() + .map(|v| v.encode_to_bytes()) + .transpose() + .unwrap() + .unwrap_or_default(); + let version_bytes = version.to_be_bytes(); + + let mut fkey = Vec::with_capacity(32 + version_bytes.len()); + fkey.extend_from_slice(&value_key); + fkey.extend_from_slice(&version_bytes); + tx_values.insert(fkey, encoded_value.as_slice()).unwrap(); + } + + Ok::<(), sled::transaction::ConflictableTransactionError>(()) + }) + .unwrap(); + + Ok(()) + } +} diff --git a/crates/storage/src/tests/mod.rs b/crates/storage/src/tests/mod.rs index aa6d7229..44d8c591 100644 --- a/crates/storage/src/tests/mod.rs +++ b/crates/storage/src/tests/mod.rs @@ -1,23 +1,24 @@ -use crate::rocksdb::*; use tempfile::TempDir; -use crate::Database; +use crate::{Database, database::StorageBackend, inmemory::InMemoryDatabase, sled::*}; use jmt::{ KeyHash, OwnedValue, Version, storage::{NodeBatch, TreeReader, TreeWriter}, }; use prism_common::digest::Digest; - -fn setup_db() -> (TempDir, RocksDBConnection) { - let temp_dir = TempDir::new().unwrap(); - let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); - let db = RocksDBConnection::new(&cfg).unwrap(); - (temp_dir, db) +use prism_da::SuccinctProof; + +fn setup_db(backend: StorageBackend) -> Box { + match backend { + #[cfg(feature = "rocksdb")] + StorageBackend::RocksDB(cfg) => Box::new(RocksDBConnection::new(&cfg).unwrap()), + StorageBackend::Sled(cfg) => Box::new(SledConnection::new(&cfg).unwrap()), + StorageBackend::InMemory => Box::new(InMemoryDatabase::new()), + } } -#[test] -fn test_rw_commitment() { - let (_temp_dir, db) = setup_db(); +fn test_rw_commitment(backend: StorageBackend) { + let db = setup_db(backend); let epoch = 1; let commitment = Digest([1; 32]); @@ -28,9 +29,8 @@ fn test_rw_commitment() { assert_eq!(read_commitment, commitment); } -#[test] -fn test_write_and_read_value() { - let (_temp_dir, db) = setup_db(); +fn test_write_and_read_value(backend: StorageBackend) { + let db = setup_db(backend); let key_hash = KeyHash([1; 32]); let value: OwnedValue = vec![4, 5, 6]; @@ -45,9 +45,8 @@ fn test_write_and_read_value() { assert_eq!(read_value, Some(value)); } -#[test] -fn test_get_value_option_with_multiple_versions() { - let (_temp_dir, db) = setup_db(); +fn test_get_value_option_with_multiple_versions(backend: StorageBackend) { + let db = setup_db(backend); let key_hash = KeyHash([2; 32]); let value1: OwnedValue = vec![1, 1, 1]; @@ -66,3 +65,114 @@ fn test_get_value_option_with_multiple_versions() { ); assert_eq!(db.get_value_option(3, key_hash).unwrap(), Some(value2)); } + +fn test_sync_height(backend: StorageBackend) { + let db = setup_db(backend); + + let height = 12345u64; + db.set_last_synced_height(&height).unwrap(); + let read_height = db.get_last_synced_height().unwrap(); + + assert_eq!(read_height, height); +} + +fn test_transaction_consistency(backend: StorageBackend) { + let db = setup_db(backend); + + let key_hash = KeyHash([3; 32]); + let value1: OwnedValue = vec![1, 2, 3]; + let value2: OwnedValue = vec![4, 5, 6]; + + // Write two values in a single batch - should be atomic + let mut batch = NodeBatch::default(); + batch.insert_value(1, key_hash, value1.clone()); + batch.insert_value(2, key_hash, value2.clone()); + + db.write_node_batch(&batch).unwrap(); + + // Both values should be retrievable + assert_eq!(db.get_value_option(1, key_hash).unwrap(), Some(value1)); + assert_eq!(db.get_value_option(2, key_hash).unwrap(), Some(value2)); +} + +fn test_epoch_operations(backend: StorageBackend) { + let db = setup_db(backend); + + // Test that getting latest epoch height fails when no epochs exist + assert!(db.get_latest_epoch_height().is_err()); + + let epoch = prism_da::FinalizedEpoch { + height: 0, + tip_da_height: 1, + prev_commitment: Digest::hash("a"), + current_commitment: Digest::hash("b"), + snark: SuccinctProof::default(), + stark: SuccinctProof::default(), + signature: None, + }; + + db.add_epoch(&epoch).unwrap(); + + let latest_height = db.get_latest_epoch_height().unwrap(); + assert_eq!(latest_height, 0); + + let retrieved_epoch = db.get_epoch(&0).unwrap(); + assert_eq!(retrieved_epoch.height, 0); +} + +fn test_range_iteration(backend: StorageBackend) { + let db = setup_db(backend); + + let key_hash = KeyHash([4; 32]); + + // Insert multiple versions + for i in 0..10 { + let mut batch = NodeBatch::default(); + let value: OwnedValue = vec![i as u8; 10]; + batch.insert_value(i, key_hash, value); + db.write_node_batch(&batch).unwrap(); + } + + // Test getting values at different max versions + for max_version in 0..10 { + let result = db.get_value_option(max_version, key_hash).unwrap(); + assert!(result.is_some()); + let value = result.unwrap(); + assert_eq!(value[0], max_version as u8); + } +} + +macro_rules! generate_storage_tests { + ($test_fn:ident) => { + paste::paste! { + #[test] + fn [<$test_fn _sled>]() { + let temp_dir = TempDir::new().unwrap(); + let db_path = temp_dir.path().join("test_sled"); + let cfg = SledConfig::new(db_path.to_str().unwrap()); + $test_fn(StorageBackend::Sled(cfg)); + } + + #[test] + fn [<$test_fn _inmemory>]() { + $test_fn(StorageBackend::InMemory); + } + + #[cfg(feature = "rocksdb")] + #[test] + fn [<$test_fn _rocksdb>]() { + let temp_dir = TempDir::new().unwrap(); + let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); + $test_fn(StorageBackend::RocksDB(&cfg)); + } + } + }; +} + +generate_storage_tests!(test_rw_commitment); +generate_storage_tests!(test_write_and_read_value); +generate_storage_tests!(test_get_value_option_with_multiple_versions); +generate_storage_tests!(test_sync_height); +generate_storage_tests!(test_transaction_consistency); +generate_storage_tests!(test_epoch_operations); +generate_storage_tests!(test_range_iteration); diff --git a/crates/tests/src/lib.rs b/crates/tests/src/lib.rs index eece3f16..28f351d1 100644 --- a/crates/tests/src/lib.rs +++ b/crates/tests/src/lib.rs @@ -20,7 +20,7 @@ use prism_prover::{ }; use prism_storage::{ Database, - rocksdb::{RocksDBConfig, RocksDBConnection}, + sled::{SledConfig, SledConnection}, }; use rand::{Rng, SeedableRng, rngs::StdRng}; use std::sync::Arc; @@ -36,8 +36,8 @@ const BRIDGE_0_ADDR: &str = "ws://localhost:26658"; fn setup_db() -> Arc> { let temp_dir = TempDir::new().unwrap(); - let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); - let db = RocksDBConnection::new(&cfg).unwrap(); + let cfg = SledConfig::new(temp_dir.path().to_str().unwrap()); + let db = SledConnection::new(&cfg).unwrap(); Arc::new(Box::new(db) as Box) } diff --git a/crates/tree/Cargo.toml b/crates/tree/Cargo.toml index 67cefecd..bf5e9017 100644 --- a/crates/tree/Cargo.toml +++ b/crates/tree/Cargo.toml @@ -29,9 +29,16 @@ anyhow.workspace = true tracing.workspace = true [dev-dependencies] -paste.workspace = true -prism-storage.workspace = true +paste = { workspace = true } +prism-storage = { workspace = true } tempfile.workspace = true +criterion = { version = "0.5", features = ["html_reports"] } +jmt = { workspace = true } + +[[bench]] +name = "database_comparison" +harness = false [features] default = [] +rocksdb = ["prism-storage/rocksdb"] diff --git a/crates/tree/benches/database_comparison.rs b/crates/tree/benches/database_comparison.rs new file mode 100644 index 00000000..8369fe33 --- /dev/null +++ b/crates/tree/benches/database_comparison.rs @@ -0,0 +1,588 @@ +use criterion::{BenchmarkId, Criterion, black_box, criterion_group, criterion_main}; +use std::sync::Arc; +use tempfile::TempDir; + +use jmt::{ + KeyHash, OwnedValue, Version, + storage::{TreeReader, TreeWriter}, +}; +use prism_common::test_transaction_builder::TestTransactionBuilder; +use prism_keys::CryptoAlgorithm; +use prism_tree::snarkable_tree::SnarkableTree; +// Import your database implementations +use prism_tree::{hasher::TreeHasher, key_directory_tree::KeyDirectoryTree}; + +use prism_storage::{ + Database, + sled::{SledConfig, SledConnection}, +}; + +#[cfg(feature = "rocksdb")] +use prism_storage::rocksdb::{RocksDBConfig, RocksDBConnection}; + +// Benchmark helper functions +#[cfg(feature = "rocksdb")] +fn setup_rocksdb() -> (TempDir, RocksDBConnection) { + let temp_dir = TempDir::new().unwrap(); + let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); + let db = RocksDBConnection::new(&cfg).unwrap(); + (temp_dir, db) +} + +fn setup_sled() -> (TempDir, SledConnection) { + let temp_dir = TempDir::new().unwrap(); + let db_path = temp_dir.path().join("test_sled"); + let cfg = SledConfig::new(db_path.to_str().unwrap()); + let db = SledConnection::new(&cfg).unwrap(); + (temp_dir, db) +} + +// Benchmark database operations directly +fn bench_direct_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("direct_operations"); + + // Test commitment operations + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_commitment_write", |b| { + let (_temp_dir, db) = setup_rocksdb(); + b.iter(|| { + for i in 0..100 { + let epoch = black_box(i); + let commitment = prism_common::digest::Digest([i as u8; 32]); + db.set_commitment(&epoch, &commitment).unwrap(); + } + }); + }); + + group.bench_function("sled_commitment_write", |b| { + let (_temp_dir, db) = setup_sled(); + b.iter(|| { + for i in 0..100 { + let epoch = black_box(i); + let commitment = prism_common::digest::Digest([i as u8; 32]); + db.set_commitment(&epoch, &commitment).unwrap(); + } + }); + }); + + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_commitment_read", |b| { + let (_temp_dir, db) = setup_rocksdb(); + // Setup data + for i in 0..100 { + let commitment = prism_common::digest::Digest([i as u8; 32]); + db.set_commitment(&i, &commitment).unwrap(); + } + + b.iter(|| { + for i in 0..100 { + let epoch = black_box(i); + let _ = db.get_commitment(&epoch).unwrap(); + } + }); + }); + + group.bench_function("sled_commitment_read", |b| { + let (_temp_dir, db) = setup_sled(); + // Setup data + for i in 0..100 { + let commitment = prism_common::digest::Digest([i as u8; 32]); + db.set_commitment(&i, &commitment).unwrap(); + } + + b.iter(|| { + for i in 0..100 { + let epoch = black_box(i); + let _ = db.get_commitment(&epoch).unwrap(); + } + }); + }); + + group.finish(); +} + +// Benchmark JMT operations +fn bench_jmt_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("jmt_operations"); + + group.bench_function("sled_node_batch_write", |b| { + let (_temp_dir, db) = setup_sled(); + b.iter(|| { + let mut batch = jmt::storage::NodeBatch::default(); + for i in 0..50 { + let key_hash = KeyHash([i as u8; 32]); + let value: OwnedValue = vec![i as u8; 100]; + batch.insert_value(black_box(i as Version), key_hash, value); + } + db.write_node_batch(&batch).unwrap(); + }); + }); + + // Test node batch operations + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_node_batch_write", |b| { + let (_temp_dir, db) = setup_rocksdb(); + b.iter(|| { + let mut batch = jmt::storage::NodeBatch::default(); + for i in 0..50 { + let key_hash = KeyHash([i as u8; 32]); + let value: OwnedValue = vec![i as u8; 100]; + batch.insert_value(black_box(i as Version), key_hash, value); + } + db.write_node_batch(&batch).unwrap(); + }); + }); + + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_value_read", |b| { + let (_temp_dir, db) = setup_rocksdb(); + // Setup data + let mut batch = jmt::storage::NodeBatch::default(); + for i in 0..100 { + let key_hash = KeyHash([i as u8; 32]); + let value: OwnedValue = vec![i as u8; 100]; + batch.insert_value(i as Version, key_hash, value); + } + db.write_node_batch(&batch).unwrap(); + + b.iter(|| { + for i in 0..100 { + let key_hash = KeyHash([i as u8; 32]); + let version = black_box(i as Version); + let _ = db.get_value_option(version, key_hash).unwrap(); + } + }); + }); + + group.bench_function("sled_value_read", |b| { + let (_temp_dir, db) = setup_sled(); + // Setup data + let mut batch = jmt::storage::NodeBatch::default(); + for i in 0..100 { + let key_hash = KeyHash([i as u8; 32]); + let value: OwnedValue = vec![i as u8; 100]; + batch.insert_value(i as Version, key_hash, value); + } + db.write_node_batch(&batch).unwrap(); + + b.iter(|| { + for i in 0..100 { + let key_hash = KeyHash([i as u8; 32]); + let version = black_box(i as Version); + let _ = db.get_value_option(version, key_hash).unwrap(); + } + }); + }); + + group.finish(); +} + +// Benchmark KeyDirectoryTree operations +fn bench_key_directory_tree(c: &mut Criterion) { + let mut group = c.benchmark_group("key_directory_tree"); + + // Test service registration and account creation + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_service_and_account_creation", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_rocksdb(); + let tree = KeyDirectoryTree::new(Arc::new(db)); + let tx_builder = TestTransactionBuilder::new(); + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..10 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(black_box(account_tx)).unwrap(); + } + }, + ); + }); + + group.bench_function("sled_service_and_account_creation", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_sled(); + let tree = KeyDirectoryTree::new(Arc::new(db)); + let tx_builder = TestTransactionBuilder::new(); + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..10 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(black_box(account_tx)).unwrap(); + } + }, + ); + }); + + group.finish(); +} + +// Benchmark key updates and data operations +fn bench_update_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("update_operations"); + + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_key_updates", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_rocksdb(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..10 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + } + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + for i in 0..10 { + let account_name = format!("acc_{}", i); + let key_tx = tx_builder + .add_random_key_verified_with_root(CryptoAlgorithm::Ed25519, &account_name) + .commit(); + tree.process_transaction(black_box(key_tx)).unwrap(); + } + }, + ); + }); + + group.bench_function("sled_key_updates", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_sled(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..10 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + } + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + for i in 0..10 { + let account_name = format!("acc_{}", i); + let key_tx = tx_builder + .add_random_key_verified_with_root(CryptoAlgorithm::Ed25519, &account_name) + .commit(); + tree.process_transaction(black_box(key_tx)).unwrap(); + } + }, + ); + }); + + group.finish(); +} + +// Benchmark data operations +fn bench_data_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("data_operations"); + + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_data_operations", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_rocksdb(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + "acc_1", + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + for i in 0..10 { + let data = format!("test data {}", i).into_bytes(); + let data_tx = tx_builder + .add_internally_signed_data_verified_with_root("acc_1", data) + .commit(); + tree.process_transaction(black_box(data_tx)).unwrap(); + } + }, + ); + }); + + group.bench_function("sled_data_operations", |b| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_sled(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + "acc_1", + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + for i in 0..10 { + let data = format!("test data {}", i).into_bytes(); + let data_tx = tx_builder + .add_internally_signed_data_verified_with_root("acc_1", data) + .commit(); + tree.process_transaction(black_box(data_tx)).unwrap(); + } + }, + ); + }); + + group.finish(); +} + +// Benchmark read operations +fn bench_read_operations(c: &mut Criterion) { + let mut group = c.benchmark_group("read_operations"); + + #[cfg(feature = "rocksdb")] + group.bench_function("rocksdb_batch_reads", |b| { + let (_temp_dir, db) = setup_rocksdb(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..100 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + } + + b.iter(|| { + for i in 0..100 { + let account_name = format!("acc_{}", i); + let key_hash = KeyHash::with::(&account_name); + let _ = tree.get(black_box(key_hash)).unwrap(); + } + }); + }); + + group.bench_function("sled_batch_reads", |b| { + let (_temp_dir, db) = setup_sled(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys(CryptoAlgorithm::Ed25519, "service_1") + .commit(); + tree.process_transaction(service_tx).unwrap(); + + for i in 0..100 { + let account_name = format!("acc_{}", i); + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + &account_name, + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + } + + b.iter(|| { + for i in 0..100 { + let account_name = format!("acc_{}", i); + let key_hash = KeyHash::with::(&account_name); + let _ = tree.get(black_box(key_hash)).unwrap(); + } + }); + }); + + group.finish(); +} + +// Benchmark different payload sizes +fn bench_payload_sizes(c: &mut Criterion) { + let mut group = c.benchmark_group("payload_sizes"); + + for size in [100, 1000, 10000, 100000].iter() { + #[cfg(feature = "rocksdb")] + group.bench_with_input( + BenchmarkId::new("rocksdb_large_data", size), + size, + |b, &size| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_rocksdb(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys( + CryptoAlgorithm::Ed25519, + "service_1", + ) + .commit(); + tree.process_transaction(service_tx).unwrap(); + + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + "acc_1", + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + let large_data = vec![0u8; size]; + let data_tx = tx_builder + .add_internally_signed_data_verified_with_root("acc_1", large_data) + .commit(); + tree.process_transaction(black_box(data_tx)).unwrap(); + }, + ); + }, + ); + + group.bench_with_input( + BenchmarkId::new("sled_large_data", size), + size, + |b, &size| { + b.iter_with_setup( + || { + let (_temp_dir, db) = setup_sled(); + let mut tree = KeyDirectoryTree::new(Arc::new(db)); + let mut tx_builder = TestTransactionBuilder::new(); + + // Setup initial data + let service_tx = tx_builder + .register_service_with_random_keys( + CryptoAlgorithm::Ed25519, + "service_1", + ) + .commit(); + tree.process_transaction(service_tx).unwrap(); + + let account_tx = tx_builder + .create_account_with_random_key_signed( + CryptoAlgorithm::Ed25519, + "acc_1", + "service_1", + ) + .commit(); + tree.process_transaction(account_tx).unwrap(); + + (tree, tx_builder) + }, + |(mut tree, mut tx_builder)| { + let large_data = vec![0u8; size]; + let data_tx = tx_builder + .add_internally_signed_data_verified_with_root("acc_1", large_data) + .commit(); + tree.process_transaction(black_box(data_tx)).unwrap(); + }, + ); + }, + ); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_direct_operations, + bench_jmt_operations, + bench_key_directory_tree, + bench_update_operations, + bench_data_operations, + bench_read_operations, + bench_payload_sizes +); + +criterion_main!(benches); diff --git a/crates/tree/src/tests/mod.rs b/crates/tree/src/tests/mod.rs index 188219c7..d93e571f 100644 --- a/crates/tree/src/tests/mod.rs +++ b/crates/tree/src/tests/mod.rs @@ -9,17 +9,22 @@ use prism_common::{operation::SignatureBundle, test_transaction_builder::TestTra use prism_keys::{CryptoAlgorithm, SigningKey}; use prism_storage::{ inmemory::InMemoryDatabase, - rocksdb::{RocksDBConfig, RocksDBConnection}, + sled::{SledConfig, SledConnection}, }; -use tempfile::TempDir; use crate::{ AccountResponse::*, hasher::TreeHasher, key_directory_tree::KeyDirectoryTree, proofs::Proof, snarkable_tree::SnarkableTree, }; +#[cfg(feature = "rocksdb")] +use prism_storage::rocksdb::{RocksDBConfig, RocksDBConnection}; +use tempfile::TempDir; + enum DBType { + #[cfg(feature = "rocksdb")] RocksDB, + Sled, InMemory, Mock, } @@ -28,19 +33,30 @@ trait TreeReadWriter: TreeReader + TreeWriter + Send + Sync {} impl TreeReadWriter for InMemoryDatabase {} impl TreeReadWriter for Box {} +impl TreeReadWriter for SledConnection {} +impl TreeReadWriter for Box {} +#[cfg(feature = "rocksdb")] impl TreeReadWriter for RocksDBConnection {} +#[cfg(feature = "rocksdb")] impl TreeReadWriter for Box {} impl TreeReadWriter for MockTreeStore {} impl TreeReadWriter for Box {} fn setup_db(db: DBType) -> Arc> { match db { + #[cfg(feature = "rocksdb")] DBType::RocksDB => { let temp_dir = TempDir::new().unwrap(); let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); let db = RocksDBConnection::new(&cfg).unwrap(); Arc::new(Box::new(db)) } + DBType::Sled => { + let temp_dir = TempDir::new().unwrap(); + let cfg = SledConfig::new(temp_dir.path().to_str().unwrap()); + let db = SledConnection::new(&cfg).unwrap(); + Arc::new(Box::new(db)) + } DBType::InMemory => Arc::new(Box::new(InMemoryDatabase::new())), DBType::Mock => Arc::new(Box::new(MockTreeStore::default())), } @@ -428,6 +444,7 @@ fn test_batch_writing(algorithm: CryptoAlgorithm, db: DBType) { macro_rules! generate_algorithm_tests { ($test_fn:ident) => { paste::paste! { + #[cfg(feature = "rocksdb")] #[test] fn [<$test_fn _ed25519_rocksdb>]() { $test_fn(CryptoAlgorithm::Ed25519, DBType::RocksDB); @@ -438,6 +455,11 @@ macro_rules! generate_algorithm_tests { $test_fn(CryptoAlgorithm::Secp256k1, DBType::InMemory); } + #[test] + fn [<$test_fn _secp256r1_sled>]() { + $test_fn(CryptoAlgorithm::Secp256r1, DBType::Sled); + } + #[test] fn [<$test_fn _secp256r1_mock>]() { $test_fn(CryptoAlgorithm::Secp256r1, DBType::Mock); From 9259ef2e079b65631e7fe563425e3043761be63a Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 18 Jul 2025 13:51:27 +0200 Subject: [PATCH 2/5] adding imports --- crates/storage/src/tests/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/storage/src/tests/mod.rs b/crates/storage/src/tests/mod.rs index 44d8c591..5bd101fd 100644 --- a/crates/storage/src/tests/mod.rs +++ b/crates/storage/src/tests/mod.rs @@ -1,5 +1,7 @@ use tempfile::TempDir; +#[cfg(feature = "rocksdb")] +use crate::rocksdb::*; use crate::{Database, database::StorageBackend, inmemory::InMemoryDatabase, sled::*}; use jmt::{ KeyHash, OwnedValue, Version, From f61fc16a17dd5e272b05cffaf73f9ff0b159b684 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 18 Jul 2025 14:16:06 +0200 Subject: [PATCH 3/5] upsi --- crates/storage/src/tests/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/storage/src/tests/mod.rs b/crates/storage/src/tests/mod.rs index 5bd101fd..21df32f3 100644 --- a/crates/storage/src/tests/mod.rs +++ b/crates/storage/src/tests/mod.rs @@ -165,7 +165,7 @@ macro_rules! generate_storage_tests { fn [<$test_fn _rocksdb>]() { let temp_dir = TempDir::new().unwrap(); let cfg = RocksDBConfig::new(temp_dir.path().to_str().unwrap()); - $test_fn(StorageBackend::RocksDB(&cfg)); + $test_fn(StorageBackend::RocksDB(cfg)); } } }; From 3bf7506e56193a2c9aff260b441991b4325c8f8c Mon Sep 17 00:00:00 2001 From: sebasti810 Date: Mon, 21 Jul 2025 12:57:24 +0200 Subject: [PATCH 4/5] fix: wasm ci --- crates/node_types/wasm-lightclient/src/tests/mod.rs | 4 ++-- crates/storage/src/sled.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/node_types/wasm-lightclient/src/tests/mod.rs b/crates/node_types/wasm-lightclient/src/tests/mod.rs index 791aa206..1ca4766e 100644 --- a/crates/node_types/wasm-lightclient/src/tests/mod.rs +++ b/crates/node_types/wasm-lightclient/src/tests/mod.rs @@ -211,7 +211,7 @@ mod tests { // should separate these cases. assert!(events.len() > 0); assert!(events.iter().any(|e| e == "Updated DA height to 100")); - assert!(events.iter().any(|e| e == "Starting backwards sync at height 100")); + assert!(events.iter().any(|e| e == "Starting historical sync at height 100")); assert!(events.iter().any(|e| e == "Starting recursive verification at height 100")); assert!( events.iter().any(|e| e @@ -317,7 +317,7 @@ mod tests { let events = received_events.lock().unwrap(); // Should complete without finding any epochs - assert!(events.iter().any(|e| e.contains("Backwards sync complete"))); + assert!(events.iter().any(|e| e.contains("Historical sync complete"))); assert!(events.iter().any(|e| e.contains("found epoch: false"))); } } diff --git a/crates/storage/src/sled.rs b/crates/storage/src/sled.rs index f59b392d..89ba5d3c 100644 --- a/crates/storage/src/sled.rs +++ b/crates/storage/src/sled.rs @@ -258,7 +258,7 @@ impl TreeWriter for SledConnection { for (node_key, node) in node_batch.nodes() { let key = node_key.encode_to_bytes().unwrap(); let value = node.encode_to_bytes().unwrap(); - tx_node.insert(key, value.as_slice()).unwrap(); + tx_node.insert(key, value).unwrap(); } for ((version, key_hash), value) in node_batch.values() { @@ -274,7 +274,7 @@ impl TreeWriter for SledConnection { let mut fkey = Vec::with_capacity(32 + version_bytes.len()); fkey.extend_from_slice(&value_key); fkey.extend_from_slice(&version_bytes); - tx_values.insert(fkey, encoded_value.as_slice()).unwrap(); + tx_values.insert(fkey, encoded_value).unwrap(); } Ok::<(), sled::transaction::ConflictableTransactionError>(()) From e90e94eba424f8b344ffbfd9edc02d4ea951ebf0 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 5 Sep 2025 10:22:13 +0200 Subject: [PATCH 5/5] clippy uwu --- crates/storage/src/database.rs | 9 --------- crates/storage/src/sled.rs | 3 ++- crates/storage/src/tests/mod.rs | 11 ++++++++++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/storage/src/database.rs b/crates/storage/src/database.rs index 992a60ff..c4eda465 100644 --- a/crates/storage/src/database.rs +++ b/crates/storage/src/database.rs @@ -3,15 +3,6 @@ use auto_impl::auto_impl; use jmt::storage::{TreeReader, TreeWriter}; use prism_common::digest::Digest; use prism_da::FinalizedEpoch; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum StorageBackend { - #[cfg(feature = "rocksdb")] - RocksDB(crate::rocksdb::RocksDBConfig), - InMemory, - Sled(crate::sled::SledConfig), -} #[auto_impl(&, Box, Arc)] pub trait Database: Send + Sync + TreeReader + TreeWriter { diff --git a/crates/storage/src/sled.rs b/crates/storage/src/sled.rs index 89ba5d3c..21cf31c4 100644 --- a/crates/storage/src/sled.rs +++ b/crates/storage/src/sled.rs @@ -28,6 +28,7 @@ pub struct SledConfig { pub flush_every_ms: Option, } +#[allow(dead_code)] impl SledConfig { pub fn new(path: &str) -> Self { Self { @@ -57,7 +58,7 @@ pub struct SledConnection { } impl SledConnection { - pub fn new(cfg: &SledConfig) -> Result { + pub fn new(cfg: &SledConfig) -> Result { let mut config = sled::Config::new().path(&cfg.path); if let Some(capacity) = cfg.cache_capacity { diff --git a/crates/storage/src/tests/mod.rs b/crates/storage/src/tests/mod.rs index 21df32f3..2d71ab84 100644 --- a/crates/storage/src/tests/mod.rs +++ b/crates/storage/src/tests/mod.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use tempfile::TempDir; #[cfg(feature = "rocksdb")] use crate::rocksdb::*; -use crate::{Database, database::StorageBackend, inmemory::InMemoryDatabase, sled::*}; +use crate::{Database, inmemory::InMemoryDatabase, sled::*}; use jmt::{ KeyHash, OwnedValue, Version, storage::{NodeBatch, TreeReader, TreeWriter}, @@ -10,6 +11,14 @@ use jmt::{ use prism_common::digest::Digest; use prism_da::SuccinctProof; +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum StorageBackend { + #[cfg(feature = "rocksdb")] + RocksDB(crate::rocksdb::RocksDBConfig), + InMemory, + Sled(crate::sled::SledConfig), +} + fn setup_db(backend: StorageBackend) -> Box { match backend { #[cfg(feature = "rocksdb")]