|
|
@@ -1,20 +1,16 @@
|
|
|
use async_executor::Executor;
|
|
|
use async_std::sync::Arc;
|
|
|
use async_trait::async_trait;
|
|
|
-use chrono::Utc;
|
|
|
-use futures::{select, FutureExt};
|
|
|
use futures_lite::future;
|
|
|
-use log::{debug, error, info, warn};
|
|
|
+use log::{error, info};
|
|
|
use serde_derive::Deserialize;
|
|
|
use serde_json::{json, Value};
|
|
|
-use std::time::Duration;
|
|
|
use structopt::StructOpt;
|
|
|
use structopt_toml::StructOptToml;
|
|
|
use url::Url;
|
|
|
|
|
|
use darkfi::{
|
|
|
async_daemonize, cli_desc, net,
|
|
|
- net::P2pPtr,
|
|
|
rpc::{
|
|
|
jsonrpc::{
|
|
|
ErrorCode::{InvalidParams, MethodNotFound},
|
|
|
@@ -25,7 +21,6 @@ use darkfi::{
|
|
|
util::{
|
|
|
cli::{get_log_config, get_log_level, spawn_config},
|
|
|
path::get_config_path,
|
|
|
- sleep,
|
|
|
},
|
|
|
Result,
|
|
|
};
|
|
|
@@ -33,16 +28,15 @@ use darkfi::{
|
|
|
mod error;
|
|
|
use error::{server_error, RpcError};
|
|
|
|
|
|
-mod structures;
|
|
|
-use structures::{Dht, DhtPtr, KeyRequest, KeyResponse, LookupRequest};
|
|
|
+mod dht;
|
|
|
+use dht::{Dht, DhtPtr};
|
|
|
+
|
|
|
+mod messages;
|
|
|
|
|
|
mod protocol;
|
|
|
-use protocol::Protocol;
|
|
|
|
|
|
const CONFIG_FILE: &str = "dhtd_config.toml";
|
|
|
const CONFIG_FILE_CONTENTS: &str = include_str!("../dhtd_config.toml");
|
|
|
-const REQUEST_TIMEOUT: u64 = 2400;
|
|
|
-const SEEN_DURATION: i64 = 120;
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
|
|
|
#[serde(default)]
|
|
|
@@ -87,22 +81,11 @@ struct Args {
|
|
|
pub struct Dhtd {
|
|
|
/// Daemon dht state
|
|
|
dht: DhtPtr,
|
|
|
- /// P2P network pointer
|
|
|
- p2p: P2pPtr,
|
|
|
- /// Channel to receive responses from P2P
|
|
|
- p2p_recv_channel: async_channel::Receiver<KeyResponse>,
|
|
|
- /// Stop signal channel to terminate background processes
|
|
|
- stop_signal: async_channel::Receiver<()>,
|
|
|
}
|
|
|
|
|
|
impl Dhtd {
|
|
|
- pub async fn new(
|
|
|
- dht: DhtPtr,
|
|
|
- p2p: P2pPtr,
|
|
|
- p2p_recv_channel: async_channel::Receiver<KeyResponse>,
|
|
|
- stop_signal: async_channel::Receiver<()>,
|
|
|
- ) -> Result<Self> {
|
|
|
- Ok(Self { dht, p2p, p2p_recv_channel, stop_signal })
|
|
|
+ pub async fn new(dht: DhtPtr) -> Result<Self> {
|
|
|
+ Ok(Self { dht })
|
|
|
}
|
|
|
|
|
|
// RPCAPI:
|
|
|
@@ -115,56 +98,22 @@ impl Dhtd {
|
|
|
return JsonError::new(InvalidParams, None, id).into()
|
|
|
}
|
|
|
|
|
|
- // Node verifies the key exist in the lookup map.
|
|
|
let key = params[0].to_string();
|
|
|
- let peers = match self.dht.read().await.lookup.get(&key) {
|
|
|
- Some(v) => v.clone(),
|
|
|
- None => {
|
|
|
- info!("Did not find key: {}", key);
|
|
|
- return server_error(RpcError::UnknownKey, id).into()
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- debug!("Key is in peers: {:?}", peers);
|
|
|
-
|
|
|
- // Each node holds a local map, acting as its cache.
|
|
|
- // When the node receives a request for a key it doesn't hold,
|
|
|
- // it will query the P2P network and saves the response in its local cache.
|
|
|
- match self.dht.read().await.map.get(&key) {
|
|
|
- Some(v) => {
|
|
|
- let string = std::str::from_utf8(&v).unwrap();
|
|
|
- return JsonResponse::new(json!(string), id).into()
|
|
|
- }
|
|
|
- None => info!("Requested key doesn't exist locally, querying the network..."),
|
|
|
- };
|
|
|
-
|
|
|
- // We retrieve p2p network connected channels, to verify if we
|
|
|
- // are connected to a network.
|
|
|
- // Using len here because is_empty() uses unstable library feature
|
|
|
- // called 'exact_size_is_empty'.
|
|
|
- if self.p2p.channels().lock().await.values().len() == 0 {
|
|
|
- warn!("Node is not connected to other nodes");
|
|
|
- return server_error(RpcError::UnknownKey, id).into()
|
|
|
- }
|
|
|
-
|
|
|
- // We create a key request, and broadcast it to the network
|
|
|
- let daemon = self.dht.read().await.id.to_string();
|
|
|
- // We choose last known peer as request recipient
|
|
|
- let peer = peers.iter().last().unwrap().to_string();
|
|
|
- let request = KeyRequest::new(daemon.clone(), peer, key.clone());
|
|
|
- // TODO: ask connected peers directly, not broadcast
|
|
|
- if let Err(e) = self.p2p.broadcast(request).await {
|
|
|
- error!("Failed broadcasting request: {}", e);
|
|
|
- return server_error(RpcError::RequestBroadcastFail, id)
|
|
|
- }
|
|
|
-
|
|
|
- // Waiting network response
|
|
|
- match self.waiting_for_response().await {
|
|
|
- Ok(resp) => match resp {
|
|
|
- Some(response) => {
|
|
|
+ let result = self.dht.read().await.get(key.clone()).await;
|
|
|
+ match result {
|
|
|
+ Ok(res) => match res {
|
|
|
+ Some(value) => {
|
|
|
info!("Key found!");
|
|
|
- let string = std::str::from_utf8(&response.value).unwrap().to_string();
|
|
|
- self.insert_pair(id, response.key, string).await
|
|
|
+ // Optionally, we insert the key to our local map.
|
|
|
+ // This must happen here because we got blocking/race conditions
|
|
|
+ // if we try to insert the value in dht.get().
|
|
|
+ if let Err(e) = self.dht.write().await.insert(key.clone(), value.clone()).await
|
|
|
+ {
|
|
|
+ error!("Failed to insert key: {}", e);
|
|
|
+ return server_error(RpcError::KeyInsertFail, id)
|
|
|
+ }
|
|
|
+ let string = std::str::from_utf8(&value).unwrap().to_string();
|
|
|
+ JsonResponse::new(json!((key, string)), id).into()
|
|
|
}
|
|
|
None => {
|
|
|
info!("Did not find key: {}", key);
|
|
|
@@ -178,31 +127,8 @@ impl Dhtd {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Auxilary function to wait for a key response from the P2P network.
|
|
|
- async fn waiting_for_response(&self) -> Result<Option<KeyResponse>> {
|
|
|
- let ex = Arc::new(async_executor::Executor::new());
|
|
|
- let (timeout_s, timeout_r) = async_channel::unbounded::<()>();
|
|
|
- ex.spawn(async move {
|
|
|
- sleep(Duration::from_millis(REQUEST_TIMEOUT).as_secs()).await;
|
|
|
- timeout_s.send(()).await.unwrap_or(());
|
|
|
- })
|
|
|
- .detach();
|
|
|
-
|
|
|
- loop {
|
|
|
- select! {
|
|
|
- msg = self.p2p_recv_channel.recv().fuse() => {
|
|
|
- let response = msg?;
|
|
|
- return Ok(Some(response))
|
|
|
- },
|
|
|
- _ = self.stop_signal.recv().fuse() => break,
|
|
|
- _ = timeout_r.recv().fuse() => break,
|
|
|
- }
|
|
|
- }
|
|
|
- Ok(None)
|
|
|
- }
|
|
|
-
|
|
|
// RPCAPI:
|
|
|
- // Insert key value pair in local map.
|
|
|
+ // Insert key value pair in dht.
|
|
|
// --> {"jsonrpc": "2.0", "method": "insert", "params": ["key", "value"], "id": 1}
|
|
|
// <-- {"jsonrpc": "2.0", "result": "(key, value)", "id": 1}
|
|
|
async fn insert(&self, id: Value, params: &[Value]) -> JsonResult {
|
|
|
@@ -213,23 +139,12 @@ impl Dhtd {
|
|
|
let key = params[0].to_string();
|
|
|
let value = params[1].to_string();
|
|
|
|
|
|
- self.insert_pair(id, key, value).await
|
|
|
- }
|
|
|
-
|
|
|
- /// Auxilary function to handle pair insertion to dht
|
|
|
- async fn insert_pair(&self, id: Value, key: String, value: String) -> JsonResult {
|
|
|
- if let Err(e) = self.dht.write().await.insert(key.clone(), value.as_bytes().to_vec()) {
|
|
|
+ if let Err(e) = self.dht.write().await.insert(key.clone(), value.as_bytes().to_vec()).await
|
|
|
+ {
|
|
|
error!("Failed to insert key: {}", e);
|
|
|
return server_error(RpcError::KeyInsertFail, id)
|
|
|
}
|
|
|
|
|
|
- let daemon = self.dht.read().await.id.to_string();
|
|
|
- let request = LookupRequest::new(daemon, key.clone(), 0);
|
|
|
- if let Err(e) = self.p2p.broadcast(request).await {
|
|
|
- error!("Failed broadcasting request: {}", e);
|
|
|
- return server_error(RpcError::RequestBroadcastFail, id)
|
|
|
- }
|
|
|
-
|
|
|
JsonResponse::new(json!((key, value)), id).into()
|
|
|
}
|
|
|
|
|
|
@@ -244,19 +159,11 @@ impl Dhtd {
|
|
|
|
|
|
let key = params[0].to_string();
|
|
|
// Check if key value pair existed and act accordingly
|
|
|
- let result = self.dht.write().await.remove(key.clone());
|
|
|
+ let result = self.dht.write().await.remove(key.clone()).await;
|
|
|
match result {
|
|
|
Ok(option) => match option {
|
|
|
Some(k) => {
|
|
|
info!("Key removed: {}", k);
|
|
|
-
|
|
|
- let daemon = self.dht.read().await.id.to_string();
|
|
|
- let request = LookupRequest::new(daemon, key.clone(), 1);
|
|
|
- if let Err(e) = self.p2p.broadcast(request).await {
|
|
|
- error!("Failed broadcasting request: {}", e);
|
|
|
- return server_error(RpcError::RequestBroadcastFail, id)
|
|
|
- }
|
|
|
-
|
|
|
JsonResponse::new(json!(k), id).into()
|
|
|
}
|
|
|
None => {
|
|
|
@@ -310,32 +217,6 @@ impl RequestHandler for Dhtd {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Auxilary function to periodically prun seen messages, based on when they were received.
|
|
|
-// This helps us to prevent broadcasting loops.
|
|
|
-async fn prune_seen_messages(dht: DhtPtr) {
|
|
|
- loop {
|
|
|
- sleep(SEEN_DURATION as u64).await;
|
|
|
- debug!("Pruning seen messages");
|
|
|
-
|
|
|
- let now = Utc::now().timestamp();
|
|
|
-
|
|
|
- let mut prune = vec![];
|
|
|
- let map = dht.read().await.seen.clone();
|
|
|
- for (k, v) in map.iter() {
|
|
|
- if now - v > SEEN_DURATION {
|
|
|
- prune.push(k);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let mut map = map.clone();
|
|
|
- for i in prune {
|
|
|
- map.remove(i);
|
|
|
- }
|
|
|
-
|
|
|
- dht.write().await.seen = map;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
async_daemonize!(realmain);
|
|
|
async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
|
|
|
// We use this handler to block this function after detaching all
|
|
|
@@ -347,9 +228,6 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
|
|
|
})
|
|
|
.unwrap();
|
|
|
|
|
|
- // Initialize daemon dht
|
|
|
- let dht = Dht::new(None).await?;
|
|
|
-
|
|
|
// P2P network
|
|
|
let network_settings = net::Settings {
|
|
|
inbound: args.p2p_accept,
|
|
|
@@ -360,27 +238,15 @@ async fn realmain(args: Args, ex: Arc<Executor<'_>>) -> Result<()> {
|
|
|
..Default::default()
|
|
|
};
|
|
|
|
|
|
- let (p2p_send_channel, p2p_recv_channel) = async_channel::unbounded::<KeyResponse>();
|
|
|
let p2p = net::P2p::new(network_settings).await;
|
|
|
- let registry = p2p.protocol_registry();
|
|
|
-
|
|
|
- info!("Registering P2P protocols...");
|
|
|
- let _dht = dht.clone();
|
|
|
- registry
|
|
|
- .register(net::SESSION_ALL, move |channel, p2p| {
|
|
|
- let sender = p2p_send_channel.clone();
|
|
|
- let dht = _dht.clone();
|
|
|
- async move { Protocol::init(channel, sender, dht, p2p).await.unwrap() }
|
|
|
- })
|
|
|
- .await;
|
|
|
+
|
|
|
+ // Initialize daemon dht
|
|
|
+ let dht = Dht::new(None, p2p.clone(), shutdown.clone(), ex.clone()).await?;
|
|
|
|
|
|
// Initialize daemon
|
|
|
- let dhtd = Dhtd::new(dht.clone(), p2p.clone(), p2p_recv_channel, shutdown.clone()).await?;
|
|
|
+ let dhtd = Dhtd::new(dht.clone()).await?;
|
|
|
let dhtd = Arc::new(dhtd);
|
|
|
|
|
|
- // Task to periodically clean up daemon seen messages
|
|
|
- ex.spawn(prune_seen_messages(dht.clone())).detach();
|
|
|
-
|
|
|
// JSON-RPC server
|
|
|
info!("Starting JSON-RPC server");
|
|
|
ex.spawn(listen_and_serve(args.rpc_listen, dhtd.clone())).detach();
|