Fix: Protocol implementation error
Build and Push Docker Image / build_docker_image (push) Has been cancelled

This commit is contained in:
2025-12-01 01:33:50 +09:00
parent 61924823a6
commit 355ea8963b
6 changed files with 310 additions and 49 deletions
+1
View File
@@ -22,3 +22,4 @@ target/
.idea/
.vscode/
.zed/
.env
@@ -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"
}
+13 -7
View File
@@ -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<String> =
Lazy::new(|| env::var("RELAY_URL").unwrap_or_else(|_| "".to_string()));
pub static SERVER_INFO: Lazy<ServerInfo> = 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<ServerInfo> = 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()
+21 -1
View File
@@ -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<String> {
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";
+6 -6
View File
@@ -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<u32>,
pub version: &'static str,
pub version: String,
pub auth_required: bool,
}
+208 -18
View File
@@ -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<RwLock<ClientConnection>>,
@@ -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<u8> = 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,12 +202,25 @@ 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)
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): {}",
@@ -201,42 +228,189 @@ impl NostrEventExt for NostrEvent {
);
} 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 {
debug!(
"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::<Vec<&str>>();
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::<i64>().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 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<u64> = sqlx::query_scalar(sql)
.bind(&self.pubkey)
.bind(self.kind)
.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<u64> = 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 (?, ?, ?, ?, ?, ?, ?);";
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)
@@ -245,8 +419,11 @@ impl NostrEventExt for NostrEvent {
.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) {