diff --git a/src/delegation.rs b/src/delegation.rs new file mode 100644 index 0000000..1cc57d1 --- /dev/null +++ b/src/delegation.rs @@ -0,0 +1,72 @@ +//! Event parsing and validation +//use crate::error::Error::*; +//use crate::error::Result; +//use crate::utils::unix_time; +//use bitcoin_hashes::{sha256, Hash}; +//use lazy_static::lazy_static; +//use secp256k1::{schnorr, Secp256k1, VerifyOnly, XOnlyPublicKey}; +use serde::{Deserialize, Serialize}; +//use serde_json::value::Value; +//use serde_json::Number; +//use std::collections::HashMap; +//use std::collections::HashSet; +//use std::str::FromStr; +//use tracing::{debug, info}; + +// This handles everything related to delegation, in particular the +// condition/rune parsing and logic. + +// Conditions are poorly specified, so we will implement the minimum +// necessary for now. + +// fields MUST be either "kind" or "created_at". +// operators supported are ">", "<", "=", "!". +// no operations on 'content' are supported. + +// this allows constraints for: +// valid date ranges (valid from X->Y dates). +// specific kinds (publish kind=1,5) +// kind ranges (publish ephemeral events, kind>19999&kind<30001) + +// for more complex scenarios (allow delegatee to publish ephemeral +// AND replacement events), it may be necessary to generate and use +// different condition strings, since we do not support grouping or +// "OR" logic. + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub enum Field { + Kind, + CreatedAt, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub enum Operator { + LessThan, + GreaterThan, + Equals, + NotEquals, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub enum Value { + Number(u64), + List(Vec), +} + +/// Parsed delegation statement +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub struct Delegation { + pub(crate) pubkey: String, + pub(crate) conditions: Vec, + pub(crate) signature: String, +} + +/// Parsed delegation condition +/// see https://github.com/nostr-protocol/nips/pull/28#pullrequestreview-1084903800 +/// An example complex condition would be: kind=1,2,3&created_at<1665265999 +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] +pub struct Condition { + pub(crate) field: Field, + pub(crate) operator: Operator, + pub(crate) value: Value, +} diff --git a/src/event.rs b/src/event.rs index 2366e42..3f2098d 100644 --- a/src/event.rs +++ b/src/event.rs @@ -110,6 +110,16 @@ impl Event { None } + // is this event delegated (properly)? + // does the signature match, and are conditions valid? + pub fn is_delegated(&self) -> bool { + // is there a delegation tag? + let _delegation_tag = self.tag_values_by_name("delegation"); + // delegation tags should have exactly 3 elements after the name (pubkey, condition, sig) + // try to construct a delegation object ( + todo!(); + } + /// Build an event tag index fn build_index(&mut self) { // if there are no tags; just leave the index as None @@ -388,6 +398,32 @@ mod tests { assert_eq!(v, vec!["foo", "bar", "baz"]); } + #[test] + fn event_no_tag_select() { + let e = Event { + id: "999".to_owned(), + pubkey: "012345".to_owned(), + created_at: 501234, + kind: 1, + tags: vec![ + vec!["j".to_owned(), "abc".to_owned()], + vec!["e".to_owned(), "foo".to_owned()], + vec!["e".to_owned(), "baz".to_owned()], + vec![ + "p".to_owned(), + "aaaa".to_owned(), + "ws://example.com".to_owned(), + ], + ], + content: "this is a test".to_owned(), + sig: "abcde".to_owned(), + tagidx: None, + }; + let v = e.tag_values_by_name("x"); + // asking for tags that don't exist just returns zero-length vector + assert_eq!(v.len(), 0); + } + #[test] fn event_canonical_with_tags() { let e = Event { diff --git a/src/lib.rs b/src/lib.rs index 885f155..dc00507 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod close; pub mod config; pub mod conn; pub mod db; +pub mod delegation; pub mod error; pub mod event; pub mod hexrange; diff --git a/src/schema.rs b/src/schema.rs index 8f5638e..bca9bd1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -20,7 +20,7 @@ pragma mmap_size = 536870912; -- 512MB of mmap "##; /// Latest database version -pub const DB_VERSION: usize = 6; +pub const DB_VERSION: usize = 7; /// Schema definition const INIT_SQL: &str = formatcp!( @@ -40,6 +40,7 @@ event_hash BLOB NOT NULL, -- 4-byte hash first_seen INTEGER NOT NULL, -- when the event was first seen (not authored!) (seconds since 1970) created_at INTEGER NOT NULL, -- when the event was authored author BLOB NOT NULL, -- author pubkey +delegator BLOB, -- delegator pubkey (NIP-26) kind INTEGER NOT NULL, -- event kind hidden INTEGER, -- relevant for queries content TEXT NOT NULL -- serialized json of event object @@ -152,6 +153,9 @@ pub fn upgrade_db(conn: &mut PooledConnection) -> Result<()> { if curr_version == 5 { curr_version = mig_5_to_6(conn)?; } + if curr_version == 6 { + curr_version = mig_6_to_7(conn)?; + } if curr_version == DB_VERSION { info!( "All migration scripts completed successfully. Welcome to v{}.", @@ -348,3 +352,22 @@ fn mig_5_to_6(conn: &mut PooledConnection) -> Result { info!("vacuumed DB after tags rebuild in {:?}", start.elapsed()); Ok(6) } + +fn mig_6_to_7(conn: &mut PooledConnection) -> Result { + info!("database schema needs update from 6->7"); + // only change is adding a hidden column to events. + let upgrade_sql = r##" +ALTER TABLE event ADD delegator BLOB; +PRAGMA user_version = 7; +"##; + match conn.execute_batch(upgrade_sql) { + Ok(()) => { + info!("database schema upgraded v6 -> v7"); + } + Err(err) => { + error!("update failed: {}", err); + panic!("database could not be upgraded"); + } + } + Ok(7) +}