2016-10-29 02:52:40 -07:00
|
|
|
use std::thread;
|
|
|
|
use std::sync::Arc;
|
2016-10-24 23:32:56 -07:00
|
|
|
use std::cmp::{min, max};
|
2016-10-26 07:33:28 -07:00
|
|
|
use std::collections::HashMap;
|
2016-10-24 23:32:56 -07:00
|
|
|
use std::collections::hash_map::Entry;
|
2016-10-29 02:52:40 -07:00
|
|
|
use std::sync::mpsc::{channel, Sender, Receiver};
|
|
|
|
use parking_lot::Mutex;
|
|
|
|
use db;
|
2016-10-25 06:05:10 -07:00
|
|
|
use chain::{Block, RepresentH256};
|
2016-10-24 23:32:56 -07:00
|
|
|
use primitives::hash::H256;
|
2016-10-26 05:56:52 -07:00
|
|
|
use hash_queue::HashPosition;
|
2016-10-29 02:52:40 -07:00
|
|
|
use synchronization_peers::Peers;
|
|
|
|
#[cfg(test)] use synchronization_peers::{Information as PeersInformation};
|
|
|
|
use synchronization_chain::{ChainRef, BlockState};
|
|
|
|
#[cfg(test)]
|
|
|
|
use synchronization_chain::{Information as ChainInformation};
|
|
|
|
use verification::{ChainVerifier, Error as VerificationError, Verify};
|
|
|
|
use time;
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
///! Blocks synchronization process:
|
|
|
|
///!
|
|
|
|
///! TODO: Current assumptions:
|
|
|
|
///! 1) unknown blocks in `inventory` messages are returned as a consequent range, sorted from oldest to newest
|
|
|
|
///! 2) no forks support
|
|
|
|
///!
|
|
|
|
///! When new peer is connected:
|
2016-10-26 02:24:04 -07:00
|
|
|
///! 1) send `inventory` message with full block locator hashes
|
2016-10-24 23:32:56 -07:00
|
|
|
///!
|
|
|
|
///! When `inventory` message is received from peer:
|
|
|
|
///! 1) if synchronization queue is empty:
|
|
|
|
///! 1.1) append all unknown blocks hashes to the `queued_hashes`
|
|
|
|
///! 1.2) mark peer as 'useful' for current synchronization stage (TODO)
|
|
|
|
///! 1.3) stop
|
|
|
|
///! 2) if intersection(`queued_hashes`, unknown blocks) is not empty && there are new unknown blocks:
|
|
|
|
///! 2.1) append new unknown blocks to the queued_hashes
|
|
|
|
///! 2.2) mark peer as 'useful' for current synchronization stage (TODO)
|
|
|
|
///! 2.3) stop
|
|
|
|
///! 3) if intersection(`queued_hashes`, unknown blocks) is not empty && there are no new unknown blocks:
|
|
|
|
///! 3.1) looks like peer is behind us in the blockchain (or these are blocks for the future)
|
|
|
|
///! 3.2) mark peer as 'suspicious' for current synchronization stage (TODO)
|
|
|
|
///! 3.3) stop
|
|
|
|
///!
|
|
|
|
///! After receiving `block` message:
|
|
|
|
///! 1) if any basic verification is failed (TODO):
|
|
|
|
///! 1.1) penalize peer
|
|
|
|
///! 1.2) stop
|
2016-10-26 02:24:04 -07:00
|
|
|
///! 1) if not(remove block) [i.e. block was not requested]:
|
2016-10-24 23:32:56 -07:00
|
|
|
///! 1.1) ignore it (TODO: try to append to the chain)
|
|
|
|
///! 1.2) stop
|
|
|
|
///! 2) if this block is first block in the `requested_hashes`:
|
|
|
|
///! 2.1) append to the verification queue (+ append to `verifying_hashes`) (TODO)
|
|
|
|
///! 2.2) for all children (from `orphaned_blocks`): append to the verification queue (TODO)
|
|
|
|
///! 2.3) stop
|
|
|
|
///! 3) remember in `orphaned_blocks`
|
|
|
|
///!
|
|
|
|
///! After receiving `inventory` message OR receiving `block` message:
|
|
|
|
///! 1) if there are blocks hashes in `queued_hashes`:
|
|
|
|
///! 1.1) select idle peers
|
|
|
|
///! 1.2) for each idle peer: query blocks from `queued_hashes`
|
|
|
|
///! 1.3) move requested blocks hashes from `queued_hashes` to `requested_hashes`
|
|
|
|
///! 1.4) mark idle peers as active
|
|
|
|
///! 2) if `queued_hashes` queue is not yet saturated:
|
|
|
|
///! 2.1) for each idle peer: send shortened `getblocks` message
|
|
|
|
///! 2.2) 'forget' idle peers (mark them as not useful for synchronization) (TODO)
|
|
|
|
///!
|
|
|
|
///! TODO: spawn management thread [watch for not-stalling sync]
|
|
|
|
///! TODO: check + optimize algorithm for Saturated state
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Approximate maximal number of blocks hashes in scheduled queue.
|
2016-10-26 05:56:52 -07:00
|
|
|
const MAX_SCHEDULED_HASHES: u64 = 4 * 1024;
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Approximate maximal number of blocks hashes in requested queue.
|
2016-10-26 05:56:52 -07:00
|
|
|
const MAX_REQUESTED_BLOCKS: u64 = 512;
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Approximate maximal number of blocks in verifying queue.
|
|
|
|
const MAX_VERIFYING_BLOCKS: u64 = 512;
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Minimum number of blocks to request from peer
|
2016-10-26 05:56:52 -07:00
|
|
|
const MIN_BLOCKS_IN_REQUEST: u64 = 32;
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Maximum number of blocks to request from peer
|
2016-10-26 05:56:52 -07:00
|
|
|
const MAX_BLOCKS_IN_REQUEST: u64 = 512;
|
2016-10-24 23:32:56 -07:00
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Thread-safe reference to the `Synchronization`
|
|
|
|
pub type SynchronizationRef<T> = Arc<Mutex<Synchronization<T>>>;
|
|
|
|
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Synchronization task for the peer.
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
pub enum Task {
|
|
|
|
/// Request given blocks.
|
|
|
|
RequestBlocks(usize, Vec<H256>),
|
|
|
|
/// Request full inventory using block_locator_hashes.
|
|
|
|
RequestInventory(usize),
|
|
|
|
/// Request inventory using best block locator only.
|
|
|
|
RequestBestInventory(usize),
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
2016-10-24 23:32:56 -07:00
|
|
|
pub enum State {
|
2016-10-29 02:52:40 -07:00
|
|
|
Synchronizing(f64, u64),
|
2016-10-24 23:32:56 -07:00
|
|
|
Saturated,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information on current synchronization state.
|
2016-10-29 02:52:40 -07:00
|
|
|
#[cfg(test)]
|
2016-10-24 23:32:56 -07:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Information {
|
|
|
|
/// Current synchronization state.
|
|
|
|
pub state: State,
|
2016-10-26 07:33:28 -07:00
|
|
|
/// Information on synchronization peers.
|
|
|
|
pub peers: PeersInformation,
|
2016-10-26 05:56:52 -07:00
|
|
|
/// Current synchronization chain inormation.
|
|
|
|
pub chain: ChainInformation,
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Number of currently orphaned blocks.
|
|
|
|
pub orphaned: usize,
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Synchronization task executor
|
|
|
|
pub trait TaskExecutor {
|
|
|
|
fn execute(&mut self, task: Task);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Verification thread tasks
|
|
|
|
enum VerificationTask {
|
|
|
|
/// Verify single block
|
|
|
|
VerifyBlock(Block),
|
|
|
|
/// Stop verification thread
|
|
|
|
Stop,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Synchronization config
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct Config {
|
|
|
|
/// Skip blocks verification
|
|
|
|
pub skip_block_verification: bool,
|
|
|
|
}
|
|
|
|
|
2016-10-24 23:32:56 -07:00
|
|
|
/// New blocks synchronization process.
|
2016-10-29 02:52:40 -07:00
|
|
|
pub struct Synchronization<T: TaskExecutor + Send + 'static> {
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Synchronization state.
|
|
|
|
state: State,
|
|
|
|
/// Synchronization peers.
|
|
|
|
peers: Peers,
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Task executor.
|
|
|
|
executor: Arc<Mutex<T>>,
|
2016-10-26 05:56:52 -07:00
|
|
|
/// Chain reference.
|
|
|
|
chain: ChainRef,
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Blocks from requested_hashes, but received out-of-order.
|
|
|
|
orphaned_blocks: HashMap<H256, Block>,
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Verification work transmission channel.
|
|
|
|
verification_work_sender: Option<Sender<VerificationTask>>,
|
|
|
|
/// Verification thread.
|
|
|
|
verification_worker_thread: Option<thread::JoinHandle<()>>,
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
impl State {
|
|
|
|
pub fn is_synchronizing(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
&State::Synchronizing(_, _) => true,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Drop for Synchronization<T> where T: TaskExecutor + Send + 'static {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
if let Some(join_handle) = self.verification_worker_thread.take() {
|
|
|
|
self.verification_work_sender
|
|
|
|
.take()
|
|
|
|
.expect("Some(join_handle) => Some(verification_work_sender)")
|
|
|
|
.send(VerificationTask::Stop).expect("TODO");
|
|
|
|
join_handle.join().expect("Clean shutdown.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Synchronization<T> where T: TaskExecutor + Send + 'static {
|
2016-10-24 23:32:56 -07:00
|
|
|
/// Create new synchronization window
|
2016-10-29 02:52:40 -07:00
|
|
|
pub fn new(config: Config, executor: Arc<Mutex<T>>, chain: ChainRef) -> SynchronizationRef<T> {
|
|
|
|
let sync = SynchronizationRef::new(Mutex::new(
|
|
|
|
Synchronization {
|
|
|
|
state: State::Saturated,
|
|
|
|
peers: Peers::new(),
|
|
|
|
executor: executor,
|
|
|
|
chain: chain.clone(),
|
|
|
|
orphaned_blocks: HashMap::new(),
|
|
|
|
verification_work_sender: None,
|
|
|
|
verification_worker_thread: None,
|
|
|
|
}
|
|
|
|
));
|
|
|
|
|
|
|
|
if !config.skip_block_verification {
|
|
|
|
let (verification_work_sender, verification_work_receiver) = channel();
|
|
|
|
let csync = sync.clone();
|
|
|
|
let mut lsync = sync.lock();
|
|
|
|
let storage = chain.read().storage();
|
|
|
|
lsync.verification_work_sender = Some(verification_work_sender);
|
|
|
|
lsync.verification_worker_thread = Some(thread::Builder::new()
|
|
|
|
.name("Sync verification thread".to_string())
|
|
|
|
.spawn(move || {
|
|
|
|
Synchronization::verification_worker_proc(csync, storage, verification_work_receiver)
|
|
|
|
})
|
|
|
|
.expect("Error creating verification thread"));
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
2016-10-29 02:52:40 -07:00
|
|
|
|
|
|
|
sync
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Get information on current synchronization state.
|
2016-10-29 02:52:40 -07:00
|
|
|
#[cfg(test)]
|
2016-10-24 23:32:56 -07:00
|
|
|
pub fn information(&self) -> Information {
|
|
|
|
Information {
|
|
|
|
state: self.state,
|
2016-10-26 07:33:28 -07:00
|
|
|
peers: self.peers.information(),
|
2016-10-26 05:56:52 -07:00
|
|
|
chain: self.chain.read().information(),
|
2016-10-24 23:32:56 -07:00
|
|
|
orphaned: self.orphaned_blocks.len(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Try to queue synchronization of unknown blocks when new inventory is received.
|
2016-10-31 04:26:39 -07:00
|
|
|
pub fn on_new_blocks_inventory(&mut self, peer_index: usize, peer_hashes: Vec<H256>) {
|
|
|
|
self.process_new_blocks_inventory(peer_index, peer_hashes);
|
2016-10-29 02:52:40 -07:00
|
|
|
self.execute_synchronization_tasks();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Process new block.
|
|
|
|
pub fn on_peer_block(&mut self, peer_index: usize, block: Block) {
|
|
|
|
let block_hash = block.hash();
|
|
|
|
|
|
|
|
// update peers to select next tasks
|
|
|
|
self.peers.on_block_received(peer_index, &block_hash);
|
|
|
|
|
|
|
|
self.process_peer_block(block_hash, block);
|
|
|
|
self.execute_synchronization_tasks();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Reset synchronization process
|
|
|
|
pub fn reset(&mut self) {
|
|
|
|
self.peers.reset();
|
|
|
|
self.orphaned_blocks.clear();
|
|
|
|
// TODO: reset verification queue
|
|
|
|
|
|
|
|
let mut chain = self.chain.write();
|
|
|
|
self.state = State::Synchronizing(time::precise_time_s(), chain.best_block().height);
|
|
|
|
chain.remove_blocks_with_state(BlockState::Requested);
|
|
|
|
chain.remove_blocks_with_state(BlockState::Scheduled);
|
|
|
|
chain.remove_blocks_with_state(BlockState::Verifying);
|
2016-10-31 04:26:39 -07:00
|
|
|
|
|
|
|
warn!(target: "sync", "Synchronization process restarting from block {:?}", chain.best_block());
|
2016-10-29 02:52:40 -07:00
|
|
|
}
|
|
|
|
|
2016-10-31 04:26:39 -07:00
|
|
|
/// Process new blocks inventory
|
|
|
|
fn process_new_blocks_inventory(&mut self, peer_index: usize, mut peer_hashes: Vec<H256>) {
|
2016-10-24 23:32:56 -07:00
|
|
|
// | requested | QUEUED |
|
|
|
|
// --- [1]
|
|
|
|
// --- [2] +
|
|
|
|
// --- [3] +
|
|
|
|
// --- [4]
|
|
|
|
// -+- [5] +
|
|
|
|
// -+- [6] +
|
|
|
|
// -+- [7] +
|
|
|
|
// ---+---------+--- [8] +
|
|
|
|
// ---+--------+--- [9] +
|
|
|
|
// ---+---------+--------+--- [10]
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
let mut chain = self.chain.write();
|
|
|
|
|
|
|
|
// new block is scheduled => move to synchronizing state
|
|
|
|
if !self.state.is_synchronizing() {
|
|
|
|
self.state = State::Synchronizing(time::precise_time_s(), chain.best_block().height);
|
|
|
|
}
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
// when synchronization is idling
|
|
|
|
// => request full inventory
|
2016-10-26 05:56:52 -07:00
|
|
|
if !chain.has_blocks_of_state(BlockState::Scheduled)
|
|
|
|
&& !chain.has_blocks_of_state(BlockState::Requested) {
|
|
|
|
chain.schedule_blocks_hashes(peer_hashes);
|
2016-10-24 23:32:56 -07:00
|
|
|
self.peers.insert(peer_index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// cases: [2], [5], [6], [8]
|
|
|
|
// if last block from peer_hashes is in window { requested_hashes + queued_hashes }
|
|
|
|
// => no new blocks for synchronization, but we will use this peer in synchronization
|
|
|
|
let peer_hashes_len = peer_hashes.len();
|
2016-10-26 05:56:52 -07:00
|
|
|
if chain.block_has_state(&peer_hashes[peer_hashes_len - 1], BlockState::Scheduled)
|
|
|
|
|| chain.block_has_state(&peer_hashes[peer_hashes_len - 1], BlockState::Requested) {
|
2016-10-24 23:32:56 -07:00
|
|
|
self.peers.insert(peer_index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// cases: [1], [3], [4], [7], [9], [10]
|
|
|
|
// try to find new blocks for synchronization from inventory
|
|
|
|
let mut last_known_peer_hash_index = peer_hashes_len - 1;
|
|
|
|
loop {
|
|
|
|
if last_known_peer_hash_index == 0 {
|
|
|
|
// either these are blocks from the future or blocks from the past
|
|
|
|
// => TODO: ignore this peer during synchronization
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-26 05:56:52 -07:00
|
|
|
if chain.block_has_state(&peer_hashes[last_known_peer_hash_index], BlockState::Scheduled) {
|
|
|
|
// we have found first block which is scheduled
|
2016-10-24 23:32:56 -07:00
|
|
|
// => blocks in range [(last_known_peer_hash_index + 1)..peer_hashes_len] are unknown
|
2016-10-26 05:56:52 -07:00
|
|
|
let unknown_peer_hashes = peer_hashes.split_off(last_known_peer_hash_index + 1);
|
|
|
|
chain.schedule_blocks_hashes(unknown_peer_hashes);
|
2016-10-24 23:32:56 -07:00
|
|
|
self.peers.insert(peer_index);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_known_peer_hash_index -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
/// Process new peer block
|
|
|
|
fn process_peer_block(&mut self, block_hash: H256, block: Block) {
|
2016-10-24 23:32:56 -07:00
|
|
|
// this block is not requested for synchronization
|
2016-10-26 05:56:52 -07:00
|
|
|
let mut chain = self.chain.write();
|
|
|
|
let block_position = chain.remove_block_with_state(&block_hash, BlockState::Requested);
|
|
|
|
if block_position == HashPosition::Missing {
|
2016-10-24 23:32:56 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
// requested block is received => move to saturated state if there are no more blocks
|
2016-10-26 05:56:52 -07:00
|
|
|
if !chain.has_blocks_of_state(BlockState::Scheduled)
|
2016-10-29 02:52:40 -07:00
|
|
|
&& !chain.has_blocks_of_state(BlockState::Requested)
|
|
|
|
&& !chain.has_blocks_of_state(BlockState::Verifying) {
|
2016-10-24 23:32:56 -07:00
|
|
|
self.state = State::Saturated;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if this block is next block in the blockchain
|
2016-10-26 05:56:52 -07:00
|
|
|
if block_position == HashPosition::Front {
|
2016-10-29 02:52:40 -07:00
|
|
|
// check if this parent of this block is current best_block
|
|
|
|
let expecting_previous_header_hash = chain.best_block_of_state(BlockState::Verifying)
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
chain.best_block_of_state(BlockState::Stored)
|
|
|
|
.expect("storage with genesis block is required")
|
|
|
|
}).hash;
|
|
|
|
if block.block_header.previous_header_hash != expecting_previous_header_hash {
|
|
|
|
// TODO: penalize peer
|
|
|
|
warn!(target: "sync", "Out-of-order block {:?} was dropped. Expecting block with parent hash {:?}", block_hash, expecting_previous_header_hash);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-10-24 23:32:56 -07:00
|
|
|
// this is next block in the blockchain => queue for verification
|
2016-10-29 02:52:40 -07:00
|
|
|
// also unwrap all dependent orphan blocks
|
|
|
|
let mut current_block = block;
|
|
|
|
let mut current_block_hash = block_hash;
|
|
|
|
loop {
|
|
|
|
match self.verification_work_sender {
|
|
|
|
Some(ref verification_work_sender) => {
|
|
|
|
chain.verify_block_hash(current_block_hash.clone());
|
|
|
|
verification_work_sender
|
|
|
|
.send(VerificationTask::VerifyBlock(current_block))
|
|
|
|
.expect("Verification thread have the same lifetime as `Synchronization`");
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
chain.insert_best_block(current_block)
|
|
|
|
.expect("Error inserting to db.");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// proceed to the next orphaned block
|
|
|
|
if let Entry::Occupied(orphaned_block_entry) = self.orphaned_blocks.entry(current_block_hash) {
|
|
|
|
let (orphaned_parent_hash, orphaned_block) = orphaned_block_entry.remove_entry();
|
|
|
|
current_block_hash = orphaned_parent_hash;
|
|
|
|
current_block = orphaned_block;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this block is not the next one => mark it as orphaned
|
|
|
|
self.orphaned_blocks.insert(block_hash, block);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Schedule new synchronization tasks, if any.
|
2016-10-29 02:52:40 -07:00
|
|
|
fn execute_synchronization_tasks(&mut self) {
|
2016-10-24 23:32:56 -07:00
|
|
|
let mut tasks: Vec<Task> = Vec::new();
|
2016-10-31 04:46:11 -07:00
|
|
|
let idle_peers = self.peers.idle_peers();
|
|
|
|
let idle_peers_len = idle_peers.len() as u64;
|
2016-10-24 23:32:56 -07:00
|
|
|
|
2016-10-31 04:26:39 -07:00
|
|
|
// prepare synchronization tasks
|
2016-10-31 04:46:11 -07:00
|
|
|
if idle_peers_len != 0 {
|
2016-10-29 02:52:40 -07:00
|
|
|
// display information if processed many blocks || enough time has passed since sync start
|
|
|
|
let mut chain = self.chain.write();
|
|
|
|
if let State::Synchronizing(timestamp, num_of_blocks) = self.state {
|
|
|
|
let new_timestamp = time::precise_time_s();
|
|
|
|
let timestamp_diff = new_timestamp - timestamp;
|
|
|
|
let new_num_of_blocks = chain.best_block().height;
|
|
|
|
let blocks_diff = if new_num_of_blocks > num_of_blocks { new_num_of_blocks - num_of_blocks} else { 0 };
|
|
|
|
if timestamp_diff >= 60.0 || blocks_diff > 1000 {
|
|
|
|
self.state = State::Synchronizing(new_timestamp, new_num_of_blocks);
|
|
|
|
|
|
|
|
info!(target: "sync", "Processed {} blocks in {} seconds. Chain information: {:?}"
|
|
|
|
, blocks_diff, timestamp_diff
|
|
|
|
, chain.information());
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
}
|
2016-10-29 02:52:40 -07:00
|
|
|
|
|
|
|
// TODO: instead of issuing duplicated inventory requests, wait until enough new blocks are verified, then issue
|
|
|
|
// check if we can query some blocks hashes
|
|
|
|
let scheduled_hashes_len = chain.length_of_state(BlockState::Scheduled);
|
|
|
|
if scheduled_hashes_len < MAX_SCHEDULED_HASHES {
|
|
|
|
if self.state.is_synchronizing() {
|
2016-10-31 04:46:11 -07:00
|
|
|
tasks.push(Task::RequestBestInventory(idle_peers[0]));
|
|
|
|
self.peers.on_inventory_requested(idle_peers[0]);
|
2016-10-29 02:52:40 -07:00
|
|
|
}
|
|
|
|
else {
|
2016-10-31 04:46:11 -07:00
|
|
|
tasks.push(Task::RequestInventory(idle_peers[0]));
|
|
|
|
self.peers.on_inventory_requested(idle_peers[0]);
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
// check if we can move some blocks from scheduled to requested queue
|
|
|
|
let requested_hashes_len = chain.length_of_state(BlockState::Requested);
|
|
|
|
let verifying_hashes_len = chain.length_of_state(BlockState::Verifying);
|
|
|
|
if requested_hashes_len + verifying_hashes_len < MAX_REQUESTED_BLOCKS + MAX_VERIFYING_BLOCKS && scheduled_hashes_len != 0 {
|
2016-10-31 04:46:11 -07:00
|
|
|
let chunk_size = min(MAX_BLOCKS_IN_REQUEST, max(scheduled_hashes_len / idle_peers_len, MIN_BLOCKS_IN_REQUEST));
|
|
|
|
for idle_peer in idle_peers {
|
|
|
|
let peer_chunk_size = min(chain.length_of_state(BlockState::Scheduled), chunk_size);
|
|
|
|
if peer_chunk_size == 0 {
|
|
|
|
break;
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
2016-10-31 04:46:11 -07:00
|
|
|
|
|
|
|
let requested_hashes = chain.request_blocks_hashes(peer_chunk_size);
|
|
|
|
self.peers.on_blocks_requested(idle_peer, &requested_hashes);
|
|
|
|
tasks.push(Task::RequestBlocks(idle_peer, requested_hashes));
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
// execute synchronization tasks
|
|
|
|
for task in tasks {
|
|
|
|
self.executor.lock().execute(task);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Thread procedure for handling verification tasks
|
|
|
|
fn verification_worker_proc(sync: SynchronizationRef<T>, storage: Arc<db::Store>, work_receiver: Receiver<VerificationTask>) {
|
|
|
|
let verifier = ChainVerifier::new(storage);
|
|
|
|
while let Ok(task) = work_receiver.recv() {
|
|
|
|
match task {
|
|
|
|
VerificationTask::VerifyBlock(block) => {
|
|
|
|
match verifier.verify(&block) {
|
|
|
|
Ok(_chain) => {
|
|
|
|
sync.lock().on_block_verification_success(block)
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
sync.lock().on_block_verification_error(&err, &block.hash())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Process successful block verification
|
|
|
|
fn on_block_verification_success(&mut self, block: Block) {
|
2016-10-31 04:26:39 -07:00
|
|
|
{
|
|
|
|
let hash = block.hash();
|
|
|
|
let mut chain = self.chain.write();
|
2016-10-29 02:52:40 -07:00
|
|
|
|
2016-10-31 04:26:39 -07:00
|
|
|
// remove from verifying queue
|
|
|
|
assert_eq!(chain.remove_block_with_state(&hash, BlockState::Verifying), HashPosition::Front);
|
2016-10-29 02:52:40 -07:00
|
|
|
|
2016-10-31 04:26:39 -07:00
|
|
|
// insert to storage
|
|
|
|
chain.insert_best_block(block)
|
|
|
|
.expect("Error inserting to db.");
|
|
|
|
}
|
|
|
|
|
|
|
|
// continue with synchronization
|
|
|
|
self.execute_synchronization_tasks();
|
2016-10-29 02:52:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Process failed block verification
|
|
|
|
fn on_block_verification_error(&mut self, err: &VerificationError, hash: &H256) {
|
|
|
|
warn!(target: "sync", "Block {:?} verification failed with error {:?}", hash, err);
|
|
|
|
|
|
|
|
// reset synchronization process
|
|
|
|
self.reset();
|
2016-10-31 04:26:39 -07:00
|
|
|
|
|
|
|
// start new tasks
|
|
|
|
self.execute_synchronization_tasks();
|
2016-10-29 02:52:40 -07:00
|
|
|
}
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2016-10-29 02:52:40 -07:00
|
|
|
use std::sync::Arc;
|
|
|
|
use std::mem::replace;
|
|
|
|
use parking_lot::{Mutex, RwLock};
|
2016-10-25 06:05:46 -07:00
|
|
|
use chain::{Block, RepresentH256};
|
2016-10-29 02:52:40 -07:00
|
|
|
use super::{Synchronization, SynchronizationRef, Config, Task, TaskExecutor};
|
2016-10-26 07:46:47 -07:00
|
|
|
use synchronization_chain::{Chain, ChainRef};
|
2016-10-29 02:52:40 -07:00
|
|
|
use db;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct DummyTaskExecutor {
|
|
|
|
pub tasks: Vec<Task>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DummyTaskExecutor {
|
|
|
|
pub fn take_tasks(&mut self) -> Vec<Task> {
|
|
|
|
replace(&mut self.tasks, Vec::new())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TaskExecutor for DummyTaskExecutor {
|
|
|
|
fn execute(&mut self, task: Task) {
|
|
|
|
self.tasks.push(task);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_sync() -> (Arc<Mutex<DummyTaskExecutor>>, SynchronizationRef<DummyTaskExecutor>) {
|
|
|
|
let storage = Arc::new(db::TestStorage::with_genesis_block());
|
|
|
|
let chain = ChainRef::new(RwLock::new(Chain::new(storage.clone())));
|
|
|
|
let executor = Arc::new(Mutex::new(DummyTaskExecutor::default()));
|
|
|
|
(executor.clone(), Synchronization::new(Config {
|
|
|
|
skip_block_verification: true,
|
|
|
|
}, executor, chain))
|
|
|
|
}
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn synchronization_saturated_on_start() {
|
2016-10-29 02:52:40 -07:00
|
|
|
let (_, sync) = create_sync();
|
|
|
|
let sync = sync.lock();
|
2016-10-24 23:32:56 -07:00
|
|
|
let info = sync.information();
|
2016-10-29 02:52:40 -07:00
|
|
|
assert!(!info.state.is_synchronizing());
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(info.orphaned, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn synchronization_in_order_block_path() {
|
2016-10-29 02:52:40 -07:00
|
|
|
let (executor, sync) = create_sync();
|
2016-10-24 23:32:56 -07:00
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
let mut sync = sync.lock();
|
2016-10-24 23:32:56 -07:00
|
|
|
let block1: Block = "010000006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000982051fd1e4ba744bbbe680e1fee14677ba1a3c3540bf7b1cdb606e857233e0e61bc6649ffff001d01e362990101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000".into();
|
|
|
|
let block2: Block = "010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000".into();
|
|
|
|
|
2016-10-31 04:46:11 -07:00
|
|
|
sync.on_new_blocks_inventory(5, vec![block1.hash()]);
|
2016-10-29 02:52:40 -07:00
|
|
|
let tasks = executor.lock().take_tasks();
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(tasks.len(), 2);
|
|
|
|
assert_eq!(tasks[0], Task::RequestBestInventory(5));
|
|
|
|
assert_eq!(tasks[1], Task::RequestBlocks(5, vec![block1.hash()]));
|
2016-10-29 02:52:40 -07:00
|
|
|
assert!(sync.information().state.is_synchronizing());
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(sync.information().orphaned, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().chain.scheduled, 0);
|
|
|
|
assert_eq!(sync.information().chain.requested, 1);
|
|
|
|
assert_eq!(sync.information().chain.stored, 1);
|
|
|
|
assert_eq!(sync.information().peers.idle, 0);
|
|
|
|
assert_eq!(sync.information().peers.active, 1);
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
// push unknown block => nothing should change
|
2016-10-26 07:33:28 -07:00
|
|
|
sync.on_peer_block(5, block2);
|
2016-10-29 02:52:40 -07:00
|
|
|
assert!(sync.information().state.is_synchronizing());
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(sync.information().orphaned, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().chain.scheduled, 0);
|
|
|
|
assert_eq!(sync.information().chain.requested, 1);
|
|
|
|
assert_eq!(sync.information().chain.stored, 1);
|
|
|
|
assert_eq!(sync.information().peers.idle, 0);
|
|
|
|
assert_eq!(sync.information().peers.active, 1);
|
2016-10-24 23:32:56 -07:00
|
|
|
|
2016-10-29 02:52:40 -07:00
|
|
|
// push requested block => should be moved to the test storage
|
2016-10-26 07:33:28 -07:00
|
|
|
sync.on_peer_block(5, block1);
|
2016-10-29 02:52:40 -07:00
|
|
|
assert!(!sync.information().state.is_synchronizing());
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(sync.information().orphaned, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().chain.scheduled, 0);
|
|
|
|
assert_eq!(sync.information().chain.requested, 0);
|
|
|
|
assert_eq!(sync.information().chain.stored, 2);
|
2016-10-31 04:46:11 -07:00
|
|
|
// we have just requested new `inventory` from the peer => peer is forgotten
|
|
|
|
assert_eq!(sync.information().peers.idle, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().peers.active, 0);
|
2016-10-24 23:32:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn synchronization_out_of_order_block_path() {
|
2016-10-29 02:52:40 -07:00
|
|
|
let (_, sync) = create_sync();
|
|
|
|
let mut sync = sync.lock();
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
let block2: Block = "010000004860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000d5fdcc541e25de1c7a5addedf24858b8bb665c9f36ef744ee42c316022c90f9bb0bc6649ffff001d08d2bd610101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d010bffffffff0100f2052a010000004341047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77ac00000000".into();
|
|
|
|
|
2016-10-31 04:46:11 -07:00
|
|
|
sync.on_new_blocks_inventory(5, vec![block2.hash()]);
|
2016-10-26 07:33:28 -07:00
|
|
|
sync.on_peer_block(5, block2);
|
2016-10-24 23:32:56 -07:00
|
|
|
|
|
|
|
// out-of-order block was presented by the peer
|
2016-10-29 02:52:40 -07:00
|
|
|
assert!(!sync.information().state.is_synchronizing());
|
2016-10-24 23:32:56 -07:00
|
|
|
assert_eq!(sync.information().orphaned, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().chain.scheduled, 0);
|
|
|
|
assert_eq!(sync.information().chain.requested, 0);
|
|
|
|
assert_eq!(sync.information().chain.stored, 1);
|
2016-10-31 04:46:11 -07:00
|
|
|
// we have just requested new `inventory` from the peer => peer is forgotten
|
|
|
|
assert_eq!(sync.information().peers.idle, 0);
|
2016-10-26 07:33:28 -07:00
|
|
|
assert_eq!(sync.information().peers.active, 0);
|
2016-10-24 23:32:56 -07:00
|
|
|
// TODO: check that peer is penalized
|
|
|
|
}
|
|
|
|
}
|