notedeck

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

onboarding.rs (4674B)


      1 use std::{cell::RefCell, rc::Rc};
      2 
      3 use egui_virtual_list::VirtualList;
      4 use enostr::{Pubkey, RelayPool};
      5 use nostrdb::{Filter, Ndb, NoteKey, Transaction};
      6 use notedeck::{create_nip51_set, filter::default_limit, Nip51SetCache, UnknownIds};
      7 use uuid::Uuid;
      8 
      9 use crate::subscriptions::Subscriptions;
     10 
     11 #[derive(Debug)]
     12 enum OnboardingState {
     13     AwaitingTrustedPksList(Vec<Filter>),
     14     HaveFollowPacks(Nip51SetCache),
     15 }
     16 
     17 /// Manages the onboarding process. Responsible for retriving the kind 30000 list of trusted pubkeys
     18 /// and then retrieving all follow packs from the trusted pks updating when new ones arrive
     19 #[derive(Default)]
     20 pub struct Onboarding {
     21     state: Option<Result<OnboardingState, OnboardingError>>,
     22     pub list: Rc<RefCell<VirtualList>>,
     23 }
     24 
     25 impl Onboarding {
     26     pub fn get_follow_packs(&self) -> Option<&Nip51SetCache> {
     27         let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &self.state else {
     28             return None;
     29         };
     30 
     31         Some(packs)
     32     }
     33 
     34     pub fn get_follow_packs_mut(&mut self) -> Option<&mut Nip51SetCache> {
     35         let Some(Ok(OnboardingState::HaveFollowPacks(packs))) = &mut self.state else {
     36             return None;
     37         };
     38 
     39         Some(packs)
     40     }
     41 
     42     pub fn process(
     43         &mut self,
     44         pool: &mut RelayPool,
     45         ndb: &Ndb,
     46         subs: &mut Subscriptions,
     47         unknown_ids: &mut UnknownIds,
     48     ) {
     49         match &self.state {
     50             Some(res) => {
     51                 let Ok(OnboardingState::AwaitingTrustedPksList(filter)) = res else {
     52                     return;
     53                 };
     54 
     55                 let txn = Transaction::new(ndb).expect("txns");
     56                 let Ok(res) = ndb.query(&txn, filter, 1) else {
     57                     return;
     58                 };
     59 
     60                 if res.is_empty() {
     61                     return;
     62                 }
     63 
     64                 let key = res.first().expect("checked empty").note_key;
     65 
     66                 let new_state = get_trusted_authors(ndb, &txn, key).and_then(|trusted_pks| {
     67                     let pks: Vec<&[u8; 32]> = trusted_pks.iter().map(|f| f.bytes()).collect();
     68                     Nip51SetCache::new(pool, ndb, &txn, unknown_ids, vec![follow_packs_filter(pks)])
     69                         .map(OnboardingState::HaveFollowPacks)
     70                         .ok_or(OnboardingError::InvalidNip51Set)
     71                 });
     72 
     73                 self.state = Some(new_state);
     74             }
     75             None => {
     76                 let filter = vec![trusted_pks_list_filter()];
     77 
     78                 let subid = Uuid::new_v4().to_string();
     79                 pool.subscribe(subid.clone(), filter.clone());
     80                 subs.subs
     81                     .insert(subid, crate::subscriptions::SubKind::OneShot);
     82 
     83                 let new_state = Some(Ok(OnboardingState::AwaitingTrustedPksList(filter)));
     84                 self.state = new_state;
     85             }
     86         }
     87     }
     88 
     89     // Unsubscribe and clear state
     90     pub fn end_onboarding(&mut self, pool: &mut RelayPool, ndb: &mut Ndb) {
     91         let Some(Ok(OnboardingState::HaveFollowPacks(state))) = &mut self.state else {
     92             self.state = None;
     93             return;
     94         };
     95 
     96         let unified = &state.sub;
     97 
     98         pool.unsubscribe(unified.remote.clone());
     99         let _ = ndb.unsubscribe(unified.local);
    100 
    101         self.state = None;
    102     }
    103 }
    104 
    105 #[derive(Debug)]
    106 pub enum OnboardingError {
    107     InvalidNip51Set,
    108     InvalidTrustedPksListKind,
    109     NdbCouldNotFindNote,
    110 }
    111 
    112 // author providing the list of trusted follow pack authors
    113 const FOLLOW_PACK_AUTHOR: [u8; 32] = [
    114     0x89, 0x5c, 0x2a, 0x90, 0xa8, 0x60, 0xac, 0x18, 0x43, 0x4a, 0xa6, 0x9e, 0x7b, 0x0d, 0xa8, 0x46,
    115     0x57, 0x21, 0x21, 0x6f, 0xa3, 0x6e, 0x42, 0xc0, 0x22, 0xe3, 0x93, 0x57, 0x9c, 0x48, 0x6c, 0xba,
    116 ];
    117 
    118 fn trusted_pks_list_filter() -> Filter {
    119     Filter::new()
    120         .kinds([30000])
    121         .limit(1)
    122         .authors(&[FOLLOW_PACK_AUTHOR])
    123         .tags(["trusted-follow-pack-authors"], 'd')
    124         .build()
    125 }
    126 
    127 pub fn follow_packs_filter(pks: Vec<&[u8; 32]>) -> Filter {
    128     Filter::new()
    129         .kinds([39089])
    130         .limit(default_limit())
    131         .authors(pks)
    132         .build()
    133 }
    134 
    135 /// gets the pubkeys from a kind 30000 follow set
    136 fn get_trusted_authors(
    137     ndb: &Ndb,
    138     txn: &Transaction,
    139     key: NoteKey,
    140 ) -> Result<Vec<Pubkey>, OnboardingError> {
    141     let Ok(note) = ndb.get_note_by_key(txn, key) else {
    142         return Result::Err(OnboardingError::NdbCouldNotFindNote);
    143     };
    144 
    145     if note.kind() != 30000 {
    146         return Result::Err(OnboardingError::InvalidTrustedPksListKind);
    147     }
    148 
    149     let Some(nip51set) = create_nip51_set(note) else {
    150         return Result::Err(OnboardingError::InvalidNip51Set);
    151     };
    152 
    153     Ok(nip51set.pks)
    154 }