refinery.rs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. sync::Arc,
  20. time::{Duration, Instant, UNIX_EPOCH},
  21. };
  22. use log::{debug, warn};
  23. use url::Url;
  24. use super::{
  25. super::p2p::{P2p, P2pPtr},
  26. store::HostColor,
  27. };
  28. use crate::{
  29. net::{
  30. connector::Connector, hosts::store::HostState, protocol::ProtocolVersion, session::Session,
  31. },
  32. system::{
  33. run_until_completion, sleep, timeout::timeout, LazyWeak, StoppableTask, StoppableTaskPtr,
  34. },
  35. Error,
  36. };
  37. pub type GreylistRefineryPtr = Arc<GreylistRefinery>;
  38. /// Probe random peers on the greylist. If a peer is responsive, update the last_seen field and
  39. /// add it to the whitelist. If a node does not respond, remove it from the greylist.
  40. /// Called periodically.
  41. pub struct GreylistRefinery {
  42. /// Weak pointer to parent p2p object
  43. pub(in crate::net) p2p: LazyWeak<P2p>,
  44. process: StoppableTaskPtr,
  45. }
  46. impl GreylistRefinery {
  47. pub fn new() -> Arc<Self> {
  48. Arc::new(Self { p2p: LazyWeak::new(), process: StoppableTask::new() })
  49. }
  50. pub async fn start(self: Arc<Self>) {
  51. match self.p2p().hosts().container.load_all(&self.p2p().settings().hostlist).await {
  52. Ok(()) => {
  53. debug!(target: "net::refinery::start()", "Load hosts successful!");
  54. }
  55. Err(e) => {
  56. warn!(target: "net::refinery::start()", "Error loading hosts {}", e);
  57. }
  58. }
  59. let ex = self.p2p().executor();
  60. self.process.clone().start(
  61. async move {
  62. //self.listen_for_channels().await;
  63. self.run().await;
  64. unreachable!();
  65. },
  66. // Ignore stop handler
  67. |_| async {},
  68. Error::NetworkServiceStopped,
  69. ex,
  70. );
  71. }
  72. pub async fn stop(self: Arc<Self>) {
  73. self.process.stop().await;
  74. match self.p2p().hosts().container.save_all(&self.p2p().settings().hostlist).await {
  75. Ok(()) => {
  76. debug!(target: "net::refinery::stop()", "Save hosts successful!");
  77. }
  78. Err(e) => {
  79. warn!(target: "net::refinery::stop()", "Error saving hosts {}", e);
  80. }
  81. }
  82. }
  83. // Randomly select a peer on the greylist and probe it.
  84. // This method will remove from the greylist and store on the whitelist
  85. // providing the peer is responsive.
  86. async fn run(self: Arc<Self>) {
  87. let settings = self.p2p().settings();
  88. let hosts = self.p2p().hosts();
  89. loop {
  90. sleep(settings.greylist_refinery_interval).await;
  91. if hosts.container.is_empty(HostColor::Grey).await {
  92. debug!(target: "net::refinery",
  93. "Greylist is empty! Cannot start refinery process");
  94. continue
  95. }
  96. // Pause the refinery if we've had zero connections for longer than the configured
  97. // limit.
  98. let offline_limit = Duration::from_secs(settings.time_with_no_connections);
  99. let offline_timer = Instant::now().duration_since(*hosts.last_connection.read().await);
  100. if hosts.channels().await.is_empty() && offline_timer >= offline_limit {
  101. warn!(target: "net::refinery", "No connections for {}s. Refinery paused.",
  102. offline_timer.as_secs());
  103. // It is neccessary to clear suspended hosts at this point, otherwise these
  104. // hosts cannot be connected to in Outbound Session. Failure to do this could
  105. // result in the refinery being paused forver (since connections could never be
  106. // made).
  107. let suspended_hosts = hosts.suspended().await;
  108. for host in suspended_hosts {
  109. hosts.unregister(&host).await;
  110. }
  111. continue
  112. }
  113. // Only attempt to refine peers that match our transports.
  114. match hosts
  115. .container
  116. .fetch_random_with_schemes(HostColor::Grey, &settings.allowed_transports)
  117. .await
  118. {
  119. Some((entry, position)) => {
  120. let url = &entry.0;
  121. if hosts.try_register(url.clone(), HostState::Refine).await.is_err() {
  122. continue
  123. }
  124. if !ping_node(url.clone(), self.p2p().clone()).await {
  125. hosts.container.remove(HostColor::Grey, url, position).await;
  126. debug!(
  127. target: "net::refinery",
  128. "Peer {} is non-responsive. Removed from greylist", url,
  129. );
  130. // Remove this entry from HostRegistry to avoid this host getting
  131. // stuck in the Refining state.
  132. //
  133. // It is not necessary to call this when the refinery passes, since the
  134. // state will be changed to Connected.
  135. hosts.unregister(url).await;
  136. continue
  137. }
  138. let last_seen = UNIX_EPOCH.elapsed().unwrap().as_secs();
  139. // Append to the whitelist.
  140. hosts.container.store_or_update(HostColor::White, url.clone(), last_seen).await;
  141. // Remove whitelisted peer from the greylist.
  142. hosts.container.remove(HostColor::Grey, url, position).await;
  143. }
  144. None => {
  145. debug!(target: "net::refinery", "No matching greylist entries found. Cannot proceed with refinery");
  146. continue
  147. }
  148. }
  149. }
  150. }
  151. fn p2p(&self) -> P2pPtr {
  152. self.p2p.upgrade()
  153. }
  154. }
  155. /// Check a node is online by establishing a channel with it and conducting a handshake with a
  156. /// version exchange.
  157. ///
  158. /// We must use run_until_completion() to ensure this code will complete even if the parent task
  159. /// has been destroyed. Otherwise ping_node() will become a zombie process if the rest of the p2p
  160. /// network has been shutdown but the handshake it still ongoing.
  161. ///
  162. /// Other parts of the p2p stack have safe shutdown methods built into them due to the ownership
  163. /// structure. Here we are creating a outbound session that is not owned by anything and is not
  164. /// so is not safely cancelled on shutdown.
  165. pub async fn ping_node(addr: Url, p2p: P2pPtr) -> bool {
  166. let ex = p2p.executor();
  167. run_until_completion(ping_node_impl(addr.clone(), p2p), ex).await
  168. }
  169. async fn ping_node_impl(addr: Url, p2p: P2pPtr) -> bool {
  170. let session_outbound = p2p.session_outbound();
  171. let parent = Arc::downgrade(&session_outbound);
  172. let connector = Connector::new(p2p.settings(), parent);
  173. debug!(target: "net::refinery::ping_node()", "Attempting to connect to {}", addr);
  174. match connector.connect(&addr).await {
  175. Ok((url, channel)) => {
  176. debug!(target: "net::refinery::ping_node()", "Successfully created a channel with {}", url);
  177. // First initialize the version protocol and its Version, Verack subscribers.
  178. let proto_ver = ProtocolVersion::new(channel.clone(), p2p.settings()).await;
  179. debug!(target: "net::refinery::ping_node()", "Performing handshake protocols with {}", url);
  180. // Then run the version exchange, store the channel and subscribe to a stop signal.
  181. let handshake_task = session_outbound.perform_handshake_protocols(
  182. proto_ver,
  183. channel.clone(),
  184. p2p.executor(),
  185. );
  186. debug!(target: "net::refinery::ping_node()", "Starting channel {}", url);
  187. channel.clone().start(p2p.executor());
  188. // Ensure the channel gets stopped by adding a timeout to the handshake. Otherwise if
  189. // the handshake does not finish channel.stop() will never get called, resulting in
  190. // zombie processes.
  191. let result = timeout(Duration::from_secs(5), handshake_task).await;
  192. debug!(target: "net::refinery::ping_node()", "Stopping channel {}", url);
  193. channel.stop().await;
  194. match result {
  195. Ok(_) => {
  196. debug!(target: "net::refinery::ping_node()", "Handshake success!");
  197. true
  198. }
  199. Err(e) => {
  200. debug!(target: "net::refinery::ping_node()", "Handshake err: {}", e);
  201. false
  202. }
  203. }
  204. }
  205. Err(e) => {
  206. debug!(target: "net::refinery::ping_node()", "Failed to connect to {}, ({})", addr, e);
  207. false
  208. }
  209. }
  210. }