/* 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 .
*/
use async_std::sync::Arc;
use async_trait::async_trait;
use log::{debug, error, info};
use smol::Executor;
use darkfi_sdk::blockchain::Slot;
use crate::{
consensus::{
block::{BlockInfo, BlockOrder, BlockResponse},
state::{SlotRequest, SlotResponse},
ValidatorStatePtr,
},
net::{
ChannelPtr, MessageSubscription, P2pPtr, ProtocolBase, ProtocolBasePtr,
ProtocolJobsManager, ProtocolJobsManagerPtr,
},
Result,
};
// Constant defining how many blocks we send during syncing.
const BATCH: u64 = 10;
pub struct ProtocolSync {
channel: ChannelPtr,
request_sub: MessageSubscription,
slot_request_sub: MessageSubscription,
block_sub: MessageSubscription,
slots_sub: MessageSubscription,
jobsman: ProtocolJobsManagerPtr,
state: ValidatorStatePtr,
p2p: P2pPtr,
consensus_mode: bool,
}
impl ProtocolSync {
pub async fn init(
channel: ChannelPtr,
state: ValidatorStatePtr,
p2p: P2pPtr,
consensus_mode: bool,
) -> 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;
let request_sub = channel.subscribe_msg::().await?;
let slot_request_sub = channel.subscribe_msg::().await?;
let block_sub = channel.subscribe_msg::().await?;
let slots_sub = channel.subscribe_msg::().await?;
Ok(Arc::new(Self {
channel: channel.clone(),
request_sub,
slot_request_sub,
block_sub,
slots_sub,
jobsman: ProtocolJobsManager::new("SyncProtocol", channel),
state,
p2p,
consensus_mode,
}))
}
async fn handle_receive_request(self: Arc) -> Result<()> {
debug!(
target: "consensus::protocol_sync::handle_receive_request()",
"START"
);
loop {
let order = match self.request_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_request()",
"recv fail: {}",
e
);
continue
}
};
debug!(
target: "consensus::protocol_sync::handle_receive_request()",
"received {:?}",
order
);
// Extra validations can be added here
/*
let key = order.slot;
let blocks = match self.state.read().await.blockchain.get_blocks_after(key, BATCH) {
Ok(v) => v,
Err(e) => {
error!(
target: "consensus::protocol_sync::handle_receive_request()",
"get_blocks_after fail: {}",
e
);
continue
}
};
debug!(
target: "consensus::protocol_sync::handle_receive_request()",
"Found {} blocks",
blocks.len()
);
*/
let blocks = vec![BlockInfo::default()];
let response = BlockResponse { blocks };
if let Err(e) = self.channel.send(&response).await {
error!(
target: "consensus::protocol_sync::handle_receive_request()",
"channel send fail: {}",
e
)
};
}
}
async fn handle_receive_block(self: Arc) -> Result<()> {
debug!(target: "consensus::protocol_sync::handle_receive_block()", "START");
let _exclude_list = [self.channel.address()];
loop {
let info = match self.block_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
if !self.state.read().await.synced {
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"Node still syncing blockchain, skipping..."
);
continue
}
// Check if node started participating in consensus.
// Consensus-mode enabled nodes have already performed these steps,
// during proposal finalization. They still listen to this sub,
// in case they go out of sync and become a none-consensus node.
if self.consensus_mode {
let lock = self.state.read().await;
let current = lock.consensus.time_keeper.current_slot();
let participating = lock.consensus.participating;
if participating.is_some() {
let slot = participating.unwrap();
if current >= slot {
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"node runs in consensus mode, skipping..."
);
continue
}
}
}
info!(
target: "consensus::protocol_sync::handle_receive_block()",
"Received block: {}",
info.blockhash()
);
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"Processing received block"
);
/*
let info_copy = (*info).clone();
match self.state.write().await.receive_finalized_block(info_copy.clone()).await {
Ok(v) => {
if v {
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"block processed successfully, broadcasting..."
);
self.p2p.broadcast_with_exclude(&info_copy, &exclude_list).await;
}
}
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_block()",
"error processing finalized block: {}",
e
);
}
};
*/
}
}
async fn handle_receive_slot_request(self: Arc) -> Result<()> {
debug!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"START"
);
loop {
let request = match self.slot_request_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"recv fail: {}",
e
);
continue
}
};
debug!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"received {:?}",
request
);
// Extra validations can be added here
let key = request.slot;
let slots = match self.state.read().await.blockchain.get_slots_after(key, BATCH) {
Ok(v) => v,
Err(e) => {
error!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"get_slots_after fail: {}",
e
);
continue
}
};
debug!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"Found {} slots",
slots.len()
);
let response = SlotResponse { slots };
if let Err(e) = self.channel.send(&response).await {
error!(
target: "consensus::protocol_sync::handle_receive_slot_request()",
"channel send fail: {}",
e
)
};
}
}
async fn handle_receive_slot(self: Arc) -> Result<()> {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"START"
);
let exclude_list = vec![self.channel.address().clone()];
loop {
let slot = match self.slots_sub.receive().await {
Ok(v) => v,
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"recv fail: {}",
e
);
continue
}
};
// Check if node has finished syncing its blockchain
if !self.state.read().await.synced {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"Node still syncing blockchain, skipping..."
);
continue
}
// Check if node started participating in consensus.
// Consensus-mode enabled nodes have already performed these steps,
// during proposal finalization. They still listen to this sub,
// in case they go out of sync and become a none-consensus node.
if self.consensus_mode {
let lock = self.state.read().await;
let current = lock.consensus.time_keeper.current_slot();
let participating = lock.consensus.participating;
if participating.is_some() {
let slot = participating.unwrap();
if current >= slot {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"node runs in consensus mode, skipping..."
);
continue
}
}
}
info!(
target: "consensus::protocol_sync::handle_receive_slot()",
"Received slot: {}",
slot.id
);
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"Processing received slot"
);
let slot_copy = (*slot).clone();
match self.state.write().await.receive_finalized_slots(slot_copy.clone()).await {
Ok(v) => {
if v {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"slot processed successfully, broadcasting..."
);
self.p2p.broadcast_with_exclude(&slot_copy, &exclude_list).await;
}
}
Err(e) => {
debug!(
target: "consensus::protocol_sync::handle_receive_slot()",
"error processing finalized slot: {}",
e
);
}
};
}
}
}
#[async_trait]
impl ProtocolBase for ProtocolSync {
async fn start(self: Arc, executor: Arc>) -> Result<()> {
debug!(target: "consensus::protocol_sync::start()", "START");
self.jobsman.clone().start(executor.clone());
self.jobsman.clone().spawn(self.clone().handle_receive_request(), executor.clone()).await;
self.jobsman
.clone()
.spawn(self.clone().handle_receive_slot_request(), executor.clone())
.await;
self.jobsman.clone().spawn(self.clone().handle_receive_block(), executor.clone()).await;
self.jobsman.clone().spawn(self.clone().handle_receive_slot(), executor.clone()).await;
debug!(target: "consensus::protocol_sync::start()", "END");
Ok(())
}
fn name(&self) -> &'static str {
"ProtocolSync"
}
}