| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- /* This file is part of DarkFi (https://dark.fi)
- *
- * Copyright (C) 2020-2024 Dyne.org foundation
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
- //! Seed sync session creates a connection to the seed nodes specified in settings.
- //!
- //! A new seed sync session is created every time we call [`P2p::start()`]. The
- //! seed sync session loops through all the configured seeds and creates a corresponding
- //! `Slot`. `Slot`'s are started, but sit in a suspended state until they are activated
- //! by a call to notify (see: `p2p.seed()`).
- //!
- //! When a `Slot` has been activated by a call to `notify()`, it will try to connect
- //! to the given seed address using a [`Connector`]. This will either connect successfully
- //! or fail with a warning. With gather the results of each `Slot` in an `AtomicBool`
- //! so that we can handle the error elsewhere in the code base.
- //!
- //! If a seed node connects successfully, it runs a version exchange protocol,
- //! stores the channel in the p2p list of channels, and disconnects, removing
- //! the channel from the channel list.
- //!
- //! The channel is registered using the [`Session::register_channel()`] trait
- //! method. This invokes the Protocol Registry method `attach()`. Usually this
- //! returns a list of protocols that we loop through and start. In this case,
- //! `attach()` uses the bitflag selector to identify seed sessions and exclude
- //! them.
- //!
- //! The version exchange occurs inside `register_channel()`. We create a handshake
- //! task that runs the version exchange with the `perform_handshake_protocols()`
- //! function. This runs the version exchange protocol, stores the channel in the
- //! p2p list of channels, and subscribes to a stop signal.
- use std::sync::{
- atomic::{AtomicBool, Ordering::SeqCst},
- Arc, Weak,
- };
- use async_trait::async_trait;
- use futures::stream::{FuturesUnordered, StreamExt};
- use log::{debug, info, warn};
- use smol::lock::{Mutex as AsyncMutex, RwLock as AsyncRwLock};
- use url::Url;
- use super::{
- super::{
- connector::Connector,
- hosts::HostColor,
- p2p::{P2p, P2pPtr},
- settings::Settings,
- },
- Session, SessionBitFlag, SESSION_SEED,
- };
- use crate::{
- net::hosts::HostState,
- system::{CondVar, StoppableTask, StoppableTaskPtr},
- Error,
- };
- pub type SeedSyncSessionPtr = Arc<SeedSyncSession>;
- /// Defines seed connections session
- pub struct SeedSyncSession {
- pub(in crate::net) p2p: Weak<P2p>,
- slots: AsyncMutex<Vec<Arc<Slot>>>,
- }
- impl SeedSyncSession {
- /// Create a new seed sync session instance
- pub(crate) fn new(p2p: Weak<P2p>) -> SeedSyncSessionPtr {
- Arc::new(Self { p2p, slots: AsyncMutex::new(Vec::new()) })
- }
- /// Initialize the seedsync session. Each slot is suspended while it waits
- /// for a call to notify().
- pub(crate) async fn start(self: Arc<Self>) {
- // Activate mutex lock on connection slots.
- let mut slots = self.slots.lock().await;
- let mut futures = FuturesUnordered::new();
- let self_ = Arc::downgrade(&self);
- // Initialize a slot for each configured seed.
- // Connections will be started by not yet activated.
- for seed in &self.p2p().settings().read().await.seeds {
- let slot = Slot::new(self_.clone(), seed.clone(), self.p2p().settings());
- futures.push(slot.clone().start());
- slots.push(slot);
- }
- while (futures.next().await).is_some() {}
- }
- /// Activate the slots so they can continue with the seedsync process.
- /// Called in `p2p.seed()`.
- pub(crate) async fn notify(&self) {
- let slots = &*self.slots.lock().await;
- for slot in slots {
- slot.notify();
- }
- }
- /// Stop the seedsync session.
- pub(crate) async fn stop(&self) {
- debug!(target: "net::seedsync_session", "Stopping seed sync session...");
- let slots = &*self.slots.lock().await;
- let mut futures = FuturesUnordered::new();
- for slot in slots {
- futures.push(slot.clone().stop());
- }
- while (futures.next().await).is_some() {}
- debug!(target: "net::seedsync_session", "Seed sync session stopped!");
- }
- /// Returns true if every seed attempt per slot has failed.
- async fn failed(&self) -> bool {
- let slots = &*self.slots.lock().await;
- slots.iter().all(|s| s.failed())
- }
- }
- #[async_trait]
- impl Session for SeedSyncSession {
- fn p2p(&self) -> P2pPtr {
- self.p2p.upgrade().unwrap()
- }
- fn type_id(&self) -> SessionBitFlag {
- SESSION_SEED
- }
- }
- struct Slot {
- addr: Url,
- process: StoppableTaskPtr,
- wakeup_self: CondVar,
- session: Weak<SeedSyncSession>,
- connector: Connector,
- failed: AtomicBool,
- }
- impl Slot {
- fn new(
- session: Weak<SeedSyncSession>,
- addr: Url,
- settings: Arc<AsyncRwLock<Settings>>,
- ) -> Arc<Self> {
- Arc::new(Self {
- addr,
- process: StoppableTask::new(),
- wakeup_self: CondVar::new(),
- session: session.clone(),
- connector: Connector::new(settings, session),
- failed: AtomicBool::new(false),
- })
- }
- async fn start(self: Arc<Self>) {
- let ex = self.p2p().executor();
- self.process.clone().start(
- async move {
- self.run().await;
- unreachable!();
- },
- // Ignore stop handler
- |_| async {},
- Error::NetworkServiceStopped,
- ex,
- );
- }
- /// Main seedsync connection process that is started on `p2p.start()` but does
- /// not proceed until it receives a call to `notify()` (called in `p2p.seed()`).
- /// Resets the CondVar after each run to re-suspend the connection process until
- /// `notify()` is called again.
- async fn run(self: Arc<Self>) {
- let ex = self.p2p().executor();
- let hosts = self.p2p().hosts();
- loop {
- // Wait for a signal from notify() before proceeding with the seedsync.
- self.wait().await;
- debug!(
- target: "net::session::seedsync_session", "SeedSyncSession::start_seed() [START]",
- );
- if let Err(e) = hosts.try_register(self.addr.clone(), HostState::Connect) {
- debug!(target: "net::session::seedsync_session",
- "Cannot connect to seed={}, err={}", &self.addr, e);
- // Reset the CondVar for future use.
- self.reset();
- continue
- }
- match self.connector.connect(&self.addr).await {
- Ok((url, ch)) => {
- info!(
- target: "net::session::seedsync_session",
- "[P2P] Connected seed [{}]", url,
- );
- match self.session().register_channel(ch.clone(), ex.clone()).await {
- Ok(()) => {
- self.failed.store(false, SeqCst);
- info!(
- target: "net::session::seedsync_session",
- "[P2P] Disconnecting from seed [{}]",
- url,
- );
- ch.stop().await;
- // Seed process complete
- if hosts.container.is_empty(HostColor::Grey) {
- warn!(target: "net::session::seedsync_session()",
- "[P2P] Greylist empty after seeding");
- }
- // Reset the CondVar for future use.
- self.reset();
- }
- Err(e) => {
- self.handle_failure(e, &url);
- continue
- }
- }
- }
- Err(e) => {
- self.handle_failure(e, &self.addr);
- continue
- }
- }
- debug!(
- target: "net::session::seedsync_session",
- "SeedSyncSession::start_seed() [END]",
- );
- }
- }
- fn handle_failure(&self, error: Error, addr: &Url) {
- warn!(
- target: "net::session::seedsync_session",
- "[P2P] Unable to connect to seed [{}]: {}",
- self.addr, error,
- );
- self.failed.store(true, SeqCst);
- // Free up this addr for future operations.
- self.p2p().hosts().unregister(addr);
- // Reset the CondVar for future use.
- self.reset();
- }
- fn failed(&self) -> bool {
- self.failed.load(SeqCst)
- }
- fn session(&self) -> SeedSyncSessionPtr {
- self.session.upgrade().unwrap()
- }
- fn p2p(&self) -> P2pPtr {
- self.session().p2p()
- }
- async fn wait(&self) {
- self.wakeup_self.wait().await;
- }
- fn reset(&self) {
- self.wakeup_self.reset()
- }
- fn notify(&self) {
- self.wakeup_self.notify()
- }
- async fn stop(self: Arc<Self>) {
- self.connector.stop();
- self.process.stop().await;
- }
- }
|