diff --git a/crates/nostr-relay-pool/src/pool/error.rs b/crates/nostr-relay-pool/src/pool/error.rs index a9483c5ac..3ef8558cf 100644 --- a/crates/nostr-relay-pool/src/pool/error.rs +++ b/crates/nostr-relay-pool/src/pool/error.rs @@ -5,6 +5,7 @@ use async_utility::thread; use nostr::message::MessageHandleError; use nostr::types::url; +use nostr::PublicKey; use nostr_database::DatabaseError; use thiserror::Error; @@ -52,6 +53,9 @@ pub enum Error { /// Relay not found #[error("relay not found")] RelayNotFound, + /// Relay not found + #[error("relay metadata not found for `{0}` public key")] + RelayMetadataNotFound(PublicKey), /// Notification Handler error #[error("notification handler error: {0}")] Handler(String), diff --git a/crates/nostr-relay-pool/src/pool/internal.rs b/crates/nostr-relay-pool/src/pool/internal.rs index c579d4cef..d817b46a3 100644 --- a/crates/nostr-relay-pool/src/pool/internal.rs +++ b/crates/nostr-relay-pool/src/pool/internal.rs @@ -13,7 +13,11 @@ use std::time::Duration; use async_utility::thread::JoinHandle; use async_utility::{thread, time}; use atomic_destructor::AtomicDestroyer; -use nostr::{ClientMessage, Event, EventId, Filter, SubscriptionId, Timestamp, TryIntoUrl, Url}; +use nostr::nips::nip65::{self, RelayMetadata}; +use nostr::{ + ClientMessage, Event, EventId, Filter, Kind, PublicKey, SubscriptionId, Timestamp, TryIntoUrl, + Url, +}; use nostr_database::{DynNostrDatabase, IntoNostrDatabase, Order}; use tokio::sync::{broadcast, Mutex, RwLock}; @@ -30,7 +34,7 @@ pub struct InternalRelayPool { notification_sender: broadcast::Sender, subscriptions: Arc>>>, blacklist: RelayBlacklist, - // opts: RelayPoolOptions, + opts: RelayPoolOptions, } impl AtomicDestroyer for InternalRelayPool { @@ -61,7 +65,7 @@ impl InternalRelayPool { notification_sender, subscriptions: Arc::new(RwLock::new(HashMap::new())), blacklist: RelayBlacklist::empty(), - //opts, + opts, } } @@ -848,3 +852,79 @@ impl InternalRelayPool { } } } + +// Gossip methods +impl InternalRelayPool { + pub async fn add_discovery_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::DISCOVERY); + + // Add relay + let opts: RelayOptions = RelayOptions::default().flags(flags); + self.add_relay(url, opts).await + } + + pub async fn add_inbox_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::READ); + flags.add(RelayServiceFlags::INBOX); + + // Add relay + let opts: RelayOptions = RelayOptions::default().flags(flags); + self.add_relay(url, opts).await + } + + pub async fn add_outbox_relay(&self, url: U) -> Result + where + U: TryIntoUrl, + Error: From<::Err>, + { + // Compose flags + let mut flags: RelayServiceFlags = RelayServiceFlags::NONE; + flags.add(RelayServiceFlags::WRITE); + flags.add(RelayServiceFlags::OUTBOX); + + // Add relay + let opts: RelayOptions = RelayOptions::default().flags(flags); + self.add_relay(url, opts).await + } + + pub async fn get_relays_for_public_key( + &self, + public_key: PublicKey, + timeout: Duration, + ) -> Result>, Error> { + // Get discovery relays + let relays = self + .relays_with_flag(RelayServiceFlags::DISCOVERY) + .await + .into_keys(); + + // Get events + let filter: Filter = Filter::default() + .author(public_key) + .kind(Kind::RelayList) + .limit(1); + let events: Vec = self + .get_events_from(relays, vec![filter], timeout, FilterOptions::ExitOnEOSE) + .await?; + + // Extract relay list (NIP65) + let event: &Event = events + .first() + .ok_or(Error::RelayMetadataNotFound(public_key))?; + Ok(nip65::extract_relay_list(event) + .map(|(u, m)| (u.clone(), *m)) + .collect()) + } +} diff --git a/crates/nostr-relay-pool/src/pool/options.rs b/crates/nostr-relay-pool/src/pool/options.rs index 7b73b8437..252524d94 100644 --- a/crates/nostr-relay-pool/src/pool/options.rs +++ b/crates/nostr-relay-pool/src/pool/options.rs @@ -8,25 +8,38 @@ #[derive(Debug, Clone, Copy)] pub struct RelayPoolOptions { pub(super) notification_channel_size: usize, + pub(super) gossip: bool, + pub(super) restore_relays_from_database: bool, } impl Default for RelayPoolOptions { fn default() -> Self { Self { notification_channel_size: 4096, + gossip: false, + restore_relays_from_database: false, } } } impl RelayPoolOptions { /// New default options + #[inline] pub fn new() -> Self { Self::default() } /// Notification channel size (default: 4096) + #[inline] pub fn notification_channel_size(mut self, size: usize) -> Self { self.notification_channel_size = size; self } + + /// Enable gossip model (default: false) + #[inline] + pub fn gossip(mut self, enable: bool) -> Self { + self.gossip = enable; + self + } } diff --git a/crates/nostr-relay-pool/src/relay/flags.rs b/crates/nostr-relay-pool/src/relay/flags.rs index 4cfe6cbf3..a10ec19f1 100644 --- a/crates/nostr-relay-pool/src/relay/flags.rs +++ b/crates/nostr-relay-pool/src/relay/flags.rs @@ -138,7 +138,7 @@ impl AtomicRelayServiceFlags { let f: RelayServiceFlags = RelayServiceFlags(_f); f.has(flags) } - + /// Check if `READ` service is enabled pub fn has_read(&self) -> bool { self.has(RelayServiceFlags::READ) diff --git a/crates/nostr-sdk/src/client/options.rs b/crates/nostr-sdk/src/client/options.rs index bf4d8031c..e0788fda0 100644 --- a/crates/nostr-sdk/src/client/options.rs +++ b/crates/nostr-sdk/src/client/options.rs @@ -26,6 +26,7 @@ pub struct Options { pub(super) connection_timeout: Option, send_timeout: Option, nip42_auto_authentication: Arc, + pub(super) gossip: bool, #[cfg(not(target_arch = "wasm32"))] pub(super) proxy: Proxy, pub(super) relay_limits: RelayLimits, @@ -45,6 +46,7 @@ impl Default for Options { connection_timeout: None, send_timeout: Some(DEFAULT_SEND_TIMEOUT), nip42_auto_authentication: Arc::new(AtomicBool::new(true)), + gossip: false, #[cfg(not(target_arch = "wasm32"))] proxy: Proxy::default(), relay_limits: RelayLimits::default(), @@ -176,6 +178,13 @@ impl Options { self } + /// Enable gossip model (default: false) + #[inline] + pub fn gossip(mut self, enable: bool) -> Self { + self.gossip = enable; + self + } + #[inline] pub(super) fn is_nip42_auto_authentication_enabled(&self) -> bool { self.nip42_auto_authentication.load(Ordering::SeqCst)