//! Proof-of-concept for applied operation use. //! //! These functions should obviously be moved to [`super`] at one point. use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; use rand::{Rng, seq::SliceRandom}; use tor_basic_utils::retry::RetryDelay; use tor_dircommon::{authority::AuthorityContacts, config::DirTolerance}; use tor_netdoc::doc::netstatus::ConsensusFlavor; use tor_rtcompat::PreferredRuntime; use crate::{ database::Timestamp, err::IsFatal, mirror::operation::{ConsensusBoundData, StaticEngine}, }; /// Proof-of-concept main execution function for this module /// /// Right now, this is a proof-of-concept that just panics in the case of a /// fatal error, but does proper retry handling for non-fatal errors. // TODO DIRMIRROR: Make this not a poc. // TODO DIRMIRROR: Add logging. // TODO DIRMIRROR: Diziet thinks the endpoint selection/retry logic is broken // eg that it could reach `expect("attempted all authorities")`. // At the very least it is confusing. See // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/3664#note_3352738 async fn serve Timestamp>( pool: &Pool, flavor: ConsensusFlavor, authorities: AuthorityContacts, tolerance: DirTolerance, rng: &mut R, now_fn: F, ) { let mut data = ConsensusBoundData::None; let engine = StaticEngine { flavor, authorities, tolerance, rt: PreferredRuntime::current().expect("unable to get runtime"), }; // Shuffle the list of download endpoints. let mut downloads = engine.authorities.downloads().clone(); downloads.shuffle(rng); // Keeps track of the authority we currently use, i.e. preferred authority. let mut current = 0; let mut retry = RetryDelay::default(); loop { let endpoint = downloads.get(current).expect("attempted all authorities"); // Perform the FSM execution. let res = engine .execute(pool, &mut data, endpoint, now_fn(), rng) .await; match res { Ok(()) => { retry.reset(); // Swap the currently used authority with the front and reset // current to zero. // // With this design, we will loose track on which authorities // were successful and which were not on every successful // return. At one point, we have to do this. Probably after // every consensus, but not after every Ok. However, for this // we would need a way to learn when we got a new consensus. // It would probably make most sense to modify the return type // of execute() to return something like the next state plus // previous state or maybe an even simpler bool that returns // true when the consensus got replaced. downloads.swap(0, current); } Err(e) => { // Check whether the error is fatal. if e.is_fatal() { panic!("fatal error: {e}"); } // Non-fatal error means we should wait and try again. current += 1; let delay = retry.next_delay(rng); tokio::time::sleep(delay).await; } } } }