/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 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::{collections::HashSet, sync::Arc};
use tracing::{error, info};
use rand::{rngs::OsRng, Rng};
use smol::{stream::StreamExt, Executor};
use structopt_toml::{serde::Deserialize, structopt::StructOpt, StructOptToml};
use darkfi::{
async_daemonize, cli_desc, impl_p2p_message,
net::{
metering::{MeteringConfiguration, DEFAULT_METERING_CONFIGURATION},
protocol::protocol_generic::{
ProtocolGenericAction, ProtocolGenericHandler, ProtocolGenericHandlerPtr,
},
session::SESSION_DEFAULT,
settings::SettingsOpt,
Message, P2p, P2pPtr, Settings,
},
system::{sleep, StoppableTask, StoppableTaskPtr},
Error, Result,
};
use darkfi_serial::{async_trait, SerialDecodable, SerialEncodable};
const CONFIG_FILE: &str = "generic_node_config.toml";
const CONFIG_FILE_CONTENTS: &str = include_str!("../generic_node_config.toml");
#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
#[serde(default)]
#[structopt(name = "generic-node", about = cli_desc!())]
struct Args {
#[structopt(short, long)]
/// Configuration file to use
config: Option,
#[structopt(short, long)]
/// Set log file to ouput into
log: Option,
#[structopt(short, parse(from_occurrences))]
/// Increase verbosity (-vvv supported)
verbose: u8,
#[structopt(short, long)]
/// Node ID, used in the dummy messages
node_id: u64,
/// P2P network settings
#[structopt(flatten)]
net: SettingsOpt,
}
// Generic messages
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
struct GenericStringMessage {
msg: String,
}
impl_p2p_message!(
GenericStringMessage,
"generic_string_message",
0,
0,
DEFAULT_METERING_CONFIGURATION
);
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
struct GenericNumberMessage {
num: u64,
}
impl_p2p_message!(
GenericNumberMessage,
"generic_number_message",
0,
0,
DEFAULT_METERING_CONFIGURATION
);
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
struct GenericRequestMessage {
msg: String,
}
impl_p2p_message!(
GenericRequestMessage,
"generic_request_message",
0,
0,
DEFAULT_METERING_CONFIGURATION
);
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
struct GenericResponseMessage {
msg: String,
}
impl_p2p_message!(
GenericResponseMessage,
"generic_response_message",
0,
0,
DEFAULT_METERING_CONFIGURATION
);
/// Generic daemon structure
struct Genericd {
/// Node ID, used in the dummy messages
node_id: u64,
/// P2P network pointer
p2p: P2pPtr,
/// GenericStringMessage handler
generic_string_msg_handler:
ProtocolGenericHandlerPtr,
/// GenericNumberMessage handler
generic_number_msg_handler:
ProtocolGenericHandlerPtr,
/// GenericRequestMessage handler
generic_request_msg_handler:
ProtocolGenericHandlerPtr,
/// Broadcasting messages task
broadcast_task: StoppableTaskPtr,
}
impl Genericd {
// Initialize daemon with all its required stuff.
async fn new(
node_id: u64,
settings: &Settings,
executor: &Arc>,
) -> Result {
// Generating the p2p configuration and attaching our protocols
let p2p = P2p::new(settings.clone(), executor.clone()).await?;
// Add a generic protocol handler for GenericStringMessage
let generic_string_msg_handler =
ProtocolGenericHandler::new(&p2p, "ProtocolGenericString", SESSION_DEFAULT).await;
// Add a generic protocol for GenericNumberMessage
let generic_number_msg_handler =
ProtocolGenericHandler::new(&p2p, "ProtocolGenericNumber", SESSION_DEFAULT).await;
// Add a generic protocol for GenericRequestMessage
let generic_request_msg_handler =
ProtocolGenericHandler::new(&p2p, "ProtocolGenericRequest", SESSION_DEFAULT).await;
let broadcast_task = StoppableTask::new();
Ok(Self {
node_id,
p2p,
generic_string_msg_handler,
generic_number_msg_handler,
generic_request_msg_handler,
broadcast_task,
})
}
/// Start all daemon background tasks.
async fn start(&self) -> Result<()> {
info!(target: "genericd", "Starting tasks...");
self.generic_string_msg_handler.task.clone().start(
handle_generic_string_msg(self.generic_string_msg_handler.clone()),
|res| async move {
match res {
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
Err(e) => error!(target: "genericd", "Failed starting protocol generic string handler task: {e}"),
}
},
Error::DetachedTaskStopped,
self.p2p.executor(),
);
self.generic_number_msg_handler.task.clone().start(
handle_generic_number_msg(self.generic_number_msg_handler.clone()),
|res| async move {
match res {
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
Err(e) => error!(target: "genericd", "Failed starting protocol generic number handler task: {e}"),
}
},
Error::DetachedTaskStopped,
self.p2p.executor(),
);
self.generic_request_msg_handler.task.clone().start(
handle_generic_request_msg(self.node_id, self.generic_request_msg_handler.clone()),
|res| async move {
match res {
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
Err(e) => error!(target: "genericd", "Failed starting protocol generic request handler task: {e}"),
}
},
Error::DetachedTaskStopped,
self.p2p.executor(),
);
self.p2p.clone().start().await?;
self.broadcast_task.clone().start(
broadcast_messages(self.node_id, self.p2p.clone()),
|res| async move {
match res {
Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
Err(e) => error!(target: "genericd", "Failed starting broadcasting task: {e}"),
}
},
Error::DetachedTaskStopped,
self.p2p.executor(),
);
info!(target: "genericd", "All tasks started!");
Ok(())
}
/// Stop all daemon background tasks.
async fn stop(&self) {
info!(target: "genericd", "Terminating tasks...");
self.broadcast_task.stop().await;
self.p2p.stop().await;
self.generic_string_msg_handler.task.stop().await;
self.generic_number_msg_handler.task.stop().await;
self.generic_request_msg_handler.task.stop().await;
info!(target: "genericd", "All tasks terminated!");
}
}
/// Background handler function for GenericStringMessage.
async fn handle_generic_string_msg(
handler: ProtocolGenericHandlerPtr,
) -> Result<()> {
let mut seen = HashSet::new();
loop {
// Wait for a new message
let (channel, msg) = handler.receiver.recv().await?;
if seen.contains(&msg.msg) {
handler.send_action(channel, ProtocolGenericAction::Skip).await;
continue
}
info!(target: "handle_generic_string_msg", "Received string message from channel {channel}: {}", msg.msg);
seen.insert(msg.msg);
handler.send_action(channel, ProtocolGenericAction::Broadcast).await;
}
}
/// Background handler function for GenericNumberMessage.
async fn handle_generic_number_msg(
handler: ProtocolGenericHandlerPtr,
) -> Result<()> {
let mut seen = HashSet::new();
loop {
// Wait for a new message
let (channel, msg) = handler.receiver.recv().await?;
if seen.contains(&msg.num) {
handler.send_action(channel, ProtocolGenericAction::Skip).await;
continue
}
info!(target: "handle_generic_number_msg", "Received number message from channel {channel}: {}", msg.num);
seen.insert(msg.num);
handler.send_action(channel, ProtocolGenericAction::Broadcast).await;
}
}
/// Background handler function for GenericRequestMessage.
async fn handle_generic_request_msg(
node_id: u64,
handler: ProtocolGenericHandlerPtr,
) -> Result<()> {
let response = GenericResponseMessage { msg: format!("Pong from node {node_id}!") };
loop {
// Wait for a new message
let (channel, msg) = handler.receiver.recv().await?;
info!(target: "handle_generic_request_msg", "Received request message from channel {channel}: {}", msg.msg);
handler.send_action(channel, ProtocolGenericAction::Response(response.clone())).await;
}
}
/// Background function to send messages at random intervals.
async fn broadcast_messages(node_id: u64, p2p: P2pPtr) -> Result<()> {
let comms_timeout = p2p.settings().read().await.outbound_connect_timeout;
let request = GenericRequestMessage { msg: format!("Ping from node {node_id}!") };
let mut counter = 0;
loop {
let sleep_time = OsRng.gen_range(1..=10);
info!(target: "broadcast_messages", "Sleeping {sleep_time} till next broadcast...");
sleep(sleep_time).await;
info!(target: "broadcast_messages", "Broacasting messages...");
// Broadcast a generic string message
let string_msg =
GenericStringMessage { msg: format!("Hello from node {node_id}({counter})!") };
p2p.broadcast(&string_msg).await;
// Broadcast a generic number message
let number_msg = GenericNumberMessage { num: node_id + counter };
p2p.broadcast(&number_msg).await;
// Perform a direct request to each peer and grab their response
let peers = p2p.hosts().channels();
for peer in peers {
info!(target: "broadcast_messages", "Sending request message to peer {peer:?}: {}", request.msg);
let Ok(response_sub) = peer.subscribe_msg::().await else {
error!(target: "broadcast_messages", "Failure during `GenericResponseMessage` communication setup with peer: {peer:?}");
continue
};
if let Err(e) = peer.send(&request).await {
error!(target: "broadcast_messages", "Failure during `GenericResponseMessage` send to peer {peer:?}: {e}");
continue
};
let Ok(response) = response_sub.receive_with_timeout(comms_timeout).await else {
error!(target: "broadcast_messages", "Timeout while waiting for `GenericResponseMessage` from peer: {peer:?}");
continue
};
info!(target: "broadcast_messages", "Received response message from peer {peer:?}: {}", response.msg);
}
counter += 1;
}
}
async_daemonize!(realmain);
async fn realmain(args: Args, ex: Arc>) -> Result<()> {
info!(target: "generic-node", "Initializing generic node...");
let genericd = Genericd::new(args.node_id, &args.net.into(), &ex).await?;
genericd.start().await?;
// Signal handling for graceful termination.
let (signals_handler, signals_task) = SignalHandler::new(ex)?;
signals_handler.wait_termination(signals_task).await?;
info!(target: "generic-node", "Caught termination signal, cleaning up and exiting...");
info!(target: "generic-node", "Stopping genericd...");
genericd.stop().await;
Ok(())
}