From 355ea8963b25e4fb23256eb4f5a79d182cff7ef1 Mon Sep 17 00:00:00 2001 From: laoXong Date: Mon, 1 Dec 2025 01:33:50 +0900 Subject: [PATCH] Fix: Protocol implementation error --- .gitignore | 1 + ...21da5d37b0d946f7a0e083afaa9e8fb296e3f.json | 44 +++ src/constants.rs | 20 +- src/database.rs | 22 +- src/models.rs | 12 +- src/nostr/event.rs | 260 +++++++++++++++--- 6 files changed, 310 insertions(+), 49 deletions(-) create mode 100644 .sqlx/query-20cd6a8251512848aaf6b0a5ea721da5d37b0d946f7a0e083afaa9e8fb296e3f.json diff --git a/.gitignore b/.gitignore index ba31e9e..e9aa925 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ target/ .idea/ .vscode/ .zed/ +.env \ No newline at end of file diff --git a/.sqlx/query-20cd6a8251512848aaf6b0a5ea721da5d37b0d946f7a0e083afaa9e8fb296e3f.json b/.sqlx/query-20cd6a8251512848aaf6b0a5ea721da5d37b0d946f7a0e083afaa9e8fb296e3f.json new file mode 100644 index 0000000..4bd0fe7 --- /dev/null +++ b/.sqlx/query-20cd6a8251512848aaf6b0a5ea721da5d37b0d946f7a0e083afaa9e8fb296e3f.json @@ -0,0 +1,44 @@ +{ + "db_name": "SQLite", + "query": "SELECT * FROM deleted_events WHERE event_id = ? AND pubkey = ?", + "describe": { + "columns": [ + { + "name": "event_id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "pubkey", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "kind", + "ordinal": 2, + "type_info": "Integer" + }, + { + "name": "d_tag", + "ordinal": 3, + "type_info": "Text" + }, + { + "name": "deleted_at", + "ordinal": 4, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + true, + false, + true, + true, + true + ] + }, + "hash": "20cd6a8251512848aaf6b0a5ea721da5d37b0d946f7a0e083afaa9e8fb296e3f" +} diff --git a/src/constants.rs b/src/constants.rs index c8ab535..0ca7cbb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -6,15 +6,18 @@ pub const DEFAULT_BIND_ADDR: &str = "0.0.0.0:8080"; pub const DEFAULT_DB_PATH: &str = "nostr.db"; pub const MAX_EVENT_TAGS: u32 = 5000; pub const MAX_LIMIT: u64 = 500; -pub const DEFAULT_LIMIT: u64 = 10; +pub const DEFAULT_LIMIT: u64 = 50; pub const MAX_FILTERS_PER_REQ: usize = 100; pub const BROADCAST_CHANNEL_SIZE: usize = 100; pub const CLIENT_CHANNEL_SIZE: usize = 32; pub const MAX_SUBSCRIPTIONS: usize = 20; +pub static RELAY_URL: Lazy = + Lazy::new(|| env::var("RELAY_URL").unwrap_or_else(|_| "".to_string())); pub static SERVER_INFO: Lazy = Lazy::new(|| ServerInfo { - contact: "https://www.moec.top/", - description: "Powered by laoXong.", + contact: env::var("RELAY_CONTACT").unwrap_or_else(|_| "https://www.moec.top/".to_string()), + description: env::var("RELAY_DESCRIPTION") + .unwrap_or_else(|_| "Powered by laoXong.".to_string()), limitation: Limitation { max_event_tags: MAX_EVENT_TAGS, max_event_time_newer_than_now: 900, @@ -26,11 +29,14 @@ pub static SERVER_INFO: Lazy = Lazy::new(|| ServerInfo { max_subscriptions: MAX_SUBSCRIPTIONS as u32, // usize to u32 cast min_prefix: 10, }, - name: "A rust nostr relay by laoXong", - pubkey: "63abd4f817e39cca4e6abb6e6cf3e133bb718cf8ec28b38c1645e84d7a6190c6", - software: "https://git.moe.gift/laoxong/nostr-relay", + name: env::var("RELAY_NAME").unwrap_or_else(|_| "A rust nostr relay by laoXong".to_string()), + pubkey: env::var("RELAY_PUBKEY").unwrap_or_else(|_| { + "63abd4f817e39cca4e6abb6e6cf3e133bb718cf8ec28b38c1645e84d7a6190c6".to_string() + }), + software: env::var("RELAY_SOFTWARE") + .unwrap_or_else(|_| "https://git.moe.gift/laoxong/nostr-relay".to_string()), supported_nips: vec![1, 2, 5, 9, 11, 42, 50, 65], - version: env!("CARGO_PKG_VERSION"), // 从 Cargo.toml 获取版本 + version: env!("CARGO_PKG_VERSION").to_string(), // 从 Cargo.toml 获取版本 auth_required: env::var("AUTH_REQUIRED") .unwrap_or_else(|_| "false".to_string()) .to_lowercase() diff --git a/src/database.rs b/src/database.rs index de356a2..1208d35 100644 --- a/src/database.rs +++ b/src/database.rs @@ -15,19 +15,39 @@ pub async fn init_database(pool: &SqlitePool) -> Result<()> { tags TEXT NOT NULL, content TEXT NOT NULL, sig TEXT NOT NULL, + d_tag TEXT, indexed_at INTEGER DEFAULT (unixepoch()) ); "#; + let create_deleted_events_table = r#" + CREATE TABLE IF NOT EXISTS deleted_events ( + event_id TEXT, + pubkey TEXT NOT NULL, + kind INTEGER, + d_tag TEXT, + deleted_at INTEGER DEFAULT (unixepoch()) + ); + "#; + let create_indexes = vec![ "CREATE INDEX IF NOT EXISTS idx_events_pubkey ON events(pubkey);", "CREATE INDEX IF NOT EXISTS idx_events_kind ON events(kind);", "CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);", "CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind ON events(pubkey, kind);", "CREATE INDEX IF NOT EXISTS idx_events_kind_created_at_desc ON events(kind, created_at DESC);", + "CREATE INDEX IF NOT EXISTS idx_events_pubkey_kind_d_tag ON events(pubkey, kind, d_tag);", + "CREATE INDEX IF NOT EXISTS idx_deleted_events_pubkey ON deleted_events(pubkey, event_id);", + "CREATE INDEX IF NOT EXISTS idx_deleted_events_kind_d_tag ON deleted_events(kind, pubkey, d_tag);", ]; query(create_events_table).execute(pool).await?; + // 尝试添加 d_tag 列,如果已存在则忽略错误 + let _ = query("ALTER TABLE events ADD COLUMN d_tag TEXT DEFAULT ''") + .execute(pool) + .await; + + query(create_deleted_events_table).execute(pool).await?; for index_sql in create_indexes { query(index_sql).execute(pool).await?; @@ -39,7 +59,7 @@ pub async fn init_database(pool: &SqlitePool) -> Result<()> { // 获取信任账户列表 pub async fn get_trust_accounts(pool: &SqlitePool) -> Vec { - let pubkey = SERVER_INFO.pubkey; // 获取服务器公钥 + let pubkey = &SERVER_INFO.pubkey; // 获取服务器公钥 // 查询最新的 kind 3 事件(联系人列表),获取其中的 p 标签作为信任账户 let sql = "SELECT tags FROM events WHERE kind = 3 AND pubkey = ? ORDER BY created_at DESC LIMIT 1"; diff --git a/src/models.rs b/src/models.rs index d0342e6..bfb8287 100644 --- a/src/models.rs +++ b/src/models.rs @@ -7,14 +7,14 @@ use uuid::Uuid; // 服务器信息结构体(用于 NIP-11) #[derive(Serialize)] pub struct ServerInfo { - pub contact: &'static str, - pub description: &'static str, + pub contact: String, + pub description: String, pub limitation: Limitation, - pub name: &'static str, - pub pubkey: &'static str, - pub software: &'static str, + pub name: String, + pub pubkey: String, + pub software: String, pub supported_nips: Vec, - pub version: &'static str, + pub version: String, pub auth_required: bool, } diff --git a/src/nostr/event.rs b/src/nostr/event.rs index ac609d6..d9d49b3 100644 --- a/src/nostr/event.rs +++ b/src/nostr/event.rs @@ -7,12 +7,13 @@ use serde_json::json; use sha2::{Digest, Sha256}; use sqlx::SqlitePool; use std::{ + result, sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tokio::sync::{RwLock, mpsc}; -use crate::constants::SERVER_INFO; +use crate::constants::{RELAY_URL, SERVER_INFO}; use crate::models::ClientConnection; use crate::nostr::NostrEvent; use crate::nostr::messages::RelayMessage; @@ -20,7 +21,7 @@ use crate::nostr::messages::RelayMessage; pub trait NostrEventExt { fn serialize_for_id(&self) -> String; // 用于计算事件 ID 的序列化 fn verify(&self) -> bool; // 验证 ID、时间戳、标签数量和签名 - async fn save(&self, pool: &SqlitePool) -> Result<(), sqlx::Error>; // 保存事件到数据库 + async fn save(&self, pool: &SqlitePool) -> Result<(), anyhow::Error>; // 保存事件到数据库 async fn handle_auth_event( &self, client_conn: &Arc>, @@ -89,6 +90,11 @@ impl NostrEventExt for NostrEvent { ); return false; } + // 验证只有一个e标签 + if self.tags.iter().filter(|tag| tag[0] == "e").count() > 1 { + debug!("Event has more than one e tag"); + return false; + } // 4. 解析公钥:从十六进制字符串解析 XOnlyPublicKey let pubkey_bytes: Vec = match hex::decode(&self.pubkey) { @@ -174,9 +180,17 @@ impl NostrEventExt for NostrEvent { } // 保存事件到数据库,并处理可替换事件和删除事件 - async fn save(&self, pool: &SqlitePool) -> Result<(), sqlx::Error> { + async fn save(&self, pool: &SqlitePool) -> Result<(), anyhow::Error> { let tags_json = serde_json::to_string(&self.tags).unwrap(); + // 提取 d_tag + let d_tag = self + .tags + .iter() + .find(|tag| tag.len() >= 2 && tag[0] == "d") + .map(|tag| tag[1].clone()) + .unwrap_or_default(); // 默认为空字符串 + match self.kind { // NIP-09 事件删除 (Event Deletion) 5 => { @@ -188,65 +202,228 @@ impl NostrEventExt for NostrEvent { "Attempting to delete event with ID: {} by request of pubkey {}", event_id_to_delete, self.pubkey ); - let sql = "DELETE FROM events WHERE id = ? AND pubkey = ?"; - let result = sqlx::query(sql) - .bind(event_id_to_delete) - .bind(&self.pubkey) - .execute(pool) - .await?; + let result = + sqlx::query("DELETE FROM events WHERE id = ? AND pubkey = ?") + .bind(event_id_to_delete) + .bind(&self.pubkey) + .execute(pool) + .await?; if result.rows_affected() > 0 { info!( "Deleted event {} by pubkey (kind 5): {}", event_id_to_delete, self.pubkey ); + let result = sqlx::query( + "INSERT INTO deleted_events (event_id, pubkey, deleted_at) VALUES (?, ?, ?)", + ) + .bind(event_id_to_delete) + .bind(&self.pubkey) + .bind(self.created_at as i64) + .execute(pool) + .await?; + if result.rows_affected() > 0 { + info!( + "Deleted event {} by pubkey (kind 5): {}", + event_id_to_delete, self.pubkey + ); + } else { + debug!( + "Could not delete event {} for pubkey {}.", + event_id_to_delete, self.pubkey + ); + } } else { debug!( - "Could not delete event {} for pubkey {}. It might not exist or unauthorized.", + "Could not delete event {} for pubkey {}.", event_id_to_delete, self.pubkey ); } + } else if tag.get(0).map(|s| s == "a").unwrap_or(false) { + let d_tag_value = &tag[1]; + let tag_d_vector = d_tag_value.splitn(3, ':').collect::>(); + if tag_d_vector.len() != 3 { + debug!("Invalid a-tag format: {}", d_tag_value); + continue; + } + let result = sqlx::query( + "DELETE FROM events WHERE kind = ? AND pubkey = ? AND d_tag = ? AND created_at <= ?;", + ) + .bind(tag_d_vector[0].parse::().unwrap()) + .bind(&self.pubkey) + .bind(tag_d_vector[2]) + .bind(self.created_at as i64) + .execute(pool) + .await?; + if result.rows_affected() > 0 { + info!( + "Deleted event {:?} by pubkey (kind 5): {}", + tag_d_vector, self.pubkey + ); + let result = sqlx::query( + "INSERT INTO deleted_events (kind, pubkey, d_tag, deleted_at) VALUES (?, ?, ?, ?)", + ) + .bind(tag_d_vector[0]) + .bind(&self.pubkey) + .bind(tag_d_vector[2]) + .bind(self.created_at as i64) + .execute(pool) + .await?; + if result.rows_affected() > 0 { + info!( + "Deleted event {:?} by pubkey (kind 5): {}", + tag_d_vector, self.pubkey + ); + } else { + debug!( + "Could not delete event {:?} for pubkey {}.", + tag_d_vector, self.pubkey + ); + } + } else { + debug!( + "Could not delete event {:?} for pubkey {}.", + tag_d_vector, self.pubkey + ); + } } } } } // NIP-01 可替换事件 (Replaceable Events): kind 0 (Metadata), kind 3 (Contact List) // NIP-16 可替换事件 (Replaceable Events): kinds 10000 to 19999 - // NIP-33 参数化可替换事件 (Parameterized Replaceable Events): kinds 30000 to 39999 - // 对于所有这些可替换事件,新事件会替换掉旧事件。 - // 这里的简化处理是,对于所有可替换事件,都基于 (pubkey, kind) 来删除旧事件。 - // 对于 NIP-33 事件,严格来说还需要匹配 'd' tag。 - // 但考虑到数据库操作的复杂性以及原始代码的实现,这里仍采用 (pubkey, kind) 的方式。 - // 一个更健壮的 NIP-33 实现可能需要单独的字段来存储 'd' tag 或更复杂的 SQL。 - 0 | 3 | _ - if (self.kind >= 10000 && self.kind < 20000) - || (self.kind >= 30000 && self.kind < 40000) => - { + // 对于这些可替换事件,新事件会替换掉旧事件。 + 0 | 3 | _ if (self.kind >= 10000 && self.kind < 20000) => { debug!( "Attempting to delete previous replaceable event for pubkey: {}, kind: {}", self.pubkey, self.kind ); - let sql = "DELETE FROM events WHERE pubkey = ? AND kind = ?"; - sqlx::query(sql) + let is_deleted = sqlx::query( + "SELECT 1 FROM deleted_events WHERE pubkey = ? AND kind = ? AND deleted_at > ?", + ) + .bind(&self.pubkey) + .bind(self.kind) + .bind(self.created_at as i64) + .fetch_optional(pool) + .await?; + + if is_deleted.is_some() { + return Err(anyhow::anyhow!("Event already deleted")); + } + let sql = "SELECT created_at FROM events WHERE pubkey = ? AND kind = ? ORDER BY created_at DESC LIMIT 1"; + let created_at: Option = sqlx::query_scalar(sql) .bind(&self.pubkey) .bind(self.kind) - .execute(pool) + .fetch_optional(pool) .await?; + + if let Some(prev_created_at) = created_at { + if prev_created_at <= self.created_at { + let sql = "DELETE FROM events WHERE pubkey = ? AND kind = ?"; + sqlx::query(sql) + .bind(&self.pubkey) + .bind(self.kind) + .execute(pool) + .await?; + debug!( + "Deleted previous replaceable event for pubkey: {}, kind: {}", + self.pubkey, self.kind + ); + } else { + debug!( + "Previous replaceable event for pubkey: {}, kind: {} is not older than current event", + self.pubkey, self.kind + ); + // 如果新事件不比旧事件新,则报错 + anyhow::bail!("Event is not newer than existing replaceable event"); + } + } + } + // NIP-33 参数化可替换事件 (Parameterized Replaceable Events): kinds 30000 to 39999 + _ if (self.kind >= 30000 && self.kind < 40000) => { + debug!( + "Attempting to delete previous parameterized replaceable event for pubkey: {}, kind: {}, d_tag: {}", + self.pubkey, self.kind, d_tag + ); + let result = sqlx::query( + "SELECT * FROM deleted_events WHERE pubkey = ? AND kind = ? AND d_tag = ? AND deleted_at > ?", + ) + .bind(&self.pubkey) + .bind(self.kind) + .bind(&d_tag) + .bind(self.created_at as i64) + .fetch_optional(pool) + .await?; + if let Some(_) = result { + debug!( + "Event {} already deleted by pubkey {}", + self.id, self.pubkey + ); + return Err(anyhow::anyhow!("Event already deleted")); + } + let sql = "SELECT created_at FROM events WHERE pubkey = ? AND kind = ? AND d_tag = ? ORDER BY created_at DESC LIMIT 1"; + let created_at: Option = sqlx::query_scalar(sql) + .bind(&self.pubkey) + .bind(self.kind) + .bind(&d_tag) + .fetch_optional(pool) + .await?; + + if let Some(prev_created_at) = created_at { + if prev_created_at < self.created_at { + let sql = "DELETE FROM events WHERE pubkey = ? AND kind = ? AND d_tag = ?"; + sqlx::query(sql) + .bind(&self.pubkey) + .bind(self.kind) + .bind(&d_tag) + .execute(pool) + .await?; + debug!( + "Deleted previous parameterized replaceable event for pubkey: {}, kind: {}, d_tag: {}", + self.pubkey, self.kind, d_tag + ); + } else { + debug!( + "Previous parameterized replaceable event for pubkey: {}, kind: {}, d_tag: {} is not older than current event", + self.pubkey, self.kind, d_tag + ); + anyhow::bail!( + "Event is not newer than existing parameterized replaceable event" + ); + } + } } _ => { /* 非可替换事件不需要在插入前删除 */ } } // 插入新事件 - let sql = "INSERT OR IGNORE INTO events (id, pubkey, created_at, kind, tags, content, sig) VALUES (?, ?, ?, ?, ?, ?, ?);"; - sqlx::query(sql) - .bind(&self.id) - .bind(&self.pubkey) - .bind(self.created_at as i64) - .bind(self.kind) - .bind(tags_json) - .bind(&self.content) - .bind(&self.sig) - .execute(pool) - .await?; + if !(self.kind >= 20000 && self.kind < 30000) { + let result = + sqlx::query("SELECT * FROM deleted_events WHERE event_id = ? AND pubkey = ?") + .bind(&self.id) + .bind(&self.pubkey) + .fetch_optional(pool) + .await?; + if let Some(_) = result { + debug!( + "Event {} already deleted by pubkey {}", + self.id, self.pubkey + ); + return Err(anyhow::anyhow!("Event already deleted")); + } + let sql = "INSERT OR IGNORE INTO events (id, pubkey, created_at, kind, tags, content, sig, d_tag) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"; + sqlx::query(sql) + .bind(&self.id) + .bind(&self.pubkey) + .bind(self.created_at as i64) + .bind(self.kind) + .bind(tags_json) + .bind(&self.content) + .bind(&self.sig) + .bind(&d_tag) + .execute(pool) + .await?; + return Ok(()); + } Ok(()) } @@ -304,10 +481,23 @@ impl NostrEventExt for NostrEvent { } }; + let relay_url = match relay_url { + Some(r) => r, + None => { + let _ = RelayMessage::send_notice( + "AUTH event missing relay tag".to_string(), + to_client_msg_tx, + ) + .await; + return Ok(()); + } + }; // 4. 验证 challenge 是否匹配客户端连接 ID,并且未过期 let is_valid_challenge = { let conn = client_conn.read().await; - if conn.id.to_string() == *challenge { + if relay_url.as_str() != RELAY_URL.as_str() { + false + } else if conn.id.to_string() == *challenge { // 检查 challenge 是否在 15 分钟内有效 (可配置) let connected_at_duration = UNIX_EPOCH + Duration::from_secs(conn.connected_at); match SystemTime::now().duration_since(connected_at_duration) {