/* 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 .
*/
// TODO: FIXME: Some of the protocols should block operations until DAG is synced.
use std::{
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::Duration,
};
use darkfi_serial::{async_trait, deserialize_async, SerialDecodable, SerialEncodable};
use log::{debug, error, trace, warn};
use smol::Executor;
use super::{Event, EventGraph, EventGraphPtr, NULL_ID};
use crate::{impl_p2p_message, net::*, system::timeout::timeout, Error, Result};
/// Malicious behaviour threshold. If the threshold is reached, we will
/// drop the peer from our P2P connection.
const MALICIOUS_THRESHOLD: usize = 5;
/// Time to wait for a parent ID reply
pub(super) const REPLY_TIMEOUT: Duration = Duration::from_secs(5);
/// P2P protocol implementation for the Event Graph.
pub struct ProtocolEventGraph {
/// Pointer to the connected peer
channel: ChannelPtr,
/// Pointer to the Event Graph instance
event_graph: EventGraphPtr,
/// `MessageSubscriber` for `EventPut`
ev_put_sub: MessageSubscription,
/// `MessageSubscriber` for `EventReq`
ev_req_sub: MessageSubscription,
/// `MessageSubscriber` for `EventRep`
ev_rep_sub: MessageSubscription,
/// `MessageSubscriber` for `TipReq`
tip_req_sub: MessageSubscription,
/// `MessageSubscriber` for `TipRep`
_tip_rep_sub: MessageSubscription,
/// Peer malicious message count
malicious_count: AtomicUsize,
/// P2P jobs manager pointer
jobsman: ProtocolJobsManagerPtr,
}
/// A P2P message representing publishing an event on the network
#[derive(Clone, SerialEncodable, SerialDecodable)]
pub struct EventPut(pub Event);
impl_p2p_message!(EventPut, "EventGraph::EventPut");
/// A P2P message representing an event request
#[derive(Clone, SerialEncodable, SerialDecodable)]
pub struct EventReq(pub blake3::Hash);
impl_p2p_message!(EventReq, "EventGraph::EventReq");
/// A P2P message representing an event reply
#[derive(Clone, SerialEncodable, SerialDecodable)]
pub struct EventRep(pub Event);
impl_p2p_message!(EventRep, "EventGraph::EventRep");
/// A P2P message representing a request for a peer's DAG tips
#[derive(Clone, SerialEncodable, SerialDecodable)]
pub struct TipReq {}
impl_p2p_message!(TipReq, "EventGraph::TipReq");
/// A P2P message representing a reply for the peer's DAG tips
#[derive(Clone, SerialEncodable, SerialDecodable)]
pub struct TipRep(pub Vec);
impl_p2p_message!(TipRep, "EventGraph::TipRep");
#[async_trait]
impl ProtocolBase for ProtocolEventGraph {
async fn start(self: Arc, ex: Arc>) -> Result<()> {
self.jobsman.clone().start(ex.clone());
self.jobsman.clone().spawn(self.clone().handle_event_put(), ex.clone()).await;
self.jobsman.clone().spawn(self.clone().handle_event_req(), ex.clone()).await;
self.jobsman.clone().spawn(self.clone().handle_tip_req(), ex.clone()).await;
Ok(())
}
fn name(&self) -> &'static str {
"ProtocolEventGraph"
}
}
impl ProtocolEventGraph {
pub async fn init(event_graph: EventGraphPtr, channel: ChannelPtr) -> Result {
let msg_subsystem = channel.message_subsystem();
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
let ev_put_sub = channel.subscribe_msg::().await?;
let ev_req_sub = channel.subscribe_msg::().await?;
let ev_rep_sub = channel.subscribe_msg::().await?;
let tip_req_sub = channel.subscribe_msg::().await?;
let _tip_rep_sub = channel.subscribe_msg::().await?;
Ok(Arc::new(Self {
channel: channel.clone(),
event_graph,
ev_put_sub,
ev_req_sub,
ev_rep_sub,
tip_req_sub,
_tip_rep_sub,
malicious_count: AtomicUsize::new(0),
jobsman: ProtocolJobsManager::new("ProtocolEventGraph", channel.clone()),
}))
}
/// Protocol function handling `EventPut`.
/// This is triggered whenever someone broadcasts (or relays) a new
/// event on the network.
async fn handle_event_put(self: Arc) -> Result<()> {
loop {
let event = match self.ev_put_sub.receive().await {
Ok(v) => v.0.clone(),
Err(_) => continue,
};
trace!(
target: "event_graph::protocol::handle_event_put()",
"Got EventPut: {} [{}]", event.id(), self.channel.address(),
);
// Check if the event is older than the genesis event. If so, we should
// not include it in our Dag.
// The genesis event marks the last time the Dag has been pruned of old
// events. The pruning interval is defined by the days_rotation field
// of [`EventGraph`].
// TODO it would be better to store/cache this instead of calculating
// on every broadcast/relay.
let genesis_timestamp =
EventGraph::generate_genesis(self.event_graph.days_rotation()).timestamp;
if event.timestamp < genesis_timestamp {
debug!(
target: "event_graph::protocol::handle_event_put()",
"Event {} is older than genesis. Event timestamp: `{}`. Genesis timestamp: `{}`",
event.id(), event.timestamp, genesis_timestamp
);
}
// We received an event. Check if we already have it in our DAG.
// Also check if we have the event's parents. In the case we do
// not have the parents, we'll request them from the peer that has
// sent this event to us. In case they do not reply in time, we drop
// the event.
// Validate the event first. If we do not consider it valid, we
// will just drop it and stay quiet. If the malicious threshold
// is reached, we will stop the connection.
if !event.validate() {
let malicious_count = self.malicious_count.fetch_add(1, SeqCst);
if malicious_count + 1 == MALICIOUS_THRESHOLD {
error!(
target: "event_graph::protocol::handle_event_put()",
"[EVENTGRAPH] Peer {} reached malicious threshold. Dropping connection.",
self.channel.address(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
warn!(
target: "event_graph::protocol::handle_event_put()",
"[EVENTGRAPH] Peer {} sent us a malicious event", self.channel.address(),
);
continue
}
// If we have already seen the event, we'll stay quiet.
let event_id = event.id();
if self.event_graph.dag.contains_key(event_id.as_bytes()).unwrap() {
debug!(
target: "event_graph::protocol::handle_event_put()",
"Event {} is already known", event_id,
);
continue
}
// At this point, this is a new event to us. Let's see if we
// have all of its parents.
debug!(
target: "event_graph::protocol::handle_event_put()",
"Event {} is new", event_id,
);
let mut missing_parents = vec![];
for parent_id in event.parents.iter() {
// `event.validate()` should have already made sure that
// not all parents are NULL, and that there are no duplicates.
if parent_id == &NULL_ID {
continue
}
if !self.event_graph.dag.contains_key(parent_id.as_bytes()).unwrap() {
missing_parents.push(*parent_id);
}
}
// If we have missing parents, then we have to attempt to
// fetch them from this peer. Do this recursively until we
// find all of them.
if !missing_parents.is_empty() {
// We track the received events in a vec. If/when we get all
// of them, we need to insert them in reverse so the DAG state
// stays correct and unreferenced tips represent the actual thing
// they should. If we insert them out of order, then we might have
// wrong unreferenced tips.
// TODO: What should we do if at some point the events become too old?
let mut received_events = vec![];
debug!(
target: "event_graph::protocol::handle_event_put()",
"Event has {} missing parents. Requesting...", missing_parents.len(),
);
while !missing_parents.is_empty() {
for parent_id in missing_parents.clone().iter() {
debug!(
target: "event_graph::protocol::handle_event_put()",
"Requesting {}...", parent_id,
);
self.channel.send(&EventReq(*parent_id)).await?;
let parent = match timeout(REPLY_TIMEOUT, self.ev_rep_sub.receive()).await {
Ok(parent) => parent?,
Err(_) => {
error!(
target: "event_graph::protocol::handle_event_put()",
"[EVENTGRAPH] Timeout while waiting for parent {} from {}",
parent_id, self.channel.address(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
};
let parent = parent.0.clone();
if &parent.id() != parent_id {
error!(
target: "event_graph::protocol::handle_event_put()",
"[EVENTGRAPH] Peer {} replied with a wrong event: {}",
self.channel.address(), parent.id(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
debug!(
target: "event_graph::protocol::handle_event_put()",
"Got correct parent event {}", parent.id(),
);
received_events.push(parent.clone());
let pos = missing_parents.iter().position(|id| id == &parent.id()).unwrap();
missing_parents.remove(pos);
// See if we have the upper parents
for upper_parent in parent.parents.iter() {
if upper_parent == &NULL_ID {
continue
}
if !self.event_graph.dag.contains_key(upper_parent.as_bytes()).unwrap()
{
debug!(
target: "event_graph::protocol::handle_event_put()",
"Found upper missing parent event{}", upper_parent,
);
missing_parents.push(*upper_parent);
}
}
}
} // <-- while !missing_parents.is_empty()
// At this point we should've got all the events.
// We should add them to the DAG.
// TODO: FIXME: Also validate these events.
let received_events_rev: Vec =
received_events.iter().rev().cloned().collect();
self.event_graph.dag_insert(&received_events_rev).await.unwrap();
} // <-- !missing_parents.is_empty()
// If we're here, we have all the parents, and we can now
// add the actual event to the DAG.
debug!(
target: "event_graph::protocol::handle_event_put()",
"Got all parents necessary for insertion",
);
self.event_graph.dag_insert(&[event.clone()]).await.unwrap();
// Relay the event to other peers.
self.event_graph
.p2p
.broadcast_with_exclude(&EventPut(event), &[self.channel.address().clone()])
.await;
}
}
/// Protocol function handling `EventReq`.
/// This is triggered whenever someone requests an event from us.
async fn handle_event_req(self: Arc) -> Result<()> {
loop {
let event_id = match self.ev_req_sub.receive().await {
Ok(v) => v.0,
Err(_) => continue,
};
trace!(
target: "event_graph::protocol::handle_event_req()",
"Got EventReq: {} [{}]", event_id, self.channel.address(),
);
// We received an event request from somebody.
// If we do have it, we will send it back to them as `EventRep`.
// Otherwise, we'll stay quiet. An honest node should always have
// something to reply with provided that the request is legitimate,
// i.e. we've sent something to them and they did not have some of
// the parents.
// Check if we expected this request to come around.
// I dunno if this is a good idea, but it seems it will help
// against malicious event requests where they want us to keep
// reading our db and steal our bandwidth.
if !self.event_graph.broadcasted_ids.read().await.contains(&event_id) {
let malicious_count = self.malicious_count.fetch_add(1, SeqCst);
if malicious_count + 1 == MALICIOUS_THRESHOLD {
error!(
target: "event_graph::protocol::handle_event_req()",
"[EVENTGRAPH] Peer {} reached malicious threshold. Dropping connection.",
self.channel.address(),
);
self.channel.stop().await;
return Err(Error::ChannelStopped)
}
warn!(
target: "event_graph::protocol::handle_event_req()",
"[EVENTGRAPH] Peer {} requested an unexpected event {}",
self.channel.address(), event_id,
);
continue
}
// At this point we should have it in our DAG.
// This code panics if this is not the case.
debug!(
target: "event_graph::protocol::handle_event_req()",
"Fetching event {} from DAG", event_id,
);
let event = self.event_graph.dag.get(event_id.as_bytes()).unwrap().unwrap();
let event: Event = deserialize_async(&event).await.unwrap();
// Check if the event is older than the genesis event. If so, something
// has gone wrong. The event should have been pruned during the last
// rotation.
// TODO it would be better to store/cache this instead of calculating
// on every broadcast/relay.
let genesis_timestamp =
EventGraph::generate_genesis(self.event_graph.days_rotation()).timestamp;
if event.timestamp < genesis_timestamp {
error!(
target: "event_graph::protocol::handle_event_req()",
"Requested event {} is older than previous rotation period. It should have been pruned.
Event timestamp: `{}`. Genesis timestamp: `{}`",
event.id(), event.timestamp, genesis_timestamp
);
}
// Now let's get the upper level of event IDs. When we reply, we could
// get requests for those IDs as well.
let mut bcast_ids = self.event_graph.broadcasted_ids.write().await;
for parent_id in event.parents.iter() {
if parent_id != &NULL_ID {
bcast_ids.insert(*parent_id);
}
}
// TODO: We should remove the reply from the bcast IDs for this specific channel.
// We can't remove them for everyone.
//bcast_ids.remove(&event_id);
drop(bcast_ids);
// Reply with the event
self.channel.send(&EventRep(event)).await?;
}
}
/// Protocol function handling `TipReq`.
/// This is triggered when someone requests the current unreferenced
/// tips of our DAG.
async fn handle_tip_req(self: Arc) -> Result<()> {
loop {
self.tip_req_sub.receive().await?;
trace!(
target: "event_graph::protocol::handle_tip_req()",
"Got TipReq [{}]", self.channel.address(),
);
// TODO: Rate limit
// We received a tip request. Let's find them, add them to
// our bcast ids list, and reply with them.
let mut tips = self.event_graph.get_unreferenced_tips().await.to_vec();
tips.retain(|x| x != &NULL_ID);
let mut bcast_ids = self.event_graph.broadcasted_ids.write().await;
for tip in tips.iter() {
bcast_ids.insert(*tip);
}
drop(bcast_ids);
self.channel.send(&TipRep(tips)).await?;
}
}
}