Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize label activity #1112

Merged
merged 2 commits into from
Apr 5, 2024
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
23 changes: 15 additions & 8 deletions mutiny-core/src/labels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub struct LabelItem {
/// List of addresses that have this label
pub addresses: HashSet<String>,
/// List of invoices that have this label
pub invoices: Vec<Bolt11Invoice>,
pub invoices: HashSet<Bolt11Invoice>,
/// Epoch time in seconds when this label was last used
pub last_used_time: u64,
}
Expand Down Expand Up @@ -234,7 +234,7 @@ impl<S: MutinyStorage> LabelStorage for S {
// Create a new label item
let label_item = LabelItem {
addresses,
invoices: vec![],
invoices: HashSet::new(),
last_used_time: now,
};
self.set_data(key, label_item, None)?;
Expand Down Expand Up @@ -263,9 +263,7 @@ impl<S: MutinyStorage> LabelStorage for S {
Some(mut label_item) => {
// Add the invoice to the label item
// and sort so we can dedup the invoices
label_item.invoices.push(invoice.clone());
label_item.invoices.sort();
label_item.invoices.dedup();
label_item.invoices.insert(invoice.clone());

// Update the last used timestamp
label_item.last_used_time = now;
Expand All @@ -281,9 +279,10 @@ impl<S: MutinyStorage> LabelStorage for S {
}
None => {
// Create a new label item
let invoices = HashSet::from_iter(vec![invoice.clone()]);
let label_item = LabelItem {
addresses: HashSet::new(),
invoices: vec![invoice.clone()],
invoices,
last_used_time: now,
};
self.set_data(key, label_item, None)?;
Expand Down Expand Up @@ -574,7 +573,7 @@ mod tests {
"test2".to_string(),
LabelItem {
addresses: HashSet::from_iter(vec!["1BitcoinEaterAddressDontSendf59kuE".to_string()]),
invoices: vec![Bolt11Invoice::from_str("lnbc923720n1pj9nr6zpp5xmvlq2u5253htn52mflh2e6gn7pk5ht0d4qyhc62fadytccxw7hqhp5l4s6qwh57a7cwr7zrcz706qx0qy4eykcpr8m8dwz08hqf362egfscqzzsxqzfvsp5pr7yjvcn4ggrf6fq090zey0yvf8nqvdh2kq7fue0s0gnm69evy6s9qyyssqjyq0fwjr22eeg08xvmz88307yqu8tqqdjpycmermks822fpqyxgshj8hvnl9mkh6srclnxx0uf4ugfq43d66ak3rrz4dqcqd23vxwpsqf7dmhm").unwrap()],
invoices: HashSet::from_iter(vec![Bolt11Invoice::from_str("lnbc923720n1pj9nr6zpp5xmvlq2u5253htn52mflh2e6gn7pk5ht0d4qyhc62fadytccxw7hqhp5l4s6qwh57a7cwr7zrcz706qx0qy4eykcpr8m8dwz08hqf362egfscqzzsxqzfvsp5pr7yjvcn4ggrf6fq090zey0yvf8nqvdh2kq7fue0s0gnm69evy6s9qyyssqjyq0fwjr22eeg08xvmz88307yqu8tqqdjpycmermks822fpqyxgshj8hvnl9mkh6srclnxx0uf4ugfq43d66ak3rrz4dqcqd23vxwpsqf7dmhm").unwrap()]),
..Default::default()
},
);
Expand Down Expand Up @@ -888,7 +887,15 @@ mod tests {

let label_item = storage.get_label(&new_label).unwrap();
assert!(label_item.is_some());
assert_eq!(label_item.clone().unwrap().invoices, vec![invoice]);
assert_eq!(
label_item
.clone()
.unwrap()
.invoices
.into_iter()
.collect_vec(),
vec![invoice]
);
assert_eq!(
label_item.unwrap().addresses.into_iter().collect_vec(),
vec![address.to_string()]
Expand Down
66 changes: 59 additions & 7 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ pub use crate::gossip::{GOSSIP_SYNC_TIME_KEY, NETWORK_GRAPH_KEY, PROB_SCORER_KEY
pub use crate::keymanager::generate_seed;
pub use crate::ldkstorage::{CHANNEL_CLOSURE_PREFIX, CHANNEL_MANAGER_KEY, MONITORS_PREFIX_KEY};
use crate::storage::{
list_payment_info, persist_payment_info, update_nostr_contact_list, IndexItem, MutinyStorage,
DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY, ONCHAIN_PREFIX,
PAYMENT_INBOUND_PREFIX_KEY, PAYMENT_OUTBOUND_PREFIX_KEY, SUBSCRIPTION_TIMESTAMP,
get_payment_hash_from_key, list_payment_info, persist_payment_info, update_nostr_contact_list,
IndexItem, MutinyStorage, DEVICE_ID_KEY, EXPECTED_NETWORK_KEY, NEED_FULL_SYNC_KEY,
ONCHAIN_PREFIX, PAYMENT_INBOUND_PREFIX_KEY, PAYMENT_OUTBOUND_PREFIX_KEY,
SUBSCRIPTION_TIMESTAMP,
};
use crate::{auth::MutinyAuthClient, hermes::HermesClient, logging::MutinyLogger};
use crate::{blindauth::BlindAuthClient, cashu::CashuHttpClient};
Expand Down Expand Up @@ -114,6 +115,7 @@ use nostr_sdk::{NostrSigner, RelayPoolNotification};
use reqwest::multipart::{Form, Part};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashSet;
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::time::Instant;
Expand Down Expand Up @@ -1720,10 +1722,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
true => PAYMENT_INBOUND_PREFIX_KEY,
false => PAYMENT_OUTBOUND_PREFIX_KEY,
};
let payment_hash_str = key
.trim_start_matches(prefix)
.splitn(2, '_') // To support the old format that had `_{node_id}` at the end
.collect::<Vec<&str>>()[0];
let payment_hash_str = get_payment_hash_from_key(key, prefix);
let hash: [u8; 32] = FromHex::from_hex(payment_hash_str)?;

return MutinyInvoice::from(info, PaymentHash(hash), inbound, labels).map(Some);
Expand Down Expand Up @@ -1810,6 +1809,59 @@ impl<S: MutinyStorage> MutinyWallet<S> {
Ok(activities)
}

/// Returns all the lightning activity for a given label
pub async fn get_label_activity(
&self,
label: &String,
) -> Result<Vec<ActivityItem>, MutinyError> {
let Some(label_item) = self.node_manager.get_label(label)? else {
return Ok(Vec::new());
};

// get all the payment hashes for this label
let payment_hashes: HashSet<sha256::Hash> = label_item
.invoices
.into_iter()
.map(|i| *i.payment_hash())
.collect();

let index = self.storage.activity_index();
let index = index.try_read()?.clone().into_iter().collect_vec();

let labels_map = self.storage.get_invoice_labels()?;

let mut activities = Vec::with_capacity(index.len());
for item in index {
if item.key.starts_with(PAYMENT_INBOUND_PREFIX_KEY) {
let payment_hash_str =
get_payment_hash_from_key(&item.key, PAYMENT_INBOUND_PREFIX_KEY);
let hash = sha256::Hash::from_str(payment_hash_str)?;

if payment_hashes.contains(&hash) {
if let Some(mutiny_invoice) =
self.get_invoice_internal(&item.key, true, &labels_map)?
{
activities.push(ActivityItem::Lightning(Box::new(mutiny_invoice)));
}
}
} else if item.key.starts_with(PAYMENT_OUTBOUND_PREFIX_KEY) {
let payment_hash_str =
get_payment_hash_from_key(&item.key, PAYMENT_OUTBOUND_PREFIX_KEY);
let hash = sha256::Hash::from_str(payment_hash_str)?;

if payment_hashes.contains(&hash) {
if let Some(mutiny_invoice) =
self.get_invoice_internal(&item.key, true, &labels_map)?
{
activities.push(ActivityItem::Lightning(Box::new(mutiny_invoice)));
}
}
}
}

Ok(activities)
}

pub fn list_invoices(&self) -> Result<Vec<MutinyInvoice>, MutinyError> {
let mut inbound_invoices = self.list_payment_info_from_persisters(true)?;
let mut outbound_invoices = self.list_payment_info_from_persisters(false)?;
Expand Down
3 changes: 2 additions & 1 deletion mutiny-core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2725,6 +2725,7 @@ mod wasm_test {
use crate::test_utils::create_node;
use crate::{error::MutinyError, storage::persist_payment_info};
use crate::{HTLCStatus, PrivacyLevel};
use itertools::Itertools;
use lightning::ln::channelmanager::PaymentId;
use lightning::ln::PaymentHash;
use lightning_invoice::Bolt11InvoiceDescription;
Expand Down Expand Up @@ -2789,7 +2790,7 @@ mod wasm_test {

assert!(label_item.last_used_time >= now);
assert!(label_item.addresses.is_empty());
assert_eq!(label_item.invoices, vec![invoice]);
assert_eq!(label_item.invoices.into_iter().collect_vec(), vec![invoice]);
}

#[test]
Expand Down
49 changes: 2 additions & 47 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
use crate::auth::MutinyAuthClient;
use crate::event::HTLCStatus;
use crate::labels::LabelStorage;
use crate::ldkstorage::CHANNEL_CLOSURE_PREFIX;
use crate::logging::LOGGING_KEY;
use crate::utils::{sleep, spawn};
use crate::ActivityItem;
use crate::MutinyInvoice;
use crate::MutinyWalletConfig;
use crate::{
Expand Down Expand Up @@ -966,47 +964,6 @@ impl<S: MutinyStorage> NodeManager<S> {
Ok(details_opt.map(|(d, _)| d))
}

/// Returns all the on-chain and lightning activity for a given label
pub async fn get_label_activity(
&self,
label: &String,
) -> Result<Vec<ActivityItem>, MutinyError> {
let Some(label_item) = self.get_label(label)? else {
return Ok(Vec::new());
};

let mut activity = vec![];
for inv in label_item.invoices.iter() {
if let Ok(ln) = self.get_invoice_by_hash(inv.payment_hash()).await {
// Only show paid and in-flight invoices
match ln.status {
HTLCStatus::Succeeded | HTLCStatus::InFlight => {
activity.push(ActivityItem::Lightning(Box::new(ln)));
}
HTLCStatus::Pending | HTLCStatus::Failed => {}
}
}
}
let onchain = self
.list_onchain()
.map_err(|e| {
log_warn!(self.logger, "Failed to get bdk history: {e}");
e
})
.unwrap_or_default();

for on in onchain {
if on.labels.contains(label) {
activity.push(ActivityItem::OnChain(on));
}
}

// Newest first
activity.sort_by(|a, b| b.cmp(a));

Ok(activity)
}

/// Adds labels to the TransactionDetails based on the address labels.
/// This will panic if the TransactionDetails does not have a transaction.
/// Make sure you flag `include_raw` when calling `list_transactions` to
Expand Down Expand Up @@ -2113,10 +2070,8 @@ pub fn create_lsp_config(
mod tests {
use crate::{
encrypt::encryption_key_from_pass,
nodemanager::{
ActivityItem, ChannelClosure, MutinyInvoice, NodeManager, TransactionDetails,
},
MutinyWalletConfigBuilder, PrivacyLevel,
nodemanager::{ChannelClosure, MutinyInvoice, NodeManager, TransactionDetails},
ActivityItem, MutinyWalletConfigBuilder, PrivacyLevel,
};
use crate::{keymanager::generate_seed, nodemanager::NodeManagerBuilder};
use bdk::chain::ConfirmationTime;
Expand Down
11 changes: 7 additions & 4 deletions mutiny-core/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -986,10 +986,7 @@ pub(crate) fn list_payment_info<S: MutinyStorage>(
Ok(map
.into_iter()
.map(|(key, value)| {
let payment_hash_str = key
.trim_start_matches(prefix)
.splitn(2, '_') // To support the old format that had `_{node_id}` at the end
.collect::<Vec<&str>>()[0];
let payment_hash_str = get_payment_hash_from_key(key.as_str(), prefix);
let hash: [u8; 32] =
FromHex::from_hex(payment_hash_str).expect("key should be a sha256 hash");
(PaymentHash(hash), value)
Expand Down Expand Up @@ -1057,6 +1054,12 @@ where
}
}

pub(crate) fn get_payment_hash_from_key<'a>(key: &'a str, prefix: &str) -> &'a str {
key.trim_start_matches(prefix)
.splitn(2, '_') // To support the old format that had `_{node_id}` at the end
.collect::<Vec<&str>>()[0]
}

#[cfg(test)]
mod tests {
use crate::test_utils::*;
Expand Down
2 changes: 1 addition & 1 deletion mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ impl MutinyWallet {
label: String,
) -> Result<JsValue /* Vec<ActivityItem> */, MutinyJsError> {
// get activity from the node manager
let activity = self.inner.node_manager.get_label_activity(&label).await?;
let activity = self.inner.get_label_activity(&label).await?;
let mut activity: Vec<ActivityItem> = activity.into_iter().map(|a| a.into()).collect();

// add contact to the activity item it has one, otherwise return the activity list
Expand Down
Loading