/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2024 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::sync::Arc;
use async_trait::async_trait;
use log::{debug, error};
use smol::Executor;
use darkfi::{
blockchain::{BlockInfo, Header, HeaderHash},
impl_p2p_message,
net::{
ChannelPtr, Message, MessageSubscription, ProtocolBase, ProtocolBasePtr,
ProtocolJobsManager, ProtocolJobsManagerPtr,
},
validator::{consensus::Proposal, ValidatorPtr},
Result,
};
use darkfi_serial::{SerialDecodable, SerialEncodable};
// Constant defining how many blocks we send during syncing.
pub const BATCH: usize = 10;
/// Structure represening a request to ask a node for their current
/// canonical(finalized) tip block hash, if they are synced. We also
/// include our own tip, so they can verify we follow the same sequence.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct TipRequest {
/// Canonical(finalized) tip block hash
pub tip: HeaderHash,
}
impl_p2p_message!(TipRequest, "tiprequest");
/// Structure representing the response to `TipRequest`,
/// containing a boolean flag to indicate if we are synced,
/// and our canonical(finalized) tip block height and hash.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct TipResponse {
/// Flag indicating the node is synced
pub synced: bool,
/// Canonical(finalized) tip block height
pub height: Option,
/// Canonical(finalized) tip block hash
pub hash: Option,
}
impl_p2p_message!(TipResponse, "tipresponse");
/// Structure represening a request to ask a node for up to `BATCH` headers before
/// the provided header height.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct HeaderSyncRequest {
/// Header height
pub height: u32,
}
impl_p2p_message!(HeaderSyncRequest, "headersyncrequest");
/// Structure representing the response to `HeaderSyncRequest`,
/// containing up to `BATCH` headers before the requested block height.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct HeaderSyncResponse {
/// Response headers
pub headers: Vec,
}
impl_p2p_message!(HeaderSyncResponse, "headersyncresponse");
/// Structure represening a request to ask a node for up to`BATCH` blocks
/// of provided headers.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct SyncRequest {
/// Header hashes
pub headers: Vec,
}
impl_p2p_message!(SyncRequest, "syncrequest");
/// Structure representing the response to `SyncRequest`,
/// containing up to `BATCH` blocks after the requested block height.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct SyncResponse {
/// Response blocks
pub blocks: Vec,
}
impl_p2p_message!(SyncResponse, "syncresponse");
/// Structure represening a request to ask a node a fork sequence.
/// If we include a specific fork tip, they have to return its sequence,
/// otherwise they respond with their best fork sequence.
/// We also include our own canonical(finalized) tip, so they can verify
/// we follow the same sequence.
#[derive(Debug, SerialEncodable, SerialDecodable)]
pub struct ForkSyncRequest {
/// Canonical(finalized) tip block hash
pub tip: HeaderHash,
/// Optional fork tip block hash
pub fork_tip: Option,
}
impl_p2p_message!(ForkSyncRequest, "forksyncrequest");
/// Structure representing the response to `ForkSyncRequest`,
/// containing the requested fork sequence.
#[derive(Debug, Clone, SerialEncodable, SerialDecodable)]
pub struct ForkSyncResponse {
/// Response fork proposals
pub proposals: Vec,
}
impl_p2p_message!(ForkSyncResponse, "forksyncresponse");
pub struct ProtocolSync {
tip_sub: MessageSubscription,
header_sub: MessageSubscription,
request_sub: MessageSubscription,
fork_request_sub: MessageSubscription,
jobsman: ProtocolJobsManagerPtr,
validator: ValidatorPtr,
channel: ChannelPtr,
}
impl ProtocolSync {
pub async fn init(channel: ChannelPtr, validator: ValidatorPtr) -> Result {
debug!(
target: "darkfid::proto::protocol_sync::init",
"Adding ProtocolSync to the protocol registry"
);
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;
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
msg_subsystem.add_dispatch::().await;
let tip_sub = channel.subscribe_msg::().await?;
let header_sub = channel.subscribe_msg::().await?;
let request_sub = channel.subscribe_msg::().await?;
let fork_request_sub = channel.subscribe_msg::().await?;
Ok(Arc::new(Self {
tip_sub,
header_sub,
request_sub,
fork_request_sub,
jobsman: ProtocolJobsManager::new("SyncProtocol", channel.clone()),
validator,
channel,
}))
}
async fn handle_receive_tip_request(self: Arc) -> Result<()> {
debug!(target: "darkfid::proto::protocol_sync::handle_receive_tip_request", "START");
loop {
let request = match self.tip_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
let response = if !*self.validator.synced.read().await {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"Node still syncing blockchain, skipping..."
);
TipResponse { synced: false, height: None, hash: None }
} else {
// Check we follow the same sequence
match self.validator.blockchain.blocks.contains(&request.tip) {
Ok(contains) => {
if !contains {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"Node doesn't follow request sequence"
);
continue
}
}
Err(e) => {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"block_store.contains fail: {}",
e
);
continue
}
}
// Grab our current tip and return it
let tip = match self.validator.blockchain.last() {
Ok(v) => v,
Err(e) => {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"blockchain.last fail: {}",
e
);
continue
}
};
TipResponse { synced: true, height: Some(tip.0), hash: Some(tip.1) }
};
if let Err(e) = self.channel.send(&response).await {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_tip_request",
"channel send fail: {}",
e
)
};
}
}
async fn handle_receive_header_request(self: Arc) -> Result<()> {
debug!(target: "darkfid::proto::protocol_sync::handle_receive_header_request", "START");
loop {
let request = match self.header_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_header_request",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
if !*self.validator.synced.read().await {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_header_request",
"Node still syncing blockchain, skipping..."
);
continue
}
let headers = match self.validator.blockchain.get_headers_before(request.height, BATCH)
{
Ok(v) => v,
Err(e) => {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_header_request",
"get_headers_before fail: {}",
e
);
continue
}
};
let response = HeaderSyncResponse { headers };
if let Err(e) = self.channel.send(&response).await {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_header_request",
"channel send fail: {}",
e
)
};
}
}
async fn handle_receive_request(self: Arc) -> Result<()> {
debug!(target: "darkfid::proto::protocol_sync::handle_receive_request", "START");
loop {
let request = match self.request_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
if !*self.validator.synced.read().await {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"Node still syncing blockchain, skipping..."
);
continue
}
// Check if request exists the configured limit
if request.headers.len() > BATCH {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"Node requested more blocks than allowed."
);
continue
}
let blocks = match self.validator.blockchain.get_blocks_by_hash(&request.headers) {
Ok(v) => v,
Err(e) => {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"get_blocks_after fail: {}",
e
);
continue
}
};
let response = SyncResponse { blocks };
if let Err(e) = self.channel.send(&response).await {
error!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"channel send fail: {}",
e
)
};
}
}
async fn handle_receive_fork_request(self: Arc) -> Result<()> {
debug!(target: "darkfid::proto::protocol_sync::handle_receive_fork_request", "START");
loop {
let request = match self.fork_request_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_fork_request",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
if !*self.validator.synced.read().await {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_fork_request",
"Node still syncing blockchain, skipping..."
);
continue
}
debug!(target: "darkfid::proto::protocol_sync::handle_receive_request", "Received request: {request:?}");
// If a fork tip is provided, grab its fork proposals sequence.
// Otherwise, grab best fork proposals sequence.
let proposals = match request.fork_tip {
Some(fork_tip) => {
self.validator.consensus.get_fork_proposals(request.tip, fork_tip).await
}
None => self.validator.consensus.get_best_fork_proposals(request.tip).await,
};
let proposals = match proposals {
Ok(p) => p,
Err(e) => {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_request",
"Getting fork proposals failed: {}",
e
);
continue
}
};
let response = ForkSyncResponse { proposals };
debug!(target: "darkfid::proto::protocol_sync::handle_receive_request", "Response: {response:?}");
if let Err(e) = self.channel.send(&response).await {
debug!(
target: "darkfid::proto::protocol_sync::handle_receive_fork_request",
"channel send fail: {}",
e
)
};
}
}
}
#[async_trait]
impl ProtocolBase for ProtocolSync {
async fn start(self: Arc, executor: Arc>) -> Result<()> {
debug!(target: "darkfid::proto::protocol_sync::start", "START");
self.jobsman.clone().start(executor.clone());
self.jobsman
.clone()
.spawn(self.clone().handle_receive_tip_request(), executor.clone())
.await;
self.jobsman
.clone()
.spawn(self.clone().handle_receive_header_request(), executor.clone())
.await;
self.jobsman.clone().spawn(self.clone().handle_receive_request(), executor.clone()).await;
self.jobsman
.clone()
.spawn(self.clone().handle_receive_fork_request(), executor.clone())
.await;
debug!(target: "darkfid::proto::protocol_sync::start", "END");
Ok(())
}
fn name(&self) -> &'static str {
"ProtocolSync"
}
}