Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/nostr_manager/subscriptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ impl NostrManager {
filter = filter.since(since);
}

self.ensure_relays_connected(&[relay_url.clone()]).await?;
self.ensure_relays_connected(std::slice::from_ref(&relay_url))
.await?;
self.client
.subscribe_with_id_to(vec![relay_url], subscription_id, filter, None)
.await?;
Expand Down
10 changes: 6 additions & 4 deletions src/whitenoise/database/group_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,12 @@ mod tests {
.unwrap();

// Find it in batch query
let results =
GroupInformation::find_by_mls_group_ids(&[group_id.clone()], &whitenoise.database)
.await
.unwrap();
let results = GroupInformation::find_by_mls_group_ids(
std::slice::from_ref(&group_id),
&whitenoise.database,
)
.await
.unwrap();

assert_eq!(results.len(), 1);
assert_eq!(results[0].id, created_group_info.id);
Expand Down
3 changes: 3 additions & 0 deletions src/whitenoise/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ pub enum WhitenoiseError {
#[error("Group has no relays configured")]
GroupMissingRelays,

#[error("Account has no key package relays configured")]
AccountMissingKeyPackageRelays,

#[error("Account not found")]
AccountNotFound,

Expand Down
25 changes: 15 additions & 10 deletions src/whitenoise/event_processor/event_handlers/handle_giftwrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,21 @@ impl Whitenoise {
.and_then(|content| EventId::parse(content).ok());

if let Some(key_package_event_id) = key_package_event_id {
self.delete_key_package_from_relays_for_account(
account,
&key_package_event_id,
false, // For now we don't want to delete the key packages from MLS storage
)
.await?;
tracing::debug!(target: "whitenoise::event_processor::process_welcome", "Deleted used key package from relays");

self.publish_key_package_for_account(account).await?;
tracing::debug!(target: "whitenoise::event_processor::process_welcome", "Published new key package");
let deleted = self
.delete_key_package_for_account(
account,
&key_package_event_id,
false, // For now we don't want to delete the key packages from MLS storage
)
.await?;

if deleted {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we were publishing no matter what here which meant that if there were multiple giftwraps that we processed (that referenced the same key package) then we'd end up publishing way more key packages than we needed. Now we only publish a new key package is we actually removed one from relays.

tracing::debug!(target: "whitenoise::event_processor::process_welcome", "Deleted used key package from relays");
self.publish_key_package_for_account(account).await?;
tracing::debug!(target: "whitenoise::event_processor::process_welcome", "Published new key package");
} else {
tracing::debug!(target: "whitenoise::event_processor::process_welcome", "Key package already deleted, skipping publish");
}
} else {
tracing::warn!(target: "whitenoise::event_processor::process_welcome", "No key package event id found in welcome event");
}
Expand Down
215 changes: 199 additions & 16 deletions src/whitenoise/key_packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ impl Whitenoise {
account: &Account,
) -> Result<(String, [Tag; 4])> {
let nostr_mls = Account::create_nostr_mls(account.pubkey, &self.config.data_dir)?;
let key_package_relay_urls = account
.key_package_relays(self)
.await?
let key_package_relays = account.key_package_relays(self).await?;

if key_package_relays.is_empty() {
return Err(WhitenoiseError::AccountMissingKeyPackageRelays);
}

let key_package_relay_urls = key_package_relays
.iter()
.map(|r| r.url.clone())
.collect::<Vec<RelayUrl>>();
Expand All @@ -25,10 +29,14 @@ impl Whitenoise {
}

/// Publishes the MLS key package for the given account to its key package relays.
pub(crate) async fn publish_key_package_for_account(&self, account: &Account) -> Result<()> {
pub async fn publish_key_package_for_account(&self, account: &Account) -> Result<()> {
// Extract key package data while holding the lock
let (encoded_key_package, tags) = self.encoded_key_package(account).await?;
let relays = account.key_package_relays(self).await?;

if relays.is_empty() {
return Err(WhitenoiseError::AccountMissingKeyPackageRelays);
}
let signer = self
.secrets_store
.get_nostr_keys_for_pubkey(&account.pubkey)?;
Expand All @@ -43,12 +51,14 @@ impl Whitenoise {
}

/// Deletes the key package from the relays for the given account.
pub(crate) async fn delete_key_package_from_relays_for_account(
///
/// Returns `true` if a key package was found and deleted, `false` if no key package was found.
pub async fn delete_key_package_for_account(
&self,
account: &Account,
event_id: &EventId,
delete_mls_stored_keys: bool,
) -> Result<()> {
) -> Result<bool> {
let key_package_filter = Filter::new()
.id(*event_id)
.kind(Kind::MlsKeyPackage)
Expand All @@ -75,18 +85,191 @@ impl Whitenoise {
nostr_mls.delete_key_package_from_storage(&key_package)?;
}

self.nostr
.publish_event_deletion_with_signer(
&event.id,
&account.key_package_relays(self).await?,
signer,
)
let key_package_relays = account.key_package_relays(self).await?;
if key_package_relays.is_empty() {
return Err(WhitenoiseError::AccountMissingKeyPackageRelays);
}

let result = self
.nostr
.publish_event_deletion_with_signer(&event.id, &key_package_relays, signer)
.await?;
} else {
tracing::warn!(target: "whitenoise::delete_key_package_from_relays_for_account", "Key package event not found for account: {}", account.pubkey.to_hex());
return Ok(());
return Ok(!result.success.is_empty());
}
Ok(false)
}

Ok(())
/// Finds and returns all key package events for the given account from its key package relays.
///
/// This method fetches all key package events (not just the latest) authored by the account
/// from the account's key package relays. This is useful for getting a complete view of
/// all published key packages.
///
/// # Arguments
///
/// * `account` - The account to find key packages for
///
/// # Returns
///
/// Returns a vector of all key package events found for the account.
///
/// # Errors
///
/// Returns an error if:
/// - Account has no key package relays configured
/// - Failed to retrieve account's key package relays
/// - Network error while fetching events from relays
/// - NostrSDK error during event streaming
pub async fn fetch_all_key_packages_for_account(
&self,
account: &Account,
) -> Result<Vec<Event>> {
let key_package_relays = account.key_package_relays(self).await?;
let relay_urls: Vec<RelayUrl> = key_package_relays.iter().map(|r| r.url.clone()).collect();

if relay_urls.is_empty() {
return Err(WhitenoiseError::AccountMissingKeyPackageRelays);
}

let key_package_filter = Filter::new()
.kind(Kind::MlsKeyPackage)
.author(account.pubkey);

let mut key_package_stream = self
.nostr
.client
.stream_events_from(relay_urls, key_package_filter, Duration::from_secs(10))
.await?;

let mut key_package_events = Vec::new();
while let Some(event) = key_package_stream.next().await {
key_package_events.push(event);
}

tracing::debug!(
target: "whitenoise::fetch_all_key_packages_for_account",
"Found {} key package events for account {}",
key_package_events.len(),
account.pubkey.to_hex()
);

Ok(key_package_events)
}

/// Deletes all key package events from relays for the given account.
///
/// This method finds all key package events authored by the account and publishes
/// deletion events to remove them from the relays. Optionally, it can also delete
/// the MLS stored keys from local storage.
///
/// # Arguments
///
/// * `account` - The account to delete key packages for
/// * `delete_mls_stored_keys` - Whether to also delete MLS keys from local storage
///
/// # Returns
///
/// Returns the number of key packages that were deleted.
///
/// # Errors
///
/// Returns an error if:
/// - Account has no key package relays configured
/// - Failed to retrieve account's key package relays
/// - Failed to get signing keys for the account
/// - Network error while fetching or publishing events
/// - Failed to delete MLS keys from storage (if requested)
pub async fn delete_all_key_packages_for_account(
&self,
account: &Account,
delete_mls_stored_keys: bool,
) -> Result<usize> {
let key_package_events = self.fetch_all_key_packages_for_account(account).await?;

if key_package_events.is_empty() {
tracing::info!(
target: "whitenoise::delete_all_key_packages_for_account",
"No key package events found for account {}",
account.pubkey.to_hex()
);
return Ok(0);
}

let signer = self
.secrets_store
.get_nostr_keys_for_pubkey(&account.pubkey)?;
let key_package_relays = account.key_package_relays(self).await?;

if key_package_relays.is_empty() {
return Err(WhitenoiseError::AccountMissingKeyPackageRelays);
}

let mut deleted_count = 0;

if delete_mls_stored_keys {
// Create NostrMls instance once for MLS storage deletion
let nostr_mls = Account::create_nostr_mls(account.pubkey, &self.config.data_dir)?;

for event in &key_package_events {
// Delete from MLS storage
match nostr_mls.parse_key_package(event) {
Ok(key_package) => {
if let Err(e) = nostr_mls.delete_key_package_from_storage(&key_package) {
tracing::warn!(
target: "whitenoise::delete_all_key_packages_for_account",
"Failed to delete key package from storage for event {}: {}",
event.id,
e
);
}
}
Err(e) => {
tracing::warn!(
target: "whitenoise::delete_all_key_packages_for_account",
"Failed to parse key package for event {}: {}",
event.id,
e
);
}
}
}
}

// Delete from relays (always happens regardless of delete_mls_stored_keys)
for event in &key_package_events {
// Publish deletion event
match self
.nostr
.publish_event_deletion_with_signer(&event.id, &key_package_relays, signer.clone())
.await
{
Ok(_) => {
deleted_count += 1;
tracing::debug!(
target: "whitenoise::delete_all_key_packages_for_account",
"Published deletion event for key package {}",
event.id
);
}
Err(e) => {
tracing::error!(
target: "whitenoise::delete_all_key_packages_for_account",
"Failed to publish deletion event for key package {}: {}",
event.id,
e
);
}
}
}
Comment on lines +241 to +263
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Count deletions only when at least one relay reports success.

Right now any Ok(_) increments the count even if no relay accepted the deletion. Match the single-delete path’s semantics.

-            match self
+            match self
                 .nostr
                 .publish_event_deletion_with_signer(&event.id, &key_package_relays, signer.clone())
                 .await
             {
-                Ok(_) => {
-                    deleted_count += 1;
-                    tracing::debug!(
-                        target: "whitenoise::delete_all_key_packages_for_account",
-                        "Published deletion event for key package {}",
-                        event.id
-                    );
-                }
+                Ok(result) => {
+                    if result.success.is_empty() {
+                        tracing::warn!(
+                            target: "whitenoise::delete_all_key_packages_for_account",
+                            "Deletion published but no relay reported success for {}",
+                            event.id
+                        );
+                    } else {
+                        deleted_count += 1;
+                        tracing::debug!(
+                            target: "whitenoise::delete_all_key_packages_for_account",
+                            "Published deletion event for key package {} ({} relay(s) ok)",
+                            event.id,
+                            result.success.len()
+                        );
+                    }
+                }
                 Err(e) => {
                     tracing::error!(
                         target: "whitenoise::delete_all_key_packages_for_account",
                         "Failed to publish deletion event for key package {}: {}",
                         event.id,
                         e
                     );
                 }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match self
.nostr
.publish_event_deletion_with_signer(&event.id, &key_package_relays, signer.clone())
.await
{
Ok(_) => {
deleted_count += 1;
tracing::debug!(
target: "whitenoise::delete_all_key_packages_for_account",
"Published deletion event for key package {}",
event.id
);
}
Err(e) => {
tracing::error!(
target: "whitenoise::delete_all_key_packages_for_account",
"Failed to publish deletion event for key package {}: {}",
event.id,
e
);
}
}
}
match self
.nostr
.publish_event_deletion_with_signer(&event.id, &key_package_relays, signer.clone())
.await
{
Ok(result) => {
if result.success.is_empty() {
tracing::warn!(
target: "whitenoise::delete_all_key_packages_for_account",
"Deletion published but no relay reported success for {}",
event.id
);
} else {
deleted_count += 1;
tracing::debug!(
target: "whitenoise::delete_all_key_packages_for_account",
"Published deletion event for key package {} ({} relay(s) ok)",
event.id,
result.success.len()
);
}
}
Err(e) => {
tracing::error!(
target: "whitenoise::delete_all_key_packages_for_account",
"Failed to publish deletion event for key package {}: {}",
event.id,
e
);
}
}
}
🤖 Prompt for AI Agents
In src/whitenoise/key_packages.rs around lines 239 to 261, the code increments
deleted_count on any Ok(_) from publish_event_deletion_with_signer even if no
relay actually accepted the deletion; change the Ok arm to inspect the returned
success information (the Ok value) and only increment deleted_count when at
least one relay reports success (e.g., check the returned per-relay results or
accepted count), log a debug message listing which relays accepted when
incrementing, and treat an Ok-with-no-accepts like a non-deletion (log a
warning/info and do not increment). Keep the Err branch unchanged.


tracing::info!(
target: "whitenoise::delete_all_key_packages_for_account",
"Deleted {} out of {} key package events for account {}",
deleted_count,
key_package_events.len(),
account.pubkey.to_hex()
);

Ok(deleted_count)
}
}