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 }