diff options
Diffstat (limited to 'src/nostr/builder.rs')
| -rw-r--r-- | src/nostr/builder.rs | 56 |
1 files changed, 35 insertions, 21 deletions
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; | |||
| 9 | use nostr::nips::nip19::ToBech32; | 9 | use nostr::nips::nip19::ToBech32; |
| 10 | use nostr::prelude::{Alphabet, SingleLetterTag}; | 10 | use nostr::prelude::{Alphabet, SingleLetterTag}; |
| 11 | use nostr::{EventId, Filter, Kind, PublicKey}; | 11 | use nostr::{EventId, Filter, Kind, PublicKey}; |
| 12 | use nostr_lmdb::NostrLMDB; | ||
| 12 | use nostr_relay_builder::prelude::*; | 13 | use nostr_relay_builder::prelude::*; |
| 13 | 14 | ||
| 14 | use crate::config::{Config, DatabaseBackend}; | 15 | use crate::config::{Config, DatabaseBackend}; |
| @@ -18,6 +19,9 @@ use crate::nostr::events::{ | |||
| 18 | KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, | 19 | KIND_PR_UPDATE, KIND_REPOSITORY_ANNOUNCEMENT, KIND_REPOSITORY_STATE, |
| 19 | }; | 20 | }; |
| 20 | 21 | ||
| 22 | /// Type alias for the shared database used by the relay | ||
| 23 | pub type SharedDatabase = Arc<dyn NostrDatabase>; | ||
| 24 | |||
| 21 | /// Result of aligning a repository with authorized state | 25 | /// Result of aligning a repository with authorized state |
| 22 | #[derive(Debug, Default)] | 26 | #[derive(Debug, Default)] |
| 23 | struct AlignmentResult { | 27 | struct AlignmentResult { |
| @@ -35,23 +39,33 @@ struct AlignmentResult { | |||
| 35 | /// | 39 | /// |
| 36 | /// Validates all events according to GRASP-01 specification: | 40 | /// Validates all events according to GRASP-01 specification: |
| 37 | /// - Repository announcements must list service in clone and relays tags | 41 | /// - Repository announcements must list service in clone and relays tags |
| 38 | /// - Repository state announcements must have valid structure | 42 | /// - Repository state announcements must have valid structure |
| 39 | /// - Other events must reference accepted repositories or events | 43 | /// - Other events must reference accepted repositories or events |
| 40 | /// - Forward references are supported (events referenced by accepted events) | 44 | /// - Forward references are supported (events referenced by accepted events) |
| 41 | /// - Orphan events with no valid references are rejected | 45 | /// - Orphan events with no valid references are rejected |
| 42 | /// | 46 | /// |
| 43 | /// Uses stateful database queries to check event relationships. | 47 | /// Uses stateful database queries to check event relationships. |
| 44 | #[derive(Debug, Clone)] | 48 | #[derive(Clone)] |
| 45 | pub struct Nip34WritePolicy { | 49 | pub struct Nip34WritePolicy { |
| 46 | domain: String, | 50 | domain: String, |
| 47 | database: Arc<MemoryDatabase>, | 51 | database: SharedDatabase, |
| 48 | git_data_path: PathBuf, | 52 | git_data_path: PathBuf, |
| 49 | } | 53 | } |
| 50 | 54 | ||
| 55 | impl std::fmt::Debug for Nip34WritePolicy { | ||
| 56 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 57 | f.debug_struct("Nip34WritePolicy") | ||
| 58 | .field("domain", &self.domain) | ||
| 59 | .field("git_data_path", &self.git_data_path) | ||
| 60 | .field("database", &"<database>") | ||
| 61 | .finish() | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 51 | impl Nip34WritePolicy { | 65 | impl Nip34WritePolicy { |
| 52 | pub fn new( | 66 | pub fn new( |
| 53 | domain: impl Into<String>, | 67 | domain: impl Into<String>, |
| 54 | database: Arc<MemoryDatabase>, | 68 | database: SharedDatabase, |
| 55 | git_data_path: impl Into<PathBuf>, | 69 | git_data_path: impl Into<PathBuf>, |
| 56 | ) -> Self { | 70 | ) -> Self { |
| 57 | Self { | 71 | Self { |
| @@ -104,7 +118,7 @@ impl Nip34WritePolicy { | |||
| 104 | /// The authorized_pubkeys should be the owner and maintainers of a specific | 118 | /// The authorized_pubkeys should be the owner and maintainers of a specific |
| 105 | /// announcement, so different owners with the same identifier don't interfere. | 119 | /// announcement, so different owners with the same identifier don't interfere. |
| 106 | async fn is_latest_state_for_identifier( | 120 | async fn is_latest_state_for_identifier( |
| 107 | database: &Arc<MemoryDatabase>, | 121 | database: &SharedDatabase, |
| 108 | state: &RepositoryState, | 122 | state: &RepositoryState, |
| 109 | authorized_pubkeys: &[PublicKey], | 123 | authorized_pubkeys: &[PublicKey], |
| 110 | ) -> Result<bool, String> { | 124 | ) -> Result<bool, String> { |
| @@ -155,7 +169,7 @@ impl Nip34WritePolicy { | |||
| 155 | /// should update HEAD in the repository of the announcement owner, | 169 | /// should update HEAD in the repository of the announcement owner, |
| 156 | /// not in the maintainer's own (possibly non-existent) repository. | 170 | /// not in the maintainer's own (possibly non-existent) repository. |
| 157 | async fn find_authorized_announcements( | 171 | async fn find_authorized_announcements( |
| 158 | database: &Arc<MemoryDatabase>, | 172 | database: &SharedDatabase, |
| 159 | identifier: &str, | 173 | identifier: &str, |
| 160 | state_author: &PublicKey, | 174 | state_author: &PublicKey, |
| 161 | ) -> Result<Vec<RepositoryAnnouncement>, String> { | 175 | ) -> Result<Vec<RepositoryAnnouncement>, String> { |
| @@ -205,7 +219,7 @@ impl Nip34WritePolicy { | |||
| 205 | /// - This state event is the latest for the identifier in that context | 219 | /// - This state event is the latest for the identifier in that context |
| 206 | async fn identify_owner_repositories( | 220 | async fn identify_owner_repositories( |
| 207 | &self, | 221 | &self, |
| 208 | database: &Arc<MemoryDatabase>, | 222 | database: &SharedDatabase, |
| 209 | state: &RepositoryState, | 223 | state: &RepositoryState, |
| 210 | ) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, String> { | 224 | ) -> Result<Vec<(RepositoryAnnouncement, std::path::PathBuf)>, String> { |
| 211 | // Find all announcements where state author is authorized | 225 | // Find all announcements where state author is authorized |
| @@ -485,7 +499,7 @@ impl Nip34WritePolicy { | |||
| 485 | /// Ok(Some(n)) if n refs were deleted, Ok(None) if no action taken, Err on failure | 499 | /// Ok(Some(n)) if n refs were deleted, Ok(None) if no action taken, Err on failure |
| 486 | async fn validate_pr_nostr_ref( | 500 | async fn validate_pr_nostr_ref( |
| 487 | &self, | 501 | &self, |
| 488 | database: &Arc<MemoryDatabase>, | 502 | database: &SharedDatabase, |
| 489 | event: &Event, | 503 | event: &Event, |
| 490 | ) -> Result<Option<usize>, String> { | 504 | ) -> Result<Option<usize>, String> { |
| 491 | let event_id = event.id.to_hex(); | 505 | let event_id = event.id.to_hex(); |
| @@ -651,7 +665,7 @@ impl Nip34WritePolicy { | |||
| 651 | /// Check if any addressable events (repositories) exist in database | 665 | /// Check if any addressable events (repositories) exist in database |
| 652 | /// Returns the first matching addressable reference found, or None if none match | 666 | /// Returns the first matching addressable reference found, or None if none match |
| 653 | async fn find_accepted_repository( | 667 | async fn find_accepted_repository( |
| 654 | database: &Arc<MemoryDatabase>, | 668 | database: &SharedDatabase, |
| 655 | addressables: &[String], | 669 | addressables: &[String], |
| 656 | ) -> Result<Option<String>, String> { | 670 | ) -> Result<Option<String>, String> { |
| 657 | if addressables.is_empty() { | 671 | if addressables.is_empty() { |
| @@ -724,7 +738,7 @@ impl Nip34WritePolicy { | |||
| 724 | /// Check if any events exist in database | 738 | /// Check if any events exist in database |
| 725 | /// Returns the first matching event ID found, or None if none match | 739 | /// Returns the first matching event ID found, or None if none match |
| 726 | async fn find_accepted_event( | 740 | async fn find_accepted_event( |
| 727 | database: &Arc<MemoryDatabase>, | 741 | database: &SharedDatabase, |
| 728 | event_ids: &[EventId], | 742 | event_ids: &[EventId], |
| 729 | ) -> Result<Option<EventId>, String> { | 743 | ) -> Result<Option<EventId>, String> { |
| 730 | if event_ids.is_empty() { | 744 | if event_ids.is_empty() { |
| @@ -752,7 +766,7 @@ impl Nip34WritePolicy { | |||
| 752 | /// This optimization recognizes that replaceable events are referenced by coordinate address, | 766 | /// This optimization recognizes that replaceable events are referenced by coordinate address, |
| 753 | /// while regular events are referenced by event ID. | 767 | /// while regular events are referenced by event ID. |
| 754 | async fn is_referenced_by_accepted( | 768 | async fn is_referenced_by_accepted( |
| 755 | database: &Arc<MemoryDatabase>, | 769 | database: &SharedDatabase, |
| 756 | event: &Event, | 770 | event: &Event, |
| 757 | ) -> Result<bool, String> { | 771 | ) -> Result<bool, String> { |
| 758 | let kind_u16 = event.kind.as_u16(); | 772 | let kind_u16 = event.kind.as_u16(); |
| @@ -1152,14 +1166,14 @@ pub struct RelayWithDatabase { | |||
| 1152 | /// The local relay instance | 1166 | /// The local relay instance |
| 1153 | pub relay: LocalRelay, | 1167 | pub relay: LocalRelay, |
| 1154 | /// The database Arc that can be used for direct queries | 1168 | /// The database Arc that can be used for direct queries |
| 1155 | pub database: Arc<MemoryDatabase>, | 1169 | pub database: SharedDatabase, |
| 1156 | } | 1170 | } |
| 1157 | 1171 | ||
| 1158 | /// Create a configured LocalRelay with full GRASP-01 validation | 1172 | /// Create a configured LocalRelay with full GRASP-01 validation |
| 1159 | /// | 1173 | /// |
| 1160 | /// Returns a `RelayWithDatabase` struct containing: | 1174 | /// Returns a `RelayWithDatabase` struct containing: |
| 1161 | /// - The `LocalRelay` for handling WebSocket connections | 1175 | /// - The `LocalRelay` for handling WebSocket connections |
| 1162 | /// - The `Arc<MemoryDatabase>` for direct database queries (e.g., push authorization) | 1176 | /// - The `SharedDatabase` for direct database queries (e.g., push authorization) |
| 1163 | pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | 1177 | pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { |
| 1164 | tracing::info!("Configuring nostr relay with GRASP-01 validation..."); | 1178 | tracing::info!("Configuring nostr relay with GRASP-01 validation..."); |
| 1165 | 1179 | ||
| @@ -1167,7 +1181,7 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | |||
| 1167 | let db_path = Path::new(&config.relay_data_path); | 1181 | let db_path = Path::new(&config.relay_data_path); |
| 1168 | 1182 | ||
| 1169 | // Create database based on configuration | 1183 | // Create database based on configuration |
| 1170 | let database = match config.database_backend { | 1184 | let database: SharedDatabase = match config.database_backend { |
| 1171 | DatabaseBackend::Memory => { | 1185 | DatabaseBackend::Memory => { |
| 1172 | tracing::info!("Using in-memory database (no persistence)"); | 1186 | tracing::info!("Using in-memory database (no persistence)"); |
| 1173 | Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { | 1187 | Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { |
| @@ -1187,13 +1201,13 @@ pub fn create_relay(config: &Config) -> Result<RelayWithDatabase> { | |||
| 1187 | } | 1201 | } |
| 1188 | DatabaseBackend::Lmdb => { | 1202 | DatabaseBackend::Lmdb => { |
| 1189 | tracing::info!("Using LMDB backend at: {}", db_path.display()); | 1203 | tracing::info!("Using LMDB backend at: {}", db_path.display()); |
| 1190 | // TODO: Implement LMDB backend once nostr-relay-builder supports it | 1204 | // Ensure the database directory exists |
| 1191 | // For now, fall back to memory database | 1205 | std::fs::create_dir_all(db_path).map_err(|e| { |
| 1192 | tracing::warn!("LMDB backend not yet implemented, using in-memory database"); | 1206 | anyhow::anyhow!("Failed to create LMDB directory {}: {}", db_path.display(), e) |
| 1193 | Arc::new(MemoryDatabase::with_opts(MemoryDatabaseOptions { | 1207 | })?; |
| 1194 | events: true, | 1208 | Arc::new(NostrLMDB::open(db_path).map_err(|e| { |
| 1195 | max_events: Some(100_000), | 1209 | anyhow::anyhow!("Failed to open LMDB database at {}: {}", db_path.display(), e) |
| 1196 | })) | 1210 | })?) |
| 1197 | } | 1211 | } |
| 1198 | }; | 1212 | }; |
| 1199 | 1213 | ||