main.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2023 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. collections::HashMap,
  20. fs::{create_dir_all, read_dir, remove_file},
  21. io::stdin,
  22. path::{Path, PathBuf},
  23. process::exit,
  24. };
  25. use async_std::{
  26. stream::StreamExt,
  27. sync::{Arc, Mutex, RwLock},
  28. task,
  29. };
  30. use dryoc::classic::crypto_secretbox::{crypto_secretbox_keygen, Key};
  31. use futures::{select, FutureExt};
  32. use lazy_static::lazy_static;
  33. use log::{debug, error, info, warn};
  34. use signal_hook::consts::{SIGHUP, SIGINT, SIGQUIT, SIGTERM};
  35. use signal_hook_async_std::Signals;
  36. use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
  37. use url::Url;
  38. use darkfi::{
  39. async_daemonize, cli_desc, net,
  40. raft::{NetMsg, ProtocolRaft, Raft, RaftSettings},
  41. rpc::server::listen_and_serve,
  42. util::{
  43. file::{load_file, load_json_file, save_file, save_json_file},
  44. path::{expand_path, get_config_path},
  45. },
  46. Result,
  47. };
  48. mod jsonrpc;
  49. use jsonrpc::JsonRpcInterface;
  50. mod lcs;
  51. use lcs::Lcs;
  52. mod patch;
  53. use patch::{EncryptedPatch, OpMethod, Patch};
  54. mod util;
  55. use util::{decrypt_patch, encrypt_patch, get_docs_paths, parse_workspaces, path_to_id};
  56. type Patches = (Vec<Patch>, Vec<Patch>, Vec<Patch>, Vec<Patch>);
  57. lazy_static! {
  58. /// This is where we hold our workspaces, so we are also able to refresh them on SIGHUP.
  59. static ref WORKSPACES: RwLock<HashMap<String, Key>> = RwLock::new(HashMap::new());
  60. }
  61. pub const CONFIG_FILE: &str = "darkwikid_config.toml";
  62. pub const CONFIG_FILE_CONTENTS: &str = include_str!("../darkwikid_config.toml");
  63. const SYNC_ID_PATH: &str = "sync";
  64. const LOCAL_ID_PATH: &str = "local";
  65. #[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
  66. #[serde(default)]
  67. #[structopt(name = "darkwikid", about = cli_desc!())]
  68. struct Args {
  69. /// Increase verbosity (-vvv supported)
  70. #[structopt(short, parse(from_occurrences))]
  71. verbose: u8,
  72. /// Configuration file to use
  73. #[structopt(short, long)]
  74. config: Option<String>,
  75. /// Workspace configuration (repeatable flag)
  76. #[structopt(short, long)]
  77. workspace: Vec<String>,
  78. /// Path where to store wiki's files
  79. #[structopt(short, long, default_value = "~/darkwiki")]
  80. docs: String,
  81. /// Sets author's name for patches
  82. #[structopt(long, default_value = "Anonymous")]
  83. author: String,
  84. /// Generate a new secret for a workspace
  85. #[structopt(long)]
  86. gen_secret: bool,
  87. /// JSON-RPC listen URL
  88. #[structopt(long, default_value = "tcp://localhost:24330")]
  89. rpc_listen: Url,
  90. /// Network settings
  91. #[structopt(flatten)]
  92. net: net::settings::SettingsOpt,
  93. }
  94. /// Settings struct used to hold some metadata for DarkWiki
  95. struct DarkWikiSettings {
  96. author: String,
  97. docs_path: PathBuf,
  98. store_path: PathBuf,
  99. }
  100. /// DarkWiki object
  101. struct DarkWiki {
  102. settings: DarkWikiSettings,
  103. #[allow(clippy::type_complexity)]
  104. rpc: (
  105. smol::channel::Sender<Vec<Vec<Patch>>>,
  106. smol::channel::Receiver<(String, bool, Vec<String>)>,
  107. ),
  108. raft: (smol::channel::Sender<EncryptedPatch>, smol::channel::Receiver<EncryptedPatch>),
  109. }
  110. impl DarkWiki {
  111. async fn start(&self) -> Result<()> {
  112. loop {
  113. select! {
  114. val = self.rpc.1.recv().fuse() => {
  115. let (cmd, dry, files) = match val {
  116. Ok(v) => v,
  117. Err(e) => {
  118. error!("Failed unwrapping val received from RPC: {}", e);
  119. continue
  120. }
  121. };
  122. match cmd.as_str() {
  123. "update" => {
  124. if let Err(e) = self.on_receive_update(dry, files).await {
  125. error!("on_receive_update returned error: {}", e);
  126. continue
  127. }
  128. }
  129. "restore" => {
  130. if let Err(e) = self.on_receive_restore(dry, files).await {
  131. error!("on_receive_restore returned error: {}", e);
  132. continue
  133. }
  134. }
  135. x => {
  136. warn!("Received unsupported command: {}", x);
  137. continue
  138. }
  139. }
  140. }
  141. patch = self.raft.1.recv().fuse() => {
  142. let patch = match patch {
  143. Ok(v) => v,
  144. Err(e) => {
  145. error!("Failed unwrapping patch received from raft: {}", e);
  146. continue
  147. }
  148. };
  149. for (workspace, key) in WORKSPACES.read().await.iter() {
  150. if let Ok(mut patch) = decrypt_patch(&patch, key) {
  151. info!("[{}] Receive a {:?}", workspace, patch);
  152. patch.workspace = workspace.clone();
  153. if let Err(e) = self.on_receive_patch(&patch) {
  154. error!("on_receive_patch returned error: {}", e);
  155. }
  156. }
  157. }
  158. }
  159. }
  160. }
  161. }
  162. fn on_receive_patch(&self, received_patch: &Patch) -> Result<()> {
  163. let sync_id_path = self.settings.store_path.join(SYNC_ID_PATH).join(&received_patch.id);
  164. let local_id_path = self.settings.store_path.join(LOCAL_ID_PATH).join(&received_patch.id);
  165. if let Ok(mut sync_patch) = load_json_file::<Patch>(&sync_id_path) {
  166. if sync_patch.timestamp == received_patch.timestamp {
  167. return Ok(())
  168. }
  169. if let Ok(local_patch) = load_json_file::<Patch>(&local_id_path) {
  170. if local_patch.timestamp == sync_patch.timestamp {
  171. sync_patch.base = local_patch.to_string();
  172. sync_patch.set_ops(received_patch.ops());
  173. } else {
  174. sync_patch.extend_ops(received_patch.ops());
  175. }
  176. }
  177. sync_patch.timestamp = received_patch.timestamp;
  178. sync_patch.author = received_patch.author.clone();
  179. save_json_file::<Patch>(&sync_id_path, &sync_patch)?;
  180. } else if !received_patch.base.is_empty() {
  181. save_json_file::<Patch>(&sync_id_path, received_patch)?;
  182. }
  183. Ok(())
  184. }
  185. async fn on_receive_update(&self, dry: bool, files: Vec<String>) -> Result<()> {
  186. let (mut local, mut sync, mut merge) = (vec![], vec![], vec![]);
  187. for (workspace, key) in WORKSPACES.read().await.iter() {
  188. let (patches, l, s, m) = self.update(
  189. dry,
  190. &self.settings.docs_path.join(workspace),
  191. files.clone(),
  192. workspace,
  193. )?;
  194. local.extend(l);
  195. sync.extend(s);
  196. merge.extend(m);
  197. if !dry {
  198. for patch in patches {
  199. info!("Send a {:?}", patch);
  200. let encrypt_patch = encrypt_patch(&patch, key)?;
  201. self.raft.0.send(encrypt_patch).await?;
  202. }
  203. }
  204. }
  205. self.rpc.0.send(vec![local, sync, merge]).await?;
  206. Ok(())
  207. }
  208. async fn on_receive_restore(&self, dry: bool, filenames: Vec<String>) -> Result<()> {
  209. let mut patches = vec![];
  210. for (workspace, _) in WORKSPACES.read().await.iter() {
  211. patches.extend(self.restore(
  212. dry,
  213. &self.settings.docs_path.join(workspace),
  214. &filenames,
  215. workspace,
  216. )?);
  217. }
  218. self.rpc.0.send(vec![patches]).await?;
  219. Ok(())
  220. }
  221. fn restore(
  222. &self,
  223. dry: bool,
  224. docs_path: &Path,
  225. filenames: &[String],
  226. workspace: &str,
  227. ) -> Result<Vec<Patch>> {
  228. let local_path = self.settings.store_path.join(LOCAL_ID_PATH);
  229. let mut patches = vec![];
  230. let local_files = read_dir(&local_path)?;
  231. for file in local_files {
  232. let file_id = file?.file_name();
  233. let file_path = local_path.join(&file_id);
  234. let local_patch: Patch = load_json_file(&file_path)?;
  235. if local_patch.workspace != workspace {
  236. continue
  237. }
  238. // TODO: FIXME: Simplify this logic, what is this? Add comments.
  239. if !filenames.is_empty() && !filenames.contains(&local_patch.path.to_string()) {
  240. continue
  241. }
  242. if let Ok(doc) = load_file(&docs_path.join(&local_patch.path)) {
  243. if local_patch.to_string() == doc {
  244. continue
  245. }
  246. }
  247. if !dry {
  248. self.save_doc(&local_patch.path, &local_patch.to_string(), workspace)?;
  249. }
  250. patches.push(local_patch);
  251. }
  252. Ok(patches)
  253. }
  254. // TODO: Add debug/info statements and refactor this function, there's too many things going on here.
  255. fn update(
  256. &self,
  257. dry: bool,
  258. docs_path: &Path,
  259. filenames: Vec<String>,
  260. workspace: &str,
  261. ) -> Result<Patches> {
  262. let (mut patches, mut local_patches, mut sync_patches, mut merge_patches) =
  263. (vec![], vec![], vec![], vec![]);
  264. let local_path = self.settings.store_path.join(LOCAL_ID_PATH);
  265. let sync_path = self.settings.store_path.join(SYNC_ID_PATH);
  266. // Save and compare docs in darkwiki and local dirs, then
  267. // merge with sync patches if any have been received.
  268. let mut docs = vec![];
  269. get_docs_paths(&mut docs, docs_path, None)?;
  270. for doc in docs {
  271. let doc_path = doc.to_str().unwrap();
  272. // FIXME: IDGI
  273. if !filenames.is_empty() && !filenames.contains(&doc_path.to_string()) {
  274. continue
  275. }
  276. // Load doc content
  277. let edit = load_file(&docs_path.join(doc_path))?;
  278. if edit.is_empty() {
  279. continue
  280. }
  281. let doc_id = path_to_id(doc_path, workspace);
  282. // Create new patch
  283. let mut new_patch = Patch::new(doc_path, &doc_id, &self.settings.author, workspace);
  284. // Check for any changes found with local doc and darkwiki doc
  285. if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&doc_id)) {
  286. // No changes found
  287. if local_patch.to_string() == edit {
  288. continue
  289. }
  290. // Check the differences with LCS algorithm
  291. let local_patch_str = local_patch.to_string();
  292. let lcs = Lcs::new(&local_patch_str, &edit);
  293. let lcs_ops = lcs.ops();
  294. // Add the change ops to the new patch
  295. for op in lcs_ops {
  296. new_patch.add_op(&op);
  297. }
  298. new_patch.base = local_patch.to_string();
  299. local_patches.push(new_patch.clone());
  300. let mut b_patch = new_patch.clone();
  301. b_patch.base = "".to_string();
  302. patches.push(b_patch);
  303. // Check if the same doc has received a patch from the network
  304. if let Ok(sync_patch) = load_json_file::<Patch>(&sync_path.join(&doc_id)) {
  305. if !Self::is_delete_patch(&sync_patch) {
  306. if sync_patch.timestamp != local_patch.timestamp {
  307. sync_patches.push(sync_patch.clone());
  308. let sync_patch_t = new_patch.transform(&sync_patch);
  309. new_patch = new_patch.merge(&sync_patch_t);
  310. if !dry {
  311. self.save_doc(doc_path, &new_patch.to_string(), workspace)?;
  312. }
  313. merge_patches.push(new_patch.clone());
  314. }
  315. } else {
  316. merge_patches.push(sync_patch);
  317. patches = vec![];
  318. }
  319. }
  320. } else {
  321. new_patch.base = edit.to_string();
  322. local_patches.push(new_patch.clone());
  323. patches.push(new_patch.clone());
  324. };
  325. if !dry {
  326. save_json_file(&local_path.join(&doc_id), &new_patch)?;
  327. save_json_file(&sync_path.join(&doc_id), &new_patch)?;
  328. }
  329. }
  330. // Check if a new patch is received and save the new changes
  331. // in both local and darkwiki dirs.
  332. let sync_files = read_dir(&sync_path)?;
  333. for file in sync_files {
  334. let file_id = file?.file_name();
  335. let file_path = sync_path.join(&file_id);
  336. let sync_patch: Patch = load_json_file(&file_path)?;
  337. if sync_patch.workspace != workspace {
  338. continue
  339. }
  340. if Self::is_delete_patch(&sync_patch) {
  341. if local_path.join(&sync_patch.id).exists() {
  342. sync_patches.push(sync_patch.clone());
  343. }
  344. if !dry {
  345. remove_file(docs_path.join(&sync_patch.path))?;
  346. remove_file(local_path.join(&sync_patch.id))?;
  347. remove_file(file_path)?;
  348. }
  349. continue
  350. }
  351. if let Ok(local_patch) = load_json_file::<Patch>(&local_path.join(&file_id)) {
  352. if local_patch.timestamp == sync_patch.timestamp {
  353. continue
  354. }
  355. }
  356. // TODO: FIXME: IDGI AGAIN, HALP
  357. if !filenames.is_empty() && !filenames.contains(&sync_patch.path.to_string()) {
  358. continue
  359. }
  360. if !dry {
  361. self.save_doc(&sync_patch.path, &sync_patch.to_string(), workspace)?;
  362. save_json_file(&local_path.join(file_id), &sync_patch)?;
  363. }
  364. if !sync_patches.contains(&sync_patch) {
  365. sync_patches.push(sync_patch);
  366. }
  367. }
  368. // Check if any doc is removed from darkwiki filesystem.
  369. let local_files = read_dir(&local_path)?;
  370. for file in local_files {
  371. let file_id = file?.file_name();
  372. let file_path = local_path.join(&file_id);
  373. let local_patch: Patch = load_json_file(&file_path)?;
  374. if local_patch.workspace != workspace {
  375. continue
  376. }
  377. // TODO: FIXME: Is it just supposed to check that filenames doesn't contain the local_patch?
  378. if !filenames.is_empty() && !filenames.contains(&local_patch.path.to_string()) {
  379. continue
  380. }
  381. if !docs_path.join(&local_patch.path).exists() {
  382. let mut new_patch = Patch::new(
  383. &local_patch.path,
  384. &local_patch.id,
  385. &self.settings.author,
  386. &local_patch.workspace,
  387. );
  388. new_patch.add_op(&OpMethod::Delete(local_patch.to_string().len() as u64));
  389. patches.push(new_patch.clone());
  390. new_patch.base = local_patch.base;
  391. local_patches.push(new_patch);
  392. if !dry {
  393. remove_file(file_path)?;
  394. }
  395. }
  396. }
  397. Ok((patches, local_patches, sync_patches, merge_patches))
  398. }
  399. fn save_doc(&self, path: &str, edit: &str, workspace: &str) -> Result<()> {
  400. let path = self.settings.docs_path.join(workspace).join(path);
  401. if let Some(p) = path.parent() {
  402. if !p.exists() && !p.to_str().unwrap().is_empty() {
  403. create_dir_all(p)?;
  404. }
  405. }
  406. save_file(&path, edit)
  407. }
  408. fn is_delete_patch(patch: &Patch) -> bool {
  409. if patch.ops().0.len() != 1 {
  410. return false
  411. }
  412. if let OpMethod::Delete(d) = patch.ops().0[0] {
  413. if patch.base.len() as u64 == d {
  414. return true
  415. }
  416. }
  417. false
  418. }
  419. }
  420. async fn handle_signals(
  421. mut signals: Signals,
  422. cfg_path: PathBuf,
  423. term_tx: smol::channel::Sender<()>,
  424. ) {
  425. debug!("Started signal handler");
  426. while let Some(signal) = signals.next().await {
  427. match signal {
  428. SIGHUP => {
  429. info!("Caught SIGHUP");
  430. let toml_contents = match std::fs::read_to_string(cfg_path.clone()) {
  431. Ok(v) => v,
  432. Err(e) => {
  433. error!("Couldn't load configuration file: {}", e);
  434. continue
  435. }
  436. };
  437. *WORKSPACES.write().await = parse_workspaces(&toml_contents);
  438. info!("Reloaded workspaces");
  439. }
  440. SIGTERM | SIGINT | SIGQUIT => {
  441. term_tx.send(()).await.unwrap();
  442. }
  443. _ => unreachable!(),
  444. }
  445. }
  446. }
  447. async_daemonize!(realmain);
  448. async fn realmain(args: Args, executor: Arc<smol::Executor<'_>>) -> Result<()> {
  449. let cfg_path = get_config_path(args.config, CONFIG_FILE)?;
  450. let docs_path = expand_path(&args.docs)?;
  451. let store_path = expand_path(docs_path.join(".log").to_str().unwrap())?;
  452. create_dir_all(docs_path.clone())?;
  453. create_dir_all(store_path.clone())?;
  454. create_dir_all(store_path.join(LOCAL_ID_PATH))?;
  455. create_dir_all(store_path.join(SYNC_ID_PATH))?;
  456. if args.gen_secret {
  457. eprintln!("Generating a new workspace");
  458. loop {
  459. eprint!("Input the name for the new workspace (use ascii chars): ");
  460. let mut workspace = String::new();
  461. stdin().read_line(&mut workspace)?;
  462. // Non-exhaustive
  463. let workspace =
  464. workspace.replace(['\t', '\r', ' ', '/', '\\', '\'', '&', '~', ':'], "_");
  465. if workspace.is_empty() || workspace.len() < 3 {
  466. eprintln!("Error: Workspace name is empty or less than 3 characters. Try again.");
  467. continue
  468. }
  469. let secret = bs58::encode(crypto_secretbox_keygen()).into_string();
  470. create_dir_all(docs_path.join(workspace.clone()))?;
  471. println!("Created workspace: {}:{}", workspace, secret);
  472. eprintln!("Please add it to the config file.");
  473. return Ok(())
  474. }
  475. }
  476. // Signal handling for config reload and graceful termination.
  477. let signals = Signals::new([SIGHUP, SIGTERM, SIGINT, SIGQUIT])?;
  478. let handle = signals.handle();
  479. let (term_tx, term_rx) = smol::channel::bounded::<()>(1);
  480. let signals_task = task::spawn(handle_signals(signals, cfg_path.clone(), term_tx));
  481. info!("Set up signal handling");
  482. {
  483. info!("Parsing configuration file for workspaces");
  484. let toml_contents = std::fs::read_to_string(cfg_path.clone())?;
  485. *WORKSPACES.write().await = parse_workspaces(&toml_contents);
  486. if WORKSPACES.read().await.is_empty() {
  487. eprintln!("Please add atleast one workspace to the config file.");
  488. eprintln!("Run \"$ darkwikid --gen-secret\" to create a new workspace.");
  489. exit(1);
  490. }
  491. }
  492. let (rpc_tx, rpc_rx) = smol::channel::unbounded::<(String, bool, Vec<String>)>();
  493. let (notify_tx, notify_rx) = smol::channel::unbounded::<Vec<Vec<Patch>>>();
  494. // ===============
  495. // JSON-RPC server
  496. // ===============
  497. let rpc_iface = Arc::new(JsonRpcInterface::new(rpc_tx, notify_rx));
  498. let _ex = executor.clone();
  499. executor.spawn(listen_and_serve(args.rpc_listen, rpc_iface, _ex)).detach();
  500. // ====
  501. // Raft
  502. // ====
  503. let seen_net_msgs = Arc::new(Mutex::new(HashMap::new()));
  504. let store_raft = store_path.join("darkwiki.db");
  505. let raft_settings = RaftSettings { datastore_path: store_raft, ..RaftSettings::default() };
  506. // FIXME: This is a bad design, and needs a proper rework.
  507. let raft =
  508. Arc::new(Mutex::new(Raft::<EncryptedPatch>::new(raft_settings, seen_net_msgs.clone())?));
  509. // =========
  510. // P2P setup
  511. // =========
  512. let mut net_settings = args.net.clone();
  513. net_settings.app_version = Some(option_env!("CARGO_PKG_VERSION").unwrap_or("").to_string());
  514. let (p2p_tx, p2p_rx) = smol::channel::unbounded::<NetMsg>();
  515. let p2p = net::P2p::new(net_settings.into()).await;
  516. let registry = p2p.protocol_registry();
  517. let raft_node_id = raft.lock().await.id();
  518. registry.register(net::SESSION_ALL, move | channel, p2p| {
  519. let raft_node_id = raft_node_id.clone();
  520. let sender = p2p_tx.clone();
  521. let seen_net_msgs = seen_net_msgs.clone();
  522. async move {
  523. ProtocolRaft::init(raft_node_id, channel, sender, p2p, seen_net_msgs).await
  524. }
  525. }).await;
  526. p2p.clone().start(executor.clone()).await?;
  527. executor.spawn(p2p.clone().run(executor.clone())).detach();
  528. // ==============
  529. // Darkwiki start
  530. // ==============
  531. let raft_tx = raft.lock().await.sender();
  532. let raft_rx = raft.lock().await.receiver();
  533. executor
  534. .spawn(async move {
  535. let settings = DarkWikiSettings { author: args.author, store_path, docs_path };
  536. let dw = DarkWiki { settings, raft: (raft_tx, raft_rx), rpc: (notify_tx, rpc_rx) };
  537. dw.start().await.unwrap();
  538. })
  539. .detach();
  540. let (raft_term_tx, raft_term_rx) = smol::channel::bounded::<()>(1);
  541. let _p2p = p2p.clone();
  542. let _ex = executor.clone();
  543. executor
  544. .spawn(async move { raft.lock().await.run(_p2p, p2p_rx, _ex, raft_term_rx).await.unwrap() })
  545. .detach();
  546. // Wait for termination signal
  547. term_rx.recv().await?;
  548. eprint!("\r");
  549. info!("Caught termination signal, cleaning up and exiting...");
  550. handle.close();
  551. signals_task.await;
  552. info!("Stopping Raft...");
  553. raft_term_tx.send(()).await.unwrap();
  554. info!("Stopping P2P network...");
  555. p2p.stop().await;
  556. info!("Bye.");
  557. Ok(())
  558. }