Skip to content

Commit 9c08c11

Browse files
erskingardneryukibtc
authored andcommitted
Add nostr-mls-memory-storage crate
This adds an in-memory implementation of the `NostrMlsStorageProvider` traits. Pull-Request: #839 Reviewed-by: Yuki Kishimoto <[email protected]> Signed-off-by: Yuki Kishimoto <[email protected]>
1 parent c197595 commit 9c08c11

File tree

12 files changed

+920
-0
lines changed

12 files changed

+920
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
* blossom: add new crate with Blossom support ([Daniel D’Aquino] at https://github.com/rust-nostr/nostr/pull/838)
3333
* mls-storage: add new crate with traits and types for mls storage implementations ([JeffG] at https://github.com/rust-nostr/nostr/pull/836)
34+
* mls-memory-storage: add an in-memory implementation for MLS ([JeffG] at https://github.com/rust-nostr/nostr/pull/839)
3435

3536
## v0.41.0 - 2025/04/15
3637

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ nostr-connect = { version = "0.41", path = "./crates/nostr-connect", default-fea
2525
nostr-database = { version = "0.41", path = "./crates/nostr-database", default-features = false }
2626
nostr-indexeddb = { version = "0.41", path = "./crates/nostr-indexeddb", default-features = false }
2727
nostr-lmdb = { version = "0.41", path = "./crates/nostr-lmdb", default-features = false }
28+
nostr-mls-memory-storage = { version = "0.41", path = "./crates/nostr-mls-memory-storage", default-features = false }
2829
nostr-mls-storage = { version = "0.41", path = "./crates/nostr-mls-storage", default-features = false }
2930
nostr-ndb = { version = "0.41", path = "./crates/nostr-ndb", default-features = false }
3031
nostr-relay-builder = { version = "0.41", path = "./crates/nostr-relay-builder", default-features = false }

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The project is split up into several crates in the `crates/` directory:
1313
* [**nostr-ndb**](./crates/nostr-ndb): [nostrdb](https://github.com/damus-io/nostrdb) storage backend
1414
* [**nostr-indexeddb**](./crates/nostr-indexeddb): IndexedDB storage backend
1515
* [**nostr-mls-storage**](./crates/nostr-mls-storage): Storage traits for using MLS messaging
16+
* [**nostr-mls-memory-storage**](./crates/nostr-mls-memory-storage): In-memory storage for nostr-mls
1617
* [**nostr-keyring**](./crates/nostr-keyring): Nostr Keyring
1718
* [**nostr-relay-pool**](./crates/nostr-relay-pool): Nostr Relay Pool
1819
* [**nostr-sdk**](./crates/nostr-sdk): High level client library

contrib/scripts/check-crates.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ buildargs=(
4242
"-p nostr-database"
4343
"-p nostr-lmdb"
4444
"-p nostr-mls-storage"
45+
"-p nostr-mls-memory-storage"
4546
"-p nostr-indexeddb --target wasm32-unknown-unknown"
4647
"-p nostr-ndb"
4748
"-p nostr-keyring"
@@ -60,6 +61,7 @@ buildargs=(
6061
skip_msrv=(
6162
"-p nostr-lmdb" # MSRV: 1.72.0
6263
"-p nostr-mls-storage" # MSRV: 1.74.0
64+
"-p nostr-mls-memory-storage" # MSRV: 1.74.0
6365
"-p nostr-keyring" # MSRV: 1.75.0
6466
"-p nostr-keyring --features async" # MSRV: 1.75.0
6567
"-p nostr-sdk --features tor" # MSRV: 1.77.0

contrib/scripts/release.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ args=(
77
"-p nostr-database"
88
"-p nostr-lmdb"
99
"-p nostr-mls-storage"
10+
"-p nostr-mls-memory-storage"
1011
"-p nostr-ndb"
1112
"-p nostr-indexeddb"
1213
"-p nostr-keyring"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "nostr-mls-memory-storage"
3+
version = "0.41.0"
4+
edition = "2021"
5+
description = "In-memory database for nostr-mls that implements the NostrMlsStorageProvider Trait"
6+
authors = ["Jeff Gardner <[email protected]>", "Yuki Kishimoto <[email protected]>", "Rust Nostr Developers"]
7+
homepage.workspace = true
8+
repository.workspace = true
9+
license.workspace = true
10+
readme = "README.md"
11+
rust-version = "1.74.0"
12+
keywords = ["nostr", "mls", "openmls", "memory"]
13+
14+
[dependencies]
15+
lru.workspace = true
16+
nostr = { workspace = true, features = ["std"] }
17+
nostr-mls-storage.workspace = true
18+
openmls_memory_storage = { version = "0.3", default-features = false }
19+
parking_lot = "0.12"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Nostr MLS Memory Storage
2+
3+
Memory-based storage implementation for [Nostr MLS](../nostr-mls). This crate provides a storage backend that implements the `NostrMlsStorageProvider` trait from the [nostr-mls-storage](../nostr-mls-storage) crate.
4+
5+
## Features
6+
7+
- Uses an LRU (Least Recently Used) caching mechanism to store data in memory
8+
- Provides both read and write operations that are thread-safe through `parking_lot::RwLock`
9+
- Configurable cache size (default: 1000 items)
10+
- Non-persistent storage that is cleared when the application terminates
11+
12+
## Performance
13+
14+
This implementation uses `parking_lot::RwLock` instead of the standard library's `std::sync::RwLock` for improved performance. The `parking_lot` implementation offers several advantages:
15+
16+
- Smaller memory footprint
17+
- Faster lock acquisition and release
18+
- No poisoning on panic
19+
- More efficient read-heavy workloads, which is ideal for this caching implementation
20+
- Consistent behavior across different platforms
21+
22+
## Example Usage
23+
24+
```rust,ignore
25+
use nostr_mls_memory_storage::NostrMlsMemoryStorage;
26+
use nostr_mls_storage::NostrMlsStorageProvider;
27+
28+
// Create a new memory storage instance
29+
let storage = NostrMlsMemoryStorage::default();
30+
31+
// Or create with a custom cache size
32+
let custom_storage = NostrMlsMemoryStorage::with_cache_size(100);
33+
```
34+
35+
For more advanced usage examples, see the tests in the source code.
36+
37+
## State
38+
39+
**This library is in an ALPHA state**, things that are implemented generally work but the API will change in breaking ways.
40+
41+
## Donations
42+
43+
`rust-nostr` is free and open-source. This means we do not earn any revenue by selling it. Instead, we rely on your financial support. If you actively use any of the `rust-nostr` libs/software/services, then please [donate](https://rust-nostr.org/donate).
44+
45+
## License
46+
47+
This project is distributed under the MIT software license - see the [LICENSE](../../LICENSE) file for details
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Memory-based storage implementation of the NostrMlsStorageProvider trait for Nostr MLS groups
2+
3+
use nostr::PublicKey;
4+
use nostr_mls_storage::groups::error::GroupError;
5+
use nostr_mls_storage::groups::types::*;
6+
use nostr_mls_storage::groups::GroupStorage;
7+
use nostr_mls_storage::messages::types::Message;
8+
9+
use crate::NostrMlsMemoryStorage;
10+
11+
impl GroupStorage for NostrMlsMemoryStorage {
12+
fn save_group(&self, group: Group) -> Result<Group, GroupError> {
13+
// Store in the MLS group ID cache
14+
{
15+
let mut cache = self.groups_cache.write();
16+
cache.put(group.mls_group_id.clone(), group.clone());
17+
}
18+
19+
// Store in the Nostr group ID cache
20+
{
21+
let mut cache = self.groups_by_nostr_id_cache.write();
22+
cache.put(group.nostr_group_id.clone(), group.clone());
23+
}
24+
25+
Ok(group)
26+
}
27+
28+
fn all_groups(&self) -> Result<Vec<Group>, GroupError> {
29+
let cache = self.groups_cache.read();
30+
// Convert the values from the cache to a Vec
31+
let groups: Vec<Group> = cache.iter().map(|(_, v)| v.clone()).collect();
32+
Ok(groups)
33+
}
34+
35+
fn find_group_by_mls_group_id(&self, mls_group_id: &[u8]) -> Result<Group, GroupError> {
36+
let cache = self.groups_cache.read();
37+
if let Some(group) = cache.peek(mls_group_id) {
38+
// Return a clone of the found group
39+
return Ok(group.clone());
40+
}
41+
42+
Err(GroupError::NotFound)
43+
}
44+
45+
fn find_group_by_nostr_group_id(&self, nostr_group_id: &str) -> Result<Group, GroupError> {
46+
let cache = self.groups_by_nostr_id_cache.read();
47+
if let Some(group) = cache.peek(nostr_group_id) {
48+
// Return a clone of the found group
49+
return Ok(group.clone());
50+
}
51+
52+
Err(GroupError::NotFound)
53+
}
54+
55+
fn messages(&self, mls_group_id: &[u8]) -> Result<Vec<Message>, GroupError> {
56+
// Check if the group exists first
57+
self.find_group_by_mls_group_id(mls_group_id)?;
58+
59+
let cache = self.messages_by_group_cache.read();
60+
if let Some(messages) = cache.peek(mls_group_id) {
61+
return Ok(messages.clone());
62+
}
63+
64+
// If not in cache but group exists, return empty vector
65+
Ok(Vec::new())
66+
}
67+
68+
fn admins(&self, mls_group_id: &[u8]) -> Result<Vec<PublicKey>, GroupError> {
69+
// Find the group first
70+
if let Ok(group) = self.find_group_by_mls_group_id(mls_group_id) {
71+
// Return the admin pubkeys from the group
72+
return Ok(group.admin_pubkeys);
73+
}
74+
75+
Err(GroupError::NotFound)
76+
}
77+
78+
fn group_relays(&self, mls_group_id: &[u8]) -> Result<Vec<GroupRelay>, GroupError> {
79+
// Check if the group exists first
80+
self.find_group_by_mls_group_id(mls_group_id)?;
81+
82+
let cache = self.group_relays_cache.read();
83+
if let Some(relays) = cache.peek(mls_group_id) {
84+
return Ok(relays.clone());
85+
}
86+
87+
// If not in cache but group exists, return empty vector
88+
Ok(Vec::new())
89+
}
90+
91+
fn save_group_relay(&self, group_relay: GroupRelay) -> Result<GroupRelay, GroupError> {
92+
let mls_group_id = group_relay.mls_group_id.clone();
93+
94+
// Check if the group exists first
95+
self.find_group_by_mls_group_id(&mls_group_id)?;
96+
97+
let group_relay_clone = group_relay.clone();
98+
99+
{
100+
let mut cache = self.group_relays_cache.write();
101+
// Get existing relays or create new vector
102+
let relays = match cache.get(&mls_group_id) {
103+
Some(existing_relays) => {
104+
let mut new_relays = existing_relays.clone();
105+
// Add the new relay if it doesn't already exist
106+
if !new_relays
107+
.iter()
108+
.any(|r| r.relay_url == group_relay.relay_url)
109+
{
110+
new_relays.push(group_relay_clone);
111+
}
112+
new_relays
113+
}
114+
None => vec![group_relay_clone],
115+
};
116+
117+
// Update the cache with the new vector
118+
cache.put(mls_group_id, relays);
119+
}
120+
121+
Ok(group_relay)
122+
}
123+
}

0 commit comments

Comments
 (0)