use std::{
collections::{HashMap, VecDeque},
sync::mpsc::Sender,
time::{Duration, Instant, SystemTime},
};
use ed25519_dalek::VerifyingKey;
use rand::seq::IteratorRandom;
use crate::{
app::{App, ValidateBlockRequest, ValidateBlockResponse},
block_sync::messages::{
AdvertiseBlock, AdvertisePC, BlockSyncAdvertiseMessage, BlockSyncRequest,
},
block_tree::{
accessors::internal::{BlockTreeError, BlockTreeSingleton},
invariants::{safe_block, safe_pc},
pluggables::KVStore,
},
events::{EndSyncEvent, Event, InsertBlockEvent, StartSyncEvent},
networking::{
network::{Network, ValidatorSetUpdateHandle},
receiving::{BlockSyncClientStub, BlockSyncResponseReceiveError},
sending::SenderHandle,
},
types::{
block::Block,
data_types::{BlockHeight, ChainID, ViewNumber},
signed_messages::{Certificate, SignedMessage},
update_sets::ValidatorSetUpdates,
validator_set::ValidatorSetUpdatesStatus,
},
};
pub(crate) struct BlockSyncClient<N: Network> {
config: BlockSyncClientConfiguration,
receiver: BlockSyncClientStub,
sender: SenderHandle<N>,
validator_set_update_handle: ValidatorSetUpdateHandle<N>,
block_sync_client_state: BlockSyncClientState,
event_publisher: Option<Sender<Event>>,
}
impl<N: Network> BlockSyncClient<N> {
pub(crate) fn new(
config: BlockSyncClientConfiguration,
receiver: BlockSyncClientStub,
sender: SenderHandle<N>,
validator_set_update_handle: ValidatorSetUpdateHandle<N>,
event_publisher: Option<Sender<Event>>,
) -> Self {
Self {
config,
receiver,
sender,
validator_set_update_handle,
block_sync_client_state: BlockSyncClientState::initialize(),
event_publisher,
}
}
pub(crate) fn on_receive_msg<K: KVStore>(
&mut self,
msg: BlockSyncAdvertiseMessage,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), BlockSyncClientError> {
match msg {
BlockSyncAdvertiseMessage::AdvertiseBlock(advertise_block) => {
self.on_receive_advertise_block(advertise_block, origin, block_tree)
}
BlockSyncAdvertiseMessage::AdvertisePC(advertise_pc) => {
self.on_receive_advertise_pc(advertise_pc, origin, block_tree, app)
}
}
}
pub(crate) fn tick<K: KVStore>(
&mut self,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), BlockSyncClientError> {
self.block_sync_client_state
.remove_expired_blacklisted_servers();
let highest_pc_view = block_tree.highest_pc()?.view;
if highest_pc_view > self.block_sync_client_state.highest_pc_view {
self.block_sync_client_state.highest_pc_view = highest_pc_view;
self.block_sync_client_state.last_progress_or_sync_time = Instant::now();
}
if Instant::now() - self.block_sync_client_state.last_progress_or_sync_time
>= self.config.block_sync_trigger_timeout
{
self.sync(block_tree, app)?;
self.block_sync_client_state.last_progress_or_sync_time = Instant::now();
};
Ok(())
}
fn on_receive_advertise_block<K: KVStore>(
&mut self,
advertise_block: AdvertiseBlock,
origin: &VerifyingKey,
block_tree: &BlockTreeSingleton<K>,
) -> Result<(), BlockSyncClientError> {
if advertise_block.chain_id != self.config.chain_id || !advertise_block.is_correct(origin) {
return Ok(());
}
if !is_sync_server_address(origin, block_tree)? {
return Ok(());
}
if self
.block_sync_client_state
.blacklist_contains_server_address(origin)
{
return Ok(());
}
self.block_sync_client_state.register_or_update_sync_server(
*origin,
advertise_block.highest_committed_block_height,
);
Ok(())
}
fn on_receive_advertise_pc<K: KVStore>(
&mut self,
advertise_pc: AdvertisePC,
origin: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), BlockSyncClientError> {
let highest_view_entered = block_tree.highest_view_entered()?;
if self
.block_sync_client_state
.blacklist_contains_server_address(origin)
{
return Ok(());
}
if advertise_pc.highest_pc.view < highest_view_entered {
return Ok(());
}
let view_difference = (advertise_pc.highest_pc.view - highest_view_entered) as u64;
if view_difference >= self.config.block_sync_trigger_min_view_difference
&& advertise_pc.highest_pc.is_correct(block_tree)?
{
self.sync(block_tree, app)?;
self.block_sync_client_state.last_progress_or_sync_time = Instant::now();
};
Ok(())
}
fn sync<K: KVStore>(
&mut self,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), BlockSyncClientError> {
let highest_committed_block_height = block_tree.highest_committed_block_height()?;
if let Some(peer) = self
.block_sync_client_state
.random_sync_server(&highest_committed_block_height)
{
self.sync_with(&peer, block_tree, app)
} else {
Ok(())
}
}
fn sync_with<K: KVStore>(
&mut self,
peer: &VerifyingKey,
block_tree: &mut BlockTreeSingleton<K>,
app: &mut impl App<K>,
) -> Result<(), BlockSyncClientError> {
Event::StartSync(StartSyncEvent {
timestamp: SystemTime::now(),
peer: peer.clone(),
})
.publish(&self.event_publisher);
let mut blocks_synced = 0;
let init_highest_committed_block_height =
match block_tree.highest_committed_block_height()? {
Some(height) => height,
None => BlockHeight::new(0),
};
loop {
let request = BlockSyncRequest {
chain_id: self.config.chain_id,
start_height: if let Some(height) = block_tree.highest_committed_block_height()? {
height + 1
} else {
BlockHeight::new(0)
},
limit: self.config.request_limit,
};
self.sender.send(*peer, request);
match self
.receiver
.recv_response(*peer, Instant::now() + self.config.response_timeout)
{
Ok(response) => {
let new_blocks: Vec<Block> = response
.blocks
.into_iter()
.skip_while(|block| block_tree.contains(&block.hash))
.collect();
if new_blocks.is_empty() {
let min_blocks_expected = *self
.block_sync_client_state
.available_sync_servers
.get(peer)
.unwrap()
- init_highest_committed_block_height;
if blocks_synced < min_blocks_expected {
self.block_sync_client_state.blacklist_sync_server(
peer.clone(),
self.config.blacklist_expiry_time,
)
}
Event::EndSync(EndSyncEvent {
timestamp: SystemTime::now(),
peer: *peer,
blocks_synced,
})
.publish(&self.event_publisher);
return Ok(());
}
for block in new_blocks {
if !block.is_correct(block_tree)?
|| !safe_block(&block, block_tree, self.config.chain_id)?
{
self.block_sync_client_state.blacklist_sync_server(
peer.clone(),
self.config.blacklist_expiry_time,
);
Event::EndSync(EndSyncEvent {
timestamp: SystemTime::now(),
peer: *peer,
blocks_synced,
})
.publish(&self.event_publisher);
return Ok(());
}
let parent_block = if block.justify.is_genesis_pc() {
None
} else {
Some(&block.justify.block)
};
let validate_block_request =
ValidateBlockRequest::new(&block, block_tree.app_view(parent_block)?);
if let ValidateBlockResponse::Valid {
app_state_updates,
validator_set_updates,
} = app.validate_block_for_sync(validate_block_request)
{
block_tree.insert(
&block,
app_state_updates.as_ref(),
validator_set_updates.as_ref(),
)?;
Event::InsertBlock(InsertBlockEvent {
timestamp: SystemTime::now(),
block: block.clone(),
})
.publish(&self.event_publisher);
let committed_validator_set_updates =
block_tree.update(&block.justify, &self.event_publisher)?;
if let Some(vs_updates) = committed_validator_set_updates {
self.validator_set_update_handle
.update_validator_set(vs_updates)
}
blocks_synced += 1;
} else {
self.block_sync_client_state.blacklist_sync_server(
peer.clone(),
self.config.blacklist_expiry_time,
);
Event::EndSync(EndSyncEvent {
timestamp: SystemTime::now(),
peer: *peer,
blocks_synced,
})
.publish(&self.event_publisher);
return Ok(());
}
if response.highest_pc.is_correct(block_tree)?
&& safe_pc(&response.highest_pc, block_tree, self.config.chain_id)?
{
block_tree.update(&response.highest_pc, &self.event_publisher)?;
}
}
}
Err(BlockSyncResponseReceiveError::Disconnected)
| Err(BlockSyncResponseReceiveError::Timeout) => {
let min_blocks_expected = *self
.block_sync_client_state
.available_sync_servers
.get(peer)
.unwrap()
- init_highest_committed_block_height;
if blocks_synced < min_blocks_expected {
self.block_sync_client_state
.blacklist_sync_server(peer.clone(), self.config.blacklist_expiry_time)
}
Event::EndSync(EndSyncEvent {
timestamp: SystemTime::now(),
peer: *peer,
blocks_synced,
})
.publish(&self.event_publisher);
return Ok(());
}
}
}
}
}
pub(crate) struct BlockSyncClientConfiguration {
pub(crate) chain_id: ChainID,
pub(crate) request_limit: u32,
pub(crate) response_timeout: Duration,
pub(crate) blacklist_expiry_time: Duration,
pub(crate) block_sync_trigger_min_view_difference: u64,
pub(crate) block_sync_trigger_timeout: Duration,
}
struct BlockSyncClientState {
available_sync_servers: HashMap<VerifyingKey, BlockHeight>,
blacklist: VecDeque<(VerifyingKey, Instant)>,
last_progress_or_sync_time: Instant,
highest_pc_view: ViewNumber,
}
impl BlockSyncClientState {
fn initialize() -> Self {
Self {
available_sync_servers: HashMap::new(),
blacklist: VecDeque::new(),
last_progress_or_sync_time: Instant::now(),
highest_pc_view: ViewNumber::new(0),
}
}
fn blacklist_contains_server_address(&self, sync_server: &VerifyingKey) -> bool {
self.blacklist
.iter()
.find(|(vk, _)| vk == sync_server)
.is_some()
}
fn register_or_update_sync_server(
&mut self,
sync_server: VerifyingKey,
highest_committed_block_height: BlockHeight,
) {
let _ = self
.available_sync_servers
.insert(sync_server, highest_committed_block_height);
}
fn blacklist_sync_server(
&mut self,
sync_server: VerifyingKey,
blacklist_expiry_time: Duration,
) {
let _ = self.available_sync_servers.remove(&sync_server);
self.blacklist
.push_back((sync_server, Instant::now() + blacklist_expiry_time))
}
fn remove_expired_blacklisted_servers(&mut self) {
let now = Instant::now();
while self
.blacklist
.front()
.is_some_and(|(_, expiry)| expiry >= &now)
{
let _ = self.blacklist.pop_front();
}
}
fn random_sync_server(
&self,
min_highest_committed_block_height: &Option<BlockHeight>,
) -> Option<VerifyingKey> {
match min_highest_committed_block_height {
None => self
.available_sync_servers
.keys()
.choose(&mut rand::thread_rng())
.copied(),
Some(min_height) => self
.available_sync_servers
.keys()
.filter(|vk| {
self.available_sync_servers
.get(vk)
.is_some_and(|height| height >= min_height)
})
.choose(&mut rand::thread_rng())
.copied(),
}
}
}
#[derive(Debug)]
pub enum BlockSyncClientError {
BlockTreeError(BlockTreeError),
}
impl From<BlockTreeError> for BlockSyncClientError {
fn from(value: BlockTreeError) -> Self {
BlockSyncClientError::BlockTreeError(value)
}
}
fn is_sync_server_address<K: KVStore>(
verifying_key: &VerifyingKey,
block_tree: &BlockTreeSingleton<K>,
) -> Result<bool, BlockSyncClientError> {
let committed_validator_set = block_tree.committed_validator_set()?;
if committed_validator_set.contains(verifying_key) {
return Ok(true);
}
match block_tree.highest_committed_block()? {
Some(block) => {
let mut speculative_vs_updates = block_tree
.blocks_in_branch(block)
.filter(|block| {
block_tree
.validator_set_updates_status(block)
.is_ok_and(|vsu_status| vsu_status.is_pending())
})
.map(|block| {
if let Ok(ValidatorSetUpdatesStatus::Pending(vs_updates)) =
block_tree.validator_set_updates_status(&block)
{
vs_updates
} else {
ValidatorSetUpdates::new()
}
});
Ok(speculative_vs_updates
.find(|vs_updates| vs_updates.get_insert(verifying_key).is_some())
.is_some())
}
None => Ok(false),
}
}