diff --git a/Cargo.lock b/Cargo.lock index 3d2ae48ea..3b7477498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1981,7 +1981,7 @@ dependencies = [ [[package]] name = "gossip-relay-picker" version = "0.2.0-unstable" -source = "git+https://github.com/mikedilger/gossip-relay-picker?rev=b82e1782406e70073fb5e6cfa1c61f1992a530da#b82e1782406e70073fb5e6cfa1c61f1992a530da" +source = "git+https://github.com/mikedilger/gossip-relay-picker?rev=1d13cacb0d7b32d72d0c8c27414461d5389ca2ec#1d13cacb0d7b32d72d0c8c27414461d5389ca2ec" dependencies = [ "async-trait", "dashmap", @@ -2819,7 +2819,7 @@ dependencies = [ [[package]] name = "nostr-types" version = "0.7.0-unstable" -source = "git+https://github.com/mikedilger/nostr-types?rev=d05ca006415152552f74a8d7b9132cfd481e6a4a#d05ca006415152552f74a8d7b9132cfd481e6a4a" +source = "git+https://github.com/mikedilger/nostr-types?rev=ef0d427a09e06103950ec4b5eebbe2c243b08cfd#ef0d427a09e06103950ec4b5eebbe2c243b08cfd" dependencies = [ "aes", "base64 0.21.5", diff --git a/gossip-bin/Cargo.toml b/gossip-bin/Cargo.toml index 908f4cb91..15b5a0d45 100644 --- a/gossip-bin/Cargo.toml +++ b/gossip-bin/Cargo.toml @@ -22,13 +22,13 @@ bech32 = "0.9" eframe = { git = "https://github.com/mikedilger/egui", rev = "50393e4f34ac6246b8c2424e42fbe5b95e4b4452", features = [ "persistence", "wayland" ] } egui-winit = { git = "https://github.com/mikedilger/egui", rev = "50393e4f34ac6246b8c2424e42fbe5b95e4b4452", features = [ "default" ] } egui-video = { git = "https://github.com/mikedilger/egui-video", rev = "d12a4859d383524f978a0dd29d61e1ebd281e735", features = [ "from_bytes" ], optional = true } -gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "b82e1782406e70073fb5e6cfa1c61f1992a530da" } +gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "1d13cacb0d7b32d72d0c8c27414461d5389ca2ec" } gossip-lib = { path = "../gossip-lib" } humansize = "2.1" image = { version = "0.24.6", features = [ "png", "jpeg" ] } lazy_static = "1.4" memoize = "0.4" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "d05ca006415152552f74a8d7b9132cfd481e6a4a", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "ef0d427a09e06103950ec4b5eebbe2c243b08cfd", features = [ "speedy" ] } paste = "1.0" qrcode = { git = "https://github.com/mikedilger/qrcode-rust", rev = "519b77b3efa3f84961169b47d3de08c5ddd86548" } resvg = "0.35.0" diff --git a/gossip-bin/src/commands.rs b/gossip-bin/src/commands.rs index 91eb66c57..a22e4477d 100644 --- a/gossip-bin/src/commands.rs +++ b/gossip-bin/src/commands.rs @@ -509,12 +509,7 @@ pub fn delete_spam_by_content( // Build up a single deletion event let mut tags: Vec = Vec::new(); for id in target_ids { - tags.push(Tag::Event { - id, - recommended_relay_url: None, - marker: None, - trailing: Vec::new(), - }); + tags.push(Tag::new_event(id, None, None)); } let event = { let public_key = GLOBALS.identity.public_key().unwrap(); diff --git a/gossip-bin/src/ui/feed/note/content.rs b/gossip-bin/src/ui/feed/note/content.rs index c15acfa0a..2c531ee64 100644 --- a/gossip-bin/src/ui/feed/note/content.rs +++ b/gossip-bin/src/ui/feed/note/content.rs @@ -8,7 +8,7 @@ use egui::{Button, Color32, Pos2, RichText, Stroke, Ui}; use gossip_lib::comms::ToOverlordMessage; use gossip_lib::FeedKind; use gossip_lib::GLOBALS; -use nostr_types::{ContentSegment, EventAddr, Id, IdHex, NostrBech32, PublicKey, Span, Tag, Url}; +use nostr_types::{ContentSegment, EventAddr, Id, IdHex, NostrBech32, PublicKey, Span, Url}; use std::{ cell::{Ref, RefCell}, rc::Rc, @@ -132,54 +132,45 @@ pub(super) fn render_content( } ContentSegment::TagReference(num) => { if let Some(tag) = note.event.tags.get(*num) { - match tag { - Tag::Pubkey { pubkey, .. } => { - if let Ok(pubkey) = - PublicKey::try_from_hex_string(pubkey.as_str(), false) - { - render_profile_link(app, ui, &pubkey); - } - } - Tag::Event { id, .. } => { - let mut render_link = true; - if read_setting!(show_mentions) { - match note.repost { - Some(RepostType::MentionOnly) - | Some(RepostType::CommentMention) - | Some(RepostType::Kind6Mention) => { - for (i, cached_id) in note.mentions.iter() { - if *i == *num { - if let Some(note_data) = - app.notes.try_update_and_get(cached_id) - { - // TODO block additional repost recursion - super::render_repost( - app, - ui, - ctx, - ¬e.repost, - note_data, - content_margin_left, - bottom_of_avatar, - ); - render_link = false; - } + if let Ok((pubkey, _, _)) = tag.parse_pubkey() { + render_profile_link(app, ui, &pubkey); + } else if let Ok((id, _, _)) = tag.parse_event() { + let mut render_link = true; + if read_setting!(show_mentions) { + match note.repost { + Some(RepostType::MentionOnly) + | Some(RepostType::CommentMention) + | Some(RepostType::Kind6Mention) => { + for (i, cached_id) in note.mentions.iter() { + if *i == *num { + if let Some(note_data) = + app.notes.try_update_and_get(cached_id) + { + // TODO block additional repost recursion + super::render_repost( + app, + ui, + ctx, + ¬e.repost, + note_data, + content_margin_left, + bottom_of_avatar, + ); + render_link = false; } } } - _ => (), } + _ => (), } - if render_link { - render_event_link(app, ui, note.event.id, *id); - } - } - Tag::Hashtag { hashtag, .. } => { - render_hashtag(ui, hashtag); } - _ => { - render_unknown_reference(ui, *num); + if render_link { + render_event_link(app, ui, note.event.id, id); } + } else if let Ok(hashtag) = tag.parse_hashtag() { + render_hashtag(ui, &hashtag); + } else { + render_unknown_reference(ui, *num); } } } diff --git a/gossip-bin/src/ui/feed/note/mod.rs b/gossip-bin/src/ui/feed/note/mod.rs index c70de01d9..5bb814302 100644 --- a/gossip-bin/src/ui/feed/note/mod.rs +++ b/gossip-bin/src/ui/feed/note/mod.rs @@ -682,7 +682,7 @@ fn render_note_inner( ui.add(Label::new( RichText::new(format!("proxied from {}: ", proxy)).color(color), )); - crate::ui::widgets::break_anywhere_hyperlink_to(ui, id, id); + crate::ui::widgets::break_anywhere_hyperlink_to(ui, &id, &id); }); }); } @@ -1088,14 +1088,11 @@ fn render_content( && !app.approved.contains(&event.id) && read_setting!(approve_content_warning) { - ui.label( - RichText::new(format!( - "Content-Warning: {}", - event.content_warning().unwrap() - )) - .monospace() - .italics(), - ); + let text = match event.content_warning().unwrap() { + Some(cw) => format!("Content-Warning: {}", cw), + None => "Content-Warning".to_string(), + }; + ui.label(RichText::new(text).monospace().italics()); if ui.button("Show Post").clicked() { app.approved.insert(event.id); app.height.remove(&event.id); // will need to be recalculated. diff --git a/gossip-bin/src/ui/feed/notedata.rs b/gossip-bin/src/ui/feed/notedata.rs index f7c01872a..2a680652b 100644 --- a/gossip-bin/src/ui/feed/notedata.rs +++ b/gossip-bin/src/ui/feed/notedata.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use nostr_types::{ ContentSegment, Event, EventDelegation, EventKind, Id, MilliSatoshi, NostrBech32, PublicKey, - ShatteredContent, Tag, + ShatteredContent, }; #[derive(PartialEq)] @@ -113,8 +113,8 @@ impl NoteData { let mentions = { let mut mentions = Vec::<(usize, Id)>::new(); for (i, tag) in event.tags.iter().enumerate() { - if let Tag::Event { id, .. } = tag { - mentions.push((i, *id)); + if let Ok((id, _, _)) = tag.parse_event() { + mentions.push((i, id)); } } mentions @@ -166,11 +166,12 @@ impl NoteData { let mut dc = format!("UNSUPPORTED EVENT KIND {}", kind_number); // support the 'alt' tag of NIP-31: for tag in &event.tags { - if let Tag::Other { tag, data } = tag { - if tag == "alt" && !data.is_empty() { - dc = - format!("UNSUPPORTED EVENT KIND {}, ALT: {}", kind_number, data[0]); - } + if tag.tagname() == "alt" && tag.value() != "" { + dc = format!( + "UNSUPPORTED EVENT KIND {}, ALT: {}", + kind_number, + tag.value() + ); } } ("".to_owned(), Some(dc)) diff --git a/gossip-bin/src/ui/feed/post.rs b/gossip-bin/src/ui/feed/post.rs index 96b519767..ae7355d00 100644 --- a/gossip-bin/src/ui/feed/post.rs +++ b/gossip-bin/src/ui/feed/post.rs @@ -270,19 +270,13 @@ fn dm_posting_area( if send_now { let mut tags: Vec = Vec::new(); if app.dm_draft_data.include_content_warning { - tags.push(Tag::ContentWarning { - warning: app.dm_draft_data.content_warning.clone(), - trailing: Vec::new(), - }); + tags.push(Tag::new_content_warning(&app.dm_draft_data.content_warning)); } if let Some(delegatee_tag) = GLOBALS.delegation.get_delegatee_tag() { tags.push(delegatee_tag); } if app.dm_draft_data.include_subject { - tags.push(Tag::Subject { - subject: app.dm_draft_data.subject.clone(), - trailing: Vec::new(), - }); + tags.push(Tag::new_subject(app.dm_draft_data.subject.clone())); } let _ = GLOBALS.to_overlord.send(ToOverlordMessage::Post { @@ -609,19 +603,13 @@ fn real_posting_area(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Fram let mut tags: Vec = Vec::new(); if app.draft_data.include_content_warning { - tags.push(Tag::ContentWarning { - warning: app.draft_data.content_warning.clone(), - trailing: Vec::new(), - }); + tags.push(Tag::new_content_warning(&app.draft_data.content_warning)); } if let Some(delegatee_tag) = GLOBALS.delegation.get_delegatee_tag() { tags.push(delegatee_tag); } if app.draft_data.include_subject { - tags.push(Tag::Subject { - subject: app.draft_data.subject.clone(), - trailing: Vec::new(), - }); + tags.push(Tag::new_subject(app.draft_data.subject.clone())); } match app.draft_data.replying_to { Some(replying_to_id) => { diff --git a/gossip-lib/Cargo.toml b/gossip-lib/Cargo.toml index ed9dfccf4..65ca21482 100644 --- a/gossip-lib/Cargo.toml +++ b/gossip-lib/Cargo.toml @@ -47,7 +47,7 @@ fallible-iterator = "0.2" filetime = "0.2" futures = "0.3" futures-util = "0.3" -gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "b82e1782406e70073fb5e6cfa1c61f1992a530da" } +gossip-relay-picker = { git = "https://github.com/mikedilger/gossip-relay-picker", rev = "1d13cacb0d7b32d72d0c8c27414461d5389ca2ec" } heed = { git = "https://github.com/meilisearch/heed", rev = "8bfdf3beeda292fe166dc6b2f468cdb23af7181b" } hex = "0.4" http = "0.2" @@ -56,7 +56,7 @@ kamadak-exif = "0.5" lazy_static = "1.4" linkify = "0.9" mime = "0.3" -nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "d05ca006415152552f74a8d7b9132cfd481e6a4a", features = [ "speedy" ] } +nostr-types = { git = "https://github.com/mikedilger/nostr-types", rev = "ef0d427a09e06103950ec4b5eebbe2c243b08cfd", features = [ "speedy" ] } parking_lot = "0.12" paste = "1.0" rand = "0.8" diff --git a/gossip-lib/src/delegation.rs b/gossip-lib/src/delegation.rs index 2bd2b1a97..dc717df3d 100644 --- a/gossip-lib/src/delegation.rs +++ b/gossip-lib/src/delegation.rs @@ -23,9 +23,9 @@ impl Delegation { } pub fn get_delegator_pubkey(&self) -> Option { - if let Some(Tag::Delegation { pubkey, .. }) = self.get_delegatee_tag() { - if let Ok(pk) = PublicKey::try_from_hex_string(pubkey.as_str(), true) { - return Some(pk); + if let Some(tag) = self.get_delegatee_tag() { + if let Ok((pubkey, _, _)) = tag.parse_delegation() { + return Some(pubkey); } } None diff --git a/gossip-lib/src/dm_channel.rs b/gossip-lib/src/dm_channel.rs index 57e9d9807..d99aa92a8 100644 --- a/gossip-lib/src/dm_channel.rs +++ b/gossip-lib/src/dm_channel.rs @@ -65,11 +65,7 @@ impl DmChannel { }; if event.kind == EventKind::EncryptedDirectMessage { - let mut people: Vec = event - .people() - .iter() - .filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok()) - .collect(); + let mut people: Vec = event.people().iter().map(|(pk, _, _)| *pk).collect(); people.push(event.pubkey); people.retain(|p| *p != my_pubkey); if people.len() > 1 { @@ -80,11 +76,8 @@ impl DmChannel { } else if event.kind == EventKind::GiftWrap { if let Ok(rumor) = GLOBALS.identity.unwrap_giftwrap(event) { let rumor_event = rumor.into_event_with_bad_signature(); - let mut people: Vec = rumor_event - .people() - .iter() - .filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok()) - .collect(); + let mut people: Vec = + rumor_event.people().iter().map(|(pk, _, _)| *pk).collect(); people.push(rumor_event.pubkey); // include author too people.retain(|p| *p != my_pubkey); Some(Self::new(&people)) @@ -93,11 +86,7 @@ impl DmChannel { } } else if event.kind == EventKind::DmChat { // unwrapped rumor - let mut people: Vec = event - .people() - .iter() - .filter_map(|(pk, _, _)| PublicKey::try_from(pk).ok()) - .collect(); + let mut people: Vec = event.people().iter().map(|(pk, _, _)| *pk).collect(); people.push(event.pubkey); // include author too people.retain(|p| *p != my_pubkey); Some(Self::new(&people)) diff --git a/gossip-lib/src/feed.rs b/gossip-lib/src/feed.rs index 89a5dbd8e..4ceb8cb54 100644 --- a/gossip-lib/src/feed.rs +++ b/gossip-lib/src/feed.rs @@ -449,7 +449,7 @@ impl Feed { if indirect { // Include if it tags me - e.people().iter().any(|(p, _, _)| *p == my_pubkey.into()) + e.people().iter().any(|(p, _, _)| *p == my_pubkey) } else { // Include if it directly references me in the content e.people_referenced_in_content() diff --git a/gossip-lib/src/gossip_identity.rs b/gossip-lib/src/gossip_identity.rs index 61115b126..f5d2a96c8 100644 --- a/gossip-lib/src/gossip_identity.rs +++ b/gossip-lib/src/gossip_identity.rs @@ -2,8 +2,8 @@ use crate::error::{Error, ErrorKind}; use crate::globals::GLOBALS; use nostr_types::{ ContentEncryptionAlgorithm, DelegationConditions, EncryptedPrivateKey, Event, EventKind, - EventV1, Id, Identity, KeySecurity, Metadata, PreEvent, PrivateKey, PublicKey, PublicKeyHex, - Rumor, RumorV1, Signature, + EventV1, EventV2, Id, Identity, KeySecurity, Metadata, PreEvent, PrivateKey, PublicKey, Rumor, + RumorV1, RumorV2, Signature, }; use parking_lot::RwLock; use std::sync::mpsc::Sender; @@ -217,6 +217,11 @@ impl GossipIdentity { Ok(self.inner.read().unwrap_giftwrap1(event)?) } + /// @deprecated for migrations only + pub fn unwrap_giftwrap2(&self, event: &EventV2) -> Result { + Ok(self.inner.read().unwrap_giftwrap2(event)?) + } + pub fn decrypt_event_contents(&self, event: &Event) -> Result { Ok(self.inner.read().decrypt_event_contents(event)?) } @@ -252,7 +257,7 @@ impl GossipIdentity { pub fn create_zap_request_event( &self, - recipient_pubkey: PublicKeyHex, + recipient_pubkey: PublicKey, zapped_event: Option, millisatoshis: u64, relays: Vec, diff --git a/gossip-lib/src/nip46.rs b/gossip-lib/src/nip46.rs index b1011bb23..f45f66f6a 100644 --- a/gossip-lib/src/nip46.rs +++ b/gossip-lib/src/nip46.rs @@ -391,12 +391,7 @@ fn send_response( pubkey: public_key, created_at: Unixtime::now().unwrap(), kind: EventKind::NostrConnect, - tags: vec![Tag::Pubkey { - pubkey: peer_pubkey.into(), - recommended_relay_url: None, - petname: None, - trailing: vec![], - }], + tags: vec![Tag::new_pubkey(peer_pubkey, None, None)], content: e, }; diff --git a/gossip-lib/src/overlord/minion/handle_websocket.rs b/gossip-lib/src/overlord/minion/handle_websocket.rs index 36e4bf0d8..d7fad8c2d 100644 --- a/gossip-lib/src/overlord/minion/handle_websocket.rs +++ b/gossip-lib/src/overlord/minion/handle_websocket.rs @@ -184,7 +184,7 @@ impl Minion { } } RelayMessage::Auth(challenge) => { - let id = self.authenticate(challenge).await?; + let id = self.authenticate(&challenge).await?; self.waiting_for_auth = Some(id); } RelayMessage::Closed(subid, message) => { diff --git a/gossip-lib/src/overlord/minion/mod.rs b/gossip-lib/src/overlord/minion/mod.rs index 58a42e339..84eda5303 100644 --- a/gossip-lib/src/overlord/minion/mod.rs +++ b/gossip-lib/src/overlord/minion/mod.rs @@ -1341,7 +1341,7 @@ impl Minion { Ok(()) } - async fn authenticate(&mut self, challenge: String) -> Result { + async fn authenticate(&mut self, challenge: &str) -> Result { if !GLOBALS.identity.is_unlocked() { return Err(ErrorKind::NoPrivateKeyForAuth(self.url.clone()).into()); } @@ -1356,14 +1356,8 @@ impl Minion { created_at: Unixtime::now().unwrap(), kind: EventKind::Auth, tags: vec![ - Tag::Other { - tag: "relay".to_string(), - data: vec![self.url.as_str().to_owned()], - }, - Tag::Other { - tag: "challenge".to_string(), - data: vec![challenge], - }, + Tag::new(&["relay", self.url.as_str()]), + Tag::new(&["challenge", challenge]), ], content: "".to_string(), }; diff --git a/gossip-lib/src/overlord/mod.rs b/gossip-lib/src/overlord/mod.rs index c9dd0a2ef..05881af85 100644 --- a/gossip-lib/src/overlord/mod.rs +++ b/gossip-lib/src/overlord/mod.rs @@ -12,8 +12,7 @@ use crate::people::{Person, PersonList}; use crate::person_relay::PersonRelay; use crate::relay::Relay; use crate::tags::{ - add_addr_to_tags, add_event_to_tags, add_pubkey_hex_to_tags, add_pubkey_to_tags, - add_subject_to_tags_if_missing, + add_addr_to_tags, add_event_to_tags, add_pubkey_to_tags, add_subject_to_tags_if_missing, }; use gossip_relay_picker::{Direction, RelayAssignment}; use heed::RwTxn; @@ -774,10 +773,8 @@ impl Overlord { .filter_relays(|r| r.has_usage_bits(Relay::INBOX) || r.has_usage_bits(Relay::OUTBOX))?; let mut tags: Vec = Vec::new(); for relay in inbox_or_outbox_relays.iter() { - tags.push(Tag::Reference { - url: relay.url.to_unchecked_url(), - marker: if relay.has_usage_bits(Relay::INBOX) && relay.has_usage_bits(Relay::OUTBOX) - { + let marker = + if relay.has_usage_bits(Relay::INBOX) && relay.has_usage_bits(Relay::OUTBOX) { None } else if relay.has_usage_bits(Relay::INBOX) { Some("read".to_owned()) // NIP-65 uses the term 'read' instead of 'inbox' @@ -785,9 +782,9 @@ impl Overlord { Some("write".to_owned()) // NIP-65 uses the term 'write' instead of 'outbox' } else { unreachable!() - }, - trailing: Vec::new(), - }); + }; + + tags.push(Tag::new_relay(relay.url.to_unchecked_url(), marker)); } let pre_event = PreEvent { @@ -963,23 +960,17 @@ impl Overlord { // Generate a deletion event for those events let event = { // Include an "a" tag for the entire group - let mut tags: Vec = vec![Tag::Address { - kind: EventKind::FollowSets, - pubkey: public_key.into(), + let ea = EventAddr { d: metadata.dtag.clone(), - relay_url: None, - marker: None, - trailing: Vec::new(), - }]; + relays: vec![], + kind: EventKind::FollowSets, + author: public_key, + }; + let mut tags: Vec = vec![Tag::new_address(&ea, None)]; // Include "e" tags for each event for bad_event in &bad_events { - tags.push(Tag::Event { - id: bad_event.id, - recommended_relay_url: None, - marker: None, - trailing: Vec::new(), - }); + tags.push(Tag::new_event(bad_event.id, None, None)); } let pre_event = PreEvent { @@ -1049,12 +1040,7 @@ impl Overlord { /// Delete a post pub async fn delete_post(&mut self, id: Id) -> Result<(), Error> { - let tags: Vec = vec![Tag::Event { - id, - recommended_relay_url: None, - marker: None, - trailing: Vec::new(), - }]; + let tags: Vec = vec![Tag::new_event(id, None, None)]; let event = { let public_key = match GLOBALS.identity.public_key() { @@ -1372,27 +1358,18 @@ impl Overlord { }; let mut tags: Vec = vec![ - Tag::Event { + Tag::new_event( id, - recommended_relay_url: Relay::recommended_relay_for_reply(id) + Relay::recommended_relay_for_reply(id) .await? .map(|rr| rr.to_unchecked_url()), - marker: None, - trailing: Vec::new(), - }, - Tag::Pubkey { - pubkey: pubkey.into(), - recommended_relay_url: None, - petname: None, - trailing: Vec::new(), - }, + None, + ), + Tag::new_pubkey(pubkey, None, None), ]; if GLOBALS.storage.read_setting_set_client_tag() { - tags.push(Tag::Other { - tag: "client".to_owned(), - data: vec!["gossip".to_owned()], - }); + tags.push(Tag::new(&["client", "gossip"])); } let pre_event = PreEvent { @@ -1615,21 +1592,16 @@ impl Overlord { pubkey: public_key, created_at: Unixtime::now().unwrap(), kind: EventKind::EncryptedDirectMessage, - tags: vec![Tag::Pubkey { - pubkey: recipient.into(), - recommended_relay_url: None, // FIXME, - petname: None, - trailing: Vec::new(), - }], + tags: vec![Tag::new_pubkey( + recipient, None, // FIXME + None, + )], content: enc_content, } } _ => { if GLOBALS.storage.read_setting_set_client_tag() { - tags.push(Tag::Other { - tag: "client".to_owned(), - data: vec!["gossip".to_owned()], - }); + tags.push(Tag::new(&["client", "gossip"])); } // Add Tags based on references in the content @@ -1642,15 +1614,7 @@ impl Overlord { for bech32 in NostrBech32::find_all_in_string(&content).iter() { match bech32 { NostrBech32::EventAddr(ea) => { - add_addr_to_tags( - &mut tags, - ea.kind, - ea.author.into(), - ea.d.clone(), - ea.relays.get(0).cloned(), - Some("mention".to_string()), - ) - .await; + add_addr_to_tags(&mut tags, ea, Some("mention".to_string())).await; } NostrBech32::EventPointer(ep) => { // NIP-10: "Those marked with "mention" denote a quoted or reposted event id." @@ -1662,12 +1626,12 @@ impl Overlord { } NostrBech32::Profile(prof) => { if dm_channel.is_none() { - add_pubkey_to_tags(&mut tags, &prof.pubkey).await; + add_pubkey_to_tags(&mut tags, prof.pubkey).await; } } NostrBech32::Pubkey(pk) => { if dm_channel.is_none() { - add_pubkey_to_tags(&mut tags, pk).await; + add_pubkey_to_tags(&mut tags, *pk).await; } } NostrBech32::Relay(_) => { @@ -1683,10 +1647,7 @@ impl Overlord { // Find and tag all hashtags for capture in GLOBALS.hashtag_regex.captures_iter(&content) { - tags.push(Tag::Hashtag { - hashtag: capture[1][1..].to_string(), - trailing: Vec::new(), - }); + tags.push(Tag::new_hashtag(capture[1][1..].to_string())); } if let Some(parent_id) = reply_to { @@ -1699,7 +1660,7 @@ impl Overlord { // Add a 'p' tag for the author we are replying to (except if it is our own key) if parent.pubkey != public_key { if dm_channel.is_none() { - add_pubkey_to_tags(&mut tags, &parent.pubkey).await; + add_pubkey_to_tags(&mut tags, parent.pubkey).await; } } @@ -1707,9 +1668,9 @@ impl Overlord { // FIXME: Should we avoid taging people who are muted? if dm_channel.is_none() { for tag in &parent.tags { - if let Tag::Pubkey { pubkey, .. } = tag { - if pubkey.as_str() != public_key.as_hex_string() { - add_pubkey_hex_to_tags(&mut tags, pubkey).await; + if let Ok((pubkey, _, _)) = tag.parse_pubkey() { + if pubkey != public_key { + add_pubkey_to_tags(&mut tags, pubkey).await; } } } @@ -1731,15 +1692,7 @@ impl Overlord { } Some(EventReference::Addr(ea)) => { // Add an 'a' tag for the root - add_addr_to_tags( - &mut tags, - ea.kind, - ea.author.into(), - ea.d, - ea.relays.first().cloned(), - Some("root".to_string()), - ) - .await; + add_addr_to_tags(&mut tags, &ea, Some("root".to_string())).await; parent_is_root = false; } None => { @@ -1763,10 +1716,12 @@ impl Overlord { let d = parent.parameter().unwrap_or("".to_owned()); add_addr_to_tags( &mut tags, - parent.kind, - parent.pubkey.into(), - d, - None, + &EventAddr { + d, + relays: vec![], + kind: parent.kind, + author: parent.pubkey, + }, Some(reply_marker.to_string()), ) .await; @@ -1774,7 +1729,7 @@ impl Overlord { // Possibly propagate a subject tag for tag in &parent.tags { - if let Tag::Subject { subject, .. } = tag { + if let Ok(subject) = tag.parse_subject() { let mut subject = subject.to_owned(); if !subject.starts_with("Re: ") { subject = format!("Re: {}", subject); @@ -1800,11 +1755,8 @@ impl Overlord { .tags .iter() .filter_map(|t| { - if let Tag::Pubkey { pubkey, .. } = t { - match PublicKey::try_from_hex_string(pubkey, true) { - Ok(pk) => Some(pk), - _ => None, - } + if let Ok((pubkey, _, _)) = t.parse_pubkey() { + Some(pubkey) } else { None } @@ -2110,39 +2062,28 @@ impl Overlord { let kind: EventKind; let mut tags: Vec = vec![ - Tag::Pubkey { - pubkey: reposted_event.pubkey.into(), - recommended_relay_url: None, - petname: None, - trailing: Vec::new(), - }, - Tag::Event { - id, - recommended_relay_url: relay_url.clone(), - marker: None, - trailing: Vec::new(), - }, + Tag::new_pubkey(reposted_event.pubkey, None, None), + Tag::new_event(id, relay_url.clone(), None), ]; if reposted_event.kind != EventKind::TextNote { kind = EventKind::GenericRepost; // Add 'k' tag - tags.push(Tag::Kind { - kind: reposted_event.kind, - trailing: Vec::new(), - }); + tags.push(Tag::new_kind(reposted_event.kind)); if reposted_event.kind.is_replaceable() { - // Add 'a' tag - tags.push(Tag::Address { - kind: reposted_event.kind, - pubkey: reposted_event.pubkey.into(), + let ea = EventAddr { d: reposted_event.parameter().unwrap_or("".to_string()), - relay_url: relay_url.clone(), - marker: None, - trailing: vec![], - }); + relays: match relay_url { + Some(url) => vec![url.clone()], + None => vec![], + }, + kind: reposted_event.kind, + author: reposted_event.pubkey, + }; + // Add 'a' tag + tags.push(Tag::new_address(&ea, None)); } } else { kind = EventKind::Repost; @@ -2158,10 +2099,7 @@ impl Overlord { }; if GLOBALS.storage.read_setting_set_client_tag() { - tags.push(Tag::Other { - tag: "client".to_owned(), - data: vec!["gossip".to_owned()], - }); + tags.push(Tag::new(&["client", "gossip"])); } let pre_event = PreEvent { @@ -2255,8 +2193,8 @@ impl Overlord { None, |event| { event.tags.iter().any(|tag| { - if let Tag::Identifier { d, .. } = tag { - if *d == ea.d { + if let Ok(d) = tag.parse_identifier() { + if d == ea.d { return true; } } @@ -2485,19 +2423,17 @@ impl Overlord { // Include write relays of all the p-tagged people // One of them must have created the ancestor. // Unfortunately, oftentimes we won't have relays for strangers. - for (pkh, opthint, _optmarker) in highest_parent.people() { + for (pk, opthint, _optmarker) in highest_parent.people() { if let Some(url) = opthint { relays.push(url); } else { - if let Ok(pk) = PublicKey::try_from(pkh) { - let tagged_person_relays: Vec = GLOBALS - .storage - .get_best_relays(pk, Direction::Write)? - .drain(..) - .map(|pair| pair.0) - .collect(); - relays.extend(tagged_person_relays); - } + let tagged_person_relays: Vec = GLOBALS + .storage + .get_best_relays(pk, Direction::Write)? + .drain(..) + .map(|pair| pair.0) + .collect(); + relays.extend(tagged_person_relays); } } @@ -2849,37 +2785,23 @@ impl Overlord { // Public entries for tag in &event.tags { - match tag { - Tag::Pubkey { - pubkey, - recommended_relay_url, - petname, - .. - } => { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - // If our list is marked private, move these public entries to private ones - let public = !metadata.private; - - // Save the pubkey - entries.push((pubkey.to_owned(), public)); - - // Deal with recommended_relay_urls and petnames - if list == PersonList::Followed { - Self::integrate_rru_and_petname( - &pubkey, - recommended_relay_url, - petname, - now, - merge, - &mut txn, - )?; - } - } - } - Tag::Title { title, .. } => { - metadata.title = title.to_owned(); + if let Ok((pubkey, rurl, petname)) = tag.parse_pubkey() { + // If our list is marked private, move these public entries to private ones + let public = !metadata.private; + + // Save the pubkey + entries.push((pubkey.to_owned(), public)); + + // Deal with recommended_relay_urls and petnames + if list == PersonList::Followed { + Self::integrate_rru_and_petname( + &pubkey, &rurl, &petname, now, merge, &mut txn, + )?; } - _ => (), + } + + if let Ok(title) = tag.parse_title() { + metadata.title = title.to_owned(); } } @@ -2892,17 +2814,12 @@ impl Overlord { let tags: Vec = serde_json::from_slice(&decrypted_content)?; for tag in &tags { - match tag { - Tag::Pubkey { pubkey, .. } => { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - // Save the pubkey - entries.push((pubkey.to_owned(), false)); - } - } - Tag::Title { title, .. } => { - metadata.title = title.to_owned(); - } - _ => (), + if let Ok((pubkey, _, _)) = tag.parse_pubkey() { + // Save the pubkey + entries.push((pubkey.to_owned(), false)); + } + if let Ok(title) = tag.parse_title() { + metadata.title = title.to_owned(); } } } else { @@ -3344,36 +3261,20 @@ impl Overlord { relays }; + let mut relays_tag = Tag::new(&["relays"]); + relays_tag.push_values(relays); + // Generate the zap request event let pre_event = PreEvent { pubkey: user_pubkey, created_at: Unixtime::now().unwrap(), kind: EventKind::ZapRequest, tags: vec![ - Tag::Event { - id, - recommended_relay_url: None, - marker: None, - trailing: Vec::new(), - }, - Tag::Pubkey { - pubkey: target_pubkey.into(), - recommended_relay_url: None, - petname: None, - trailing: Vec::new(), - }, - Tag::Other { - tag: "relays".to_owned(), - data: relays, - }, - Tag::Other { - tag: "amount".to_owned(), - data: vec![msats_string.clone()], - }, - Tag::Other { - tag: "lnurl".to_owned(), - data: vec![lnurl.as_str().to_owned()], - }, + Tag::new_event(id, None, None), + Tag::new_pubkey(target_pubkey, None, None), + relays_tag, + Tag::new(&["amount", &msats_string]), + Tag::new(&["lnurl", lnurl.as_str()]), ], content: comment, }; diff --git a/gossip-lib/src/people.rs b/gossip-lib/src/people.rs index 7b12158b1..c3934a244 100644 --- a/gossip-lib/src/people.rs +++ b/gossip-lib/src/people.rs @@ -595,16 +595,11 @@ impl People { // If FollowSets if matches!(person_list, PersonList::Custom(_)) { // Add d-tag - public_tags.push(Tag::Identifier { - d: metadata.dtag.clone(), - trailing: vec![], - }); + public_tags.push(Tag::new_identifier(metadata.dtag.clone())); // Add title if using FollowSets - let title = Tag::Title { - title: metadata.title.clone(), - trailing: vec![], - }; + let title = Tag::new_title(metadata.title.clone()); + if metadata.private { private_tags.push(title); } else { @@ -613,13 +608,11 @@ impl People { // Preserve existing tags that we don't operate on yet for t in &old_tags { - if let Tag::Other { tag, .. } = t { - if tag == "image" || tag == "description" { - if metadata.private { - private_tags.push(t.clone()); - } else { - public_tags.push(t.clone()); - } + if t.tagname() == "image" || t.tagname() == "description" { + if metadata.private { + private_tags.push(t.clone()); + } else { + public_tags.push(t.clone()); } } } @@ -629,21 +622,19 @@ impl People { if person_list == PersonList::Muted { // Preserve existing tags that we don't operate on yet for t in &old_tags { - match t { - Tag::Hashtag { .. } | Tag::Event { .. } => { + match t.tagname() { + "t" | "e" => { if metadata.private { private_tags.push(t.clone()); } else { public_tags.push(t.clone()); } } - Tag::Other { tag, .. } => { - if tag == "word" { - if metadata.private { - private_tags.push(t.clone()); - } else { - public_tags.push(t.clone()); - } + "word" => { + if metadata.private { + private_tags.push(t.clone()); + } else { + public_tags.push(t.clone()); } } _ => (), @@ -679,12 +670,7 @@ impl People { } }; - let tag = Tag::Pubkey { - pubkey: pubkey.into(), - recommended_relay_url, - petname, - trailing: vec![], - }; + let tag = Tag::new_pubkey(*pubkey, recommended_relay_url, petname); if public { public_tags.push(tag); } else { diff --git a/gossip-lib/src/process.rs b/gossip-lib/src/process.rs index 2c4a85d68..fffa2d7e0 100644 --- a/gossip-lib/src/process.rs +++ b/gossip-lib/src/process.rs @@ -206,38 +206,29 @@ pub async fn process_new_event( if seen_on.is_some() { for tag in event.tags.iter() { - match tag { - Tag::Event { - recommended_relay_url: Some(should_be_url), - .. - } => { - if let Ok(url) = RelayUrl::try_from_unchecked_url(should_be_url) { - GLOBALS.storage.write_relay_if_missing(&url, None)?; - } + if let Ok((_, Some(uurl), _optmarker)) = tag.parse_event() { + if let Ok(url) = RelayUrl::try_from_unchecked_url(&uurl) { + GLOBALS.storage.write_relay_if_missing(&url, None)?; } - Tag::Pubkey { - pubkey, - recommended_relay_url: Some(should_be_url), - .. - } => { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - if let Ok(url) = RelayUrl::try_from_unchecked_url(should_be_url) { - GLOBALS.storage.write_relay_if_missing(&url, None)?; - - // Add person if missing - GLOBALS.people.create_all_if_missing(&[pubkey])?; - - // upsert person_relay.last_suggested_bytag - let mut pr = match GLOBALS.storage.read_person_relay(pubkey, &url)? { - Some(pr) => pr, - None => PersonRelay::new(pubkey, url.clone()), - }; - pr.last_suggested_bytag = Some(now.0 as u64); - GLOBALS.storage.write_person_relay(&pr, None)?; - } + } + + if let Ok((pubkey, maybeurl, _)) = tag.parse_pubkey() { + // Add person if missing + GLOBALS.people.create_all_if_missing(&[pubkey])?; + + if let Some(uncheckedurl) = maybeurl { + if let Ok(url) = RelayUrl::try_from_unchecked_url(&uncheckedurl) { + GLOBALS.storage.write_relay_if_missing(&url, None)?; + + // upsert person_relay.last_suggested_bytag + let mut pr = match GLOBALS.storage.read_person_relay(pubkey, &url)? { + Some(pr) => pr, + None => PersonRelay::new(pubkey, url.clone()), + }; + pr.last_suggested_bytag = Some(now.0 as u64); + GLOBALS.storage.write_person_relay(&pr, None)?; } } - _ => {} } } } @@ -479,9 +470,9 @@ pub(crate) fn process_relationships_of_event<'a>( // timestamps if event.kind == EventKind::Timestamp { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::Timestamp, Some(txn), @@ -578,20 +569,20 @@ pub(crate) fn process_relationships_of_event<'a>( let mut label = ""; let mut namespace = ""; for t in &event.tags { - if let Tag::Other { tag, data } = t { - if tag == "l" && !data.is_empty() { - label = &data[0]; - if data.len() >= 2 { - namespace = &data[1]; + if t.tagname() == "l" { + if t.value() != "" { + label = t.value(); + if t.get_index(2) != "" { + namespace = t.get_index(2); } } } } for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::Labels { label: label.to_owned(), @@ -606,9 +597,9 @@ pub(crate) fn process_relationships_of_event<'a>( // ListMutesThread if event.kind == EventKind::MuteList { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::ListMutesThread, Some(txn), @@ -620,9 +611,9 @@ pub(crate) fn process_relationships_of_event<'a>( // ListPins if event.kind == EventKind::PinList { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::ListPins, Some(txn), @@ -634,32 +625,22 @@ pub(crate) fn process_relationships_of_event<'a>( // ListBookmarks if event.kind == EventKind::BookmarkList { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::ListBookmarks, Some(txn), )?; } - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::ListBookmarks, - Some(txn), - )?; - } + + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::ListBookmarks, + Some(txn), + )?; } } } @@ -667,32 +648,22 @@ pub(crate) fn process_relationships_of_event<'a>( // BookmarkSets if event.kind == EventKind::BookmarkSets { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::ListBookmarks, Some(txn), )?; } - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::ListBookmarks, - Some(txn), - )?; - } + + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::ListBookmarks, + Some(txn), + )?; } } } @@ -700,119 +671,70 @@ pub(crate) fn process_relationships_of_event<'a>( // CurationSets if event.kind == EventKind::CurationSets { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::Curation, Some(txn), )?; } - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::Curation, - Some(txn), - )?; - } + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::Curation, + Some(txn), + )?; } } } if event.kind == EventKind::LiveChatMessage { for tag in &event.tags { - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::LiveChatMessage, - Some(txn), - )?; - } + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::LiveChatMessage, + Some(txn), + )?; } } } if event.kind == EventKind::BadgeAward { for tag in &event.tags { - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::BadgeAward, - Some(txn), - )?; - } + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::BadgeAward, + Some(txn), + )?; } } } if event.kind == EventKind::HandlerRecommendation { for tag in &event.tags { - if let Tag::Address { - kind, pubkey, d, .. - } = tag - { - if let Ok(pubkey) = PublicKey::try_from_hex_string(pubkey, true) { - let event_addr = EventAddr { - d: d.to_owned(), - relays: vec![], - kind: *kind, - author: pubkey, - }; - GLOBALS.storage.write_relationship_by_addr( - event_addr, - event.id, - RelationshipByAddr::HandlerRecommendation, - Some(txn), - )?; - } + if let Ok((ea, _marker)) = tag.parse_address() { + GLOBALS.storage.write_relationship_by_addr( + ea, + event.id, + RelationshipByAddr::HandlerRecommendation, + Some(txn), + )?; } } } if event.kind == EventKind::Reporting { for tag in &event.tags { - if let Tag::Event { - id, - recommended_relay_url: Some(rru), - .. - } = tag - { - let report = &rru.0; + if let Ok((id, Some(rurl), _)) = tag.parse_event() { + let report = &rurl.0; GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::Reports(report.to_owned()), Some(txn), @@ -843,9 +765,9 @@ pub(crate) fn process_relationships_of_event<'a>( // JobResult if event.kind.is_job_result() { for tag in &event.tags { - if let Tag::Event { id, .. } = tag { + if let Ok((id, _, _)) = tag.parse_event() { GLOBALS.storage.write_relationship_by_id( - *id, + id, event.id, RelationshipById::JobResult, Some(txn), @@ -882,11 +804,7 @@ fn update_or_allocate_person_list_from_event( { metadata.event_created_at = event.created_at; - metadata.event_public_len = event - .tags - .iter() - .filter(|t| matches!(t, Tag::Pubkey { .. })) - .count(); + metadata.event_public_len = event.tags.iter().filter(|t| t.tagname() == "p").count(); if event.kind == EventKind::ContactList { metadata.event_private_len = None; @@ -894,12 +812,7 @@ fn update_or_allocate_person_list_from_event( let mut private_len: Option = None; if let Ok(bytes) = GLOBALS.identity.decrypt_nip04(&pubkey, &event.content) { if let Ok(vectags) = serde_json::from_slice::>(&bytes) { - private_len = Some( - vectags - .iter() - .filter(|t| matches!(t, Tag::Pubkey { .. })) - .count(), - ); + private_len = Some(vectags.iter().filter(|t| t.tagname() == "p").count()); } } metadata.event_private_len = private_len; diff --git a/gossip-lib/src/storage/event_tag_index1.rs b/gossip-lib/src/storage/event_tag_index1.rs index c8ac7413d..9d1d731b6 100644 --- a/gossip-lib/src/storage/event_tag_index1.rs +++ b/gossip-lib/src/storage/event_tag_index1.rs @@ -1,7 +1,7 @@ use crate::error::Error; use crate::storage::{RawDatabase, Storage}; use heed::{types::UnalignedSlice, DatabaseFlags, RwTxn}; -use nostr_types::{Event, PublicKeyHex}; +use nostr_types::{EventV2, EventV3, PublicKeyHex}; use std::sync::Mutex; // NOTE: "innerp" is a fake tag. We store events that reference a person internally under it. @@ -45,17 +45,17 @@ impl Storage { } } - pub fn write_event_tag_index1<'a>( + pub fn write_event2_tag_index1<'a>( &'a self, - event: &Event, + event: &EventV2, rw_txn: Option<&mut RwTxn<'a>>, ) -> Result<(), Error> { let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { let mut event = event; // If giftwrap, index the inner rumor instead - let rumor_event: Event; - if let Some(rumor) = self.switch_to_rumor(event, txn)? { + let rumor_event: EventV2; + if let Some(rumor) = self.switch_to_rumor2(event, txn)? { rumor_event = rumor; event = &rumor_event; } @@ -109,4 +109,69 @@ impl Storage { Ok(()) } + + pub fn write_event3_tag_index1<'a>( + &'a self, + event: &EventV3, + rw_txn: Option<&mut RwTxn<'a>>, + ) -> Result<(), Error> { + let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { + let mut event = event; + + // If giftwrap, index the inner rumor instead + let rumor_event: EventV3; + if let Some(rumor) = self.switch_to_rumor3(event, txn)? { + rumor_event = rumor; + event = &rumor_event; + } + + // our user's public key + let pk: Option = self.read_setting_public_key().map(|p| p.into()); + + for tag in &event.tags { + let tagname = tag.tagname(); + let value = tag.value(); + if value.is_empty() { + continue; // no tag value, not indexable. + } + + // Only index tags we intend to lookup later by tag. + // If that set changes, (1) add to this code and (2) do a reindex migration + if !INDEXED_TAGS.contains(&tagname) { + continue; + } + // For 'p' tags, only index them if 'p' is our user + if tagname == "p" { + match &pk { + None => continue, + Some(pk) => { + if value != pk.as_str() { + continue; + } + } + } + } + + let mut key: Vec = tagname.as_bytes().to_owned(); + key.push(b'\"'); // double quote separator, unlikely to be inside of a tagname + key.extend(value.as_bytes()); + let key = key!(&key); // limit the size + let bytes = event.id.as_slice(); + self.db_event_tag_index()?.put(txn, key, bytes)?; + } + + Ok(()) + }; + + match rw_txn { + Some(txn) => f(txn)?, + None => { + let mut txn = self.env.write_txn()?; + f(&mut txn)?; + txn.commit()?; + } + }; + + Ok(()) + } } diff --git a/gossip-lib/src/storage/events2.rs b/gossip-lib/src/storage/events2.rs index 8d6bb6f65..4a23c09ca 100644 --- a/gossip-lib/src/storage/events2.rs +++ b/gossip-lib/src/storage/events2.rs @@ -2,8 +2,8 @@ use crate::error::Error; use crate::storage::{RawDatabase, Storage}; use heed::types::UnalignedSlice; use heed::RwTxn; -use nostr_types::{EventV2, Id}; -use speedy::{Readable, Writable}; +use nostr_types::EventV2; +use speedy::Writable; use std::sync::Mutex; // Id -> Event @@ -58,7 +58,7 @@ impl Storage { // If giftwrap, index the inner rumor instead let mut eventptr: &EventV2 = event; let rumor: EventV2; - if let Some(r) = self.switch_to_rumor(event, txn)? { + if let Some(r) = self.switch_to_rumor2(event, txn)? { rumor = r; eventptr = &rumor; } @@ -70,7 +70,7 @@ impl Storage { eventptr.created_at, Some(txn), )?; - self.write_event_tag_index(eventptr, Some(txn))?; + self.write_event2_tag_index1(eventptr, Some(txn))?; for hashtag in event.hashtags() { if hashtag.is_empty() { @@ -92,42 +92,4 @@ impl Storage { Ok(()) } - - pub(crate) fn read_event2(&self, id: Id) -> Result, Error> { - let txn = self.env.read_txn()?; - match self.db_events2()?.get(&txn, id.as_slice())? { - None => Ok(None), - Some(bytes) => Ok(Some(EventV2::read_from_buffer(bytes)?)), - } - } - - pub(crate) fn has_event2(&self, id: Id) -> Result { - let txn = self.env.read_txn()?; - match self.db_events2()?.get(&txn, id.as_slice())? { - None => Ok(false), - Some(_) => Ok(true), - } - } - - pub(crate) fn delete_event2<'a>( - &'a self, - id: Id, - rw_txn: Option<&mut RwTxn<'a>>, - ) -> Result<(), Error> { - let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { - let _ = self.db_events2()?.delete(txn, id.as_slice()); - Ok(()) - }; - - match rw_txn { - Some(txn) => f(txn)?, - None => { - let mut txn = self.env.write_txn()?; - f(&mut txn)?; - txn.commit()?; - } - }; - - Ok(()) - } } diff --git a/gossip-lib/src/storage/events3.rs b/gossip-lib/src/storage/events3.rs new file mode 100644 index 000000000..f21b1537e --- /dev/null +++ b/gossip-lib/src/storage/events3.rs @@ -0,0 +1,133 @@ +use crate::error::Error; +use crate::storage::{RawDatabase, Storage}; +use heed::types::UnalignedSlice; +use heed::RwTxn; +use nostr_types::{EventV3, Id}; +use speedy::{Readable, Writable}; +use std::sync::Mutex; + +// Id -> Event +// key: id.as_slice() | Id(val[0..32].try_into()?) +// val: event.write_to_vec() | Event::read_from_buffer(val) + +static EVENTS3_DB_CREATE_LOCK: Mutex<()> = Mutex::new(()); +static mut EVENTS3_DB: Option = None; + +impl Storage { + pub(super) fn db_events3(&self) -> Result { + unsafe { + if let Some(db) = EVENTS3_DB { + Ok(db) + } else { + // Lock. This drops when anything returns. + let _lock = EVENTS3_DB_CREATE_LOCK.lock(); + + // In case of a race, check again + if let Some(db) = EVENTS3_DB { + return Ok(db); + } + + // Create it. We know that nobody else is doing this and that + // it cannot happen twice. + let mut txn = self.env.write_txn()?; + let db = self + .env + .database_options() + .types::, UnalignedSlice>() + // no .flags needed + .name("events3") + .create(&mut txn)?; + txn.commit()?; + EVENTS3_DB = Some(db); + Ok(db) + } + } + } + + pub(crate) fn write_event3<'a>( + &'a self, + event: &EventV3, + rw_txn: Option<&mut RwTxn<'a>>, + ) -> Result<(), Error> { + // write to lmdb 'events' + let bytes = event.write_to_vec()?; + + let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { + self.db_events3()?.put(txn, event.id.as_slice(), &bytes)?; + + // If giftwrap, index the inner rumor instead + let mut eventptr: &EventV3 = event; + let rumor: EventV3; + if let Some(r) = self.switch_to_rumor3(event, txn)? { + rumor = r; + eventptr = &rumor; + } + // also index the event + self.write_event_ek_pk_index(eventptr.id, eventptr.kind, eventptr.pubkey, Some(txn))?; + self.write_event_ek_c_index( + eventptr.id, + eventptr.kind, + eventptr.created_at, + Some(txn), + )?; + self.write_event3_tag_index1(eventptr, Some(txn))?; + + for hashtag in event.hashtags() { + if hashtag.is_empty() { + continue; + } // upstream bug + self.add_hashtag(&hashtag, event.id, Some(txn))?; + } + Ok(()) + }; + + match rw_txn { + Some(txn) => f(txn)?, + None => { + let mut txn = self.env.write_txn()?; + f(&mut txn)?; + txn.commit()?; + } + }; + + Ok(()) + } + + pub(crate) fn read_event3(&self, id: Id) -> Result, Error> { + let txn = self.env.read_txn()?; + match self.db_events3()?.get(&txn, id.as_slice())? { + None => Ok(None), + Some(bytes) => Ok(Some(EventV3::read_from_buffer(bytes)?)), + } + } + + pub(crate) fn has_event3(&self, id: Id) -> Result { + let txn = self.env.read_txn()?; + match self.db_events3()?.get(&txn, id.as_slice())? { + None => Ok(false), + Some(_) => Ok(true), + } + } + + pub(crate) fn delete_event3<'a>( + &'a self, + id: Id, + rw_txn: Option<&mut RwTxn<'a>>, + ) -> Result<(), Error> { + let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { + let _ = self.db_events3()?.delete(txn, id.as_slice()); + Ok(()) + }; + + match rw_txn { + Some(txn) => f(txn)?, + None => { + let mut txn = self.env.write_txn()?; + f(&mut txn)?; + txn.commit()?; + } + }; + + Ok(()) + } +} diff --git a/gossip-lib/src/storage/migrations/m20.rs b/gossip-lib/src/storage/migrations/m20.rs index 22a1b2a75..1cbbe17c9 100644 --- a/gossip-lib/src/storage/migrations/m20.rs +++ b/gossip-lib/src/storage/migrations/m20.rs @@ -1,13 +1,15 @@ -use crate::error::Error; +use crate::error::{Error, ErrorKind}; use crate::globals::GLOBALS; use crate::storage::types::PersonList1; use crate::storage::Storage; use heed::RwTxn; -use nostr_types::Tag; +use nostr_types::{EventKind, EventV2, PublicKey, TagV2, Unixtime}; +use speedy::Readable; impl Storage { pub(super) fn m20_trigger(&self) -> Result<(), Error> { let _ = self.db_person_lists_metadata1()?; + let _ = self.db_events2()?; Ok(()) } @@ -25,6 +27,69 @@ impl Storage { Ok(()) } + /// Get the matching replaceable event (possibly parameterized) + /// TBD: optimize this by storing better event indexes + pub fn m20_get_replaceable_event2( + &self, + kind: EventKind, + pubkey: PublicKey, + parameter: &str, + ) -> Result, Error> { + if !kind.is_replaceable() { + return Err(ErrorKind::General("Event kind is not replaceable".to_owned()).into()); + } + + Ok(self + .m20_find_events2( + &[kind], + &[pubkey], + None, // any time + |e| { + if kind.is_parameterized_replaceable() { + e.parameter().as_deref() == Some(parameter) + } else { + true + } + }, + true, // sorted in reverse time order + )? + .first() + .cloned()) + } + + pub fn m20_find_events2( + &self, + kinds: &[EventKind], + pubkeys: &[PublicKey], + since: Option, + f: F, + sort: bool, + ) -> Result, Error> + where + F: Fn(&EventV2) -> bool, + { + let ids = self.find_event_ids(kinds, pubkeys, since)?; + + // Now that we have that Ids, fetch the events + let txn = self.env.read_txn()?; + let mut events: Vec = Vec::new(); + for id in ids { + // this is like self.read_event(), but we supply our existing transaction + if let Some(bytes) = self.db_events2()?.get(&txn, id.as_slice())? { + let event = EventV2::read_from_buffer(bytes)?; + if f(&event) { + events.push(event); + } + } + } + + if sort { + events.sort_by(|a, b| b.created_at.cmp(&a.created_at).then(b.id.cmp(&a.id))); + } + + Ok(events) + } + fn m20_initialize_person_list_event_metadata<'a>( &'a self, txn: &mut RwTxn<'a>, @@ -37,19 +102,19 @@ impl Storage { for (list, mut metadata) in self.get_all_person_list_metadata1()? { if let Ok(Some(event)) = - self.get_replaceable_event(list.event_kind(), pk, &metadata.dtag) + self.m20_get_replaceable_event2(list.event_kind(), pk, &metadata.dtag) { metadata.event_created_at = event.created_at; metadata.event_public_len = event .tags .iter() - .filter(|t| matches!(t, Tag::Pubkey { .. })) + .filter(|t| matches!(t, TagV2::Pubkey { .. })) .count(); metadata.event_private_len = { let mut private_len: Option = None; if !matches!(list, PersonList1::Followed) && GLOBALS.identity.is_unlocked() { if let Ok(bytes) = GLOBALS.identity.decrypt_nip04(&pk, &event.content) { - if let Ok(vectags) = serde_json::from_slice::>(&bytes) { + if let Ok(vectags) = serde_json::from_slice::>(&bytes) { private_len = Some(vectags.len()); } } diff --git a/gossip-lib/src/storage/migrations/m25.rs b/gossip-lib/src/storage/migrations/m25.rs new file mode 100644 index 000000000..a9f0db99b --- /dev/null +++ b/gossip-lib/src/storage/migrations/m25.rs @@ -0,0 +1,56 @@ +use crate::error::Error; +use crate::storage::Storage; +use heed::RwTxn; +use nostr_types::{EventV2, EventV3, TagV3}; +use speedy::Readable; + +impl Storage { + pub(super) fn m25_trigger(&self) -> Result<(), Error> { + let _ = self.db_events2()?; + let _ = self.db_events3()?; + Ok(()) + } + + pub(super) fn m25_migrate<'a>( + &'a self, + prefix: &str, + txn: &mut RwTxn<'a>, + ) -> Result<(), Error> { + // Info message + tracing::info!("{prefix}: migrating events..."); + + // Migrate + self.m25_migrate_to_events3(txn)?; + + Ok(()) + } + + fn m25_migrate_to_events3<'a>(&'a self, txn: &mut RwTxn<'a>) -> Result<(), Error> { + let loop_txn = self.env.read_txn()?; + let mut count: usize = 0; + for result in self.db_events2()?.iter(&loop_txn)? { + let (_key, val) = result?; + let event2 = EventV2::read_from_buffer(val)?; + let tags_json: String = serde_json::to_string(&event2.tags)?; + let tags3: Vec = serde_json::from_str(&tags_json)?; + let event3 = EventV3 { + id: event2.id, + pubkey: event2.pubkey, + created_at: event2.created_at, + kind: event2.kind, + sig: event2.sig, + content: event2.content, + tags: tags3, + }; + self.write_event3(&event3, Some(txn))?; + count += 1; + } + + tracing::info!("Migrated {} events", count); + + // clear events2 database (we don't have an interface to delete it) + self.db_events2()?.clear(txn)?; + + Ok(()) + } +} diff --git a/gossip-lib/src/storage/migrations/mod.rs b/gossip-lib/src/storage/migrations/mod.rs index b32819beb..6747b6f02 100644 --- a/gossip-lib/src/storage/migrations/mod.rs +++ b/gossip-lib/src/storage/migrations/mod.rs @@ -17,6 +17,7 @@ mod m21; mod m22; mod m23; mod m24; +mod m25; mod m3; mod m4; mod m5; @@ -30,7 +31,7 @@ use crate::error::{Error, ErrorKind}; use heed::RwTxn; impl Storage { - const MAX_MIGRATION_LEVEL: u32 = 24; + const MAX_MIGRATION_LEVEL: u32 = 25; /// Initialize the database from empty pub(super) fn init_from_empty(&self) -> Result<(), Error> { @@ -109,6 +110,7 @@ impl Storage { 22 => self.m22_trigger()?, 23 => self.m23_trigger()?, 24 => self.m24_trigger()?, + 25 => self.m25_trigger()?, _ => panic!("Unreachable migration level"), } @@ -142,6 +144,7 @@ impl Storage { 22 => self.m22_migrate(&prefix, txn)?, 23 => self.m23_migrate(&prefix, txn)?, 24 => self.m24_migrate(&prefix, txn)?, + 25 => self.m25_migrate(&prefix, txn)?, _ => panic!("Unreachable migration level"), }; diff --git a/gossip-lib/src/storage/mod.rs b/gossip-lib/src/storage/mod.rs index aca31986a..b59e56285 100644 --- a/gossip-lib/src/storage/mod.rs +++ b/gossip-lib/src/storage/mod.rs @@ -23,6 +23,7 @@ mod event_tag_index1; mod event_viewed1; mod events1; mod events2; +mod events3; mod hashtags1; mod nip46servers1; mod people1; @@ -40,6 +41,7 @@ mod relays1; mod relays2; mod reprel1; mod unindexed_giftwraps1; +mod versioned; use crate::dm_channel::{DmChannel, DmChannelData}; use crate::error::{Error, ErrorKind}; @@ -55,7 +57,7 @@ use heed::types::UnalignedSlice; use heed::{Database, Env, EnvFlags, EnvOpenOptions, RwTxn}; use nostr_types::{ EncryptedPrivateKey, Event, EventAddr, EventKind, EventReference, Id, MilliSatoshi, PublicKey, - RelayUrl, Tag, Unixtime, + RelayUrl, Unixtime, }; use paste::paste; use speedy::{Readable, Writable}; @@ -288,7 +290,7 @@ impl Storage { #[inline] pub(crate) fn db_events(&self) -> Result { - self.db_events2() + self.db_events3() } #[inline] @@ -1163,9 +1165,9 @@ impl Storage { let mut inbox_relays: Vec = Vec::new(); let mut outbox_relays: Vec = Vec::new(); for tag in event.tags.iter() { - if let Tag::Reference { url, marker, .. } = tag { - if let Ok(relay_url) = RelayUrl::try_from_unchecked_url(url) { - if let Some(m) = marker { + if let Ok((uurl, optmarker)) = tag.parse_relay() { + if let Ok(relay_url) = RelayUrl::try_from_unchecked_url(&uurl) { + if let Some(m) = optmarker { match &*m.trim().to_lowercase() { "read" => { // 'read' means inbox and not outbox @@ -1294,26 +1296,26 @@ impl Storage { event: &Event, rw_txn: Option<&mut RwTxn<'a>>, ) -> Result<(), Error> { - self.write_event2(event, rw_txn) + self.write_event3(event, rw_txn) } /// Read an event #[inline] pub fn read_event(&self, id: Id) -> Result, Error> { - self.read_event2(id) + self.read_event3(id) } /// If we have th event #[inline] pub fn has_event(&self, id: Id) -> Result { - self.has_event2(id) + self.has_event3(id) } /// Delete the event pub fn delete_event<'a>(&'a self, id: Id, rw_txn: Option<&mut RwTxn<'a>>) -> Result<(), Error> { let f = |txn: &mut RwTxn<'a>| -> Result<(), Error> { // Delete from the events table - self.delete_event2(id, Some(txn))?; + self.delete_event3(id, Some(txn))?; // Delete from event_seen_on_relay { @@ -1657,26 +1659,7 @@ impl Storage { event: &Event, txn: &mut RwTxn<'a>, ) -> Result, Error> { - if event.kind == EventKind::GiftWrap { - match GLOBALS.identity.unwrap_giftwrap(event) { - Ok(rumor) => { - let mut rumor_event = rumor.into_event_with_bad_signature(); - rumor_event.id = event.id; // lie, so it indexes it under the giftwrap - Ok(Some(rumor_event)) - } - Err(e) => { - if matches!(e.kind, ErrorKind::NoPrivateKey) { - // Store as unindexed for later indexing - let bytes = vec![]; - self.db_unindexed_giftwraps()? - .put(txn, event.id.as_slice(), &bytes)?; - } - Err(e) - } - } - } else { - Ok(None) - } + self.switch_to_rumor3(event, txn) } // We don't call this externally. Whenever we write an event, we do this. @@ -1745,7 +1728,7 @@ impl Storage { event: &Event, rw_txn: Option<&mut RwTxn<'a>>, ) -> Result<(), Error> { - self.write_event_tag_index1(event, rw_txn) + self.write_event3_tag_index1(event, rw_txn) } /// Find events having a given tag, and passing the filter. @@ -2334,7 +2317,7 @@ impl Storage { eventptr.created_at, Some(txn), )?; - self.write_event_tag_index(&eventptr, Some(txn))?; + self.write_event_tag_index(eventptr, Some(txn))?; for hashtag in event.hashtags() { if hashtag.is_empty() { continue; diff --git a/gossip-lib/src/storage/versioned.rs b/gossip-lib/src/storage/versioned.rs new file mode 100644 index 000000000..214235e0f --- /dev/null +++ b/gossip-lib/src/storage/versioned.rs @@ -0,0 +1,59 @@ +use crate::error::Error; +use crate::globals::GLOBALS; +use crate::storage::Storage; +use heed::RwTxn; +use nostr_types::{EventKind, EventV2, EventV3}; + +impl Storage { + pub(super) fn switch_to_rumor2<'a>( + &'a self, + event: &EventV2, + txn: &mut RwTxn<'a>, + ) -> Result, Error> { + if event.kind == EventKind::GiftWrap { + match GLOBALS.identity.unwrap_giftwrap2(event) { + Ok(rumor) => { + let mut rumor_event = rumor.into_event_with_bad_signature(); + rumor_event.id = event.id; // lie, so it indexes it under the giftwrap + Ok(Some(rumor_event)) + } + Err(_) => { + // Store as unindexed for later indexing + let bytes = vec![]; + self.db_unindexed_giftwraps()? + .put(txn, event.id.as_slice(), &bytes)?; + // and do not throw the error + Ok(None) + } + } + } else { + Ok(None) + } + } + + pub(super) fn switch_to_rumor3<'a>( + &'a self, + event: &EventV3, + txn: &mut RwTxn<'a>, + ) -> Result, Error> { + if event.kind == EventKind::GiftWrap { + match GLOBALS.identity.unwrap_giftwrap(event) { + Ok(rumor) => { + let mut rumor_event = rumor.into_event_with_bad_signature(); + rumor_event.id = event.id; // lie, so it indexes it under the giftwrap + Ok(Some(rumor_event)) + } + Err(_) => { + // Store as unindexed for later indexing + let bytes = vec![]; + self.db_unindexed_giftwraps()? + .put(txn, event.id.as_slice(), &bytes)?; + // and do not throw the error + Ok(None) + } + } + } else { + Ok(None) + } + } +} diff --git a/gossip-lib/src/tags.rs b/gossip-lib/src/tags.rs index 13fe87f83..1209f5030 100644 --- a/gossip-lib/src/tags.rs +++ b/gossip-lib/src/tags.rs @@ -1,19 +1,15 @@ use crate::relay::Relay; -use nostr_types::{EventKind, Id, PublicKey, PublicKeyHex, Tag, UncheckedUrl}; +use nostr_types::{EventAddr, Id, PublicKey, Tag, UncheckedUrl}; -pub async fn add_pubkey_hex_to_tags(existing_tags: &mut Vec, hex: &PublicKeyHex) -> usize { - let newtag = Tag::Pubkey { - pubkey: hex.to_owned(), - recommended_relay_url: None, - petname: None, - trailing: Vec::new(), - }; +pub async fn add_pubkey_to_tags(existing_tags: &mut Vec, added: PublicKey) -> usize { + let newtag = Tag::new_pubkey(added, None, None); match existing_tags.iter().position(|existing_tag| { - matches!( - existing_tag, - Tag::Pubkey { pubkey: existing_p, .. } if existing_p == hex - ) + if let Ok((pubkey, _, __)) = existing_tag.parse_pubkey() { + pubkey == added + } else { + false + } }) { None => { // FIXME: include relay hint @@ -24,35 +20,28 @@ pub async fn add_pubkey_hex_to_tags(existing_tags: &mut Vec, hex: &PublicKe } } -pub async fn add_pubkey_to_tags(existing_tags: &mut Vec, added: &PublicKey) -> usize { - add_pubkey_hex_to_tags(existing_tags, &added.as_hex_string().into()).await -} - pub async fn add_event_to_tags( existing_tags: &mut Vec, added: Id, relay_url: Option, marker: &str, ) -> usize { - let newtag = Tag::Event { - id: added, - recommended_relay_url: match relay_url { - Some(url) => Some(url), - None => Relay::recommended_relay_for_reply(added) - .await - .ok() - .flatten() - .map(|rr| rr.to_unchecked_url()), - }, - marker: Some(marker.to_string()), - trailing: Vec::new(), + let optrelay = match relay_url { + Some(url) => Some(url), + None => Relay::recommended_relay_for_reply(added) + .await + .ok() + .flatten() + .map(|rr| rr.to_unchecked_url()), }; + let newtag = Tag::new_event(added, optrelay, Some(marker.to_string())); match existing_tags.iter().position(|existing_tag| { - matches!( - existing_tag, - Tag::Event { id: existing_e, .. } if existing_e.0 == added.0 - ) + if let Ok((id, _rurl, _optmarker)) = existing_tag.parse_event() { + id == added + } else { + false + } }) { None => { existing_tags.push(newtag); @@ -65,42 +54,27 @@ pub async fn add_event_to_tags( // FIXME pass in and set marker pub async fn add_addr_to_tags( existing_tags: &mut Vec, - kind: EventKind, - pubkey: PublicKeyHex, - d: String, - relay_url: Option, + addr: &EventAddr, marker: Option, ) -> usize { match existing_tags.iter().position(|existing_tag| { - matches!( - existing_tag, - Tag::Address { kind: k, pubkey: p, d: md, .. } if *k==kind && *p==pubkey && *md==d - ) + if let Ok((ea, _optmarker)) = existing_tag.parse_address() { + ea.kind == addr.kind && ea.author == addr.author && ea.d == addr.d + } else { + false + } }) { + Some(idx) => idx, None => { - existing_tags.push(Tag::Address { - kind, - pubkey, - d, - relay_url, - marker, - trailing: Vec::new(), - }); + existing_tags.push(Tag::new_address(addr, marker)); existing_tags.len() - 1 } - Some(idx) => idx, } } pub fn add_subject_to_tags_if_missing(existing_tags: &mut Vec, subject: String) { - if !existing_tags - .iter() - .any(|t| matches!(t, Tag::Subject { .. })) - { - existing_tags.push(Tag::Subject { - subject, - trailing: Vec::new(), - }); + if !existing_tags.iter().any(|t| t.tagname() == "subject") { + existing_tags.push(Tag::new_subject(subject)); } }