diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/transport/identity.rs | 90 |
1 files changed, 83 insertions, 7 deletions
diff --git a/src/transport/identity.rs b/src/transport/identity.rs index 0c4cda4..94faded 100644 --- a/src/transport/identity.rs +++ b/src/transport/identity.rs @@ -20,7 +20,9 @@ //! (`learn_addresses`): hints, not proofs. A message to a node goes to the addresses //! it proved itself at, best first (`Paths::rank`, `Paths::send_first`: the next //! when a send fails); with only hints, those are asked WHO, and the send is refused -//! until one answers (the node retries refused sends). +//! until one answers (the node retries refused sends). A node proven on some +//! transports is asked WHO at its hints on the others too, now and then +//! (`OTHER_PATHS_ASK_INTERVAL`), so that each transport both have becomes a path. //! //! Addresses and the transports behind them are the paths layer's //! (`transport::paths`): the books keep addresses as paths keep them. @@ -50,6 +52,10 @@ pub(crate) const WHO_LABEL: &[u8] = b"continuum/who/1"; pub(crate) const ME_LABEL: &[u8] = b"continuum/me/1"; /// An address is asked WHO at most this often, unless a seed is being resolved. const ASK_INTERVAL: Duration = Duration::from_secs(5); +/// A node's addresses on transports it hasn't proven itself on are asked WHO at most +/// this often (`Book::unproven_hints`): over Tor, a WHO is a dial that takes seconds, +/// and an address that doesn't answer shouldn't be pressed. +const OTHER_PATHS_ASK_INTERVAL: Duration = Duration::from_secs(60); /// How long resolving a seed waits for its ME. const RESOLVE_TIMEOUT: Duration = Duration::from_secs(10); /// Addresses kept per node from hints: about one per transport. @@ -190,19 +196,44 @@ impl Book { } } + /// Where `node` is said to be on transports it hasn't proven itself on yet. + fn unproven_hints(&self, node: &str) -> Vec<String> { + let proven = self.proven.get(node); + self.hints + .get(node) + .into_iter() + .flatten() + .filter(|hint| { + proven.is_none_or(|proven| { + !proven + .iter() + .any(|address| scheme_of(address) == scheme_of(hint)) + }) + }) + .cloned() + .collect() + } + /// Whether to ask `address` WHO now (always, if `force`), noting that it was asked. fn ask(&mut self, address: &str, now: Instant, force: bool) -> bool { - if !force - && self - .asked - .get(address) - .is_some_and(|at| now.duration_since(*at) < ASK_INTERVAL) + let interval = if force { Duration::ZERO } else { ASK_INTERVAL }; + self.ask_every(address, now, interval) + } + + /// Whether to ask `address` WHO now, if it wasn't within `interval`, noting that it + /// was asked. + fn ask_every(&mut self, address: &str, now: Instant, interval: Duration) -> bool { + if self + .asked + .get(address) + .is_some_and(|at| now.duration_since(*at) < interval) { return false; } if self.asked.len() >= MAX_ENTRIES { + // The longest interval: other paths' asks are kept until they are due again. self.asked - .retain(|_, at| now.duration_since(*at) < ASK_INTERVAL); + .retain(|_, at| now.duration_since(*at) < OTHER_PATHS_ASK_INTERVAL); } self.asked.insert(address.to_owned(), now); true @@ -301,6 +332,27 @@ impl Shared { if !self.lock().ask(address, Instant::now(), force) { return; } + self.send_who(address); + } + + /// Asks `node`'s addresses on transports it hasn't proven itself on WHO, now and then + /// (`OTHER_PATHS_ASK_INTERVAL`): its ME there proves another path to it, which the + /// paths layer then measures, and fails over to. + fn ask_other_paths(self: &Arc<Self>, node: &str) { + let due: Vec<String> = { + let mut book = self.lock(); + let now = Instant::now(); + book.unproven_hints(node) + .into_iter() + .filter(|address| book.ask_every(address, now, OTHER_PATHS_ASK_INTERVAL)) + .collect() + }; + for address in due { + self.send_who(&address); + } + } + + fn send_who(self: &Arc<Self>, address: &str) { let shared = Arc::clone(self); let address = address.to_owned(); tokio::spawn(async move { shared.send_proof(MessageKind::Who, &address).await }); @@ -571,6 +623,9 @@ impl Transport for IdentityTransport { /// fails (`Paths::send_first`); refused if it proved itself nowhere yet. async fn send_encoded(&self, message: &EncodedMessage) -> Result<()> { let proven = self.shared.proven_at(&message.peer); + if !proven.is_empty() { + self.shared.ask_other_paths(&message.peer); + } if proven.is_empty() { let hints = { let mut book = self.shared.lock(); @@ -652,6 +707,27 @@ mod tests { } #[test] + fn other_paths_are_the_hints_on_transports_not_proven() { + let now = Instant::now(); + let mut book = Book::default(); + book.hints + .insert("n1".into(), vec!["s:x".into(), "t:x".into(), "u:x".into()]); + // Proven nowhere: every hint. + assert_eq!(book.unproven_hints("n1"), ["s:x", "t:x", "u:x"]); + // Proven on s (even at another address): the others. + book.bind("s:y", "n1", now); + assert_eq!(book.unproven_hints("n1"), ["t:x", "u:x"]); + assert!(book.ask_every("t:x", now, OTHER_PATHS_ASK_INTERVAL)); + assert!(!book.ask_every("t:x", now + ASK_INTERVAL, OTHER_PATHS_ASK_INTERVAL)); + assert!(book.ask_every( + "t:x", + now + OTHER_PATHS_ASK_INTERVAL, + OTHER_PATHS_ASK_INTERVAL + )); + assert!(book.unproven_hints("n2").is_empty()); + } + + #[test] fn asking_is_rate_limited() { let now = Instant::now(); let mut book = Book::default(); |
