chore: improve nip4e implementation (#204)
* patch * update ui * add load response * fix * . * wip: rewrite gossip * new gossip implementation * clean up * . * debug * . * . * update * . * fix * fix
This commit is contained in:
@@ -16,8 +16,7 @@ pub use tracker::*;
|
||||
mod storage;
|
||||
mod tracker;
|
||||
|
||||
pub const GIFTWRAP_SUBSCRIPTION: &str = "default-inbox";
|
||||
pub const ENCRYPTION_GIFTWARP_SUBSCRIPTION: &str = "encryption-inbox";
|
||||
pub const GIFTWRAP_SUBSCRIPTION: &str = "gift-wrap-events";
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
NostrRegistry::set_global(cx.new(NostrRegistry::new), cx);
|
||||
@@ -30,15 +29,15 @@ impl Global for GlobalNostrRegistry {}
|
||||
/// Nostr Registry
|
||||
#[derive(Debug)]
|
||||
pub struct NostrRegistry {
|
||||
/// Nostr client instance
|
||||
client: Arc<Client>,
|
||||
/// Nostr Client
|
||||
client: Client,
|
||||
|
||||
/// Custom gossip implementation
|
||||
gossip: Arc<RwLock<Gossip>>,
|
||||
|
||||
/// Tracks activity related to Nostr events
|
||||
tracker: Arc<RwLock<EventTracker>>,
|
||||
|
||||
/// Manages caching of nostr events
|
||||
cache_manager: Arc<RwLock<CacheManager>>,
|
||||
|
||||
/// Tasks for asynchronous operations
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
}
|
||||
@@ -63,11 +62,7 @@ impl NostrRegistry {
|
||||
.install_default()
|
||||
.ok();
|
||||
|
||||
let path = config_dir().join("nostr");
|
||||
let lmdb = NostrLMDB::open(path).expect("Failed to initialize database");
|
||||
let gossip = NostrGossipMemory::unbounded();
|
||||
|
||||
// Nostr client options
|
||||
// Construct the nostr client options
|
||||
let opts = ClientOptions::new()
|
||||
.automatic_authentication(false)
|
||||
.verify_subscriptions(false)
|
||||
@@ -76,16 +71,12 @@ impl NostrRegistry {
|
||||
});
|
||||
|
||||
// Construct the nostr client
|
||||
let client = Arc::new(
|
||||
ClientBuilder::default()
|
||||
.gossip(gossip)
|
||||
.database(lmdb)
|
||||
.opts(opts)
|
||||
.build(),
|
||||
);
|
||||
let path = config_dir().join("nostr");
|
||||
let lmdb = NostrLMDB::open(path).expect("Failed to initialize database");
|
||||
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
|
||||
|
||||
let tracker = Arc::new(RwLock::new(EventTracker::default()));
|
||||
let cache_manager = Arc::new(RwLock::new(CacheManager::default()));
|
||||
let gossip = Arc::new(RwLock::new(Gossip::default()));
|
||||
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
@@ -94,8 +85,8 @@ impl NostrRegistry {
|
||||
//
|
||||
// And handle notifications from the nostr relay pool channel
|
||||
cx.background_spawn({
|
||||
let client = Arc::clone(&client);
|
||||
let cache_manager = Arc::clone(&cache_manager);
|
||||
let client = client.clone();
|
||||
let gossip = Arc::clone(&gossip);
|
||||
let tracker = Arc::clone(&tracker);
|
||||
let _ = initialized_at();
|
||||
|
||||
@@ -104,7 +95,7 @@ impl NostrRegistry {
|
||||
Self::connect(&client).await;
|
||||
|
||||
// Handle notifications from the relay pool
|
||||
Self::handle_notifications(&client, &cache_manager, &tracker).await;
|
||||
Self::handle_notifications(&client, &gossip, &tracker).await;
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -112,7 +103,7 @@ impl NostrRegistry {
|
||||
Self {
|
||||
client,
|
||||
tracker,
|
||||
cache_manager,
|
||||
gossip,
|
||||
_tasks: tasks,
|
||||
}
|
||||
}
|
||||
@@ -135,12 +126,10 @@ impl NostrRegistry {
|
||||
|
||||
async fn handle_notifications(
|
||||
client: &Client,
|
||||
cache: &Arc<RwLock<CacheManager>>,
|
||||
gossip: &Arc<RwLock<Gossip>>,
|
||||
tracker: &Arc<RwLock<EventTracker>>,
|
||||
) {
|
||||
let mut notifications = client.notifications();
|
||||
log::info!("Listening for notifications");
|
||||
|
||||
let mut processed_events = HashSet::new();
|
||||
|
||||
while let Ok(notification) = notifications.recv().await {
|
||||
@@ -158,53 +147,50 @@ impl NostrRegistry {
|
||||
|
||||
match event.kind {
|
||||
Kind::RelayList => {
|
||||
if Self::is_self_authored(client, &event).await {
|
||||
log::info!("Found relay list event for the current user");
|
||||
let author = event.pubkey;
|
||||
let announcement = Kind::Custom(10044);
|
||||
let mut gossip = gossip.write().await;
|
||||
gossip.insert_relays(&event);
|
||||
|
||||
// Fetch user's messaging relays event
|
||||
_ = Self::subscribe(client, author, Kind::InboxRelays).await;
|
||||
// Fetch user's encryption announcement event
|
||||
_ = Self::subscribe(client, author, announcement).await;
|
||||
let urls: Vec<RelayUrl> = Self::extract_write_relays(&event);
|
||||
let author = event.pubkey;
|
||||
|
||||
// Fetch user's encryption announcement event
|
||||
Self::get(client, &urls, author, Kind::Custom(10044)).await;
|
||||
// Fetch user's messaging relays event
|
||||
Self::get(client, &urls, author, Kind::InboxRelays).await;
|
||||
|
||||
// Verify if the event is belonging to the current user
|
||||
if Self::is_self_authored(client, &event).await {
|
||||
// Fetch user's metadata event
|
||||
_ = Self::subscribe(client, author, Kind::Metadata).await;
|
||||
Self::get(client, &urls, author, Kind::Metadata).await;
|
||||
// Fetch user's contact list event
|
||||
_ = Self::subscribe(client, author, Kind::ContactList).await;
|
||||
Self::get(client, &urls, author, Kind::ContactList).await;
|
||||
}
|
||||
}
|
||||
Kind::InboxRelays => {
|
||||
// Extract up to 3 messaging relays
|
||||
let urls: Vec<RelayUrl> =
|
||||
nip17::extract_relay_list(&event).take(3).cloned().collect();
|
||||
let mut gossip = gossip.write().await;
|
||||
gossip.insert_messaging_relays(&event);
|
||||
|
||||
// Subscribe to gift wrap events if event is from current user
|
||||
if Self::is_self_authored(client, &event).await {
|
||||
log::info!("Found messaging list event for the current user");
|
||||
// Extract user's messaging relays
|
||||
let urls: Vec<RelayUrl> =
|
||||
nip17::extract_relay_list(&event).cloned().collect();
|
||||
|
||||
if let Err(e) =
|
||||
Self::get_messages(client, &urls, event.pubkey).await
|
||||
{
|
||||
log::error!("Failed to subscribe to gift wrap events: {e}");
|
||||
}
|
||||
// Fetch user's inbox messages in the extracted relays
|
||||
Self::get_messages(client, event.pubkey, &urls).await;
|
||||
}
|
||||
|
||||
// Cache the messaging relays
|
||||
let mut cache = cache.write().await;
|
||||
cache.insert_relay(event.pubkey, urls);
|
||||
}
|
||||
Kind::Custom(10044) => {
|
||||
// Cache the announcement event
|
||||
if let Ok(announcement) = Self::extract_announcement(&event) {
|
||||
let mut cache = cache.write().await;
|
||||
cache.insert_announcement(event.pubkey, Some(announcement));
|
||||
}
|
||||
let mut gossip = gossip.write().await;
|
||||
gossip.insert_announcement(&event);
|
||||
}
|
||||
Kind::ContactList => {
|
||||
if Self::is_self_authored(client, &event).await {
|
||||
let pubkeys: Vec<_> = event.tags.public_keys().copied().collect();
|
||||
let public_keys: Vec<PublicKey> =
|
||||
event.tags.public_keys().copied().collect();
|
||||
|
||||
if let Err(e) = Self::get_metadata_for_list(client, pubkeys).await {
|
||||
if let Err(e) =
|
||||
Self::get_metadata_for_list(client, public_keys).await
|
||||
{
|
||||
log::error!("Failed to get metadata for list: {e}");
|
||||
}
|
||||
}
|
||||
@@ -242,47 +228,59 @@ impl NostrRegistry {
|
||||
false
|
||||
}
|
||||
|
||||
/// Subscribe for events that match the given kind for a given author
|
||||
async fn subscribe(client: &Client, author: PublicKey, kind: Kind) -> Result<(), Error> {
|
||||
/// Get event that match the given kind for a given author
|
||||
async fn get(client: &Client, urls: &[RelayUrl], author: PublicKey, kind: Kind) {
|
||||
// Skip if no relays are provided
|
||||
if urls.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure relay connections
|
||||
for url in urls.iter() {
|
||||
client.add_relay(url).await.ok();
|
||||
client.connect_relay(url).await.ok();
|
||||
}
|
||||
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let filter = Filter::new().author(author).kind(kind).limit(1);
|
||||
|
||||
// Subscribe to filters from the user's write relays
|
||||
client.subscribe(filter, Some(opts)).await?;
|
||||
|
||||
Ok(())
|
||||
if let Err(e) = client.subscribe_to(urls, filter, Some(opts)).await {
|
||||
log::error!("Failed to subscribe: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all gift wrap events in the messaging relays for a given public key
|
||||
async fn get_messages(
|
||||
client: &Client,
|
||||
urls: &[RelayUrl],
|
||||
public_key: PublicKey,
|
||||
) -> Result<(), Error> {
|
||||
pub async fn get_messages(client: &Client, public_key: PublicKey, urls: &[RelayUrl]) {
|
||||
// Verify that there are relays provided
|
||||
if urls.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure relay connection
|
||||
for url in urls.iter() {
|
||||
client.add_relay(url).await.ok();
|
||||
client.connect_relay(url).await.ok();
|
||||
}
|
||||
|
||||
let id = SubscriptionId::new(GIFTWRAP_SUBSCRIPTION);
|
||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||
|
||||
// Verify that there are relays provided
|
||||
if urls.is_empty() {
|
||||
return Err(anyhow!("No relays provided"));
|
||||
}
|
||||
|
||||
// Add and connect relays
|
||||
for url in urls {
|
||||
client.add_relay(url).await?;
|
||||
client.connect_relay(url).await?;
|
||||
}
|
||||
// Unsubscribe from the previous subscription
|
||||
client.unsubscribe(&id).await;
|
||||
|
||||
// Subscribe to filters to user's messaging relays
|
||||
client.subscribe_with_id_to(urls, id, filter, None).await?;
|
||||
|
||||
Ok(())
|
||||
if let Err(e) = client.subscribe_with_id_to(urls, id, filter, None).await {
|
||||
log::error!("Failed to subscribe: {}", e);
|
||||
} else {
|
||||
log::info!("Subscribed to gift wrap events for public key {public_key}",);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get metadata for a list of public keys
|
||||
async fn get_metadata_for_list(client: &Client, pubkeys: Vec<PublicKey>) -> Result<(), Error> {
|
||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
|
||||
let kinds = vec![Kind::Metadata, Kind::ContactList];
|
||||
|
||||
// Return if the list is empty
|
||||
if pubkeys.is_empty() {
|
||||
@@ -290,7 +288,7 @@ impl NostrRegistry {
|
||||
}
|
||||
|
||||
let filter = Filter::new()
|
||||
.limit(pubkeys.len() * kinds.len() + 10)
|
||||
.limit(pubkeys.len() * kinds.len())
|
||||
.authors(pubkeys)
|
||||
.kinds(kinds);
|
||||
|
||||
@@ -302,6 +300,19 @@ impl NostrRegistry {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_write_relays(event: &Event) -> Vec<RelayUrl> {
|
||||
nip65::extract_relay_list(event)
|
||||
.filter_map(|(url, metadata)| {
|
||||
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(3)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Extract an encryption keys announcement from an event.
|
||||
pub fn extract_announcement(event: &Event) -> Result<Announcement, Error> {
|
||||
let public_key = event
|
||||
@@ -342,8 +353,8 @@ impl NostrRegistry {
|
||||
}
|
||||
|
||||
/// Returns a reference to the nostr client.
|
||||
pub fn client(&self) -> Arc<Client> {
|
||||
Arc::clone(&self.client)
|
||||
pub fn client(&self) -> Client {
|
||||
self.client.clone()
|
||||
}
|
||||
|
||||
/// Returns a reference to the event tracker.
|
||||
@@ -352,7 +363,7 @@ impl NostrRegistry {
|
||||
}
|
||||
|
||||
/// Returns a reference to the cache manager.
|
||||
pub fn cache_manager(&self) -> Arc<RwLock<CacheManager>> {
|
||||
Arc::clone(&self.cache_manager)
|
||||
pub fn gossip(&self) -> Arc<RwLock<Gossip>> {
|
||||
Arc::clone(&self.gossip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::collections::{HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use nostr_sdk::prelude::*;
|
||||
|
||||
use crate::NostrRegistry;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Announcement {
|
||||
id: EventId,
|
||||
@@ -56,32 +58,129 @@ impl Response {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CacheManager {
|
||||
/// Cache of messaging relays for each public key
|
||||
relay: HashMap<PublicKey, HashSet<RelayUrl>>,
|
||||
pub struct Gossip {
|
||||
/// Gossip relays for each public key
|
||||
relays: HashMap<PublicKey, HashSet<(RelayUrl, Option<RelayMetadata>)>>,
|
||||
|
||||
/// Cache of device announcement for each public key
|
||||
announcement: HashMap<PublicKey, Option<Announcement>>,
|
||||
/// Messaging relays for each public key
|
||||
messaging_relays: HashMap<PublicKey, HashSet<RelayUrl>>,
|
||||
|
||||
/// Encryption announcement for each public key
|
||||
announcements: HashMap<PublicKey, Option<Announcement>>,
|
||||
}
|
||||
|
||||
impl CacheManager {
|
||||
pub fn relay(&self, public_key: &PublicKey) -> Option<&HashSet<RelayUrl>> {
|
||||
self.relay.get(public_key)
|
||||
impl Gossip {
|
||||
/// Get inbox relays for a public key
|
||||
pub fn inbox_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||
self.relays
|
||||
.get(public_key)
|
||||
.map(|relays| {
|
||||
relays
|
||||
.iter()
|
||||
.filter_map(|(url, metadata)| {
|
||||
if metadata.is_none() || metadata == &Some(RelayMetadata::Write) {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn insert_relay(&mut self, public_key: PublicKey, urls: Vec<RelayUrl>) {
|
||||
self.relay.entry(public_key).or_default().extend(urls);
|
||||
/// Get outbox relays for a public key
|
||||
pub fn outbox_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||
self.relays
|
||||
.get(public_key)
|
||||
.map(|relays| {
|
||||
relays
|
||||
.iter()
|
||||
.filter_map(|(url, metadata)| {
|
||||
if metadata.is_none() || metadata == &Some(RelayMetadata::Read) {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn announcement(&self, public_key: &PublicKey) -> Option<&Option<Announcement>> {
|
||||
self.announcement.get(public_key)
|
||||
/// Insert gossip relays for a public key
|
||||
pub fn insert_relays(&mut self, event: &Event) {
|
||||
self.relays.entry(event.pubkey).or_default().extend(
|
||||
event
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|tag| {
|
||||
if let Some(TagStandard::RelayMetadata {
|
||||
relay_url,
|
||||
metadata,
|
||||
}) = tag.clone().to_standardized()
|
||||
{
|
||||
Some((relay_url, metadata))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(3),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn insert_announcement(
|
||||
&mut self,
|
||||
public_key: PublicKey,
|
||||
announcement: Option<Announcement>,
|
||||
) {
|
||||
self.announcement.insert(public_key, announcement);
|
||||
/// Get messaging relays for a public key
|
||||
pub fn messaging_relays(&self, public_key: &PublicKey) -> Vec<RelayUrl> {
|
||||
self.messaging_relays
|
||||
.get(public_key)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Insert messaging relays for a public key
|
||||
pub fn insert_messaging_relays(&mut self, event: &Event) {
|
||||
self.messaging_relays
|
||||
.entry(event.pubkey)
|
||||
.or_default()
|
||||
.extend(
|
||||
event
|
||||
.tags
|
||||
.iter()
|
||||
.filter_map(|tag| {
|
||||
if let Some(TagStandard::Relay(url)) = tag.as_standardized() {
|
||||
Some(url.to_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.take(3),
|
||||
);
|
||||
}
|
||||
|
||||
/// Ensure connections for the given relay list
|
||||
pub async fn ensure_connections(&self, client: &Client, urls: &[RelayUrl]) {
|
||||
for url in urls {
|
||||
client.add_relay(url).await.ok();
|
||||
client.connect_relay(url).await.ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get announcement for a public key
|
||||
pub fn announcement(&self, public_key: &PublicKey) -> Option<Announcement> {
|
||||
self.announcements
|
||||
.get(public_key)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Insert announcement for a public key
|
||||
pub fn insert_announcement(&mut self, event: &Event) {
|
||||
let announcement = NostrRegistry::extract_announcement(event).ok();
|
||||
|
||||
self.announcements
|
||||
.entry(event.pubkey)
|
||||
.or_insert(announcement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,6 @@ pub fn initialized_at() -> &'static Timestamp {
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EventTracker {
|
||||
/// Tracking events that have failed to unwrap
|
||||
pub failed_unwrap_events: Vec<Event>,
|
||||
|
||||
/// Tracking events that have been resent by Coop in the current session
|
||||
pub resent_ids: Vec<Output<EventId>>,
|
||||
|
||||
@@ -28,10 +25,6 @@ pub struct EventTracker {
|
||||
}
|
||||
|
||||
impl EventTracker {
|
||||
pub fn failed_unwrap_events(&self) -> &Vec<Event> {
|
||||
&self.failed_unwrap_events
|
||||
}
|
||||
|
||||
pub fn resent_ids(&self) -> &Vec<Output<EventId>> {
|
||||
&self.resent_ids
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user