mirror of
https://github.com/scsibug/nostr-rs-relay.git
synced 2024-11-25 01:59:08 -05:00
feat(NIP-33): parameterized replaceable events
This commit is contained in:
parent
e2869e8fad
commit
c8f7420334
137
src/event.rs
137
src/event.rs
|
@ -130,6 +130,33 @@ impl Event {
|
||||||
self.kind == 0 || self.kind == 3 || self.kind == 41 || (self.kind >= 10000 && self.kind < 20000)
|
self.kind == 0 || self.kind == 3 || self.kind == 41 || (self.kind >= 10000 && self.kind < 20000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should this event be replaced with newer timestamps from same author, for distinct `d` tag values?
|
||||||
|
#[must_use] pub fn is_param_replaceable(&self) -> bool {
|
||||||
|
self.kind >= 30000 && self.kind < 40000
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What is the replaceable `d` tag value?
|
||||||
|
|
||||||
|
/// Should this event be replaced with newer timestamps from same author, for distinct `d` tag values?
|
||||||
|
#[must_use] pub fn distinct_param(&self) -> Option<String> {
|
||||||
|
if self.is_param_replaceable() {
|
||||||
|
let default = "".to_string();
|
||||||
|
let dvals:Vec<&String> = self.tags
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.len() >= 1)
|
||||||
|
.filter(|x| x.get(0).unwrap() == "d")
|
||||||
|
.map(|x| x.get(1).unwrap_or_else(|| &default)).take(1)
|
||||||
|
.collect();
|
||||||
|
let dval_first = dvals.get(0);
|
||||||
|
match dval_first {
|
||||||
|
Some(_) => {dval_first.map(|x| x.to_string())},
|
||||||
|
None => Some(default)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pull a NIP-05 Name out of the event, if one exists
|
/// Pull a NIP-05 Name out of the event, if one exists
|
||||||
#[must_use] pub fn get_nip05_addr(&self) -> Option<nip05::Nip05Name> {
|
#[must_use] pub fn get_nip05_addr(&self) -> Option<nip05::Nip05Name> {
|
||||||
if self.is_kind_metadata() {
|
if self.is_kind_metadata() {
|
||||||
|
@ -364,7 +391,7 @@ mod tests {
|
||||||
fn empty_event_tag_match() {
|
fn empty_event_tag_match() {
|
||||||
let event = Event::simple_event();
|
let event = Event::simple_event();
|
||||||
assert!(!event
|
assert!(!event
|
||||||
.generic_tag_val_intersect('e', &HashSet::from(["foo".to_owned(), "bar".to_owned()])));
|
.generic_tag_val_intersect('e', &HashSet::from(["foo".to_owned(), "bar".to_owned()])));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -509,6 +536,19 @@ mod tests {
|
||||||
assert_eq!(c, expected);
|
assert_eq!(c, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ephemeral_event() {
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind=20000;
|
||||||
|
assert!(event.is_ephemeral());
|
||||||
|
event.kind=29999;
|
||||||
|
assert!(event.is_ephemeral());
|
||||||
|
event.kind=30000;
|
||||||
|
assert!(!event.is_ephemeral());
|
||||||
|
event.kind=19999;
|
||||||
|
assert!(!event.is_ephemeral());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replaceable_event() {
|
fn replaceable_event() {
|
||||||
let mut event = Event::simple_event();
|
let mut event = Event::simple_event();
|
||||||
|
@ -516,9 +556,102 @@ mod tests {
|
||||||
assert!(event.is_replaceable());
|
assert!(event.is_replaceable());
|
||||||
event.kind=3;
|
event.kind=3;
|
||||||
assert!(event.is_replaceable());
|
assert!(event.is_replaceable());
|
||||||
event.kind=12000;
|
event.kind=10000;
|
||||||
assert!(event.is_replaceable());
|
assert!(event.is_replaceable());
|
||||||
|
event.kind=19999;
|
||||||
|
assert!(event.is_replaceable());
|
||||||
|
event.kind=20000;
|
||||||
|
assert!(!event.is_replaceable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_event() {
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
assert!(event.is_param_replaceable());
|
||||||
|
event.kind = 39999;
|
||||||
|
assert!(event.is_param_replaceable());
|
||||||
|
event.kind = 29999;
|
||||||
|
assert!(!event.is_param_replaceable());
|
||||||
|
event.kind = 40000;
|
||||||
|
assert!(!event.is_param_replaceable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_1() {
|
||||||
|
// NIP case #1: "tags":[["d",""]]
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["d".to_owned(), "".to_owned()]];
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_2() {
|
||||||
|
// NIP case #2: "tags":[]: implicit d tag with empty value
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_3() {
|
||||||
|
// NIP case #3: "tags":[["d"]]: implicit empty value ""
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["d".to_owned()]];
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_4() {
|
||||||
|
// NIP case #4: "tags":[["d",""],["d","not empty"]]: only first d tag is considered
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["d".to_owned(), "".to_string()],
|
||||||
|
vec!["d".to_owned(), "not empty".to_string()]
|
||||||
|
];
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_4b() {
|
||||||
|
// Variation of #4 with
|
||||||
|
// NIP case #4: "tags":[["d","not empty"],["d",""]]: only first d tag is considered
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["d".to_owned(), "not empty".to_string()],
|
||||||
|
vec!["d".to_owned(), "".to_string()]
|
||||||
|
];
|
||||||
|
assert_eq!(event.distinct_param(), Some("not empty".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_5() {
|
||||||
|
// NIP case #5: "tags":[["d"],["d","some value"]]: only first d tag is considered
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["d".to_owned()],
|
||||||
|
vec!["d".to_owned(), "second value".to_string()],
|
||||||
|
vec!["d".to_owned(), "third value".to_string()]
|
||||||
|
];
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn param_replaceable_value_case_6() {
|
||||||
|
// NIP case #6: "tags":[["e"]]: same as no tags
|
||||||
|
let mut event = Event::simple_event();
|
||||||
|
event.kind = 30000;
|
||||||
|
event.tags = vec![
|
||||||
|
vec!["e".to_owned()],
|
||||||
|
];
|
||||||
|
assert_eq!(event.distinct_param(), Some("".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,25 @@ impl SqliteRepo {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// check for parameterized replaceable events that would be hidden; don't insert these either.
|
||||||
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
|
let repl_count;
|
||||||
|
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
||||||
|
repl_count = tx.query_row(
|
||||||
|
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND e.kind=? AND t.name='d' AND t.value_hex=? AND e.created_at >= ? LIMIT 1;",
|
||||||
|
params![pubkey_blob, e.kind, hex::decode(d_tag).ok(), e.created_at],|row| row.get::<usize, usize>(0));
|
||||||
|
} else {
|
||||||
|
repl_count = tx.query_row(
|
||||||
|
"SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.author=? AND e.kind=? AND t.name='d' AND t.value=? AND e.created_at >= ? LIMIT 1;",
|
||||||
|
params![pubkey_blob, e.kind, d_tag, e.created_at],|row| row.get::<usize, usize>(0));
|
||||||
|
}
|
||||||
|
// if any rows were returned, then some newer event with
|
||||||
|
// the same author/kind/tag value exist, and we can ignore
|
||||||
|
// this event.
|
||||||
|
if repl_count.ok().is_some() {
|
||||||
|
return Ok(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
// ignore if the event hash is a duplicate.
|
// ignore if the event hash is a duplicate.
|
||||||
let mut ins_count = tx.execute(
|
let mut ins_count = tx.execute(
|
||||||
"INSERT OR IGNORE INTO event (event_hash, created_at, kind, author, delegated_by, content, first_seen, hidden) VALUES (?1, ?2, ?3, ?4, ?5, ?6, strftime('%s','now'), FALSE);",
|
"INSERT OR IGNORE INTO event (event_hash, created_at, kind, author, delegated_by, content, first_seen, hidden) VALUES (?1, ?2, ?3, ?4, ?5, ?6, strftime('%s','now'), FALSE);",
|
||||||
|
@ -174,6 +193,27 @@ impl SqliteRepo {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// if this event is parameterized replaceable, remove other events.
|
||||||
|
if let Some(d_tag) = e.distinct_param() {
|
||||||
|
let update_count;
|
||||||
|
if is_lower_hex(&d_tag) && (d_tag.len() % 2 == 0) {
|
||||||
|
update_count = tx.execute(
|
||||||
|
"DELETE FROM event WHERE kind=? AND author=? AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=? AND e.author=? AND t.name='d' AND t.value_hex=? ORDER BY created_at DESC LIMIT 1);",
|
||||||
|
params![e.kind, pubkey_blob, e.kind, pubkey_blob, hex::decode(d_tag).ok()])?;
|
||||||
|
} else {
|
||||||
|
update_count = tx.execute(
|
||||||
|
"DELETE FROM event WHERE kind=? AND author=? AND id NOT IN (SELECT e.id FROM event e LEFT JOIN tag t ON e.id=t.event_id WHERE e.kind=? AND e.author=? AND t.name='d' AND t.value=? ORDER BY created_at DESC LIMIT 1);",
|
||||||
|
params![e.kind, pubkey_blob, e.kind, pubkey_blob, d_tag])?;
|
||||||
|
}
|
||||||
|
if update_count > 0 {
|
||||||
|
info!(
|
||||||
|
"removed {} older parameterized replaceable kind {} events for author: {:?}",
|
||||||
|
update_count,
|
||||||
|
e.kind,
|
||||||
|
e.get_author_prefix()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
// if this event is a deletion, hide the referenced events from the same author.
|
// if this event is a deletion, hide the referenced events from the same author.
|
||||||
if e.kind == 5 {
|
if e.kind == 5 {
|
||||||
let event_candidates = e.tag_values_by_name("e");
|
let event_candidates = e.tag_values_by_name("e");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user