/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 .
*/
use std::{sync::Arc, time::UNIX_EPOCH};
use log::{debug, warn};
use url::Url;
use super::super::p2p::{P2p, P2pPtr};
use crate::{
net::{connector::Connector, protocol::ProtocolVersion, session::Session},
system::{sleep, LazyWeak, StoppableTask, StoppableTaskPtr},
Error,
};
pub type GreylistRefineryPtr = Arc;
//// Probe random peers on the greylist. If a peer is responsive, update the last_seen field and
//// add it to the whitelist. If a node does not respond, remove it from the greylist.
//// Called periodically.
// NOTE: in monero this is called "greylist housekeeping" but that's a bit verbose.
pub struct GreylistRefinery {
/// Weak pointer to parent p2p object
pub(in crate::net) p2p: LazyWeak,
process: StoppableTaskPtr,
}
impl GreylistRefinery {
pub fn new() -> Arc {
Arc::new(Self { p2p: LazyWeak::new(), process: StoppableTask::new() })
}
pub async fn start(self: Arc) {
let ex = self.p2p().executor();
self.process.clone().start(
async move {
self.run().await;
unreachable!();
},
// Ignore stop handler
|_| async {},
Error::NetworkServiceStopped,
ex,
);
}
pub async fn stop(self: Arc) {
self.process.stop().await
}
//// Randomly select a peer on the greylist and probe it.
//// TODO: This frequency of this call can be set in net::Settings.
async fn run(self: Arc) {
debug!(target: "net::refinery::run()", "START");
loop {
let hosts = self.p2p().hosts();
if hosts.is_empty_greylist().await {
warn!(target: "net::refinery::run()",
"Greylist is empty! Cannot start refinery process");
} else {
debug!(target: "net::refinery::run()", "Starting refinery process");
// Randomly select an entry from the greylist.
let (entry, position) = hosts.greylist_fetch_random().await;
let url = &entry.0;
if ping_node(url, self.p2p().clone()).await {
// Peer is responsive. Update last_seen and add it to the whitelist.
let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
// Append to the whitelist.
hosts.whitelist_store_or_update(&[(url.clone(), last_seen)]).await.unwrap();
// Remove whitelisted peer from the greylist.
hosts.greylist_remove(url, position).await;
} else {
let mut greylist = hosts.greylist.write().await;
greylist.remove(position);
debug!(target: "net::refinery::run()", "Peer {} is not response. Removed from greylist", url);
}
}
// TODO: create a custom net setting for this timer
debug!(target: "net::greylist_refinery::run()", "Sleeping...");
sleep(10).await;
}
}
fn p2p(&self) -> P2pPtr {
self.p2p.upgrade()
}
}
// Ping a node to check it's online.
// TODO: make this an actual ping-pong method, rather than a version exchange.
pub async fn ping_node(addr: &Url, p2p: P2pPtr) -> bool {
let session_outbound = p2p.session_outbound();
let parent = Arc::downgrade(&session_outbound);
let connector = Connector::new(p2p.settings(), parent);
debug!(target: "net::refinery::ping_node()", "Attempting to connect to {}", addr);
match connector.connect(addr).await {
Ok((_url, channel)) => {
debug!(target: "net::refinery::ping_node()", "Connected successfully!");
let proto_ver = ProtocolVersion::new(channel.clone(), p2p.settings()).await;
let handshake_task = session_outbound.perform_handshake_protocols(
proto_ver,
channel.clone(),
p2p.executor(),
);
channel.clone().start(p2p.executor());
match handshake_task.await {
Ok(()) => {
debug!(target: "net::refinery::ping_node()", "Handshake success! Stopping channel.");
channel.stop().await;
true
}
Err(e) => {
debug!(target: "net::refinery::ping_node()", "Handshake failure! {}", e);
false
}
}
}
Err(e) => {
debug!(target: "net::refinery::ping_node()", "Failed to connect to {}, ({})", addr, e);
false
}
}
}