diff --git a/src/error.rs b/src/error.rs index e4d54d6..7821eed 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,9 @@ pub enum ChorusError { // Crypto Crypto(secp256k1::Error), + // Deleted event + Deleted, + // Duplicate event Duplicate, @@ -131,7 +134,8 @@ impl std::fmt::Display for ChorusError { ChorusError::ChannelSend(e) => write!(f, "{e}"), ChorusError::Config(e) => write!(f, "{e}"), ChorusError::Crypto(e) => write!(f, "{e}"), - ChorusError::Duplicate => write!(f, "Duplicate"), + ChorusError::Deleted => write!(f, "Event was previously deleted"), + ChorusError::Duplicate => write!(f, "Duplicate event"), ChorusError::EndOfInput => write!(f, "End of input"), ChorusError::EventIsInvalid(s) => write!(f, "Event is invalid: {s}"), ChorusError::Http(e) => write!(f, "{e}"), diff --git a/src/nostr.rs b/src/nostr.rs index 5d8a51d..ec9a230 100644 --- a/src/nostr.rs +++ b/src/nostr.rs @@ -169,8 +169,14 @@ impl WebSocketService { PERSONAL_MSG.to_owned(), ), ChorusError::Duplicate => { - NostrReply::Ok(id, false, NostrReplyPrefix::Duplicate, "".to_string()) + NostrReply::Ok(id, true, NostrReplyPrefix::Duplicate, "".to_string()) } + ChorusError::Deleted => NostrReply::Ok( + id, + true, + NostrReplyPrefix::None, + "That event is deleted".to_string(), + ), ChorusError::EventIsInvalid(why) => { NostrReply::Ok(id, false, NostrReplyPrefix::Invalid, why) } @@ -395,10 +401,8 @@ fn screen_outgoing_event( } // Forbid if it is a private event (DM or GiftWrap) and the author isn't them - if event.kind() == Kind(4) || event.kind() == Kind(1059) { - if !authored_by_requester { - return false; - } + if (event.kind() == Kind(4) || event.kind() == Kind(1059)) && !authored_by_requester { + return false; } // Allow if an authorized_user is asking diff --git a/src/store/mod.rs b/src/store/mod.rs index ff3c86c..c0a3e15 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -17,7 +17,8 @@ pub struct Store { akci: Database, OwnedType>, atci: Database, OwnedType>, ktci: Database, OwnedType>, - deleted: Database, Unit>, + deleted_offsets: Database, Unit>, + deleted_events: Database, Unit>, allow_scraping: bool, } @@ -64,10 +65,15 @@ impl Store { .types::, OwnedType>() .name("ktci") .create(&mut txn)?; - let deleted = env + let deleted_offsets = env .database_options() .types::, Unit>() - .name("deleted") + .name("deleted_offsets") + .create(&mut txn)?; + let deleted_events = env + .database_options() + .types::, Unit>() + .name("deleted-events") .create(&mut txn)?; txn.commit()?; @@ -82,7 +88,8 @@ impl Store { akci, atci, ktci, - deleted, + deleted_offsets, + deleted_events, allow_scraping, }) } @@ -111,13 +118,22 @@ impl Store { // Only if it doesn't already exist if self.ids.get(&txn, event.id().0.as_slice())?.is_none() { + // Reject event if it was deleted + { + let deleted_key = Self::key_deleted_events(event.id(), event.pubkey()); + if self.deleted_events.get(&txn, &deleted_key)?.is_some() { + return Err(ChorusError::Deleted.into()); + } + } + + // Store the event offset = self.events.store_event(event)?; if event.kind().is_ephemeral() { // Do not index ephemeral events, not even by id. // But save them in the deleted table let offset_u64 = offset as u64; - self.deleted.put(&mut txn, &offset_u64, &())?; + self.deleted_offsets.put(&mut txn, &offset_u64, &())?; } else { // Index the event self.index(&mut txn, event, offset)?; @@ -129,6 +145,11 @@ impl Store { self.delete_replaced(&mut txn, event)?; } + // Handle deletion events + if event.kind() == Kind(5) { + self.handle_deletion_event(&mut txn, event)?; + } + txn.commit()?; } else { return Err(ChorusError::Duplicate.into()); @@ -137,6 +158,31 @@ impl Store { Ok(offset) } + pub fn handle_deletion_event(&self, txn: &mut RwTxn<'_>, event: &Event) -> Result<(), Error> { + for mut tag in event.tags()?.iter() { + if let Some(tagname) = tag.next() { + if tagname == b"e" { + if let Some(id_hex) = tag.next() { + if let Ok(id) = Id::read_hex(id_hex) { + // Add deletion pair to the event_deleted table + let deleted_key = Self::key_deleted_events(id, event.pubkey()); + self.deleted_events.put(txn, &deleted_key, &())?; + + // Delete pair + if let Some(target) = self.get_event_by_id(id)? { + if target.pubkey() == event.pubkey() { + self.delete(txn, id)?; + } + } + } + } + } + } + } + + Ok(()) + } + /// Get an event by its offset. pub fn get_event_by_offset(&self, offset: usize) -> Result, Error> { self.events.get_event_by_offset(offset) @@ -322,11 +368,12 @@ impl Store { } /// Delete an event by id - pub fn delete(&self, id: Id) -> Result<(), Error> { - let txn = self.env.read_txn()?; - if let Some(offset) = self.ids.get(&txn, id.0.as_slice())? { - drop(txn); - self.set_event_as_deleted(offset)?; + fn delete(&self, txn: &mut RwTxn<'_>, id: Id) -> Result<(), Error> { + if let Some(offset) = self.ids.get(txn, id.0.as_slice())? { + self.set_offset_as_deleted(offset)?; + + // Also remove from the id index + self.ids.delete(txn, id.0.as_slice())?; } Ok(()) } @@ -431,18 +478,18 @@ impl Store { // Set an event as deleted // This removes it from indexes (except the id index) and adds it to the deleted table - fn set_event_as_deleted(&self, offset: usize) -> Result<(), Error> { + fn set_offset_as_deleted(&self, offset: usize) -> Result<(), Error> { let mut txn = self.env.write_txn()?; let offset_u64 = offset as u64; // Check if it is already deleted - if self.deleted.get(&txn, &offset_u64)?.is_some() { + if self.deleted_offsets.get(&txn, &offset_u64)?.is_some() { return Ok(()); } // Add to deleted database in case we need to get at it in the future. - self.deleted.put(&mut txn, &offset_u64, &())?; + self.deleted_offsets.put(&mut txn, &offset_u64, &())?; // Get event let event = match self.events.get_event_by_offset(offset)? { @@ -486,7 +533,7 @@ impl Store { let (_key, offset) = result?; // Delete the event - self.set_event_as_deleted(offset)?; + self.set_offset_as_deleted(offset)?; } } else if event.kind().is_parameterized_replaceable() { let tags = event.tags()?; @@ -511,7 +558,7 @@ impl Store { let (_key, offset) = result?; // Delete the event - self.set_event_as_deleted(offset)?; + self.set_offset_as_deleted(offset)?; } } } @@ -583,4 +630,12 @@ impl Store { key.extend(id.as_slice()); key } + + fn key_deleted_events(id: Id, pubkey: Pubkey) -> Vec { + let mut key: Vec = + Vec::with_capacity(std::mem::size_of::() + std::mem::size_of::()); + key.extend(id.as_slice()); + key.extend(pubkey.as_slice()); + key + } } diff --git a/src/web.rs b/src/web.rs index 2d4da75..b6091df 100644 --- a/src/web.rs +++ b/src/web.rs @@ -35,16 +35,16 @@ pub async fn serve_nip11(peer: SocketAddr) -> Result, Error> { fn build_rid(config: &Config) -> String { let mut rid: String = String::with_capacity(255); - const SUPPORTED_NIPS: [u8; 6] = [ + const SUPPORTED_NIPS: [u8; 7] = [ 1, // nostr 4, // DMs + 9, // Event Deletion 11, // relay information document 42, // AUTH 59, // GiftWrap 65, // Relay List Metadata ]; - const _UNSUPPORTED_NIPS: [u8; 8] = [ - 9, // Event Deletion + const _UNSUPPORTED_NIPS: [u8; 7] = [ 26, // Delegated Event Signing 28, // Public Chat 40, // Expiration Timestamp