Skip to content

Commit

Permalink
Merge #200: database: add support to event deletion by coordinates
Browse files Browse the repository at this point in the history
  • Loading branch information
yukibtc committed Dec 4, 2023
2 parents 6d59334 + 308a871 commit 358ab9a
Show file tree
Hide file tree
Showing 16 changed files with 420 additions and 148 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
-p nostr --no-default-features --features alloc,
-p nostr --no-default-features --features "alloc all-nips",
-p nostr --features blocking,
-p nostr-database
-p nostr-database,
-p nostr-sdk,
-p nostr-sdk --no-default-features,
-p nostr-sdk --features blocking,
Expand Down
147 changes: 134 additions & 13 deletions crates/nostr-database/src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ impl DatabaseIndexes {

if event.is_replaceable() {
let filter: Filter = Filter::new().author(event.pubkey).kind(event.kind);
for ev in self.internal_query(&index, filter).await {
for ev in self.internal_query(&index, filter, false).await {
if ev.created_at > event.created_at {
should_insert = false;
} else if ev.created_at <= event.created_at {
Expand All @@ -179,7 +179,7 @@ impl DatabaseIndexes {
.author(event.pubkey)
.kind(event.kind)
.identifier(identifier);
for ev in self.internal_query(&index, filter).await {
for ev in self.internal_query(&index, filter, false).await {
if ev.created_at >= event.created_at {
should_insert = false;
} else if ev.created_at < event.created_at {
Expand All @@ -191,16 +191,30 @@ impl DatabaseIndexes {
}
} else if event.kind == Kind::EventDeletion {
let mut deleted = self.deleted.write().await;
let ids = event.event_ids().copied();
let filter = Filter::new().ids(ids);
let pubkey_prefix: PublicKeyPrefix = PublicKeyPrefix::from(event.pubkey);
for ev in self.internal_query(&index, filter).await {

// Check `e` tags
let ids = event.event_ids().copied();
let filter: Filter = Filter::new().ids(ids);
for ev in self.internal_query(&index, filter, false).await {
if ev.pubkey == pubkey_prefix {
to_discard.insert(ev.event_id);
deleted.insert(ev.event_id);
}
}
// TODO: support event deletion by coordinate (`a` tag)

// Check `a` tags
for coordinate in event.coordinates() {
let coordinate_pubkey_prefix: PublicKeyPrefix =
PublicKeyPrefix::from(coordinate.pubkey);
if coordinate_pubkey_prefix == pubkey_prefix {
let filter: Filter = coordinate.into();
for ev in self.internal_query(&index, filter, false).await {
to_discard.insert(ev.event_id);
deleted.insert(ev.event_id);
}
}
}
}

// Remove events
Expand All @@ -223,19 +237,24 @@ impl DatabaseIndexes {
&self,
index: &'a BTreeSet<EventIndex>,
filter: Filter,
allow_empty_filter: bool,
) -> impl Iterator<Item = &'a EventIndex> {
let authors: HashSet<PublicKeyPrefix> = filter
.authors
.iter()
.map(|p| PublicKeyPrefix::from(*p))
.collect();
index.iter().filter(move |m| {
(filter.ids.is_empty() || filter.ids.contains(&m.event_id))
&& filter.since.map_or(true, |t| m.created_at >= t)
&& filter.until.map_or(true, |t| m.created_at <= t)
&& (filter.authors.is_empty() || authors.contains(&m.pubkey))
&& (filter.kinds.is_empty() || filter.kinds.contains(&m.kind))
&& m.filter_tags_match(&filter)
if (filter.is_empty() && allow_empty_filter) || !filter.is_empty() {
(filter.ids.is_empty() || filter.ids.contains(&m.event_id))
&& filter.since.map_or(true, |t| m.created_at >= t)
&& filter.until.map_or(true, |t| m.created_at <= t)
&& (filter.authors.is_empty() || authors.contains(&m.pubkey))
&& (filter.kinds.is_empty() || filter.kinds.contains(&m.kind))
&& m.filter_tags_match(&filter)
} else {
false
}
})
}

Expand All @@ -257,7 +276,7 @@ impl DatabaseIndexes {
}

let limit: Option<usize> = filter.limit;
let iter = self.internal_query(&index, filter).await;
let iter = self.internal_query(&index, filter, true).await;
if let Some(limit) = limit {
matching_ids.extend(iter.take(limit))
} else {
Expand All @@ -280,3 +299,105 @@ impl DatabaseIndexes {
index.clear();
}
}

#[cfg(test)]
mod tests {
use nostr::nips::nip01::Coordinate;
use nostr::secp256k1::SecretKey;
use nostr::{EventBuilder, FromBech32, Keys, Tag};

use super::*;

const SECRET_KEY_A: &str = "nsec1ufnus6pju578ste3v90xd5m2decpuzpql2295m3sknqcjzyys9ls0qlc85";
const SECRET_KEY_B: &str = "nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99";

#[tokio::test]
async fn test_event_deletion() {
let indexes = DatabaseIndexes::new();

// Keys
let keys_a = Keys::new(SecretKey::from_bech32(SECRET_KEY_A).unwrap());
let keys_b = Keys::new(SecretKey::from_bech32(SECRET_KEY_B).unwrap());

// Build some events
let events = [
EventBuilder::new_text_note("Text note", &[])
.to_event(&keys_a)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32121),
"Empty 1",
&[Tag::Identifier(String::from("abdefgh:12345678"))],
)
.to_event(&keys_a)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32122),
"Empty 2",
&[Tag::Identifier(String::from("abdefgh:12345678"))],
)
.to_event(&keys_a)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32122),
"",
&[Tag::Identifier(String::from("ijklmnop:87654321"))],
)
.to_event(&keys_a)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32122),
"",
&[Tag::Identifier(String::from("abdefgh:87654321"))],
)
.to_event(&keys_b)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32122),
"",
&[Tag::Identifier(String::from("abdefgh:12345678"))],
)
.to_event(&keys_b)
.unwrap(),
EventBuilder::new(
Kind::ParameterizedReplaceable(32122),
"Test",
&[Tag::Identifier(String::from("abdefgh:12345678"))],
)
.to_event(&keys_a)
.unwrap(),
];

for event in events.iter() {
indexes.index_event(event).await;
}

// Total events: 6

// Invalid event deletion (wrong signing keys)
let coordinate =
Coordinate::new(Kind::ParameterizedReplaceable(32122), keys_a.public_key());
let event = EventBuilder::delete([coordinate])
.to_event(&keys_b)
.unwrap();
indexes.index_event(&event).await;

// Total events: 7 (6 + 1)

// Delete valid event
let coordinate =
Coordinate::new(Kind::ParameterizedReplaceable(32122), keys_a.public_key())
.identifier("ijklmnop:87654321");
let event = EventBuilder::delete([coordinate])
.to_event(&keys_a)
.unwrap();
indexes.index_event(&event).await;

// Total events: 7 (7 - 1 + 1)

// Check total number of indexes
let filter = Filter::new();
let res = indexes.query([filter]).await;
assert_eq!(res.len(), 7);
}
}
1 change: 1 addition & 0 deletions crates/nostr-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ The following crate feature flags are available:
| Feature | Default | Description |
| ------------------- | :-----: | ---------------------------------------------------------------------------------------- |
| `blocking` | No | Needed to use `NIP-05` and `NIP-11` features in not async/await context |
| `sqlite` | No | Enable SQLite Storage backend |
| `rocksdb` | No | Enable RocksDB Storage backend |
| `indexeddb` | No | Enable Web's IndexedDb Storage backend |
| `all-nips` | Yes | Enable all NIPs |
Expand Down
1 change: 0 additions & 1 deletion crates/nostr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,6 @@ The following crate feature flags are available:
|| [30 - Custom Emoji](https://github.com/nostr-protocol/nips/blob/master/30.md) |
|| [31 - Dealing with Unknown Events](https://github.com/nostr-protocol/nips/blob/master/31.md) |
|| [32 - Labeling](https://github.com/nostr-protocol/nips/blob/master/32.md) |
|| [33 - Parameterized Replaceable Events](https://github.com/nostr-protocol/nips/blob/master/33.md) |
|| [36 - Sensitive Content](https://github.com/nostr-protocol/nips/blob/master/36.md) |
|| [39 - External Identities in Profiles](https://github.com/nostr-protocol/nips/blob/master/39.md) |
|| [40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md) |
Expand Down
16 changes: 11 additions & 5 deletions crates/nostr/src/event/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use crate::nips::{nip13, nip58};
use crate::types::time::Instant;
use crate::types::time::TimeSupplier;
use crate::types::{ChannelId, Contact, Metadata, Timestamp};
use crate::util::EventIdOrCoordinate;
#[cfg(feature = "std")]
use crate::SECP256K1;
use crate::{JsonUtil, RelayMetadata, UncheckedUrl};
Expand Down Expand Up @@ -440,22 +441,27 @@ impl EventBuilder {
}

/// Create delete event
pub fn delete<I>(ids: I) -> Self
pub fn delete<I, T>(ids: I) -> Self
where
I: IntoIterator<Item = EventId>,
I: IntoIterator<Item = T>,
T: Into<EventIdOrCoordinate>,
{
Self::delete_with_reason(ids, "")
}

/// Create delete event with reason
pub fn delete_with_reason<I, S>(ids: I, reason: S) -> Self
pub fn delete_with_reason<I, T, S>(ids: I, reason: S) -> Self
where
I: IntoIterator<Item = EventId>,
I: IntoIterator<Item = T>,
T: Into<EventIdOrCoordinate>,
S: Into<String>,
{
let tags: Vec<Tag> = ids
.into_iter()
.map(|id| Tag::Event(id, None, None))
.map(|t| {
let middle: EventIdOrCoordinate = t.into();
middle.into()
})
.collect();

Self::new(Kind::EventDeletion, reason.into(), &tags)
Expand Down
6 changes: 6 additions & 0 deletions crates/nostr/src/event/id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,9 @@ impl From<EventId> for String {
event_id.to_string()
}
}

impl From<EventId> for Tag {
fn from(id: EventId) -> Self {
Tag::Event(id, None, None)
}
}
6 changes: 6 additions & 0 deletions crates/nostr/src/event/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,4 +386,10 @@ mod tests {
assert_ne!(Kind::Authentication, Kind::EncryptedDirectMessage);
assert_ne!(Kind::TextNote, Kind::Custom(2));
}

#[test]
fn test_kind_is_parameterized_replaceable() {
assert!(Kind::ParameterizedReplaceable(32122).is_parameterized_replaceable());
assert!(!Kind::ParameterizedReplaceable(1).is_parameterized_replaceable());
}
}
19 changes: 19 additions & 0 deletions crates/nostr/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub use self::kind::Kind;
pub use self::partial::{MissingPartialEvent, PartialEvent};
pub use self::tag::{Marker, Tag, TagIndexValues, TagIndexes, TagKind};
pub use self::unsigned::UnsignedEvent;
use crate::nips::nip01::Coordinate;
#[cfg(feature = "std")]
use crate::types::time::Instant;
use crate::types::time::TimeSupplier;
Expand Down Expand Up @@ -292,6 +293,24 @@ impl Event {
})
}

/// Extract coordinates from tags (`a` tag)
pub fn coordinates(&self) -> impl Iterator<Item = Coordinate> + '_ {
self.tags.iter().filter_map(|t| match t {
Tag::A {
kind,
public_key,
identifier,
..
} => Some(Coordinate {
kind: *kind,
pubkey: *public_key,
identifier: identifier.clone(),
relays: Vec::new(),
}),
_ => None,
})
}

/// Build tags index
pub fn build_tags_index(&self) -> TagIndexes {
TagIndexes::from(self.tags.iter().map(|t| t.as_vec()))
Expand Down
14 changes: 14 additions & 0 deletions crates/nostr/src/message/subscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ impl Filter {
&& self.authors_match(event)
&& self.tag_match(event)
}

/// Check if [`Filter`] is empty
pub fn is_empty(&self) -> bool {
self == &Filter::default()
}
}

/// Filters match event trait
Expand Down Expand Up @@ -980,4 +985,13 @@ mod test {
assert!(!filter.match_event(&event));
assert!(!filter.match_event(&event_with_empty_tags));
}

#[test]
fn test_filter_is_empty() {
let filter = Filter::new().identifier("test");
assert!(!filter.is_empty());

let filter = Filter::new();
assert!(filter.is_empty());
}
}
2 changes: 2 additions & 0 deletions crates/nostr/src/nips/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//!
//! See all at <https://github.com/nostr-protocol/nips>
pub mod nip01;
#[cfg(feature = "nip04")]
pub mod nip04;
#[cfg(all(feature = "std", feature = "nip05"))]
Expand All @@ -18,6 +19,7 @@ pub mod nip15;
pub mod nip19;
pub mod nip21;
pub mod nip26;
#[deprecated(since = "0.26.0", note = "moved to `nip01`")]
pub mod nip33;
#[cfg(feature = "nip44")]
pub mod nip44;
Expand Down
Loading

0 comments on commit 358ab9a

Please sign in to comment.