From 1dafba706d577a0896581005255aa5f6698cc489 Mon Sep 17 00:00:00 2001
From: Nick Mathewson <nickm@torproject.org>
Date: Wed, 11 May 2022 11:19:17 -0400
Subject: [PATCH] Make reset_time() for incomplete directories more generous.

Since we want to be willing to use older consensuses, we don't
necessarily want to reset a download just because the consensus is
expired.

This new behavior isn't ideal either; I've added a TODO that relates
to #433.

Related of #412
---
 crates/tor-dirmgr/src/state.rs | 45 ++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 10 deletions(-)

diff --git a/crates/tor-dirmgr/src/state.rs b/crates/tor-dirmgr/src/state.rs
index cdef51e2ee..29f5e014bc 100644
--- a/crates/tor-dirmgr/src/state.rs
+++ b/crates/tor-dirmgr/src/state.rs
@@ -558,7 +558,10 @@ impl<R: Runtime> DirState for GetCertsState<R> {
         }
     }
     fn reset_time(&self) -> Option<SystemTime> {
-        Some(self.consensus_meta.lifetime().valid_until())
+        Some(
+            self.consensus_meta.lifetime().valid_until()
+                + self.config.tolerance.post_valid_tolerance,
+        )
     }
     fn reset(self: Box<Self>) -> Result<Box<dyn DirState>> {
         Ok(Box::new(GetConsensusState::new(
@@ -628,8 +631,9 @@ enum PendingNetDir {
         ///            instantiate it.
         ///            (This code assumes that it doesn't add more needed microdescriptors later!)
         missing_microdescs: HashSet<MdDigest>,
-        /// The time at which we should renew this netdir.
-        reset_time: SystemTime,
+        /// The time at which we should renew this netdir, assuming we have
+        /// driven it to a "usable" state.
+        replace_dir_time: SystemTime,
     },
     /// A dummy value, so we can use `mem::replace`.
     Dummy,
@@ -707,12 +711,12 @@ impl PendingNetDir {
                 PendingNetDir::Partial(p) => match p.unwrap_if_sufficient() {
                     Ok(nd) => {
                         let missing = nd.missing_microdescs().copied().collect();
-                        let reset_time = pick_download_time(nd.lifetime());
+                        let replace_dir_time = pick_download_time(nd.lifetime());
                         *self = PendingNetDir::Yielding {
                             netdir: Some(nd),
                             collected_microdescs: vec![],
                             missing_microdescs: missing,
-                            reset_time,
+                            replace_dir_time,
                         };
                     }
                     Err(p) => {
@@ -738,7 +742,7 @@ impl<R: Runtime> GetMicrodescsState<R> {
         prev_netdir: Option<Arc<dyn PreviousNetDir>>,
         #[cfg(feature = "dirfilter")] filter: Arc<dyn crate::filter::DirFilter>,
     ) -> Self {
-        let reset_time = consensus.lifetime().valid_until();
+        let reset_time = consensus.lifetime().valid_until() + config.tolerance.post_valid_tolerance;
         let n_microdescs = consensus.relays().len();
 
         let params = &config.override_net_params;
@@ -919,8 +923,25 @@ impl<R: Runtime> DirState for GetMicrodescsState<R> {
         Ok(self)
     }
     fn reset_time(&self) -> Option<SystemTime> {
+        // TODO(nickm): The reset logic is a little wonky here: we don't truly
+        // want to _reset_ this state at `replace_dir_time`.  In fact, we ought
+        // to be able to have multiple states running in parallel: one filling
+        // in the mds for an old consensus, and one trying to fetch a better
+        // one.  That's likely to require some amount of refactoring of the
+        // bootstrap code.
+
         Some(match self.partial {
-            PendingNetDir::Yielding { reset_time, .. } => reset_time,
+            // If the client has taken a completed netdir, the netdir is now
+            // usable: We can reset our download attempt when we choose to try
+            // to replace this directory.
+            PendingNetDir::Yielding {
+                replace_dir_time,
+                netdir: None,
+                ..
+            } => replace_dir_time,
+            // We don't have a completed netdir: Keep trying to fill this one in
+            // until it is _definitely_ unusable.  (Our clock might be skewed;
+            // there might be no up-to-date consensus.)
             _ => self.reset_time,
         })
     }
@@ -1268,8 +1289,12 @@ mod test {
             assert!(!state.can_advance());
             assert!(!state.is_ready(Readiness::Complete));
             assert!(!state.is_ready(Readiness::Usable));
-            let consensus_expires = datetime!(2020-08-07 12:43:20 UTC).into();
-            assert_eq!(state.reset_time(), Some(consensus_expires));
+            let consensus_expires: SystemTime = datetime!(2020-08-07 12:43:20 UTC).into();
+            let post_valid_tolerance = crate::DirSkewTolerance::default().post_valid_tolerance;
+            assert_eq!(
+                state.reset_time(),
+                Some(consensus_expires + post_valid_tolerance)
+            );
             let retry = state.dl_config();
             assert_eq!(retry, DownloadScheduleConfig::default().retry_certs);
 
@@ -1398,7 +1423,7 @@ mod test {
                 let fresh_until: SystemTime = datetime!(2021-10-27 21:27:00 UTC).into();
                 let valid_until: SystemTime = datetime!(2021-10-27 21:27:20 UTC).into();
                 assert!(reset_time >= fresh_until);
-                assert!(reset_time <= valid_until);
+                assert!(reset_time <= valid_until + state.config.tolerance.post_valid_tolerance);
             }
             let retry = state.dl_config();
             assert_eq!(retry, DownloadScheduleConfig::default().retry_microdescs);
-- 
GitLab