From 2f8ecd482077d82f2d1a937c7f979eaaa87a27b2 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Wed, 3 Dec 2025 08:54:00 +0000 Subject: feat: implement LMDB database backend - Add nostr-lmdb dependency (v0.44) for persistent storage - Create SharedDatabase type alias for database abstraction - Update all database-related functions to use trait object - Support runtime selection via NGIT_DATABASE_BACKEND env var Database backends: - memory: In-memory (default, fastest, no persistence) - lmdb: LMDB backend (persistent, general purpose) All 34 tests pass with the new implementation. --- .env.example | 2 +- Cargo.lock | 239 +++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/git/authorization.rs | 12 +-- src/git/handlers.rs | 7 +- src/http/mod.rs | 9 +- src/nostr/builder.rs | 56 ++++++----- 7 files changed, 289 insertions(+), 37 deletions(-) diff --git a/.env.example b/.env.example index 0a27bbb..796415e 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,7 @@ NGIT_RELAY_DATA_PATH=./data/relay # Database backend (memory, nostrdb, lmdb) # - memory: In-memory database (default, fastest, no persistence) # - nostrdb: NostrDB backend (persistent, optimized for Nostr) [Not yet implemented] -# - lmdb: LMDB backend (persistent, general purpose) [Not yet implemented] +# - lmdb: LMDB backend (persistent, general purpose) NGIT_DATABASE_BACKEND=memory # Server configuration diff --git a/Cargo.lock b/Cargo.lock index aa78542..6fcb65f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,9 @@ name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -273,6 +276,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -432,6 +441,21 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-common" version = "0.1.6" @@ -477,6 +501,15 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "doxygen-rs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "415b6ec780d34dcf624666747194393603d0373b7141eef01d12ee58881507d9" +dependencies = [ + "phf", +] + [[package]] name = "either" version = "1.15.0" @@ -520,6 +553,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +[[package]] +name = "flatbuffers" +version = "25.9.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b6620799e7340ebd9968d2e0708eb82cf1971e9a16821e2091b6d6e475eed5" +dependencies = [ + "bitflags 2.10.0", + "rustc_version", +] + [[package]] name = "flate2" version = "1.1.5" @@ -530,6 +573,18 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -768,6 +823,40 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "heed" +version = "0.20.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4f449bab7320c56003d37732a917e18798e2f1709d80263face2b4f9436ddb" +dependencies = [ + "bitflags 2.10.0", + "byteorder", + "heed-traits", + "heed-types", + "libc", + "lmdb-master-sys", + "once_cell", + "page_size", + "synchronoise", + "url", +] + +[[package]] +name = "heed-traits" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3130048d404c57ce5a1ac61a903696e8fcde7e8c2991e9fcfc1f27c3ef74ff" + +[[package]] +name = "heed-types" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d3f528b053a6d700b2734eabcd0fd49cb8230647aa72958467527b0b7917114" +dependencies = [ + "byteorder", + "heed-traits", +] + [[package]] name = "hex" version = "0.4.3" @@ -1151,6 +1240,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lmdb-master-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864808e0b19fb6dd3b70ba94ee671b82fce17554cf80aeb0a155c65bb08027df" +dependencies = [ + "cc", + "doxygen-rs", + "libc", +] + [[package]] name = "lock_api" version = "0.4.14" @@ -1214,6 +1314,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -1250,6 +1359,7 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-util", + "nostr-lmdb", "nostr-relay-builder", "nostr-sdk 0.44.1", "serde", @@ -1327,6 +1437,7 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7462c9d8ae5ef6a28d66a192d399ad2530f1f2130b13186296dbb11bdef5b3d1" dependencies = [ + "flatbuffers", "lru", "nostr 0.44.1", "tokio", @@ -1341,6 +1452,21 @@ dependencies = [ "nostr 0.44.1", ] +[[package]] +name = "nostr-lmdb" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1201bcf1f900c352f9f2cea5249960dc6b23049b65699a516e1327243becf6a2" +dependencies = [ + "async-utility", + "flume", + "heed", + "nostr 0.44.1", + "nostr-database 0.44.0", + "tokio", + "tracing", +] + [[package]] name = "nostr-relay-builder" version = "0.44.0" @@ -1501,6 +1627,16 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1551,6 +1687,48 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1773,6 +1951,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "1.1.2" @@ -1920,6 +2107,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2027,6 +2220,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" @@ -2059,6 +2258,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -2094,6 +2302,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synchronoise" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dbc01390fc626ce8d1cffe3376ded2b72a11bb70e1c75f404a210e4daa4def2" +dependencies = [ + "crossbeam-queue", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -2624,6 +2841,28 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index 3e836f9..02c66ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ nostr-relay-builder = "0.44" # Nostr nostr-sdk = "0.44" +nostr-lmdb = "0.44" # Utilities futures-util = "0.3" diff --git a/src/git/authorization.rs b/src/git/authorization.rs index 3b0e759..4896fc0 100644 --- a/src/git/authorization.rs +++ b/src/git/authorization.rs @@ -31,9 +31,9 @@ use anyhow::{anyhow, Result}; use nostr_relay_builder::prelude::*; use nostr_sdk::{EventId, ToBech32}; use std::collections::{HashMap, HashSet}; -use std::sync::Arc; use tracing::debug; +use crate::nostr::builder::SharedDatabase; use crate::nostr::events::{ RepositoryAnnouncement, RepositoryState, KIND_PR, KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, @@ -56,7 +56,7 @@ pub struct RepositoryData { /// This performs a single database query to fetch both announcement and state events, /// which is more efficient than separate queries. pub async fn fetch_repository_data( - database: &Arc, + database: &SharedDatabase, identifier: &str, ) -> Result { let filter = Filter::new() @@ -284,7 +284,7 @@ pub fn is_latest_state( /// /// Returns an `AuthorizationResult` that indicates whether a push is authorized. pub async fn get_authorization_from_db( - database: &Arc, + database: &SharedDatabase, identifier: &str, ) -> Result { // Fetch all repository data with a single query @@ -340,7 +340,7 @@ pub async fn get_authorization_from_db( /// /// Returns an `AuthorizationResult` that indicates whether a push is authorized. pub async fn get_authorization_for_owner( - database: &Arc, + database: &SharedDatabase, identifier: &str, owner_pubkey: &str, ) -> Result { @@ -817,7 +817,7 @@ pub fn npub_to_pubkey(npub: &str) -> Result { /// - `Ok(None)` if the event doesn't exist (push should be allowed) /// - `Err(_)` on database errors pub async fn get_event_commit_tag( - database: &Arc, + database: &SharedDatabase, event_id: &EventId, ) -> Result> { // Query for PR (1618) and PR Update (1619) events with this ID @@ -872,7 +872,7 @@ pub async fn get_event_commit_tag( /// * `Ok(())` if all refs/nostr/ pushes are valid /// * `Err(_)` if any ref has invalid event ID format or fails commit validation pub async fn validate_nostr_ref_pushes( - database: &Arc, + database: &SharedDatabase, pushed_refs: &[(String, String, String)], ) -> Result<()> { for (_, new_oid, ref_name) in pushed_refs { diff --git a/src/git/handlers.rs b/src/git/handlers.rs index e84cabb..8e5f5e1 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -4,9 +4,7 @@ use http_body_util::Full; use hyper::{body::Bytes, Response, StatusCode}; -use nostr_relay_builder::prelude::MemoryDatabase; use std::path::PathBuf; -use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tracing::{debug, error, info, warn}; @@ -18,6 +16,7 @@ use super::protocol::{GitService, PktLine}; use super::subprocess::GitSubprocess; use super::try_set_head_if_available; +use crate::nostr::builder::SharedDatabase; use crate::nostr::events::RepositoryState; /// Handle GET /info/refs?service=git-{upload,receive}-pack @@ -178,7 +177,7 @@ pub async fn handle_upload_pack( pub async fn handle_receive_pack( repo_path: PathBuf, request_body: Bytes, - database: Option>, + database: Option, identifier: &str, owner_pubkey: &str, ) -> Result>, GitError> { @@ -310,7 +309,7 @@ pub async fn handle_receive_pack( /// 5. Validates that pushed refs match the state /// 6. Validates refs/nostr/ has valid event id and if event exists, `c` tag matches ref async fn authorize_push( - database: &Arc, + database: &SharedDatabase, identifier: &str, owner_pubkey: &str, request_body: &Bytes, diff --git a/src/http/mod.rs b/src/http/mod.rs index 5cf8dbe..4665281 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -7,7 +7,6 @@ pub mod nip11; use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; -use std::sync::Arc; use base64::Engine; use http_body_util::{BodyExt, Full}; @@ -17,7 +16,6 @@ use hyper::server::conn::http1; use hyper::service::Service; use hyper::{Method, Request, Response}; use hyper_util::rt::TokioIo; -use nostr_relay_builder::prelude::MemoryDatabase; use nostr_relay_builder::LocalRelay; use nostr_sdk::hashes::sha1::Hash as Sha1Hash; use nostr_sdk::hashes::{Hash, HashEngine}; @@ -26,6 +24,7 @@ use tokio::net::TcpListener; use crate::config::Config; use crate::git; +use crate::nostr::builder::SharedDatabase; /// CORS headers required by GRASP-01 specification (lines 40-47) const CORS_ALLOW_ORIGIN: &str = "*"; @@ -90,7 +89,7 @@ struct HttpService { config: Config, remote: SocketAddr, /// Database reference for direct queries (e.g., push authorization) - database: Arc, + database: SharedDatabase, } impl HttpService { @@ -98,7 +97,7 @@ impl HttpService { relay: LocalRelay, config: Config, remote: SocketAddr, - database: Arc, + database: SharedDatabase, ) -> Self { Self { relay, @@ -423,7 +422,7 @@ fn derive_accept_key(request_key: &[u8]) -> String { pub async fn run_server( config: Config, relay: LocalRelay, - database: Arc, + database: SharedDatabase, ) -> anyhow::Result<()> { let bind_addr: SocketAddr = config.bind_address.parse()?; diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs index 2f182ea..eabb38f 100644 --- a/src/nostr/builder.rs +++ b/src/nostr/builder.rs @@ -9,6 +9,7 @@ use std::sync::Arc; use nostr::nips::nip19::ToBech32; use nostr::prelude::{Alphabet, SingleLetterTag}; use nostr::{EventId, Filter, Kind, PublicKey}; +use nostr_lmdb::NostrLMDB; use nostr_relay_builder::prelude::*; use crate::config::{Config, DatabaseBackend}; @@ -18,6 +19,9 @@ use crate::nostr::events::{ KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, }; +/// Type alias for the shared database used by the relay +pub type SharedDatabase = Arc; + /// Result of aligning a repository with authorized state #[derive(Debug, Default)] struct AlignmentResult { @@ -35,23 +39,33 @@ struct AlignmentResult { /// /// Validates all events according to GRASP-01 specification: /// - Repository announcements must list service in clone and relays tags -/// - Repository state announcements must have valid structure +/// - Repository state announcements must have valid structure /// - Other events must reference accepted repositories or events /// - Forward references are supported (events referenced by accepted events) /// - Orphan events with no valid references are rejected /// /// Uses stateful database queries to check event relationships. -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Nip34WritePolicy { domain: String, - database: Arc, + database: SharedDatabase, git_data_path: PathBuf, } +impl std::fmt::Debug for Nip34WritePolicy { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Nip34WritePolicy") + .field("domain", &self.domain) + .field("git_data_path", &self.git_data_path) + .field("database", &"") + .finish() + } +} + impl Nip34WritePolicy { pub fn new( domain: impl Into, - database: Arc, + database: SharedDatabase, git_data_path: impl Into, ) -> Self { Self { @@ -104,7 +118,7 @@ impl Nip34WritePolicy { /// The authorized_pubkeys should be the owner and maintainers of a specific /// announcement, so different owners with the same identifier don't interfere. async fn is_latest_state_for_identifier( - database: &Arc, + database: &SharedDatabase, state: &RepositoryState, authorized_pubkeys: &[PublicKey], ) -> Result { @@ -155,7 +169,7 @@ impl Nip34WritePolicy { /// should update HEAD in the repository of the announcement owner, /// not in the maintainer's own (possibly non-existent) repository. async fn find_authorized_announcements( - database: &Arc, + database: &SharedDatabase, identifier: &str, state_author: &PublicKey, ) -> Result, String> { @@ -205,7 +219,7 @@ impl Nip34WritePolicy { /// - This state event is the latest for the identifier in that context async fn identify_owner_repositories( &self, - database: &Arc, + database: &SharedDatabase, state: &RepositoryState, ) -> Result, String> { // Find all announcements where state author is authorized @@ -485,7 +499,7 @@ impl Nip34WritePolicy { /// Ok(Some(n)) if n refs were deleted, Ok(None) if no action taken, Err on failure async fn validate_pr_nostr_ref( &self, - database: &Arc, + database: &SharedDatabase, event: &Event, ) -> Result, String> { let event_id = event.id.to_hex(); @@ -651,7 +665,7 @@ impl Nip34WritePolicy { /// Check if any addressable events (repositories) exist in database /// Returns the first matching addressable reference found, or None if none match async fn find_accepted_repository( - database: &Arc, + database: &SharedDatabase, addressables: &[String], ) -> Result, String> { if addressables.is_empty() { @@ -724,7 +738,7 @@ impl Nip34WritePolicy { /// Check if any events exist in database /// Returns the first matching event ID found, or None if none match async fn find_accepted_event( - database: &Arc, + database: &SharedDatabase, event_ids: &[EventId], ) -> Result, String> { if event_ids.is_empty() { @@ -752,7 +766,7 @@ impl Nip34WritePolicy { /// This optimization recognizes that replaceable events are referenced by coordinate address, /// while regular events are referenced by event ID. async fn is_referenced_by_accepted( - database: &Arc, + database: &SharedDatabase, event: &Event, ) -> Result { let kind_u16 = event.kind.as_u16(); @@ -1152,14 +1166,14 @@ pub struct RelayWithDatabase { /// The local relay instance pub relay: LocalRelay, /// The database Arc that can be used for direct queries - pub database: Arc, + pub database: SharedDatabase, } /// Create a configured LocalRelay with full GRASP-01 validation /// /// Returns a `RelayWithDatabase` struct containing: /// - The `LocalRelay` for handling WebSocket connections -/// - The `Arc` for direct database queries (e.g., push authorization) +/// - The `SharedDatabase` for direct database queries (e.g., push authorization) pub fn create_relay(config: &Config) -> Result { tracing::info!("Configuring nostr relay with GRASP-01 validation..."); @@ -1167,7 +1181,7 @@ pub fn create_relay(config: &Config) -> Result { let db_path = Path::new(&config.relay_data_path); // Create database based on configuration - let database = match config.database_backend { + let database: SharedDatabase = match config.database_backend { DatabaseBackend::Memory => { tracing::info!("Using in-memory database (no persistence)"); Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { @@ -1187,13 +1201,13 @@ pub fn create_relay(config: &Config) -> Result { } DatabaseBackend::Lmdb => { tracing::info!("Using LMDB backend at: {}", db_path.display()); - // TODO: Implement LMDB backend once nostr-relay-builder supports it - // For now, fall back to memory database - tracing::warn!("LMDB backend not yet implemented, using in-memory database"); - Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { - events: true, - max_events: Some(100_000), - })) + // Ensure the database directory exists + std::fs::create_dir_all(db_path).map_err(|e| { + anyhow::anyhow!("Failed to create LMDB directory {}: {}", db_path.display(), e) + })?; + Arc::new(NostrLMDB::open(db_path).map_err(|e| { + anyhow::anyhow!("Failed to open LMDB database at {}: {}", db_path.display(), e) + })?) } }; -- cgit v1.2.3