NIP-09 deletion support

This commit is contained in:
Mike Dilger 2024-02-19 12:06:48 +13:00
parent 733de827d6
commit 2b636f6776
4 changed files with 87 additions and 24 deletions

View File

@ -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}"),

View File

@ -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

View File

@ -17,7 +17,8 @@ pub struct Store {
akci: Database<UnalignedSlice<u8>, OwnedType<usize>>,
atci: Database<UnalignedSlice<u8>, OwnedType<usize>>,
ktci: Database<UnalignedSlice<u8>, OwnedType<usize>>,
deleted: Database<U64<BigEndian>, Unit>,
deleted_offsets: Database<U64<BigEndian>, Unit>,
deleted_events: Database<UnalignedSlice<u8>, Unit>,
allow_scraping: bool,
}
@ -64,10 +65,15 @@ impl Store {
.types::<UnalignedSlice<u8>, OwnedType<usize>>()
.name("ktci")
.create(&mut txn)?;
let deleted = env
let deleted_offsets = env
.database_options()
.types::<U64<BigEndian>, Unit>()
.name("deleted")
.name("deleted_offsets")
.create(&mut txn)?;
let deleted_events = env
.database_options()
.types::<UnalignedSlice<u8>, 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<Option<Event>, 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<u8> {
let mut key: Vec<u8> =
Vec::with_capacity(std::mem::size_of::<Id>() + std::mem::size_of::<Pubkey>());
key.extend(id.as_slice());
key.extend(pubkey.as_slice());
key
}
}

View File

@ -35,16 +35,16 @@ pub async fn serve_nip11(peer: SocketAddr) -> Result<Response<Body>, 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