/* 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 log::{info, warn};
use crate::{
consensus::{
state::{
ConsensusRequest, ConsensusResponse, ConsensusSlotCheckpointsRequest,
ConsensusSlotCheckpointsResponse,
},
ValidatorStatePtr,
},
net::P2pPtr,
util::async_util::sleep,
Result,
};
/// async task used for consensus state syncing.
/// Returns flag if node is not connected to other peers or consensus hasn't started,
/// so it can immediately start proposing proposals.
pub async fn consensus_sync_task(p2p: P2pPtr, state: ValidatorStatePtr) -> Result {
info!(target: "consensus::consensus_sync", "Starting consensus state sync...");
let current_slot = state.read().await.consensus.current_slot();
// Loop through connected channels
let channels_map = p2p.channels().lock().await;
let values = channels_map.values();
// Using len here because is_empty() uses unstable library feature
// called 'exact_size_is_empty'.
if values.len() == 0 {
warn!(target: "consensus::consensus_sync", "Node is not connected to other nodes");
let mut lock = state.write().await;
lock.consensus.bootstrap_slot = current_slot;
lock.consensus.init_coins().await?;
info!(target: "consensus::consensus_sync", "Consensus state synced!");
return Ok(true)
}
// Node iterates the channel peers to check if at least on peer has seen slot checkpoints
let mut peer = None;
for channel in values {
// Communication setup
let msg_subsystem = channel.get_message_subsystem();
msg_subsystem.add_dispatch::().await;
let response_sub = channel.subscribe_msg::().await?;
// Node creates a `ConsensusSlotCheckpointsRequest` and sends it
let request = ConsensusSlotCheckpointsRequest {};
channel.send(request).await?;
// Node checks response
let response = response_sub.receive().await?;
if response.bootstrap_slot == current_slot {
warn!(target: "consensus::consensus_sync", "Network was just bootstraped, checking rest nodes");
continue
}
if response.is_empty {
warn!(target: "consensus::consensus_sync", "Node has not seen any slot checkpoints, retrying...");
continue
}
// Keep peer to ask for consensus state
peer = Some(channel.clone());
break
}
// Release channels lock
drop(channels_map);
// If no peer knows about any slot checkpoints, that means that the network was bootstrapped or restarted
// and no node has started consensus.
if peer.is_none() {
warn!(target: "consensus::consensus_sync", "No node that has seen any slot checkpoints was found, or network was just boostrapped.");
let mut lock = state.write().await;
lock.consensus.bootstrap_slot = current_slot;
lock.consensus.init_coins().await?;
info!(target: "consensus::consensus_sync", "Consensus state synced!");
return Ok(true)
}
let peer = peer.unwrap();
// Listen for next finalization
info!(target: "consensus::consensus_sync", "Waiting for next finalization...");
let subscriber = state.read().await.subscribers.get("blocks").unwrap().clone();
let subscription = subscriber.subscribe().await;
subscription.receive().await;
subscription.unsubscribe().await;
// After finalization occurs, sync our consensus state.
// This ensures that the received state always consists of 1 fork with one proposal.
info!(target: "consensus::consensus_sync", "Finalization signal received, requesting consensus state...");
// Communication setup
let msg_subsystem = peer.get_message_subsystem();
msg_subsystem.add_dispatch::().await;
let response_sub = peer.subscribe_msg::().await?;
// Node creates a `ConsensusRequest` and sends it
peer.send(ConsensusRequest {}).await?;
// Node verifies response came from a participating node.
// Extra validations can be added here.
let mut response = response_sub.receive().await?;
// Verify that peer has finished finalizing forks
loop {
if response.forks.len() != 1 || response.forks[0].sequence.len() != 1 {
warn!(target: "consensus::consensus_sync", "Peer has not finished finalization, retrying...");
sleep(1).await;
peer.send(ConsensusRequest {}).await?;
response = response_sub.receive().await?;
continue
}
break
}
// Verify that the node has received all finalized blocks
let last_finalized_slot = response.forks[0].sequence[0].proposal.block.header.slot - 1;
loop {
if !state.read().await.blockchain.has_slot(last_finalized_slot)? {
warn!(target: "consensus::consensus_sync", "Node has not finished finalization, retrying...");
sleep(1).await;
continue
}
break
}
// Node stores response data.
let mut lock = state.write().await;
let mut forks = vec![];
for fork in &response.forks {
forks.push(fork.clone().into());
}
lock.consensus.bootstrap_slot = response.bootstrap_slot;
lock.consensus.forks = forks;
lock.unconfirmed_txs = response.unconfirmed_txs.clone();
lock.consensus.slot_checkpoints = response.slot_checkpoints.clone();
lock.consensus.leaders_history = response.leaders_history.clone();
lock.consensus.nullifiers = response.nullifiers.clone();
lock.consensus.init_coins().await?;
info!(target: "consensus::consensus_sync", "Consensus state synced!");
Ok(false)
}