nostr_lmdb/
lib.rs

1// Copyright (c) 2022-2023 Yuki Kishimoto
2// Copyright (c) 2023-2025 Rust Nostr Developers
3// Distributed under the MIT software license
4
5//! LMDB storage backend for nostr apps
6//!
7//! Fork of [Pocket](https://github.com/mikedilger/pocket) database.
8
9#![warn(missing_docs)]
10#![warn(rustdoc::bare_urls)]
11#![allow(clippy::mutable_key_type)]
12
13use std::path::{Path, PathBuf};
14
15use nostr_database::prelude::*;
16
17mod store;
18
19use self::store::Store;
20
21// 64-bit
22#[cfg(target_pointer_width = "64")]
23const MAP_SIZE: usize = 1024 * 1024 * 1024 * 32; // 32GB
24
25// 32-bit
26#[cfg(target_pointer_width = "32")]
27const MAP_SIZE: usize = 0xFFFFF000; // 4GB (2^32-4096)
28
29/// Nostr LMDB database builder
30#[derive(Debug, Clone)]
31pub struct NostrLmdbBuilder {
32    /// Database path
33    pub path: PathBuf,
34    /// Custom map size
35    ///
36    /// By default, the following map size is used:
37    /// - 32GB for 64-bit arch
38    /// - 4GB for 32-bit arch
39    pub map_size: Option<usize>,
40    /// Maximum number of reader threads
41    ///
42    /// Defaults to 126 if not set
43    pub max_readers: Option<u32>,
44    /// Number of additional databases to allocate beyond the 9 internal ones
45    ///
46    /// Defaults to 0 if not set
47    pub additional_dbs: Option<u32>,
48}
49
50impl NostrLmdbBuilder {
51    /// New LMDb builder
52    pub fn new<P>(path: P) -> Self
53    where
54        P: AsRef<Path>,
55    {
56        Self {
57            path: path.as_ref().to_path_buf(),
58            map_size: None,
59            max_readers: None,
60            additional_dbs: None,
61        }
62    }
63
64    /// Map size
65    ///
66    /// By default, the following map size is used:
67    /// - 32GB for 64-bit arch
68    /// - 4GB for 32-bit arch
69    pub fn map_size(mut self, map_size: usize) -> Self {
70        self.map_size = Some(map_size);
71        self
72    }
73
74    /// Maximum number of reader threads
75    ///
76    /// Defaults to 126 if not set
77    pub fn max_readers(mut self, max_readers: u32) -> Self {
78        self.max_readers = Some(max_readers);
79        self
80    }
81
82    /// Number of additional databases to allocate beyond the 9 internal ones
83    ///
84    /// Defaults to 0 if not set
85    pub fn additional_dbs(mut self, additional_dbs: u32) -> Self {
86        self.additional_dbs = Some(additional_dbs);
87        self
88    }
89
90    /// Build
91    pub fn build(self) -> Result<NostrLMDB, DatabaseError> {
92        let map_size: usize = self.map_size.unwrap_or(MAP_SIZE);
93        let max_readers: u32 = self.max_readers.unwrap_or(126);
94        let additional_dbs: u32 = self.additional_dbs.unwrap_or(0);
95        let db: Store = Store::open(self.path, map_size, max_readers, additional_dbs)
96            .map_err(DatabaseError::backend)?;
97        Ok(NostrLMDB { db })
98    }
99}
100
101/// LMDB Nostr Database
102#[derive(Debug)]
103pub struct NostrLMDB {
104    db: Store,
105}
106
107impl NostrLMDB {
108    /// Open LMDB database
109    #[inline]
110    pub fn open<P>(path: P) -> Result<Self, DatabaseError>
111    where
112        P: AsRef<Path>,
113    {
114        Self::builder(path).build()
115    }
116
117    /// Get a new builder
118    #[inline]
119    pub fn builder<P>(path: P) -> NostrLmdbBuilder
120    where
121        P: AsRef<Path>,
122    {
123        NostrLmdbBuilder::new(path)
124    }
125}
126
127impl NostrDatabase for NostrLMDB {
128    #[inline]
129    fn backend(&self) -> Backend {
130        Backend::LMDB
131    }
132
133    fn save_event<'a>(
134        &'a self,
135        event: &'a Event,
136    ) -> BoxedFuture<'a, Result<SaveEventStatus, DatabaseError>> {
137        Box::pin(async move {
138            self.db
139                .save_event(event)
140                .await
141                .map_err(DatabaseError::backend)
142        })
143    }
144
145    fn check_id<'a>(
146        &'a self,
147        event_id: &'a EventId,
148    ) -> BoxedFuture<'a, Result<DatabaseEventStatus, DatabaseError>> {
149        Box::pin(async move {
150            if self
151                .db
152                .event_is_deleted(event_id)
153                .map_err(DatabaseError::backend)?
154            {
155                Ok(DatabaseEventStatus::Deleted)
156            } else if self
157                .db
158                .has_event(event_id)
159                .map_err(DatabaseError::backend)?
160            {
161                Ok(DatabaseEventStatus::Saved)
162            } else {
163                Ok(DatabaseEventStatus::NotExistent)
164            }
165        })
166    }
167
168    fn event_by_id<'a>(
169        &'a self,
170        event_id: &'a EventId,
171    ) -> BoxedFuture<'a, Result<Option<Event>, DatabaseError>> {
172        Box::pin(async move {
173            self.db
174                .get_event_by_id(event_id)
175                .map_err(DatabaseError::backend)
176        })
177    }
178
179    fn count(&self, filter: Filter) -> BoxedFuture<Result<usize, DatabaseError>> {
180        Box::pin(async move { self.db.count(filter).map_err(DatabaseError::backend) })
181    }
182
183    fn query(&self, filter: Filter) -> BoxedFuture<Result<Events, DatabaseError>> {
184        Box::pin(async move { self.db.query(filter).map_err(DatabaseError::backend) })
185    }
186
187    fn negentropy_items(
188        &self,
189        filter: Filter,
190    ) -> BoxedFuture<Result<Vec<(EventId, Timestamp)>, DatabaseError>> {
191        Box::pin(async move {
192            self.db
193                .negentropy_items(filter)
194                .map_err(DatabaseError::backend)
195        })
196    }
197
198    fn delete(&self, filter: Filter) -> BoxedFuture<Result<(), DatabaseError>> {
199        Box::pin(async move { self.db.delete(filter).await.map_err(DatabaseError::backend) })
200    }
201
202    #[inline]
203    fn wipe(&self) -> BoxedFuture<Result<(), DatabaseError>> {
204        Box::pin(async move { self.db.wipe().await.map_err(DatabaseError::backend) })
205    }
206}
207
208#[cfg(test)]
209mod tests {
210    use std::collections::HashSet;
211    use std::ops::Deref;
212    use std::time::Duration;
213
214    use async_utility::time;
215    use tempfile::TempDir;
216
217    use super::*;
218
219    const EVENTS: [&str; 14] = [
220        r#"{"id":"b7b1fb52ad8461a03e949820ae29a9ea07e35bcd79c95c4b59b0254944f62805","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644581,"kind":1,"tags":[],"content":"Text note","sig":"ed73a8a4e7c26cd797a7b875c634d9ecb6958c57733305fed23b978109d0411d21b3e182cb67c8ad750884e30ca383b509382ae6187b36e76ee76e6a142c4284"}"#,
221        r#"{"id":"7296747d91c53f1d71778ef3e12d18b66d494a41f688ef244d518abf37c959b6","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644586,"kind":32121,"tags":[["d","id-1"]],"content":"Empty 1","sig":"8848989a8e808f7315e950f871b231c1dff7752048f8957d4a541881d2005506c30e85c7dd74dab022b3e01329c88e69c9d5d55d961759272a738d150b7dbefc"}"#,
222        r#"{"id":"ec6ea04ba483871062d79f78927df7979f67545b53f552e47626cb1105590442","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644591,"kind":32122,"tags":[["d","id-1"]],"content":"Empty 2","sig":"89946113a97484850fe35fefdb9120df847b305de1216dae566616fe453565e8707a4da7e68843b560fa22a932f81fc8db2b5a2acb4dcfd3caba9a91320aac92"}"#,
223        r#"{"id":"63b8b829aa31a2de870c3a713541658fcc0187be93af2032ec2ca039befd3f70","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644596,"kind":32122,"tags":[["d","id-2"]],"content":"","sig":"607b1a67bef57e48d17df4e145718d10b9df51831d1272c149f2ab5ad4993ae723f10a81be2403ae21b2793c8ed4c129e8b031e8b240c6c90c9e6d32f62d26ff"}"#,
224        r#"{"id":"6fe9119c7db13ae13e8ecfcdd2e5bf98e2940ba56a2ce0c3e8fba3d88cd8e69d","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644601,"kind":32122,"tags":[["d","id-3"]],"content":"","sig":"d07146547a726fc9b4ec8d67bbbe690347d43dadfe5d9890a428626d38c617c52e6945f2b7144c4e0c51d1e2b0be020614a5cadc9c0256b2e28069b70d9fc26e"}"#,
225        r#"{"id":"a82f6ebfc709f4e7c7971e6bf738e30a3bc112cfdb21336054711e6779fd49ef","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644606,"kind":32122,"tags":[["d","id-1"]],"content":"","sig":"96d3349b42ed637712b4d07f037457ab6e9180d58857df77eb5fa27ff1fd68445c72122ec53870831ada8a4d9a0b484435f80d3ff21a862238da7a723a0d073c"}"#,
226        r#"{"id":"8ab0cb1beceeb68f080ec11a3920b8cc491ecc7ec5250405e88691d733185832","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644611,"kind":32122,"tags":[["d","id-1"]],"content":"Test","sig":"49153b482d7110e2538eb48005f1149622247479b1c0057d902df931d5cea105869deeae908e4e3b903e3140632dc780b3f10344805eab77bb54fb79c4e4359d"}"#,
227        r#"{"id":"63dc49a8f3278a2de8dc0138939de56d392b8eb7a18c627e4d78789e2b0b09f2","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704644616,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:"]],"content":"","sig":"977e54e5d57d1fbb83615d3a870037d9eb5182a679ca8357523bbf032580689cf481f76c88c7027034cfaf567ba9d9fe25fc8cd334139a0117ad5cf9fe325eef"}"#,
228        r#"{"id":"6975ace0f3d66967f330d4758fbbf45517d41130e2639b54ca5142f37757c9eb","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704644621,"kind":5,"tags":[["a","32122:aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4:id-2"]],"content":"","sig":"9bb09e4759899d86e447c3fa1be83905fe2eda74a5068a909965ac14fcdabaed64edaeb732154dab734ca41f2fc4d63687870e6f8e56e3d9e180e4a2dd6fb2d2"}"#,
229        r#"{"id":"33f5b4e6a38e107638c20f4536db35191d4b8651ba5a2cefec983b9ec2d65084","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645586,"kind":0,"tags":[],"content":"{\"name\":\"Key A\"}","sig":"285d090f45a6adcae717b33771149f7840a8c27fb29025d63f1ab8d95614034a54e9f4f29cee9527c4c93321a7ebff287387b7a19ba8e6f764512a40e7120429"}"#,
230        r#"{"id":"90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704645606,"kind":0,"tags":[],"content":"{\"name\":\"key-a\",\"display_name\":\"Key A\",\"lud16\":\"keya@ln.address\"}","sig":"ec8f49d4c722b7ccae102d49befff08e62db775e5da43ef51b25c47dfdd6a09dc7519310a3a63cbdb6ec6b3250e6f19518eb47be604edeb598d16cdc071d3dbc"}"#,
231        r#"{"id":"a295422c636d3532875b75739e8dae3cdb4dd2679c6e4994c9a39c7ebf8bc620","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1704646569,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"d4dc8368a4ad27eef63cacf667345aadd9617001537497108234fc1686d546c949cbb58e007a4d4b632c65ea135af4fbd7a089cc60ab89b6901f5c3fc6a47b29"}"#, // Invalid event deletion
232        r#"{"id":"999e3e270100d7e1eaa98fcfab4a98274872c1f2dfdab024f32e42a5a12d5b5e","pubkey":"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4","created_at":1704646606,"kind":5,"tags":[["e","90a761aec9b5b60b399a76826141f529db17466deac85696a17e4a243aa271f9"]],"content":"","sig":"4f3a33fd52784cea7ca8428fd35d94d65049712e9aa11a70b1a16a1fcd761c7b7e27afac325728b1c00dfa11e33e78b2efd0430a7e4b28f4ede5b579b3f32614"}"#,
233        r#"{"id":"99a022e6d61c4e39c147d08a2be943b664e8030c0049325555ac1766429c2832","pubkey":"79dff8f82963424e0bb02708a22e44b4980893e3a4be0fa3cb60a43b946764e3","created_at":1705241093,"kind":30333,"tags":[["d","multi-id"],["p","aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4"]],"content":"Multi-tags","sig":"0abfb2b696a7ed7c9e8e3bf7743686190f3f1b3d4045b72833ab6187c254f7ed278d289d52dfac3de28be861c1471421d9b1bfb5877413cbc81c84f63207a826"}"#,
234    ];
235
236    fn decode_events() -> Vec<Event> {
237        EVENTS
238            .iter()
239            .map(|e| Event::from_json(e).expect("Failed to parse event"))
240            .collect()
241    }
242
243    async fn setup_db() -> (NostrLMDB, TempDir) {
244        let temp_dir = TempDir::new().expect("Failed to create temp directory");
245        let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
246        (db, temp_dir)
247    }
248
249    #[tokio::test]
250    async fn test_save_and_query() {
251        let (db, _temp_dir) = setup_db().await;
252        let events = decode_events();
253
254        // Save all events (some will be rejected due to invalid deletions)
255        for (i, event) in events.iter().enumerate() {
256            let status = db.save_event(event).await.expect("Failed to save event");
257            if i == 7 || i == 11 {
258                // These should be rejected for invalid deletions
259                assert!(!status.is_success());
260            } else {
261                assert!(matches!(status, SaveEventStatus::Success));
262            }
263
264            // NOTE: Sleep prevents automatic batching - events in the same batch share
265            // a database snapshot and can't see each other's changes. Deletion events
266            // (7,11) must "see" target events, and replaceable events must observe
267            // earlier events to replace them. Sleep forces sequential processing.
268            // Use this pattern when event N must observe changes from event N-1.
269            time::sleep(Duration::from_millis(10)).await;
270        }
271
272        // Query all events
273        let saved_events = db.query(Filter::new()).await.expect("Failed to query");
274        // Expected: 8 events after applying coordinate deletion validation
275        assert_eq!(saved_events.len(), 8);
276    }
277
278    #[tokio::test]
279    async fn test_save_duplicate() {
280        let (db, _temp_dir) = setup_db().await;
281        let events = decode_events();
282        let event = &events[0];
283
284        // Save event first time
285        let status = db.save_event(event).await.expect("Failed to save event");
286        assert!(matches!(status, SaveEventStatus::Success));
287
288        // Try to save again
289        let status = db.save_event(event).await.expect("Failed to save event");
290        assert!(matches!(
291            status,
292            SaveEventStatus::Rejected(nostr_database::RejectedReason::Duplicate)
293        ));
294    }
295
296    #[tokio::test]
297    async fn test_query_by_filter() {
298        let (db, _temp_dir) = setup_db().await;
299        let events = decode_events();
300
301        // Save all events
302        for event in &events {
303            db.save_event(event).await.expect("Failed to save event");
304        }
305
306        // Query by author
307        let author_filter = Filter::new().author(events[0].pubkey);
308        let author_events = db.query(author_filter).await.expect("Failed to query");
309        assert!(!author_events.is_empty());
310        assert!(author_events.iter().all(|e| e.pubkey == events[0].pubkey));
311
312        // Query by kind
313        let kind_filter = Filter::new().kind(Kind::TextNote);
314        let kind_events = db.query(kind_filter).await.expect("Failed to query");
315        assert!(!kind_events.is_empty());
316        assert!(kind_events.iter().all(|e| e.kind == Kind::TextNote));
317
318        // Query by time range
319        let since = Timestamp::from_secs(1704644590);
320        let until = Timestamp::from_secs(1704644620);
321        let time_filter = Filter::new().since(since).until(until);
322        let time_events = db.query(time_filter).await.expect("Failed to query");
323        assert!(!time_events.is_empty());
324        assert!(time_events
325            .iter()
326            .all(|e| e.created_at >= since && e.created_at <= until));
327    }
328
329    #[tokio::test]
330    async fn test_delete_by_filter() {
331        let (db, _temp_dir) = setup_db().await;
332        let events = decode_events();
333
334        // Save all events
335        for event in &events {
336            db.save_event(event).await.expect("Failed to save event");
337        }
338
339        // Count before delete (8 visible after processing deletions/replacements)
340        let count_before = db
341            .count(Filter::new())
342            .await
343            .expect("Failed to count events");
344        assert_eq!(count_before, 8);
345
346        // Delete text notes
347        let delete_filter = Filter::new().kind(Kind::TextNote);
348        db.delete(delete_filter)
349            .await
350            .expect("Failed to delete events");
351
352        // Count after delete (text notes: indices 0,4,13 - but 0 is deleted = 2 visible text notes deleted)
353        let count_after = db
354            .count(Filter::new())
355            .await
356            .expect("Failed to count events");
357        assert_eq!(count_after, 7); // 8 - 1 text note = 7
358
359        // Verify no text notes remain
360        let text_notes = db
361            .query(Filter::new().kind(Kind::TextNote))
362            .await
363            .expect("Failed to query");
364        assert_eq!(text_notes.len(), 0);
365    }
366
367    #[tokio::test]
368    async fn test_replaceable_events() {
369        let (db, _temp_dir) = setup_db().await;
370        let keys = Keys::generate();
371
372        // Create first replaceable event (kind 0 - metadata)
373        let metadata1 = Metadata::new().name("First");
374        let event1 = EventBuilder::metadata(&metadata1)
375            .custom_created_at(Timestamp::from_secs(1000))
376            .sign_with_keys(&keys)
377            .expect("Failed to sign");
378
379        db.save_event(&event1).await.expect("Failed to save event");
380
381        // Create newer replaceable event with later timestamp
382        let metadata2 = Metadata::new().name("Second");
383        let event2 = EventBuilder::metadata(&metadata2)
384            .custom_created_at(Timestamp::from_secs(2000))
385            .sign_with_keys(&keys)
386            .expect("Failed to sign");
387
388        db.save_event(&event2).await.expect("Failed to save event");
389
390        // Query metadata events
391        let filter = Filter::new().author(keys.public_key()).kind(Kind::Metadata);
392        let results = db.query(filter).await.expect("Failed to query");
393
394        // Should only have the newer event
395        assert_eq!(results.len(), 1);
396        // Verify it's the newer event by content
397        let result_event = results.first().unwrap();
398        assert!(result_event.content.contains("Second"));
399    }
400
401    #[tokio::test]
402    async fn test_addressable_events() {
403        let (db, _temp_dir) = setup_db().await;
404        let keys = Keys::generate();
405
406        // Create first addressable event
407        let event1 = EventBuilder::new(Kind::from(32121), "Content 1")
408            .tag(Tag::identifier("test-id"))
409            .custom_created_at(Timestamp::from_secs(1000))
410            .sign_with_keys(&keys)
411            .expect("Failed to sign");
412
413        db.save_event(&event1).await.expect("Failed to save event");
414
415        // Create newer addressable event with same identifier
416        let event2 = EventBuilder::new(Kind::from(32121), "Content 2")
417            .tag(Tag::identifier("test-id"))
418            .custom_created_at(Timestamp::from_secs(2000))
419            .sign_with_keys(&keys)
420            .expect("Failed to sign");
421
422        db.save_event(&event2).await.expect("Failed to save event");
423
424        // Query addressable events
425        let filter = Filter::new()
426            .author(keys.public_key())
427            .kind(Kind::from(32121));
428        let results = db.query(filter).await.expect("Failed to query");
429
430        // Should only have the newer event
431        assert_eq!(results.len(), 1);
432        // Verify it's the newer event by content
433        let result_event = results.first().unwrap();
434        assert_eq!(result_event.content, "Content 2");
435    }
436
437    #[tokio::test]
438    async fn test_event_deletion() {
439        let (db, _temp_dir) = setup_db().await;
440        let keys = Keys::generate();
441
442        // Create events to delete
443        let event1 = EventBuilder::text_note("To be deleted 1")
444            .sign_with_keys(&keys)
445            .expect("Failed to sign");
446        let event2 = EventBuilder::text_note("To be deleted 2")
447            .sign_with_keys(&keys)
448            .expect("Failed to sign");
449
450        db.save_event(&event1).await.expect("Failed to save event");
451        db.save_event(&event2).await.expect("Failed to save event");
452
453        // Create deletion event
454        let deletion =
455            EventBuilder::delete(EventDeletionRequest::new().id(event1.id).id(event2.id))
456                .sign_with_keys(&keys)
457                .expect("Failed to sign");
458
459        db.save_event(&deletion)
460            .await
461            .expect("Failed to save deletion");
462
463        // Sleep to ensure deletion is processed in the ingester
464        time::sleep(Duration::from_millis(50)).await;
465
466        // Check events are marked as deleted
467        let status1 = db
468            .check_id(&event1.id)
469            .await
470            .expect("Failed to check event");
471        let status2 = db
472            .check_id(&event2.id)
473            .await
474            .expect("Failed to check event");
475
476        // Deleted events return Deleted status
477        // (even though they're physically removed from the database)
478        assert_eq!(status1, DatabaseEventStatus::Deleted);
479        assert_eq!(status2, DatabaseEventStatus::Deleted);
480    }
481
482    #[tokio::test]
483    async fn test_wipe_database() {
484        let (db, _temp_dir) = setup_db().await;
485        let events = decode_events();
486
487        // Save all events
488        for event in &events {
489            db.save_event(event).await.expect("Failed to save event");
490        }
491
492        // Verify events exist (7 visible after processing)
493        let count = db
494            .count(Filter::new())
495            .await
496            .expect("Failed to count events");
497        assert_eq!(count, 8);
498
499        // Wipe database
500        db.wipe().await.expect("Failed to wipe database");
501
502        // Verify database is empty
503        let count_after = db
504            .count(Filter::new())
505            .await
506            .expect("Failed to count events");
507        assert_eq!(count_after, 0);
508    }
509
510    #[tokio::test]
511    async fn test_negentropy_items() {
512        let (db, _temp_dir) = setup_db().await;
513        let events = decode_events();
514
515        // Save all events
516        for event in &events {
517            db.save_event(event).await.expect("Failed to save event");
518        }
519
520        // Get negentropy items (7 visible events)
521        let items = db
522            .negentropy_items(Filter::new())
523            .await
524            .expect("Failed to get negentropy items");
525
526        assert_eq!(items.len(), 8);
527
528        // Verify items are from the original events
529        let event_ids: HashSet<EventId> = events.iter().map(|e| e.id).collect();
530
531        for (id, _timestamp) in items {
532            assert!(
533                event_ids.contains(&id),
534                "Unexpected event ID in negentropy items"
535            );
536        }
537    }
538
539    struct TempDatabase {
540        db: NostrLMDB,
541        // Needed to avoid the drop and deletion of temp folder
542        _temp: TempDir,
543    }
544
545    impl Deref for TempDatabase {
546        type Target = NostrLMDB;
547
548        fn deref(&self) -> &Self::Target {
549            &self.db
550        }
551    }
552
553    impl TempDatabase {
554        fn new() -> Self {
555            let path = tempfile::tempdir().unwrap();
556            Self {
557                db: NostrLMDB::open(&path).unwrap(),
558                _temp: path,
559            }
560        }
561
562        // Return the number of added events
563        async fn add_random_events(&self) -> usize {
564            let keys_a = Keys::generate();
565            let keys_b = Keys::generate();
566
567            let events = vec![
568                EventBuilder::text_note("Text Note A")
569                    .sign_with_keys(&keys_a)
570                    .unwrap(),
571                EventBuilder::text_note("Text Note B")
572                    .sign_with_keys(&keys_b)
573                    .unwrap(),
574                EventBuilder::metadata(
575                    &Metadata::new().name("account-a").display_name("Account A"),
576                )
577                .sign_with_keys(&keys_a)
578                .unwrap(),
579                EventBuilder::metadata(
580                    &Metadata::new().name("account-b").display_name("Account B"),
581                )
582                .sign_with_keys(&keys_b)
583                .unwrap(),
584                EventBuilder::new(Kind::Custom(33_333), "")
585                    .tag(Tag::identifier("my-id-a"))
586                    .sign_with_keys(&keys_a)
587                    .unwrap(),
588                EventBuilder::new(Kind::Custom(33_333), "")
589                    .tag(Tag::identifier("my-id-b"))
590                    .sign_with_keys(&keys_b)
591                    .unwrap(),
592            ];
593
594            // Store
595            for event in events.iter() {
596                self.db.save_event(event).await.unwrap();
597            }
598
599            events.len()
600        }
601
602        async fn add_event(&self, builder: EventBuilder) -> (Keys, Event) {
603            let keys = Keys::generate();
604            let event = builder.sign_with_keys(&keys).unwrap();
605            self.db.save_event(&event).await.unwrap();
606            (keys, event)
607        }
608
609        async fn add_event_with_keys(
610            &self,
611            builder: EventBuilder,
612            keys: &Keys,
613        ) -> (Event, SaveEventStatus) {
614            let event = builder.sign_with_keys(keys).unwrap();
615            let status = self.db.save_event(&event).await.unwrap();
616            (event, status)
617        }
618
619        async fn count_all(&self) -> usize {
620            self.db.count(Filter::new()).await.unwrap()
621        }
622    }
623
624    #[tokio::test]
625    async fn test_event_by_id() {
626        let db = TempDatabase::new();
627
628        let added_events: usize = db.add_random_events().await;
629
630        let (_keys, expected_event) = db.add_event(EventBuilder::text_note("Test")).await;
631
632        let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
633        assert_eq!(event, expected_event);
634
635        // Check if number of events in database match the expected
636        assert_eq!(db.count_all().await, added_events + 1)
637    }
638
639    #[tokio::test]
640    async fn test_replaceable_event() {
641        let db = TempDatabase::new();
642
643        let added_events: usize = db.add_random_events().await;
644
645        let now = Timestamp::now();
646        let metadata = Metadata::new()
647            .name("my-account")
648            .display_name("My Account");
649
650        let (keys, expected_event) = db
651            .add_event(
652                EventBuilder::metadata(&metadata).custom_created_at(now - Duration::from_secs(120)),
653            )
654            .await;
655
656        // Test event by ID
657        let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
658        assert_eq!(event, expected_event);
659
660        // Test filter query
661        let events = db
662            .query(Filter::new().author(keys.public_key).kind(Kind::Metadata))
663            .await
664            .unwrap();
665        assert_eq!(events.to_vec(), vec![expected_event.clone()]);
666
667        // Check if number of events in database match the expected
668        assert_eq!(db.count_all().await, added_events + 1);
669
670        // Replace previous event
671        let (new_expected_event, status) = db
672            .add_event_with_keys(
673                EventBuilder::metadata(&metadata).custom_created_at(now),
674                &keys,
675            )
676            .await;
677        assert!(status.is_success());
678
679        // Test event by ID (MUST be None because replaced)
680        assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none());
681
682        // Test event by ID
683        let event = db
684            .event_by_id(&new_expected_event.id)
685            .await
686            .unwrap()
687            .unwrap();
688        assert_eq!(event, new_expected_event);
689
690        // Test filter query
691        let events = db
692            .query(Filter::new().author(keys.public_key).kind(Kind::Metadata))
693            .await
694            .unwrap();
695        assert_eq!(events.to_vec(), vec![new_expected_event]);
696
697        // Check if number of events in database match the expected
698        assert_eq!(db.count_all().await, added_events + 1);
699    }
700
701    #[tokio::test]
702    async fn test_param_replaceable_event() {
703        let db = TempDatabase::new();
704
705        let added_events: usize = db.add_random_events().await;
706
707        let now = Timestamp::now();
708
709        let (keys, expected_event) = db
710            .add_event(
711                EventBuilder::new(Kind::Custom(33_333), "")
712                    .tag(Tag::identifier("my-id-a"))
713                    .custom_created_at(now - Duration::from_secs(120)),
714            )
715            .await;
716        let coordinate = Coordinate::new(Kind::from(33_333), keys.public_key).identifier("my-id-a");
717
718        // Test event by ID
719        let event = db.event_by_id(&expected_event.id).await.unwrap().unwrap();
720        assert_eq!(event, expected_event);
721
722        // Test filter query
723        let events = db.query(coordinate.clone().into()).await.unwrap();
724        assert_eq!(events.to_vec(), vec![expected_event.clone()]);
725
726        // Check if number of events in database match the expected
727        assert_eq!(db.count_all().await, added_events + 1);
728
729        // Replace previous event
730        let (new_expected_event, status) = db
731            .add_event_with_keys(
732                EventBuilder::new(Kind::Custom(33_333), "Test replace")
733                    .tag(Tag::identifier("my-id-a"))
734                    .custom_created_at(now),
735                &keys,
736            )
737            .await;
738        assert!(status.is_success());
739
740        // Test event by ID (MUST be None` because replaced)
741        assert!(db.event_by_id(&expected_event.id).await.unwrap().is_none());
742
743        // Test event by ID
744        let event = db
745            .event_by_id(&new_expected_event.id)
746            .await
747            .unwrap()
748            .unwrap();
749        assert_eq!(event, new_expected_event);
750
751        // Test filter query
752        let events = db.query(coordinate.into()).await.unwrap();
753        assert_eq!(events.to_vec(), vec![new_expected_event]);
754
755        // Check if number of events in database match the expected
756        assert_eq!(db.count_all().await, added_events + 1);
757
758        // Trey to add param replaceable event with older timestamp (MUSTN'T be stored)
759        let (_, status) = db
760            .add_event_with_keys(
761                EventBuilder::new(Kind::Custom(33_333), "Test replace 2")
762                    .tag(Tag::identifier("my-id-a"))
763                    .custom_created_at(now - Duration::from_secs(2000)),
764                &keys,
765            )
766            .await;
767        assert!(!status.is_success());
768    }
769
770    #[tokio::test]
771    async fn test_full_text_search() {
772        let db = TempDatabase::new();
773
774        let _added_events: usize = db.add_random_events().await;
775
776        let events = db.query(Filter::new().search("Account A")).await.unwrap();
777        assert_eq!(events.len(), 1);
778
779        let events = db.query(Filter::new().search("account a")).await.unwrap();
780        assert_eq!(events.len(), 1);
781
782        let events = db.query(Filter::new().search("text note")).await.unwrap();
783        assert_eq!(events.len(), 2);
784
785        let events = db.query(Filter::new().search("notes")).await.unwrap();
786        assert_eq!(events.len(), 0);
787
788        let events = db.query(Filter::new().search("hola")).await.unwrap();
789        assert_eq!(events.len(), 0);
790    }
791
792    #[tokio::test]
793    async fn test_expected_query_result() {
794        let db = TempDatabase::new();
795
796        // Save events individually to avoid batching issues during test
797        for (i, event_str) in EVENTS.into_iter().enumerate() {
798            let event = Event::from_json(event_str).unwrap();
799            let status = db.save_event(&event).await.unwrap();
800
801            // Invalid deletions (Event 7 and 11) should be rejected
802            if i == 7 || i == 11 {
803                assert!(!status.is_success(), "Event {} should be rejected", i);
804            }
805
806            // Add a small delay to ensure each event is processed individually
807            time::sleep(Duration::from_millis(10)).await;
808        }
809
810        // Expected output after applying NIP-09 deletion validation
811        // Events 7 and 11 are rejected for invalid deletion attempts
812        let expected_output = vec![
813            Event::from_json(EVENTS[13]).unwrap(), // Kind:30333 latest
814            Event::from_json(EVENTS[12]).unwrap(), // Kind:5 deletion
815            Event::from_json(EVENTS[8]).unwrap(),  // Kind:5 coordinate deletion
816            Event::from_json(EVENTS[6]).unwrap(),  // Kind:32122 latest
817            Event::from_json(EVENTS[5]).unwrap(),  // Kind:32122 from different author
818            Event::from_json(EVENTS[4]).unwrap(),  // Kind:32122 from different author
819            Event::from_json(EVENTS[1]).unwrap(),  // Kind:32121
820            Event::from_json(EVENTS[0]).unwrap(),  // Kind:1 text note
821        ];
822
823        let actual = db.query(Filter::new()).await.unwrap().to_vec();
824        assert_eq!(actual, expected_output);
825        assert_eq!(db.count_all().await, 8); // 8 events after deletion validation
826    }
827
828    #[tokio::test]
829    async fn test_kind5_deletion_query_bug_fix() {
830        let temp_dir = tempfile::tempdir().unwrap();
831        let db = NostrLMDB::open(temp_dir.path()).unwrap();
832        let keys = Keys::generate();
833
834        // Create and save an event
835        let event = EventBuilder::text_note("Test event")
836            .sign_with_keys(&keys)
837            .expect("Failed to sign");
838
839        let status = db.save_event(&event).await.expect("Failed to save event");
840        assert!(matches!(status, SaveEventStatus::Success));
841
842        // Sleep to ensure it's committed
843        time::sleep(Duration::from_millis(50)).await;
844
845        // Verify it exists with ID filter
846        let before_by_id = db
847            .query(Filter::new().id(event.id))
848            .await
849            .expect("Failed to query");
850        assert_eq!(before_by_id.len(), 1);
851
852        // Verify it exists with author-kind filter
853        let before_by_author = db
854            .query(Filter::new().author(keys.public_key()).kind(Kind::TextNote))
855            .await
856            .expect("Failed to query");
857        assert_eq!(before_by_author.len(), 1);
858
859        // Create and save a Kind 5 deletion event
860        let deletion_event = EventBuilder::new(Kind::EventDeletion, "")
861            .tag(Tag::event(event.id))
862            .sign_with_keys(&keys)
863            .expect("Failed to sign");
864
865        let del_status = db
866            .save_event(&deletion_event)
867            .await
868            .expect("Failed to save deletion");
869        assert!(matches!(del_status, SaveEventStatus::Success));
870
871        // Sleep to ensure deletion is processed
872        time::sleep(Duration::from_millis(100)).await;
873
874        // Query for the deleted event by ID - should be empty after fix
875        let after_by_id = db
876            .query(Filter::new().id(event.id))
877            .await
878            .expect("Failed to query");
879        assert_eq!(
880            after_by_id.len(),
881            0,
882            "Deleted event should not be returned in ID query"
883        );
884
885        // Query for the deleted event by author-kind - should be empty after fix
886        let after_by_author = db
887            .query(Filter::new().author(keys.public_key()).kind(Kind::TextNote))
888            .await
889            .expect("Failed to query");
890        assert_eq!(
891            after_by_author.len(),
892            0,
893            "Deleted event should not be returned in author-kind query"
894        );
895
896        // The deletion event itself should still be queryable
897        let deletion_events = db
898            .query(Filter::new().kind(Kind::EventDeletion))
899            .await
900            .expect("Failed to query");
901        assert_eq!(
902            deletion_events.len(),
903            1,
904            "Deletion event should remain queryable"
905        );
906    }
907
908    #[tokio::test]
909    async fn test_nip01_replaceable_events_with_identical_timestamps() {
910        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
911        let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
912
913        // Parse the two events with identical timestamps but different IDs
914        let event1_json = r#"{"kind":0,"id":"b39eda8475d345d4da75418f0ee4a9ec183eb0483634cfdc8415cefdf5c02b96","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[],"content":"smallest id","sig":"22e9f94de060c8e0a958b8fbc42914fab12c90be5ea9153aa11f92ed8c38c18a3374221fe37cb40b461d391e8ee92d6dd5083b0be8e146bf90af694560f18e17"}"#;
915        let event2_json = r#"{"kind":0,"id":"eedcb07adabb380e853815534568e05cc5678bc8f9d8cf3dbee8513d37f1c47f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[],"content":"biggest id","sig":"b127afb6b7967b483bffe0d8ca21335509820bbfc37ba09874cc931c6c8c97dec4d44ac5d1071973d3b337c6fe614a8ca2cdc1e89c3c68cd557f4a2eca90cab3"}"#;
916
917        let event1 = Event::from_json(event1_json).expect("Failed to parse event1");
918        let event2 = Event::from_json(event2_json).expect("Failed to parse event2");
919
920        // Confirm both events have the same timestamp
921        assert_eq!(event1.created_at, event2.created_at);
922        assert_eq!(event1.created_at.as_u64(), 1754066538);
923
924        // Confirm event1 has the smaller ID (lexicographically first)
925        assert!(event1.id.to_string() < event2.id.to_string());
926
927        // Test 1: Insert event1 first, then event2
928        {
929            let status1 = db.save_event(&event1).await.expect("Failed to save event1");
930            assert!(matches!(status1, SaveEventStatus::Success));
931
932            let status2 = db.save_event(&event2).await.expect("Failed to save event2");
933            assert!(matches!(
934                status2,
935                SaveEventStatus::Rejected(RejectedReason::Replaced)
936            ));
937
938            // Query to see which event is stored
939            let filter = Filter::new()
940                .author(
941                    PublicKey::from_hex(
942                        "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
943                    )
944                    .unwrap(),
945                )
946                .kind(Kind::Metadata);
947
948            let results = db.query(filter).await.expect("Failed to query");
949            assert_eq!(results.len(), 1, "Should have exactly one metadata event");
950
951            // According to NIP-01, event1 should be retained (smaller ID)
952            assert_eq!(
953                results.first().unwrap().id,
954                event1.id,
955                "NIP-01: Event with lowest ID should be retained"
956            );
957        }
958
959        // Clean database
960        db.wipe().await.expect("Failed to wipe database");
961
962        // Test 2: Insert event2 first, then event1
963        {
964            let status2 = db.save_event(&event2).await.expect("Failed to save event2");
965            assert!(matches!(status2, SaveEventStatus::Success));
966
967            let status1 = db.save_event(&event1).await.expect("Failed to save event1");
968            assert!(matches!(status1, SaveEventStatus::Success));
969
970            // Query to see which event is stored
971            let filter = Filter::new()
972                .author(
973                    PublicKey::from_hex(
974                        "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
975                    )
976                    .unwrap(),
977                )
978                .kind(Kind::Metadata);
979
980            let results = db.query(filter).await.expect("Failed to query");
981            assert_eq!(results.len(), 1, "Should have exactly one metadata event");
982
983            // According to NIP-01, event1 should be retained (smaller ID)
984            assert_eq!(
985                results.first().unwrap().id,
986                event1.id,
987                "NIP-01: Event with lowest ID should be retained"
988            );
989        }
990    }
991
992    #[tokio::test]
993    async fn test_nip01_addressable_events_with_identical_timestamps() {
994        let temp_dir = tempfile::tempdir().expect("Failed to create temp directory");
995        let db = NostrLMDB::open(temp_dir.path()).expect("Failed to open database");
996
997        // Create two addressable events (kind 30023) with identical timestamps but different IDs
998        let event1_json = r#"{"kind":30023,"id":"a11eda8475d345d4da75418f0ee4a9ec183eb0483634cfdc8415cefdf5c02b96","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[["d","article-123"],["title","Test Article"]],"content":"Article with smallest id","sig":"22e9f94de060c8e0a958b8fbc42914fab12c90be5ea9153aa11f92ed8c38c18a3374221fe37cb40b461d391e8ee92d6dd5083b0be8e146bf90af694560f18e17"}"#;
999        let event2_json = r#"{"kind":30023,"id":"feedb07adabb380e853815534568e05cc5678bc8f9d8cf3dbee8513d37f1c47f","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1754066538,"tags":[["d","article-123"],["title","Test Article"]],"content":"Article with biggest id","sig":"b127afb6b7967b483bffe0d8ca21335509820bbfc37ba09874cc931c6c8c97dec4d44ac5d1071973d3b337c6fe614a8ca2cdc1e89c3c68cd557f4a2eca90cab3"}"#;
1000
1001        let event1 = Event::from_json(event1_json).expect("Failed to parse event1");
1002        let event2 = Event::from_json(event2_json).expect("Failed to parse event2");
1003
1004        // Confirm both events have the same timestamp and d-tag
1005        assert_eq!(event1.created_at, event2.created_at);
1006        assert_eq!(event1.created_at.as_u64(), 1754066538);
1007        assert_eq!(event1.tags.identifier(), event2.tags.identifier());
1008        assert_eq!(event1.tags.identifier(), Some("article-123"));
1009
1010        // Confirm event1 has the smaller ID (lexicographically first)
1011        assert!(event1.id.to_string() < event2.id.to_string());
1012
1013        // Test 1: Insert event1 first, then event2
1014        {
1015            let status1 = db.save_event(&event1).await.expect("Failed to save event1");
1016            assert!(matches!(status1, SaveEventStatus::Success));
1017
1018            let status2 = db.save_event(&event2).await.expect("Failed to save event2");
1019            assert!(matches!(
1020                status2,
1021                SaveEventStatus::Rejected(RejectedReason::Replaced)
1022            ));
1023
1024            // Query to see which event is stored
1025            let filter = Filter::new()
1026                .author(
1027                    PublicKey::from_hex(
1028                        "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
1029                    )
1030                    .unwrap(),
1031                )
1032                .kind(Kind::Custom(30023));
1033
1034            let results = db.query(filter).await.expect("Failed to query");
1035            assert_eq!(
1036                results.len(),
1037                1,
1038                "Should have exactly one addressable event"
1039            );
1040
1041            // According to NIP-01, event1 should be retained (smaller ID)
1042            assert_eq!(
1043                results.first().unwrap().id,
1044                event1.id,
1045                "NIP-01: Event with lowest ID should be retained"
1046            );
1047        }
1048
1049        // Clean database
1050        db.wipe().await.expect("Failed to wipe database");
1051
1052        // Test 2: Insert event2 first, then event1
1053        {
1054            let status2 = db.save_event(&event2).await.expect("Failed to save event2");
1055            assert!(matches!(status2, SaveEventStatus::Success));
1056
1057            let status1 = db.save_event(&event1).await.expect("Failed to save event1");
1058            assert!(matches!(status1, SaveEventStatus::Success));
1059
1060            // Query to see which event is stored
1061            let filter = Filter::new()
1062                .author(
1063                    PublicKey::from_hex(
1064                        "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
1065                    )
1066                    .unwrap(),
1067                )
1068                .kind(Kind::Custom(30023));
1069
1070            let results = db.query(filter).await.expect("Failed to query");
1071            assert_eq!(
1072                results.len(),
1073                1,
1074                "Should have exactly one addressable event"
1075            );
1076
1077            // According to NIP-01, event1 should be retained (smaller ID)
1078            assert_eq!(
1079                results.first().unwrap().id,
1080                event1.id,
1081                "NIP-01: Event with lowest ID should be retained"
1082            );
1083        }
1084    }
1085}