Ver código fonte

darkfid2: networking foundation added

aggstam 3 anos atrás
pai
commit
21970aed0e

+ 3 - 0
Cargo.lock

@@ -1780,6 +1780,7 @@ name = "darkfid2"
 version = "0.4.1"
 dependencies = [
  "async-std",
+ "async-trait",
  "darkfi",
  "darkfi-consensus-contract",
  "darkfi-contract-test-harness",
@@ -1789,6 +1790,7 @@ dependencies = [
  "easy-parallel",
  "log",
  "serde",
+ "serde_json",
  "signal-hook",
  "signal-hook-async-std",
  "simplelog",
@@ -1796,6 +1798,7 @@ dependencies = [
  "smol",
  "structopt",
  "structopt-toml",
+ "url",
 ]
 
 [[package]]

+ 8 - 0
bin/darkfid2/Cargo.toml

@@ -9,15 +9,23 @@ license = "AGPL-3.0-only"
 edition = "2021"
 
 [dependencies]
+# Darkfi
 darkfi = {path = "../../", features = ["async-runtime", "util"]}
 darkfi-consensus-contract = {path = "../../src/contract/consensus"}
 darkfi-money-contract = {path = "../../src/contract/money"}
 darkfi-contract-test-harness = {path = "../../src/contract/test-harness"}
 darkfi-sdk = {path = "../../src/sdk"}
 darkfi-serial = {path = "../../src/serial"}
+
+# Misc
 log = "0.4.19"
 sled = "0.34.7"
 
+# JSON-RPC
+async-trait = "0.1.71"
+serde_json = "1.0.102"
+url = "2.4.0"
+
 # Daemon
 async-std = "1.12.0"
 easy-parallel = "3.3.0"

+ 108 - 0
bin/darkfid2/darkfid_config.toml

@@ -6,5 +6,113 @@
 ## The default values are left commented. They can be overridden either by
 ## uncommenting, or by using the command-line.
 
+# JSON-RPC listen URL
+rpc_listen = "tcp://127.0.0.1:8340"
+
+# Participate in the consensus protocol
+consensus = false
+
 # Enable testing mode for local testing
 testing_mode = false
+
+## Sync P2P network settings
+[sync_net]
+# P2P accept addresses the instance listens on for inbound connections
+# You can also use an IPv6 address
+inbound = ["tls://0.0.0.0:8342"]
+# IPv6 version:
+#inbound = ["tls://[::]:8342"]
+# Combined:
+#inbound = ["tls://0.0.0.0:8342", "tls://[::]:8342"]
+
+# P2P external addresses the instance advertises so other peers can
+# reach us and connect to us, as long as inbound addrs are configured.
+# You can also use an IPv6 address
+#external_addrs = ["tls://XXX.XXX.XXX.XXX:8342"]
+# IPv6 version:
+#external_addrs = ["tls://[ipv6 address here]:8342"]
+# Combined:
+#external_addrs = ["tls://XXX.XXX.XXX.XXX:8342", "tls://[ipv6 address here]:8342"]
+
+# Peer nodes to manually connect to
+#peers = []
+
+# Seed nodes to connect to for peer discovery and/or adversising our
+# own external addresses
+#seeds = ["tls://lilith0.dark.fi:8342", "tls://lilith1.dark.fi:8342"]
+
+# Whitelisted network transports for outbound connections
+#allowed_transports = ["tls"]
+
+# Allow transport mixing (e.g. Tor would be allowed to connect to `tcp://`)
+#transport_mixing = true
+
+# Outbound connection slots number, this many connections will be
+# attempted. (This does not include manual connections)
+outbound_connections = 8
+
+# Manual connections retry limit, 0 for forever looping
+#manual_attempt_limit = 0
+
+# Outbound connection timeout (in seconds)
+#outbound_connect_timeout = 10
+
+# Exchange versions (handshake) timeout (in seconds)
+#channel_handshake_timeout = 4
+
+# Ping-pong exchange execution interval (in seconds)
+#channel_heartbeat_interval = 10
+
+# Allow localnet hosts
+#localnet = false
+
+## Sync P2P network settings
+[consensus_net]
+# P2P accept addresses the instance listens on for inbound connections
+# You can also use an IPv6 address
+#inbound = ["tls://0.0.0.0:8341"]
+# IPv6 version:
+#inbound = ["tls://[::]:8341"]
+# Combined:
+#inbound = ["tls://0.0.0.0:8341", "tls://[::]:8341"]
+
+# P2P external addresses the instance advertises so other peers can
+# reach us and connect to us, as long as inbound addrs are configured.
+# You can also use an IPv6 address
+#external_addrs = ["tls://XXX.XXX.XXX.XXX:8341"]
+# IPv6 version:
+#external_addrs = ["tls://[ipv6 address here]:8341"]
+# Combined:
+#external_addrs = ["tls://XXX.XXX.XXX.XXX:8341", "tls://[ipv6 address here]:8341"]
+
+# Peer nodes to manually connect to
+#peers = []
+
+# Seed nodes to connect to for peer discovery and/or adversising our
+# own external addresses
+#seeds = ["tls://lilith0.dark.fi:8341", "tls://lilith1.dark.fi:8341"]
+
+# Whitelisted network transports for outbound connections
+#allowed_transports = ["tls"]
+
+# Allow transport mixing (e.g. Tor would be allowed to connect to `tcp://`)
+#transport_mixing = true
+
+# Outbound connection slots number, this many connections will be
+# attempted. (This does not include manual connections)
+#outbound_connections = 8
+
+# Manual connections retry limit, 0 for forever looping
+#manual_attempt_limit = 0
+
+# Outbound connection timeout (in seconds)
+#outbound_connect_timeout = 10
+
+# Exchange versions (handshake) timeout (in seconds)
+#channel_handshake_timeout = 4
+
+# Ping-pong exchange execution interval (in seconds)
+#channel_heartbeat_interval = 10
+
+# Allow localnet hosts
+#localnet = false

+ 66 - 8
bin/darkfid2/src/main.rs

@@ -19,11 +19,14 @@
 use async_std::{stream::StreamExt, sync::Arc};
 use log::info;
 use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
+use url::Url;
 
 use darkfi::{
     async_daemonize,
     blockchain::BlockInfo,
     cli_desc,
+    net::{settings::SettingsOpt, P2p, P2pPtr},
+    rpc::server::listen_and_serve,
     util::time::TimeKeeper,
     validator::{Validator, ValidatorConfig, ValidatorPtr},
     Result,
@@ -33,6 +36,9 @@ use darkfi_contract_test_harness::vks;
 #[cfg(test)]
 mod tests;
 
+/// JSON-RPC requests handler
+mod rpc;
+
 /// Utility functions
 mod utils;
 use utils::genesis_txs_total;
@@ -48,6 +54,22 @@ struct Args {
     /// Configuration file to use
     config: Option<String>,
 
+    #[structopt(long, default_value = "tcp://127.0.0.1:8340")]
+    /// JSON-RPC listen URL
+    rpc_listen: Url,
+
+    #[structopt(long)]
+    /// Participate in the consensus protocol
+    consensus: bool,
+
+    /// Syncing network settings
+    #[structopt(flatten)]
+    sync_net: SettingsOpt,
+
+    /// Consensus network settings
+    #[structopt(flatten)]
+    consensus_net: SettingsOpt,
+
     #[structopt(long)]
     /// Enable testing mode for local testing
     testing_mode: bool,
@@ -62,19 +84,41 @@ struct Args {
 }
 
 pub struct Darkfid {
+    sync_p2p: P2pPtr,
+    consensus_p2p: Option<P2pPtr>,
     _validator: ValidatorPtr,
 }
 
 impl Darkfid {
-    pub async fn new(_validator: ValidatorPtr) -> Self {
-        Self { _validator }
+    pub async fn new(
+        sync_p2p: P2pPtr,
+        consensus_p2p: Option<P2pPtr>,
+        _validator: ValidatorPtr,
+    ) -> Self {
+        Self { sync_p2p, consensus_p2p, _validator }
     }
 }
 
 async_daemonize!(realmain);
-async fn realmain(args: Args, _ex: Arc<smol::Executor<'_>>) -> Result<()> {
+async fn realmain(args: Args, ex: Arc<smol::Executor<'_>>) -> Result<()> {
     info!("Initializing DarkFi node...");
 
+    if args.testing_mode {
+        info!("Node is configured to run in testing mode!");
+    }
+
+    // Initialize syncing P2P network
+    let sync_p2p = P2p::new(args.sync_net.into()).await;
+
+    // Initialize consensus P2P network
+    let consensus_p2p = {
+        if !args.consensus {
+            None
+        } else {
+            Some(P2p::new(args.consensus_net.into()).await)
+        }
+    };
+
     // NOTE: everything is dummy for now
     // Initialize or open sled database
     let sled_db = sled::Config::new().temporary(true).open()?;
@@ -92,21 +136,35 @@ async fn realmain(args: Args, _ex: Arc<smol::Executor<'_>>) -> Result<()> {
         args.testing_mode,
     );
 
-    if args.testing_mode {
-        info!("Node is configured to run in testing mode!");
-    }
-
     // Initialize validator
     let validator = Validator::new(&sled_db, config).await?;
 
     // Initialize node
-    let _darkfid = Darkfid::new(validator).await;
+    let darkfid = Darkfid::new(sync_p2p, consensus_p2p, validator).await;
+    let darkfid = Arc::new(darkfid);
     info!("Node initialized successfully!");
 
+    // JSON-RPC server
+    info!("Starting JSON-RPC server");
+    let _ex = ex.clone();
+    ex.spawn(listen_and_serve(args.rpc_listen, darkfid.clone(), _ex)).detach();
+
     // Signal handling for graceful termination.
     let (signals_handler, signals_task) = SignalHandler::new()?;
     signals_handler.wait_termination(signals_task).await?;
     info!("Caught termination signal, cleaning up and exiting...");
 
+    info!("Stopping syncing P2P network...");
+    darkfid.sync_p2p.stop().await;
+
+    if args.consensus {
+        info!("Stopping consensus P2P network...");
+        darkfid.consensus_p2p.clone().unwrap().stop().await;
+    }
+
+    info!("Flushing sled database...");
+    let flushed_bytes = sled_db.flush_async().await?;
+    info!("Flushed {} bytes", flushed_bytes);
+
     Ok(())
 }

+ 102 - 0
bin/darkfid2/src/rpc.rs

@@ -0,0 +1,102 @@
+/* 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 <https://www.gnu.org/licenses/>.
+ */
+
+use async_trait::async_trait;
+use log::debug;
+use serde_json::{json, Value};
+
+use darkfi::{
+    net,
+    rpc::{
+        jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
+        server::RequestHandler,
+    },
+};
+
+use crate::Darkfid;
+
+#[async_trait]
+impl RequestHandler for Darkfid {
+    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
+        if req.params.as_array().is_none() {
+            return JsonError::new(ErrorCode::InvalidRequest, None, req.id).into()
+        }
+
+        let params = req.params.as_array().unwrap();
+
+        debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap());
+
+        match req.method.as_str() {
+            Some("ping") => self.pong(req.id, params).await,
+
+            Some("dnet_switch") => self.dnet_switch(req.id, params).await,
+            Some("dnet_info") => self.dnet_info(req.id, params).await,
+            Some(_) | None => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
+        }
+    }
+}
+
+impl Darkfid {
+    // RPCAPI:
+    // Replies to a ping method.
+    // --> {"jsonrpc": "2.0", "method": "ping", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": "pong", "id": 42}
+    async fn pong(&self, id: Value, _params: &[Value]) -> JsonResult {
+        JsonResponse::new(json!("pong"), id).into()
+    }
+
+    // RPCAPI:
+    // Activate or deactivate dnet in the P2P stack.
+    // By sending `true`, dnet will be activated, and by sending `false` dnet
+    // will be deactivated. Returns `true` on success.
+    //
+    // --> {"jsonrpc": "2.0", "method": "dnet_switch", "params": [true], "id": 42}
+    // <-- {"jsonrpc": "2.0", "result": true, "id": 42}
+    async fn dnet_switch(&self, id: Value, params: &[Value]) -> JsonResult {
+        if params.len() != 1 && params[0].as_bool().is_none() {
+            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
+        }
+
+        if params[0].as_bool().unwrap() {
+            self.sync_p2p.dnet_enable().await;
+            if self.consensus_p2p.is_some() {
+                self.consensus_p2p.clone().unwrap().dnet_enable().await;
+            }
+        } else {
+            self.sync_p2p.dnet_disable().await;
+            if self.consensus_p2p.is_some() {
+                self.consensus_p2p.clone().unwrap().dnet_disable().await;
+            }
+        }
+
+        JsonResponse::new(json!(true), id).into()
+    }
+
+    // RPCAPI:
+    // Retrieves P2P network information.
+    //
+    // --> {"jsonrpc": "2.0", "method": "dnet_info", "params": [], "id": 42}
+    // <-- {"jsonrpc": "2.0", result": {"nodeID": [], "nodeinfo": [], "id": 42}
+    async fn dnet_info(&self, id: Value, _params: &[Value]) -> JsonResult {
+        let mut dnet_info = self.sync_p2p.dnet_info().await;
+        if self.consensus_p2p.is_some() {
+            dnet_info.extend(self.consensus_p2p.clone().unwrap().dnet_info().await);
+        }
+        JsonResponse::new(net::P2p::map_dnet_info(dnet_info), id).into()
+    }
+}

+ 6 - 2
bin/darkfid2/src/tests/harness.rs

@@ -18,6 +18,7 @@
 
 use darkfi::{
     blockchain::{BlockInfo, Header},
+    net::{P2p, Settings},
     util::time::TimeKeeper,
     validator::{
         consensus::{next_block_reward, pid::slot_pid_output},
@@ -74,14 +75,17 @@ impl Harness {
         );
 
         // Generate validators using pregenerated vks
+        let sync_p2p = P2p::new(Settings::default()).await;
         let sled_db = sled::Config::new().temporary(true).open()?;
         vks::inject(&sled_db)?;
         let validator = Validator::new(&sled_db, val_config.clone()).await?;
-        let alice = Darkfid::new(validator).await;
+        let alice = Darkfid::new(sync_p2p, None, validator).await;
+
+        let sync_p2p = P2p::new(Settings::default()).await;
         let sled_db = sled::Config::new().temporary(true).open()?;
         vks::inject(&sled_db)?;
         let validator = Validator::new(&sled_db, val_config.clone()).await?;
-        let bob = Darkfid::new(validator).await;
+        let bob = Darkfid::new(sync_p2p, None, validator).await;
 
         Ok(Self { config, alice, bob })
     }

+ 1 - 1
src/contract/test-harness/src/vks.rs

@@ -48,7 +48,7 @@ use darkfi_serial::{deserialize, serialize};
 use log::debug;
 
 /// Update this if any circuits are changed
-const VKS_HASH: &str = "63c998f32a8822e9353d7f5794ae2abb1a5b4f77ce4e926cdecd41102e1aac21";
+const VKS_HASH: &str = "1a514858c589cf2b6f28930d7f6fbae9f7455661d68a7944b776a947811b3430";
 
 fn vks_path() -> Result<PathBuf> {
     let output = Command::new("git").arg("rev-parse").arg("--show-toplevel").output()?.stdout;