diff --git a/src/db/mod.rs b/src/db/mod.rs index ee66f4b8..c91595f6 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -107,7 +107,7 @@ fn upgrade(db: &Connection, mut version: u16) -> Result<(), Error> { apply_sql!(db, version, 2, "schema2.sql"); apply_sql!(db, version, 3, "schema3.sql"); apply_sql!(db, version, 4, "schema4.sql"); + apply_sql!(db, version, 5, "schema5.sql"); info!("Database is at version {}", version); - Ok(()) } diff --git a/src/db/schema5.sql b/src/db/schema5.sql new file mode 100644 index 00000000..29e5b14c --- /dev/null +++ b/src/db/schema5.sql @@ -0,0 +1,2 @@ +INSERT INTO settings (key, value) values ('num_relays_per_person', '2'); +INSERT INTO settings (key, value) values ('max_relays', '15'); diff --git a/src/overlord/mod.rs b/src/overlord/mod.rs index aa41174a..ad5d0fbf 100644 --- a/src/overlord/mod.rs +++ b/src/overlord/mod.rs @@ -174,16 +174,37 @@ impl Overlord { // Pick Relays and start Minions { let pubkeys: Vec = crate::globals::followed_pubkeys().await; + let num_relays_per_person = GLOBALS.settings.lock().await.num_relays_per_person; + let max_relays = GLOBALS.settings.lock().await.max_relays; + let mut pubkey_counts: HashMap = HashMap::new(); + for pk in pubkeys.iter() { + pubkey_counts.insert(pk.clone(), num_relays_per_person); + } let mut relay_picker = RelayPicker { relays: all_relays, - pubkeys: pubkeys.clone(), + pubkey_counts, person_relays: DbPersonRelay::fetch_for_pubkeys(&pubkeys).await?, }; let mut best_relay: BestRelay; + let mut relay_count = 0; loop { + if relay_count >= max_relays { + warn!( + "Safety catch: we have picked {} relays. That's enough.", + max_relays + ); + break; + } + if relay_picker.is_degenerate() { + info!( + "Relay picker is degenerate, relays={} pubkey_counts={}, person_relays={}", + relay_picker.relays.len(), + relay_picker.pubkey_counts.len(), + relay_picker.person_relays.len() + ); break; } @@ -192,6 +213,7 @@ impl Overlord { relay_picker = rp; if best_relay.is_degenerate() { + info!("Best relay is now degenerate."); break; } @@ -206,11 +228,15 @@ impl Overlord { }); info!( - "Picked relay {}, {} people left", + "Picked relay {} covering {} people.", &best_relay.relay.url, - relay_picker.pubkeys.len() + best_relay.pubkeys.len() ); + + relay_count += 1; } + + info!("Listening on {} relays", relay_count); } // Get desired events from relays diff --git a/src/overlord/relay_picker.rs b/src/overlord/relay_picker.rs index 7a2ebce7..8f1d1548 100644 --- a/src/overlord/relay_picker.rs +++ b/src/overlord/relay_picker.rs @@ -1,18 +1,19 @@ use crate::db::{DbPersonRelay, DbRelay}; use crate::error::Error; use nostr_types::PublicKeyHex; +use std::collections::HashMap; use tracing::info; /// See RelayPicker::best() pub struct RelayPicker { pub relays: Vec, - pub pubkeys: Vec, + pub pubkey_counts: HashMap, pub person_relays: Vec, } impl RelayPicker { pub fn is_degenerate(&self) -> bool { - self.relays.is_empty() || self.pubkeys.is_empty() || self.person_relays.is_empty() + self.relays.is_empty() || self.pubkey_counts.is_empty() || self.person_relays.is_empty() } /// This function takes a RelayPicker which consists of a list of relays, @@ -21,7 +22,7 @@ impl RelayPicker { /// the public keys such a relay will cover. It also outpus a new RelayPicker /// that contains only the remaining relays and public keys. pub fn best(mut self) -> Result<(BestRelay, RelayPicker), Error> { - if self.pubkeys.is_empty() { + if self.pubkey_counts.is_empty() { return Err(Error::General( "best_relay called for zero people".to_owned(), )); @@ -35,7 +36,7 @@ impl RelayPicker { info!( "Searching for the best relay among {} for {} people", self.relays.len(), - self.pubkeys.len() + self.pubkey_counts.len() ); // Keep score @@ -57,7 +58,7 @@ impl RelayPicker { // Multiply scores by relay rank #[allow(clippy::needless_range_loop)] for i in 0..self.relays.len() { - score[i] *= self.relays[i].rank.unwrap_or(1); + score[i] *= self.relays[i].rank.unwrap_or(3); } let winner_index = score @@ -76,12 +77,30 @@ impl RelayPicker { .map(|x| PublicKeyHex(x.person.clone())) .collect(); - self.pubkeys.retain(|x| !covered_public_keys.contains(x)); + // Decrement entries where we found another relay for them + let mut changed = false; + for (pubkey, count) in self.pubkey_counts.iter_mut() { + if covered_public_keys.contains(pubkey) { + *count -= 1; + changed = true; + } + } - self.person_relays.retain(|pr| { - !covered_public_keys.contains(&PublicKeyHex(pr.person.clone())) - && pr.relay != winner.url - }); + // If the pubkey_counts did not change + if !changed { + // then we are now degenerate. + // Output a BestRelay with zero public keys to signal this + return Ok(( + BestRelay { + relay: winner, + pubkeys: vec![], + }, + self, + )); + } + + // Remove entries with 0 more relays needed + self.pubkey_counts.retain(|_, v| *v > 0); Ok(( BestRelay { diff --git a/src/settings.rs b/src/settings.rs index b0693ea2..1b67d481 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -8,6 +8,8 @@ pub const DEFAULT_AUTOFOLLOW: bool = false; pub const DEFAULT_VIEW_POSTS_REFERRED_TO: bool = true; pub const DEFAULT_VIEW_POSTS_REFERRING_TO: bool = false; pub const DEFAULT_VIEW_THREADED: bool = true; +pub const DEFAULT_NUM_RELAYS_PER_PERSON: u8 = 2; +pub const DEFAULT_MAX_RELAYS: u8 = 15; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Settings { @@ -19,6 +21,8 @@ pub struct Settings { pub view_posts_referred_to: bool, pub view_posts_referring_to: bool, pub view_threaded: bool, + pub num_relays_per_person: u8, + pub max_relays: u8, } impl Default for Settings { @@ -30,6 +34,8 @@ impl Default for Settings { view_posts_referred_to: DEFAULT_VIEW_POSTS_REFERRED_TO, view_posts_referring_to: DEFAULT_VIEW_POSTS_REFERRING_TO, view_threaded: DEFAULT_VIEW_THREADED, + num_relays_per_person: DEFAULT_NUM_RELAYS_PER_PERSON, + max_relays: DEFAULT_MAX_RELAYS, } } } @@ -60,6 +66,13 @@ impl Settings { settings.view_posts_referring_to = numstr_to_bool(row.1) } "view_threaded" => settings.view_threaded = numstr_to_bool(row.1), + "num_relays_per_person" => { + settings.num_relays_per_person = + row.1.parse::().unwrap_or(DEFAULT_NUM_RELAYS_PER_PERSON) + } + "max_relays" => { + settings.max_relays = row.1.parse::().unwrap_or(DEFAULT_MAX_RELAYS) + } _ => {} } } @@ -75,7 +88,8 @@ impl Settings { "REPLACE INTO settings (key, value) VALUES \ ('feed_chunk', ?),('overlap', ?),('autofollow', ?),\ ('view_posts_referred_to', ?),('view_posts_referring_to', ?),\ - ('view_threaded', ?)", + ('view_threaded', ?),('num_relays_per_person', ?), \ + ('max_relays', ?)", )?; stmt.execute(( self.feed_chunk, @@ -92,6 +106,8 @@ impl Settings { "0" }, if self.view_threaded { "1" } else { "0" }, + self.num_relays_per_person, + self.max_relays, ))?; Ok(()) diff --git a/src/ui/settings.rs b/src/ui/settings.rs index dd51b134..3aedbad7 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -16,6 +16,22 @@ pub(super) fn update( ui.separator(); + ui.add_space(24.0); + ui.heading("How Many Relays to Query"); + + ui.horizontal(|ui| { + ui.label("Number of relays to query per person: ").on_hover_text("We will query N relays per person. Many people share the same relays so those will be queried about multiple people. Takes affect on restart."); + ui.add(Slider::new(&mut app.settings.num_relays_per_person, 1..=5).text("relays")); + }); + + ui.horizontal(|ui| { + ui.label("Maximum total number of relays to query: ") + .on_hover_text( + "We will not connect to more than this many relays. Takes affect on restart.", + ); + ui.add(Slider::new(&mut app.settings.max_relays, 1..=30).text("relays")); + }); + ui.add_space(24.0); ui.heading("How Many Posts to Load");