notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

accounts.rs (17739B)


      1 use crate::account::cache::AccountCache;
      2 use crate::account::contacts::Contacts;
      3 use crate::account::mute::AccountMutedData;
      4 use crate::account::relay::{
      5     calculate_relays, modify_advertised_relays, write_relays, AccountRelayData, RelayAction,
      6     RelayDefaults,
      7 };
      8 use crate::scoped_subs::{RelaySelection, ScopedSubIdentity, SubConfig, SubKey};
      9 use crate::storage::AccountStorageWriter;
     10 use crate::user_account::UserAccountSerializable;
     11 use crate::{
     12     AccountStorage, MuteFun, RemoteApi, ScopedSubApi, SingleUnkIdAction, SubOwnerKey, UnknownIds,
     13     UserAccount, ZapWallet,
     14 };
     15 use enostr::{FilledKeypair, Keypair, NormRelayUrl, Pubkey, RelayId};
     16 use hashbrown::HashSet;
     17 use nostrdb::{Filter, Ndb, Note, Subscription, Transaction};
     18 
     19 use std::slice::from_ref;
     20 // TODO: remove this
     21 use std::sync::Arc;
     22 
     23 /// The interface for managing the user's accounts.
     24 /// Represents all user-facing operations related to account management.
     25 pub struct Accounts {
     26     pub cache: AccountCache,
     27     storage_writer: Option<AccountStorageWriter>,
     28     relay_defaults: RelayDefaults,
     29     ndb_subs: AccountNdbSubs,
     30     scoped_remote_initialized: bool,
     31 }
     32 
     33 impl Accounts {
     34     #[allow(clippy::too_many_arguments)]
     35     pub fn new(
     36         key_store: Option<AccountStorage>,
     37         forced_relays: Vec<String>,
     38         fallback: Pubkey,
     39         ndb: &mut Ndb,
     40         txn: &Transaction,
     41         unknown_ids: &mut UnknownIds,
     42     ) -> Self {
     43         let (mut cache, unknown_id) = AccountCache::new(UserAccount::new(
     44             Keypair::only_pubkey(fallback),
     45             AccountData::new(fallback.bytes()),
     46         ));
     47 
     48         unknown_id.process_action(unknown_ids, ndb, txn);
     49 
     50         let mut storage_writer = None;
     51         if let Some(keystore) = key_store {
     52             let (reader, writer) = keystore.rw();
     53             match reader.get_accounts() {
     54                 Ok(accounts) => {
     55                     for account in accounts {
     56                         add_account_from_storage(&mut cache, account).process_action(
     57                             unknown_ids,
     58                             ndb,
     59                             txn,
     60                         )
     61                     }
     62                 }
     63                 Err(e) => {
     64                     tracing::error!("could not get keys: {e}");
     65                 }
     66             }
     67             if let Some(selected) = reader.get_selected_key().ok().flatten() {
     68                 cache.select(selected);
     69             }
     70 
     71             storage_writer = Some(writer);
     72         };
     73 
     74         let relay_defaults = RelayDefaults::new(forced_relays);
     75 
     76         let selected = cache.selected_mut();
     77         let selected_data = &mut selected.data;
     78 
     79         selected_data.query(ndb, txn);
     80 
     81         let ndb_subs = AccountNdbSubs::new(ndb, selected_data);
     82 
     83         Accounts {
     84             cache,
     85             storage_writer,
     86             relay_defaults,
     87             ndb_subs,
     88             scoped_remote_initialized: false,
     89         }
     90     }
     91 
     92     pub(crate) fn remove_account(
     93         &mut self,
     94         pk: &Pubkey,
     95         ndb: &mut Ndb,
     96         remote: &mut RemoteApi<'_>,
     97     ) -> bool {
     98         self.remove_account_internal(pk, ndb, remote)
     99     }
    100 
    101     fn remove_account_internal(
    102         &mut self,
    103         pk: &Pubkey,
    104         ndb: &mut Ndb,
    105         remote: &mut RemoteApi<'_>,
    106     ) -> bool {
    107         let Some(resp) = self.cache.remove(pk) else {
    108             return false;
    109         };
    110 
    111         if pk != self.cache.fallback() {
    112             if let Some(key_store) = &self.storage_writer {
    113                 if let Err(e) = key_store.remove_key(&resp.deleted) {
    114                     tracing::error!("Could not remove account {pk}: {e}");
    115                 }
    116             }
    117         }
    118 
    119         if let Some(swap_to) = resp.swap_to {
    120             let old_pk = resp.deleted.pubkey;
    121             let txn = Transaction::new(ndb).expect("txn");
    122             self.select_account_internal(&swap_to, old_pk, ndb, &txn, remote);
    123         }
    124 
    125         {
    126             let mut scoped_subs = remote.scoped_subs(&*self);
    127             clear_account_remote_subs_for_account(&mut scoped_subs, resp.deleted.pubkey);
    128         }
    129 
    130         true
    131     }
    132 
    133     pub fn contains_full_kp(&self, pubkey: &enostr::Pubkey) -> bool {
    134         self.cache
    135             .get(pubkey)
    136             .is_some_and(|u| u.key.secret_key.is_some())
    137     }
    138 
    139     #[must_use = "UnknownIdAction's must be handled. Use .process_unknown_id_action()"]
    140     pub fn add_account(&mut self, kp: Keypair) -> Option<AddAccountResponse> {
    141         let acc = if let Some(acc) = self.cache.get_mut(&kp.pubkey) {
    142             if kp.secret_key.is_none() || acc.key.secret_key.is_some() {
    143                 tracing::info!("Already have account, not adding");
    144                 return None;
    145             }
    146 
    147             acc.key = kp.clone();
    148             AccType::Acc(&*acc)
    149         } else {
    150             let new_account_data = AccountData::new(kp.pubkey.bytes());
    151             AccType::Entry(
    152                 self.cache
    153                     .add(UserAccount::new(kp.clone(), new_account_data)),
    154             )
    155         };
    156 
    157         if let Some(key_store) = &self.storage_writer {
    158             if let Err(e) = key_store.write_account(&acc.get_acc().into()) {
    159                 tracing::error!("Could not add key for {:?}: {e}", kp.pubkey);
    160             }
    161         }
    162 
    163         Some(AddAccountResponse {
    164             switch_to: kp.pubkey,
    165             unk_id_action: SingleUnkIdAction::pubkey(kp.pubkey),
    166         })
    167     }
    168 
    169     /// Update the `UserAccount` via callback and save the result to disk.
    170     /// return true if the update was successful
    171     pub fn update_current_account(&mut self, update: impl FnOnce(&mut UserAccount)) -> bool {
    172         let cur_account = self.get_selected_account_mut();
    173 
    174         update(cur_account);
    175 
    176         let cur_acc = self.get_selected_account();
    177 
    178         let Some(key_store) = &self.storage_writer else {
    179             return false;
    180         };
    181 
    182         if let Err(err) = key_store.write_account(&cur_acc.into()) {
    183             tracing::error!("Could not add account {:?} to storage: {err}", cur_acc.key);
    184             return false;
    185         }
    186 
    187         true
    188     }
    189 
    190     pub fn selected_filled(&self) -> Option<FilledKeypair<'_>> {
    191         self.get_selected_account().key.to_full()
    192     }
    193 
    194     /// Get the selected account's pubkey as bytes. Common operation so
    195     /// we make it a helper here.
    196     pub fn selected_account_pubkey_bytes(&self) -> &[u8; 32] {
    197         self.get_selected_account().key.pubkey.bytes()
    198     }
    199 
    200     pub fn selected_account_pubkey(&self) -> &Pubkey {
    201         &self.get_selected_account().key.pubkey
    202     }
    203 
    204     pub fn get_selected_account(&self) -> &UserAccount {
    205         self.cache.selected()
    206     }
    207 
    208     pub fn selected_account_has_wallet(&self) -> bool {
    209         self.get_selected_account().wallet.is_some()
    210     }
    211 
    212     fn get_selected_account_mut(&mut self) -> &mut UserAccount {
    213         self.cache.selected_mut()
    214     }
    215 
    216     pub fn get_selected_wallet(&self) -> Option<&ZapWallet> {
    217         self.cache.selected().wallet.as_ref()
    218     }
    219 
    220     pub fn get_selected_wallet_mut(&mut self) -> Option<&mut ZapWallet> {
    221         self.cache.selected_mut().wallet.as_mut()
    222     }
    223 
    224     fn get_selected_account_data(&self) -> &AccountData {
    225         &self.cache.selected().data
    226     }
    227 
    228     pub(crate) fn select_account(
    229         &mut self,
    230         pk_to_select: &Pubkey,
    231         ndb: &mut Ndb,
    232         txn: &Transaction,
    233         remote: &mut RemoteApi<'_>,
    234     ) {
    235         self.select_account_internal_entry(pk_to_select, ndb, txn, remote);
    236     }
    237 
    238     fn select_account_internal_entry(
    239         &mut self,
    240         pk_to_select: &Pubkey,
    241         ndb: &mut Ndb,
    242         txn: &Transaction,
    243         remote: &mut RemoteApi<'_>,
    244     ) {
    245         let old_pk = *self.selected_account_pubkey();
    246 
    247         if !self.cache.select(*pk_to_select) {
    248             return;
    249         }
    250 
    251         self.select_account_internal(pk_to_select, old_pk, ndb, txn, remote);
    252     }
    253 
    254     /// Have already selected in `AccountCache`, updating other things
    255     fn select_account_internal(
    256         &mut self,
    257         pk_to_select: &Pubkey,
    258         old_pk: Pubkey,
    259         ndb: &mut Ndb,
    260         txn: &Transaction,
    261         remote: &mut RemoteApi<'_>,
    262     ) {
    263         if let Some(key_store) = &self.storage_writer {
    264             if let Err(e) = key_store.select_key(Some(*pk_to_select)) {
    265                 tracing::error!("Could not select key {:?}: {e}", pk_to_select);
    266             }
    267         }
    268 
    269         self.get_selected_account_mut().data.query(ndb, txn);
    270         self.ndb_subs.swap_to(ndb, &self.cache.selected().data);
    271 
    272         remote.on_account_switched(old_pk, *pk_to_select, self);
    273 
    274         self.ensure_selected_account_remote_subs(remote);
    275     }
    276 
    277     pub fn mutefun(&self) -> Box<MuteFun> {
    278         let account_data = self.get_selected_account_data();
    279 
    280         let muted = Arc::clone(&account_data.muted.muted);
    281         Box::new(move |note: &Note, thread: &[u8; 32]| muted.is_muted(note, thread))
    282     }
    283 
    284     pub fn mute(&self) -> Box<Arc<crate::Muted>> {
    285         let account_data = self.get_selected_account_data();
    286         Box::new(Arc::clone(&account_data.muted.muted))
    287     }
    288 
    289     pub fn update_max_hashtags_per_note(&mut self, max_hashtags: usize) {
    290         for account in self.cache.accounts_mut() {
    291             account.data.muted.update_max_hashtags(max_hashtags);
    292         }
    293     }
    294 
    295     #[profiling::function]
    296     pub fn update(&mut self, ndb: &mut Ndb, remote: &mut RemoteApi<'_>) {
    297         // IMPORTANT - This function is called in the UI update loop,
    298         // make sure it is fast when idle
    299 
    300         let relay_updated = self
    301             .cache
    302             .selected_mut()
    303             .data
    304             .poll_for_updates(ndb, &self.ndb_subs);
    305 
    306         if !self.scoped_remote_initialized {
    307             self.ensure_selected_account_remote_subs(remote);
    308             return;
    309         }
    310 
    311         if !relay_updated {
    312             return;
    313         }
    314 
    315         self.retarget_selected_account_read_relays(remote);
    316     }
    317 
    318     pub fn get_full<'a>(&'a self, pubkey: &Pubkey) -> Option<FilledKeypair<'a>> {
    319         self.cache.get(pubkey).and_then(|r| r.key.to_full())
    320     }
    321 
    322     pub(crate) fn process_relay_action(&mut self, remote: &mut RemoteApi<'_>, action: RelayAction) {
    323         let acc = self.cache.selected_mut();
    324         modify_advertised_relays(
    325             &acc.key,
    326             action,
    327             remote,
    328             &self.relay_defaults,
    329             &mut acc.data,
    330         );
    331 
    332         self.retarget_selected_account_read_relays(remote);
    333     }
    334 
    335     pub fn selected_account_read_relays(&self) -> HashSet<NormRelayUrl> {
    336         calculate_relays(
    337             &self.relay_defaults,
    338             &self.get_selected_account_data().relay,
    339             true,
    340         )
    341     }
    342 
    343     /// Return the selected account's advertised NIP-65 relays with marker metadata.
    344     pub fn selected_account_advertised_relays(
    345         &self,
    346     ) -> &std::collections::BTreeSet<crate::RelaySpec> {
    347         &self.get_selected_account_data().relay.advertised
    348     }
    349 
    350     pub fn selected_account_write_relays(&self) -> Vec<RelayId> {
    351         write_relays(
    352             &self.relay_defaults,
    353             &self.get_selected_account_data().relay,
    354         )
    355     }
    356 
    357     fn ensure_selected_account_remote_subs(&mut self, remote: &mut RemoteApi<'_>) {
    358         {
    359             let mut scoped_subs = remote.scoped_subs(&*self);
    360             ensure_selected_account_remote_subs_api(&mut scoped_subs, self);
    361         }
    362         self.scoped_remote_initialized = true;
    363     }
    364 
    365     fn retarget_selected_account_read_relays(&mut self, remote: &mut RemoteApi<'_>) {
    366         remote.retarget_selected_account_read_relays(self);
    367         self.scoped_remote_initialized = true;
    368     }
    369 }
    370 
    371 enum AccType<'a> {
    372     Entry(hashbrown::hash_map::OccupiedEntry<'a, Pubkey, UserAccount>),
    373     Acc(&'a UserAccount),
    374 }
    375 
    376 impl<'a> AccType<'a> {
    377     fn get_acc(&'a self) -> &'a UserAccount {
    378         match self {
    379             AccType::Entry(occupied_entry) => occupied_entry.get(),
    380             AccType::Acc(user_account) => user_account,
    381         }
    382     }
    383 }
    384 
    385 fn add_account_from_storage(
    386     cache: &mut AccountCache,
    387     user_account_serializable: UserAccountSerializable,
    388 ) -> SingleUnkIdAction {
    389     let Some(acc) = get_acc_from_storage(user_account_serializable) else {
    390         return SingleUnkIdAction::NoAction;
    391     };
    392 
    393     let pk = acc.key.pubkey;
    394     cache.add(acc);
    395 
    396     SingleUnkIdAction::pubkey(pk)
    397 }
    398 
    399 fn get_acc_from_storage(user_account_serializable: UserAccountSerializable) -> Option<UserAccount> {
    400     let keypair = user_account_serializable.key;
    401     let new_account_data = AccountData::new(keypair.pubkey.bytes());
    402 
    403     let mut wallet = None;
    404     if let Some(wallet_s) = user_account_serializable.wallet {
    405         let m_wallet: Result<crate::ZapWallet, crate::Error> = wallet_s.into();
    406         match m_wallet {
    407             Ok(w) => wallet = Some(w),
    408             Err(e) => {
    409                 tracing::error!("Problem creating wallet from disk: {e}");
    410             }
    411         };
    412     }
    413 
    414     Some(UserAccount {
    415         key: keypair,
    416         wallet,
    417         data: new_account_data,
    418     })
    419 }
    420 
    421 #[derive(Clone)]
    422 pub struct AccountData {
    423     pub(crate) relay: AccountRelayData,
    424     pub(crate) muted: AccountMutedData,
    425     pub contacts: Contacts,
    426 }
    427 
    428 impl AccountData {
    429     pub fn new(pubkey: &[u8; 32]) -> Self {
    430         Self {
    431             relay: AccountRelayData::new(pubkey),
    432             muted: AccountMutedData::new(pubkey),
    433             contacts: Contacts::new(pubkey),
    434         }
    435     }
    436 
    437     #[profiling::function]
    438     pub(super) fn poll_for_updates(&mut self, ndb: &Ndb, ndb_subs: &AccountNdbSubs) -> bool {
    439         let txn = Transaction::new(ndb).expect("txn");
    440         let relay_updated = self.relay.poll_for_updates(ndb, &txn, ndb_subs.relay_ndb);
    441 
    442         self.muted.poll_for_updates(ndb, &txn, ndb_subs.mute_ndb);
    443         self.contacts
    444             .poll_for_updates(ndb, &txn, ndb_subs.contacts_ndb);
    445 
    446         relay_updated
    447     }
    448 
    449     /// Note: query should be called as close to the subscription as possible
    450     pub(super) fn query(&mut self, ndb: &Ndb, txn: &Transaction) {
    451         self.relay.query(ndb, txn);
    452         self.muted.query(ndb, txn);
    453         self.contacts.query(ndb, txn);
    454     }
    455 }
    456 
    457 pub struct AddAccountResponse {
    458     pub switch_to: Pubkey,
    459     pub unk_id_action: SingleUnkIdAction,
    460 }
    461 
    462 fn giftwrap_filter(pk: &Pubkey) -> Filter {
    463     // TODO: since optimize
    464     nostrdb::Filter::new()
    465         .kinds([1059])
    466         .pubkeys([pk.bytes()])
    467         .build()
    468 }
    469 
    470 fn account_remote_owner_key() -> SubOwnerKey {
    471     SubOwnerKey::new("core/accounts/remote-subs")
    472 }
    473 
    474 fn ensure_selected_account_remote_subs_api(
    475     scoped_subs: &mut ScopedSubApi<'_, '_>,
    476     accounts: &Accounts,
    477 ) {
    478     let owner = account_remote_owner_key();
    479     for kind in account_remote_sub_kinds() {
    480         let key = account_remote_sub_key(kind);
    481         let identity = ScopedSubIdentity::account(owner, key);
    482         let config = selected_account_remote_config(accounts, kind);
    483         let _ = scoped_subs.ensure_sub(identity, config);
    484     }
    485 }
    486 
    487 fn clear_account_remote_subs_for_account(
    488     scoped_subs: &mut ScopedSubApi<'_, '_>,
    489     account_pk: Pubkey,
    490 ) {
    491     let owner = account_remote_owner_key();
    492     for kind in account_remote_sub_kinds() {
    493         let key = account_remote_sub_key(kind);
    494         let identity = ScopedSubIdentity::account(owner, key);
    495         let _ = scoped_subs.clear_sub_for_account(account_pk, identity);
    496     }
    497 }
    498 
    499 #[derive(Clone, Copy, Eq, Hash, PartialEq)]
    500 enum AccountRemoteSubKind {
    501     RelayList,
    502     MuteList,
    503     ContactsList,
    504     Giftwrap,
    505 }
    506 
    507 fn account_remote_sub_kinds() -> [AccountRemoteSubKind; 4] {
    508     [
    509         AccountRemoteSubKind::RelayList,
    510         AccountRemoteSubKind::MuteList,
    511         AccountRemoteSubKind::ContactsList,
    512         AccountRemoteSubKind::Giftwrap,
    513     ]
    514 }
    515 
    516 fn account_remote_sub_key(kind: AccountRemoteSubKind) -> SubKey {
    517     SubKey::new(kind)
    518 }
    519 
    520 fn make_account_remote_config(filters: Vec<Filter>, use_transparent: bool) -> SubConfig {
    521     SubConfig {
    522         relays: RelaySelection::AccountsRead,
    523         filters,
    524         use_transparent,
    525     }
    526 }
    527 
    528 fn selected_account_remote_config(accounts: &Accounts, kind: AccountRemoteSubKind) -> SubConfig {
    529     let selected = accounts.get_selected_account_data();
    530     match kind {
    531         AccountRemoteSubKind::RelayList => {
    532             make_account_remote_config(vec![selected.relay.filter.clone()], false)
    533         }
    534         AccountRemoteSubKind::MuteList => {
    535             make_account_remote_config(vec![selected.muted.filter.clone()], false)
    536         }
    537         AccountRemoteSubKind::ContactsList => {
    538             make_account_remote_config(vec![selected.contacts.filter.clone()], true)
    539         }
    540         AccountRemoteSubKind::Giftwrap => make_account_remote_config(
    541             vec![giftwrap_filter(accounts.selected_account_pubkey())],
    542             false,
    543         ),
    544     }
    545 }
    546 
    547 struct AccountNdbSubs {
    548     relay_ndb: Subscription,
    549     mute_ndb: Subscription,
    550     contacts_ndb: Subscription,
    551 }
    552 
    553 impl AccountNdbSubs {
    554     pub fn new(ndb: &mut Ndb, data: &AccountData) -> Self {
    555         let relay_ndb = ndb
    556             .subscribe(from_ref(&data.relay.filter))
    557             .expect("ndb relay list subscription");
    558         let mute_ndb = ndb
    559             .subscribe(from_ref(&data.muted.filter))
    560             .expect("ndb sub");
    561         let contacts_ndb = ndb
    562             .subscribe(from_ref(&data.contacts.filter))
    563             .expect("ndb sub");
    564         Self {
    565             relay_ndb,
    566             mute_ndb,
    567             contacts_ndb,
    568         }
    569     }
    570 
    571     pub fn swap_to(&mut self, ndb: &mut Ndb, new_selection_data: &AccountData) {
    572         let _ = ndb.unsubscribe(self.relay_ndb);
    573         let _ = ndb.unsubscribe(self.mute_ndb);
    574         let _ = ndb.unsubscribe(self.contacts_ndb);
    575 
    576         *self = AccountNdbSubs::new(ndb, new_selection_data);
    577     }
    578 }