Skip to content

Commit

Permalink
Merge pull request #1112 from MutinyWallet/opt-label-activity
Browse files Browse the repository at this point in the history
Optimize label activity
  • Loading branch information
TonyGiorgio authored Apr 5, 2024
2 parents 6623e28 + 74dd6ba commit 9144558
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 68 deletions.
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

0 comments on commit 9144558

Please sign in to comment.