mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-06 15:38:25 +00:00
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.
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
Generated
+239
@@ -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"
|
||||
|
||||
@@ -21,6 +21,7 @@ nostr-relay-builder = "0.44"
|
||||
|
||||
# Nostr
|
||||
nostr-sdk = "0.44"
|
||||
nostr-lmdb = "0.44"
|
||||
|
||||
# Utilities
|
||||
futures-util = "0.3"
|
||||
|
||||
@@ -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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
identifier: &str,
|
||||
) -> Result<RepositoryData> {
|
||||
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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
identifier: &str,
|
||||
) -> Result<AuthorizationResult> {
|
||||
// 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
identifier: &str,
|
||||
owner_pubkey: &str,
|
||||
) -> Result<AuthorizationResult> {
|
||||
@@ -817,7 +817,7 @@ pub fn npub_to_pubkey(npub: &str) -> Result<String> {
|
||||
/// - `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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
event_id: &EventId,
|
||||
) -> Result<Option<String>> {
|
||||
// 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
pushed_refs: &[(String, String, String)],
|
||||
) -> Result<()> {
|
||||
for (_, new_oid, ref_name) in pushed_refs {
|
||||
|
||||
+3
-4
@@ -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<Arc<MemoryDatabase>>,
|
||||
database: Option<SharedDatabase>,
|
||||
identifier: &str,
|
||||
owner_pubkey: &str,
|
||||
) -> Result<Response<Full<Bytes>>, GitError> {
|
||||
@@ -310,7 +309,7 @@ pub async fn handle_receive_pack(
|
||||
/// 5. Validates that pushed refs match the state
|
||||
/// 6. Validates refs/nostr/<event-id> has valid event id and if event exists, `c` tag matches ref
|
||||
async fn authorize_push(
|
||||
database: &Arc<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
identifier: &str,
|
||||
owner_pubkey: &str,
|
||||
request_body: &Bytes,
|
||||
|
||||
+4
-5
@@ -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<MemoryDatabase>,
|
||||
database: SharedDatabase,
|
||||
}
|
||||
|
||||
impl HttpService {
|
||||
@@ -98,7 +97,7 @@ impl HttpService {
|
||||
relay: LocalRelay,
|
||||
config: Config,
|
||||
remote: SocketAddr,
|
||||
database: Arc<MemoryDatabase>,
|
||||
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<MemoryDatabase>,
|
||||
database: SharedDatabase,
|
||||
) -> anyhow::Result<()> {
|
||||
let bind_addr: SocketAddr = config.bind_address.parse()?;
|
||||
|
||||
|
||||
+35
-21
@@ -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<dyn NostrDatabase>;
|
||||
|
||||
/// 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<MemoryDatabase>,
|
||||
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", &"<database>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Nip34WritePolicy {
|
||||
pub fn new(
|
||||
domain: impl Into<String>,
|
||||
database: Arc<MemoryDatabase>,
|
||||
database: SharedDatabase,
|
||||
git_data_path: impl Into<PathBuf>,
|
||||
) -> 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
state: &RepositoryState,
|
||||
authorized_pubkeys: &[PublicKey],
|
||||
) -> Result<bool, String> {
|
||||
@@ -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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
identifier: &str,
|
||||
state_author: &PublicKey,
|
||||
) -> Result<Vec<RepositoryAnnouncement>, 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
state: &RepositoryState,
|
||||
) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
event: &Event,
|
||||
) -> Result<Option<usize>, 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
addressables: &[String],
|
||||
) -> Result<Option<String>, 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
event_ids: &[EventId],
|
||||
) -> Result<Option<EventId>, 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<MemoryDatabase>,
|
||||
database: &SharedDatabase,
|
||||
event: &Event,
|
||||
) -> Result<bool, String> {
|
||||
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<MemoryDatabase>,
|
||||
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<MemoryDatabase>` 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<RelayWithDatabase> {
|
||||
tracing::info!("Configuring nostr relay with GRASP-01 validation...");
|
||||
|
||||
@@ -1167,7 +1181,7 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> {
|
||||
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<RelayWithDatabase> {
|
||||
}
|
||||
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)
|
||||
})?)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user