Skip to content

Commit

Permalink
sdk: add support to NIP17 relay list
Browse files Browse the repository at this point in the history
When `gossip` is enabled, automatically discovery NIP17 for sending and or receiving private direct messages

Closes #640

Signed-off-by: Yuki Kishimoto <[email protected]>
  • Loading branch information
yukibtc committed Nov 22, 2024
1 parent a4ebf69 commit 7d09dc1
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 97 deletions.
141 changes: 85 additions & 56 deletions crates/nostr-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,57 +1014,7 @@ impl Client {
return Ok(self.pool.send_event(event).await?);
}

// ########## Gossip ##########

// Get all public keys involved in the event
let public_keys = event
.tags
.public_keys()
.copied()
.chain(iter::once(event.pubkey));

// Check what are up-to-date in the gossip graph and which ones require an update
let outdated_public_keys = self.gossip_graph.check_outdated(public_keys).await;
self.update_outdated_gossip_graph(outdated_public_keys)
.await?;

// Get relays
let mut outbox = self.gossip_graph.get_outbox_relays(&[event.pubkey]).await;
let inbox = self
.gossip_graph
.get_inbox_relays(event.tags.public_keys())
.await;

// Add outbox relays
for url in outbox.iter() {
if self.add_gossip_relay(url).await? {
self.connect_relay(url).await?;
}
}

// Add inbox relays
for url in inbox.iter() {
if self.add_gossip_relay(url).await? {
self.connect_relay(url).await?;
}
}

// Get WRITE relays
// TODO: avoid clone of both url and relay
let write_relays = self
.pool
.relays_with_flag(RelayServiceFlags::WRITE, FlagCheck::All)
.await
.into_keys();

// Extend OUTBOX relays with WRITE ones
outbox.extend(write_relays);

// Union of OUTBOX (and WRITE) with INBOX relays
let urls = outbox.union(&inbox);

// Send event
Ok(self.pool.send_event_to(urls, event).await?)
self.gossip_send_event(event, false).await
}

/// Send multiple events at once to all relays with [`RelayServiceFlags::WRITE`] flag.
Expand Down Expand Up @@ -1325,7 +1275,10 @@ impl Client {
Ok(contacts)
}

/// Send private direct message to all relays
/// Send a private direct message
///
/// If `gossip` is enabled (see [`Options::gossip`]) the event will be sent to NIP17 relays (automatically discovered),
/// otherwise to all relays with [`RelayServiceFlags::WRITE`] flag.
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
#[inline]
Expand All @@ -1343,10 +1296,16 @@ impl Client {
let signer = self.signer().await?;
let event: Event =
EventBuilder::private_msg(&signer, receiver, message, rumor_extra_tags).await?;
self.send_event(event).await

// NOT gossip, send to all relays
if !self.opts.gossip {
return self.send_event(event).await;
}

self.gossip_send_event(event, true).await
}

/// Send private direct message to specific relays
/// Send a private direct message to specific relays
///
/// <https://github.com/nostr-protocol/nips/blob/master/17.md>
#[inline]
Expand Down Expand Up @@ -1693,8 +1652,8 @@ impl Client {
if !outdated_public_keys.is_empty() {
// Compose filters
let filter: Filter = Filter::default()
.authors(outdated_public_keys)
.kind(Kind::RelayList);
.authors(outdated_public_keys.clone())
.kinds([Kind::RelayList, Kind::InboxRelays]);

// Query from database
let database = self.database();
Expand All @@ -1716,6 +1675,11 @@ impl Client {
.fetch_events_from(relays, vec![filter], Some(Duration::from_secs(10)))
.await?;

// Update last check for these public keys
self.gossip_graph
.update_last_check(outdated_public_keys)
.await;

// Merge database and relays events
let merged: Events = events.merge(stored_events);

Expand Down Expand Up @@ -1779,6 +1743,71 @@ impl Client {
Ok(broken_down.filters)
}

async fn gossip_send_event(&self, event: Event, nip17: bool) -> Result<Output<EventId>, Error> {
// Get all public keys involved in the event
let public_keys = event
.tags
.public_keys()
.copied()
.chain(iter::once(event.pubkey));

// Check what are up to date in the gossip graph and which ones require an update
let outdated_public_keys = self.gossip_graph.check_outdated(public_keys).await;
self.update_outdated_gossip_graph(outdated_public_keys)
.await?;

let urls: HashSet<Url> = if nip17 {
// Get NIP17 relays
let relays = self
.gossip_graph
.get_nip17_inbox_relays(event.tags.public_keys().chain(iter::once(&event.pubkey)))
.await;

// Add outbox and inbox relays
for url in relays.iter() {
if self.add_gossip_relay(url).await? {
self.connect_relay(url).await?;
}
}

relays
} else {
// Get NIP65 relays
let mut outbox = self
.gossip_graph
.get_nip65_outbox_relays(&[event.pubkey])
.await;
let inbox = self
.gossip_graph
.get_nip65_inbox_relays(event.tags.public_keys())
.await;

// Add outbox and inbox relays
for url in outbox.iter().chain(inbox.iter()) {
if self.add_gossip_relay(url).await? {
self.connect_relay(url).await?;
}
}

// Get WRITE relays
// TODO: avoid clone of both url and relay
let write_relays = self
.pool
.relays_with_flag(RelayServiceFlags::WRITE, FlagCheck::All)
.await
.into_keys();

// Extend OUTBOX relays with WRITE ones
outbox.extend(write_relays);

// Union of OUTBOX (and WRITE) with INBOX relays
outbox.union(&inbox).cloned().collect()
};

// Send event
Ok(self.pool.send_event_to(urls, event).await?)
}

async fn gossip_stream_events(
&self,
filters: Vec<Filter>,
Expand Down
1 change: 1 addition & 0 deletions crates/nostr-sdk/src/gossip/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ use std::time::Duration;
/// Max number of relays allowed in NIP17/NIP65 lists
pub const MAX_RELAYS_LIST: usize = 5;
pub const PUBKEY_METADATA_OUTDATED_AFTER: Duration = Duration::from_secs(60 * 60); // 60 min
pub const CHECK_OUTDATED_INTERVAL: Duration = Duration::from_secs(60 * 5); // 5 min
Loading

0 comments on commit 7d09dc1

Please sign in to comment.