2018-06-04 10:19:50 +02:00
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
2018-05-09 12:05:34 +02:00
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity 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 General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! `BlockChain` synchronization strategy.
//! Syncs to peers and keeps up to date.
//! This implementation uses ethereum protocol v63
//!
//! Syncing strategy summary.
//! Split the chain into ranges of N blocks each. Download ranges sequentially. Split each range into subchains of M blocks. Download subchains in parallel.
//! State.
//! Sync state consists of the following data:
//! - s: State enum which can be one of the following values: `ChainHead`, `Blocks`, `Idle`
//! - H: A set of downloaded block headers
//! - B: A set of downloaded block bodies
//! - S: Set of block subchain start block hashes to download.
//! - l: Last imported / common block hash
//! - P: A set of connected peers. For each peer we maintain its last known total difficulty and starting block hash being requested if any.
//! General behaviour.
//! We start with all sets empty, l is set to the best block in the block chain, s is set to `ChainHead`.
//! If at any moment a bad block is reported by the block queue, we set s to `ChainHead`, reset l to the best block in the block chain and clear H, B and S.
//! If at any moment P becomes empty, we set s to `ChainHead`, and clear H, B and S.
//!
//! Workflow for `ChainHead` state.
//! In this state we try to get subchain headers with a single `GetBlockHeaders` request.
//! On `NewPeer` / On `Restart`:
//! If peer's total difficulty is higher and there are less than 5 peers downloading, request N/M headers with interval M+1 starting from l
//! On `BlockHeaders(R)`:
//! If R is empty:
//! If l is equal to genesis block hash or l is more than 1000 blocks behind our best hash:
//! Remove current peer from P. set l to the best block in the block chain. Select peer with maximum total difficulty from P and restart.
//! Else
//! Set l to l’ s parent and restart.
//! Else if we already have all the headers in the block chain or the block queue:
//! Set s to `Idle`,
//! Else
//! Set S to R, set s to `Blocks`.
//!
//! All other messages are ignored.
//!
//! Workflow for `Blocks` state.
//! In this state we download block headers and bodies from multiple peers.
//! On `NewPeer` / On `Restart`:
//! For all idle peers:
//! Find a set of 256 or less block hashes in H which are not in B and not being downloaded by other peers. If the set is not empty:
//! Request block bodies for the hashes in the set.
//! Else
//! Find an element in S which is not being downloaded by other peers. If found: Request M headers starting from the element.
//!
//! On `BlockHeaders(R)`:
//! If R is empty remove current peer from P and restart.
//! Validate received headers:
//! For each header find a parent in H or R or the blockchain. Restart if there is a block with unknown parent.
//! Find at least one header from the received list in S. Restart if there is none.
//! Go to `CollectBlocks`.
//!
//! On `BlockBodies(R)`:
//! If R is empty remove current peer from P and restart.
//! Add bodies with a matching header in H to B.
//! Go to `CollectBlocks`.
//!
//! `CollectBlocks`:
//! Find a chain of blocks C in H starting from h where h’ s parent equals to l. The chain ends with the first block which does not have a body in B.
//! Add all blocks from the chain to the block queue. Remove them from H and B. Set l to the hash of the last block from C.
//! Update and merge subchain heads in S. For each h in S find a chain of blocks in B starting from h. Remove h from S. if the chain does not include an element from S add the end of the chain to S.
//! If H is empty and S contains a single element set s to `ChainHead`.
//! Restart.
//!
//! All other messages are ignored.
//! Workflow for Idle state.
//! On `NewBlock`:
//! Import the block. If the block is unknown set s to `ChainHead` and restart.
//! On `NewHashes`:
//! Set s to `ChainHead` and restart.
//!
//! All other messages are ignored.
mod handler ;
mod propagator ;
mod requester ;
mod supplier ;
use std ::sync ::Arc ;
use std ::collections ::{ HashSet , HashMap } ;
use std ::cmp ;
use std ::time ::{ Duration , Instant } ;
use hash ::keccak ;
use heapsize ::HeapSizeOf ;
use ethereum_types ::{ H256 , U256 } ;
use plain_hasher ::H256FastMap ;
use parking_lot ::RwLock ;
use bytes ::Bytes ;
use rlp ::{ Rlp , RlpStream , DecoderError } ;
use network ::{ self , PeerId , PacketId } ;
use ethcore ::header ::{ BlockNumber } ;
use ethcore ::client ::{ BlockChainClient , BlockStatus , BlockId , BlockChainInfo , BlockQueueInfo } ;
use ethcore ::snapshot ::{ RestorationStatus } ;
use sync_io ::SyncIo ;
use super ::{ WarpSync , SyncConfig } ;
use block_sync ::{ BlockDownloader , BlockDownloaderImportError as DownloaderImportError } ;
use rand ::Rng ;
use snapshot ::{ Snapshot } ;
use api ::{ EthProtocolInfo as PeerInfoDigest , WARP_SYNC_PROTOCOL_ID } ;
use private_tx ::PrivateTxHandler ;
use transactions_stats ::{ TransactionsStats , Stats as TransactionStats } ;
use transaction ::UnverifiedTransaction ;
use self ::handler ::SyncHandler ;
use self ::propagator ::SyncPropagator ;
use self ::requester ::SyncRequester ;
use self ::supplier ::SyncSupplier ;
known_heap_size! ( 0 , PeerInfo ) ;
pub type PacketDecodeError = DecoderError ;
/// 63 version of Ethereum protocol.
2018-05-14 10:09:05 +02:00
pub const ETH_PROTOCOL_VERSION_63 : ( u8 , u8 ) = ( 63 , 0x11 ) ;
2018-05-09 12:05:34 +02:00
/// 62 version of Ethereum protocol.
2018-05-14 10:09:05 +02:00
pub const ETH_PROTOCOL_VERSION_62 : ( u8 , u8 ) = ( 62 , 0x11 ) ;
/// 1 version of Parity protocol and the packet count.
pub const PAR_PROTOCOL_VERSION_1 : ( u8 , u8 ) = ( 1 , 0x15 ) ;
2018-05-09 12:05:34 +02:00
/// 2 version of Parity protocol (consensus messages added).
2018-05-14 10:09:05 +02:00
pub const PAR_PROTOCOL_VERSION_2 : ( u8 , u8 ) = ( 2 , 0x16 ) ;
2018-05-09 12:05:34 +02:00
/// 3 version of Parity protocol (private transactions messages added).
2018-05-14 10:09:05 +02:00
pub const PAR_PROTOCOL_VERSION_3 : ( u8 , u8 ) = ( 3 , 0x18 ) ;
2018-05-09 12:05:34 +02:00
pub const MAX_BODIES_TO_SEND : usize = 256 ;
pub const MAX_HEADERS_TO_SEND : usize = 512 ;
pub const MAX_NODE_DATA_TO_SEND : usize = 1024 ;
pub const MAX_RECEIPTS_TO_SEND : usize = 1024 ;
pub const MAX_RECEIPTS_HEADERS_TO_SEND : usize = 256 ;
const MIN_PEERS_PROPAGATION : usize = 4 ;
const MAX_PEERS_PROPAGATION : usize = 128 ;
const MAX_PEER_LAG_PROPAGATION : BlockNumber = 20 ;
const MAX_NEW_HASHES : usize = 64 ;
const MAX_NEW_BLOCK_AGE : BlockNumber = 20 ;
// maximal packet size with transactions (cannot be greater than 16MB - protocol limitation).
const MAX_TRANSACTION_PACKET_SIZE : usize = 8 * 1024 * 1024 ;
2018-06-12 08:22:54 +02:00
// Maximal number of transactions queried from miner to propagate.
// This set is used to diff with transactions known by the peer and
// we will send a difference of length up to `MAX_TRANSACTIONS_TO_PROPAGATE`.
const MAX_TRANSACTIONS_TO_QUERY : usize = 4096 ;
2018-05-09 12:05:34 +02:00
// Maximal number of transactions in sent in single packet.
const MAX_TRANSACTIONS_TO_PROPAGATE : usize = 64 ;
// Min number of blocks to be behind for a snapshot sync
const SNAPSHOT_RESTORE_THRESHOLD : BlockNumber = 30000 ;
const SNAPSHOT_MIN_PEERS : usize = 3 ;
const STATUS_PACKET : u8 = 0x00 ;
const NEW_BLOCK_HASHES_PACKET : u8 = 0x01 ;
const TRANSACTIONS_PACKET : u8 = 0x02 ;
pub const GET_BLOCK_HEADERS_PACKET : u8 = 0x03 ;
pub const BLOCK_HEADERS_PACKET : u8 = 0x04 ;
pub const GET_BLOCK_BODIES_PACKET : u8 = 0x05 ;
const BLOCK_BODIES_PACKET : u8 = 0x06 ;
const NEW_BLOCK_PACKET : u8 = 0x07 ;
pub const GET_NODE_DATA_PACKET : u8 = 0x0d ;
pub const NODE_DATA_PACKET : u8 = 0x0e ;
pub const GET_RECEIPTS_PACKET : u8 = 0x0f ;
pub const RECEIPTS_PACKET : u8 = 0x10 ;
pub const GET_SNAPSHOT_MANIFEST_PACKET : u8 = 0x11 ;
pub const SNAPSHOT_MANIFEST_PACKET : u8 = 0x12 ;
pub const GET_SNAPSHOT_DATA_PACKET : u8 = 0x13 ;
pub const SNAPSHOT_DATA_PACKET : u8 = 0x14 ;
pub const CONSENSUS_DATA_PACKET : u8 = 0x15 ;
const PRIVATE_TRANSACTION_PACKET : u8 = 0x16 ;
const SIGNED_PRIVATE_TRANSACTION_PACKET : u8 = 0x17 ;
const MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD : usize = 3 ;
const WAIT_PEERS_TIMEOUT : Duration = Duration ::from_secs ( 5 ) ;
const STATUS_TIMEOUT : Duration = Duration ::from_secs ( 5 ) ;
const HEADERS_TIMEOUT : Duration = Duration ::from_secs ( 15 ) ;
const BODIES_TIMEOUT : Duration = Duration ::from_secs ( 20 ) ;
const RECEIPTS_TIMEOUT : Duration = Duration ::from_secs ( 10 ) ;
const FORK_HEADER_TIMEOUT : Duration = Duration ::from_secs ( 3 ) ;
const SNAPSHOT_MANIFEST_TIMEOUT : Duration = Duration ::from_secs ( 5 ) ;
const SNAPSHOT_DATA_TIMEOUT : Duration = Duration ::from_secs ( 120 ) ;
#[ derive(Copy, Clone, Eq, PartialEq, Debug) ]
/// Sync state
pub enum SyncState {
/// Collecting enough peers to start syncing.
WaitingPeers ,
/// Waiting for snapshot manifest download
SnapshotManifest ,
/// Downloading snapshot data
SnapshotData ,
/// Waiting for snapshot restoration progress.
SnapshotWaiting ,
/// Downloading new blocks
Blocks ,
/// Initial chain sync complete. Waiting for new packets
Idle ,
/// Block downloading paused. Waiting for block queue to process blocks and free some space
Waiting ,
/// Downloading blocks learned from `NewHashes` packet
NewBlocks ,
}
/// Syncing status and statistics
#[ derive(Clone, Copy) ]
pub struct SyncStatus {
/// State
pub state : SyncState ,
/// Syncing protocol version. That's the maximum protocol version we connect to.
pub protocol_version : u8 ,
/// The underlying p2p network version.
pub network_id : u64 ,
/// `BlockChain` height for the moment the sync started.
pub start_block_number : BlockNumber ,
/// Last fully downloaded and imported block number (if any).
pub last_imported_block_number : Option < BlockNumber > ,
/// Highest block number in the download queue (if any).
pub highest_block_number : Option < BlockNumber > ,
/// Total number of blocks for the sync process.
pub blocks_total : BlockNumber ,
/// Number of blocks downloaded so far.
pub blocks_received : BlockNumber ,
/// Total number of connected peers
pub num_peers : usize ,
/// Total number of active peers.
pub num_active_peers : usize ,
/// Heap memory used in bytes.
pub mem_used : usize ,
/// Snapshot chunks
pub num_snapshot_chunks : usize ,
/// Snapshot chunks downloaded
pub snapshot_chunks_done : usize ,
/// Last fully downloaded and imported ancient block number (if any).
pub last_imported_old_block_number : Option < BlockNumber > ,
}
impl SyncStatus {
/// Indicates if snapshot download is in progress
pub fn is_snapshot_syncing ( & self ) -> bool {
2018-05-16 22:01:55 +02:00
match self . state {
SyncState ::SnapshotManifest |
SyncState ::SnapshotData |
SyncState ::SnapshotWaiting = > true ,
_ = > false ,
}
2018-05-09 12:05:34 +02:00
}
/// Returns max no of peers to display in informants
pub fn current_max_peers ( & self , min_peers : u32 , max_peers : u32 ) -> u32 {
if self . num_peers as u32 > min_peers {
max_peers
} else {
min_peers
}
}
/// Is it doing a major sync?
pub fn is_syncing ( & self , queue_info : BlockQueueInfo ) -> bool {
let is_syncing_state = match self . state { SyncState ::Idle | SyncState ::NewBlocks = > false , _ = > true } ;
let is_verifying = queue_info . unverified_queue_size + queue_info . verified_queue_size > 3 ;
is_verifying | | is_syncing_state
}
}
#[ derive(PartialEq, Eq, Debug, Clone) ]
/// Peer data type requested
pub enum PeerAsking {
Nothing ,
ForkHeader ,
BlockHeaders ,
BlockBodies ,
BlockReceipts ,
SnapshotManifest ,
SnapshotData ,
}
#[ derive(PartialEq, Eq, Debug, Clone, Copy) ]
/// Block downloader channel.
pub enum BlockSet {
/// New blocks better than out best blocks
NewBlocks ,
/// Missing old blocks
OldBlocks ,
}
#[ derive(Clone, Eq, PartialEq) ]
pub enum ForkConfirmation {
/// Fork block confirmation pending.
Unconfirmed ,
2018-06-02 10:29:44 +02:00
/// Peer's chain is too short to confirm the fork.
TooShort ,
2018-05-09 12:05:34 +02:00
/// Fork is confirmed.
Confirmed ,
}
#[ derive(Clone) ]
/// Syncing peer information
pub struct PeerInfo {
/// eth protocol version
protocol_version : u8 ,
/// Peer chain genesis hash
genesis : H256 ,
/// Peer network id
network_id : u64 ,
/// Peer best block hash
latest_hash : H256 ,
/// Peer total difficulty if known
difficulty : Option < U256 > ,
/// Type of data currenty being requested from peer.
asking : PeerAsking ,
/// A set of block numbers being requested
asking_blocks : Vec < H256 > ,
/// Holds requested header hash if currently requesting block header by hash
asking_hash : Option < H256 > ,
/// Holds requested snapshot chunk hash if any.
asking_snapshot_data : Option < H256 > ,
/// Request timestamp
ask_time : Instant ,
/// Holds a set of transactions recently sent to this peer to avoid spamming.
last_sent_transactions : HashSet < H256 > ,
/// Pending request is expired and result should be ignored
expired : bool ,
/// Peer fork confirmation status
confirmation : ForkConfirmation ,
/// Best snapshot hash
snapshot_hash : Option < H256 > ,
/// Best snapshot block number
snapshot_number : Option < BlockNumber > ,
/// Block set requested
block_set : Option < BlockSet > ,
}
impl PeerInfo {
fn can_sync ( & self ) -> bool {
self . confirmation = = ForkConfirmation ::Confirmed & & ! self . expired
}
fn is_allowed ( & self ) -> bool {
self . confirmation ! = ForkConfirmation ::Unconfirmed & & ! self . expired
}
fn reset_asking ( & mut self ) {
self . asking_blocks . clear ( ) ;
self . asking_hash = None ;
// mark any pending requests as expired
if self . asking ! = PeerAsking ::Nothing & & self . is_allowed ( ) {
self . expired = true ;
}
}
}
#[ cfg(not(test)) ]
pub mod random {
use rand ;
pub fn new ( ) -> rand ::ThreadRng { rand ::thread_rng ( ) }
}
#[ cfg(test) ]
pub mod random {
use rand ::{ self , SeedableRng } ;
pub fn new ( ) -> rand ::XorShiftRng { rand ::XorShiftRng ::from_seed ( [ 0 , 1 , 2 , 3 ] ) }
}
pub type RlpResponseResult = Result < Option < ( PacketId , RlpStream ) > , PacketDecodeError > ;
pub type Peers = HashMap < PeerId , PeerInfo > ;
/// Blockchain sync handler.
/// See module documentation for more details.
pub struct ChainSync {
/// Sync state
state : SyncState ,
/// Last block number for the start of sync
starting_block : BlockNumber ,
/// Highest block number seen
highest_block : Option < BlockNumber > ,
/// All connected peers
peers : Peers ,
/// Peers active for current sync round
active_peers : HashSet < PeerId > ,
/// Block download process for new blocks
new_blocks : BlockDownloader ,
/// Block download process for ancient blocks
old_blocks : Option < BlockDownloader > ,
/// Last propagated block number
last_sent_block_number : BlockNumber ,
/// Network ID
network_id : u64 ,
/// Optional fork block to check
fork_block : Option < ( BlockNumber , H256 ) > ,
/// Snapshot downloader.
snapshot : Snapshot ,
/// Connected peers pending Status message.
/// Value is request timestamp.
handshaking_peers : HashMap < PeerId , Instant > ,
/// Sync start timestamp. Measured when first peer is connected
sync_start_time : Option < Instant > ,
/// Transactions propagation statistics
transactions_stats : TransactionsStats ,
/// Enable ancient block downloading
download_old_blocks : bool ,
/// Shared private tx service.
private_tx_handler : Arc < PrivateTxHandler > ,
/// Enable warp sync.
warp_sync : WarpSync ,
}
impl ChainSync {
/// Create a new instance of syncing strategy.
pub fn new ( config : SyncConfig , chain : & BlockChainClient , private_tx_handler : Arc < PrivateTxHandler > ) -> ChainSync {
let chain_info = chain . chain_info ( ) ;
let best_block = chain . chain_info ( ) . best_block_number ;
let state = ChainSync ::get_init_state ( config . warp_sync , chain ) ;
let mut sync = ChainSync {
state ,
starting_block : best_block ,
highest_block : None ,
peers : HashMap ::new ( ) ,
handshaking_peers : HashMap ::new ( ) ,
active_peers : HashSet ::new ( ) ,
new_blocks : BlockDownloader ::new ( false , & chain_info . best_block_hash , chain_info . best_block_number ) ,
old_blocks : None ,
last_sent_block_number : 0 ,
network_id : config . network_id ,
fork_block : config . fork_block ,
download_old_blocks : config . download_old_blocks ,
snapshot : Snapshot ::new ( ) ,
sync_start_time : None ,
transactions_stats : TransactionsStats ::default ( ) ,
private_tx_handler ,
warp_sync : config . warp_sync ,
} ;
sync . update_targets ( chain ) ;
sync
}
fn get_init_state ( warp_sync : WarpSync , chain : & BlockChainClient ) -> SyncState {
let best_block = chain . chain_info ( ) . best_block_number ;
match warp_sync {
WarpSync ::Enabled = > SyncState ::WaitingPeers ,
WarpSync ::OnlyAndAfter ( block ) if block > best_block = > SyncState ::WaitingPeers ,
_ = > SyncState ::Idle ,
}
}
/// Returns synchonization status
pub fn status ( & self ) -> SyncStatus {
let last_imported_number = self . new_blocks . last_imported_block_number ( ) ;
SyncStatus {
state : self . state . clone ( ) ,
2018-05-14 10:09:05 +02:00
protocol_version : ETH_PROTOCOL_VERSION_63 . 0 ,
2018-05-09 12:05:34 +02:00
network_id : self . network_id ,
start_block_number : self . starting_block ,
last_imported_block_number : Some ( last_imported_number ) ,
last_imported_old_block_number : self . old_blocks . as_ref ( ) . map ( | d | d . last_imported_block_number ( ) ) ,
highest_block_number : self . highest_block . map ( | n | cmp ::max ( n , last_imported_number ) ) ,
blocks_received : if last_imported_number > self . starting_block { last_imported_number - self . starting_block } else { 0 } ,
blocks_total : match self . highest_block { Some ( x ) if x > self . starting_block = > x - self . starting_block , _ = > 0 } ,
num_peers : self . peers . values ( ) . filter ( | p | p . is_allowed ( ) ) . count ( ) ,
num_active_peers : self . peers . values ( ) . filter ( | p | p . is_allowed ( ) & & p . asking ! = PeerAsking ::Nothing ) . count ( ) ,
num_snapshot_chunks : self . snapshot . total_chunks ( ) ,
snapshot_chunks_done : self . snapshot . done_chunks ( ) ,
mem_used :
self . new_blocks . heap_size ( )
+ self . old_blocks . as_ref ( ) . map_or ( 0 , | d | d . heap_size ( ) )
+ self . peers . heap_size_of_children ( ) ,
}
}
/// Returns information on peers connections
pub fn peer_info ( & self , peer_id : & PeerId ) -> Option < PeerInfoDigest > {
self . peers . get ( peer_id ) . map ( | peer_data | {
PeerInfoDigest {
version : peer_data . protocol_version as u32 ,
difficulty : peer_data . difficulty ,
head : peer_data . latest_hash ,
}
} )
}
/// Returns transactions propagation statistics
pub fn transactions_stats ( & self ) -> & H256FastMap < TransactionStats > {
self . transactions_stats . stats ( )
}
/// Updates transactions were received by a peer
pub fn transactions_received ( & mut self , txs : & [ UnverifiedTransaction ] , peer_id : PeerId ) {
if let Some ( peer_info ) = self . peers . get_mut ( & peer_id ) {
peer_info . last_sent_transactions . extend ( txs . iter ( ) . map ( | tx | tx . hash ( ) ) ) ;
}
}
/// Abort all sync activity
pub fn abort ( & mut self , io : & mut SyncIo ) {
self . reset_and_continue ( io ) ;
self . peers . clear ( ) ;
}
/// Reset sync. Clear all downloaded data but keep the queue
fn reset ( & mut self , io : & mut SyncIo ) {
self . new_blocks . reset ( ) ;
let chain_info = io . chain ( ) . chain_info ( ) ;
for ( _ , ref mut p ) in & mut self . peers {
if p . block_set ! = Some ( BlockSet ::OldBlocks ) {
p . reset_asking ( ) ;
if p . difficulty . is_none ( ) {
// assume peer has up to date difficulty
p . difficulty = Some ( chain_info . pending_total_difficulty ) ;
}
}
}
self . state = ChainSync ::get_init_state ( self . warp_sync , io . chain ( ) ) ;
// Reactivate peers only if some progress has been made
// since the last sync round of if starting fresh.
self . active_peers = self . peers . keys ( ) . cloned ( ) . collect ( ) ;
}
/// Restart sync
pub fn reset_and_continue ( & mut self , io : & mut SyncIo ) {
trace! ( target : " sync " , " Restarting " ) ;
if self . state = = SyncState ::SnapshotData {
debug! ( target :" sync " , " Aborting snapshot restore " ) ;
io . snapshot_service ( ) . abort_restore ( ) ;
}
self . snapshot . clear ( ) ;
self . reset ( io ) ;
self . continue_sync ( io ) ;
}
/// Remove peer from active peer set. Peer will be reactivated on the next sync
/// round.
fn deactivate_peer ( & mut self , _io : & mut SyncIo , peer_id : PeerId ) {
trace! ( target : " sync " , " Deactivating peer {} " , peer_id ) ;
self . active_peers . remove ( & peer_id ) ;
}
fn maybe_start_snapshot_sync ( & mut self , io : & mut SyncIo ) {
if ! self . warp_sync . is_enabled ( ) | | io . snapshot_service ( ) . supported_versions ( ) . is_none ( ) {
trace! ( target : " sync " , " Skipping warp sync. Disabled or not supported. " ) ;
return ;
}
if self . state ! = SyncState ::WaitingPeers & & self . state ! = SyncState ::Blocks & & self . state ! = SyncState ::Waiting {
trace! ( target : " sync " , " Skipping warp sync. State: {:?} " , self . state ) ;
return ;
}
// Make sure the snapshot block is not too far away from best block and network best block and
// that it is higher than fork detection block
let our_best_block = io . chain ( ) . chain_info ( ) . best_block_number ;
let fork_block = self . fork_block . map_or ( 0 , | ( n , _ ) | n ) ;
let ( best_hash , max_peers , snapshot_peers ) = {
let expected_warp_block = match self . warp_sync {
WarpSync ::OnlyAndAfter ( block ) = > block ,
_ = > 0 ,
} ;
//collect snapshot infos from peers
let snapshots = self . peers . iter ( )
. filter ( | & ( _ , p ) | p . is_allowed ( ) & & p . snapshot_number . map_or ( false , | sn |
// Snapshot must be old enough that it's usefull to sync with it
our_best_block < sn & & ( sn - our_best_block ) > SNAPSHOT_RESTORE_THRESHOLD & &
// Snapshot must have been taken after the Fork
sn > fork_block & &
// Snapshot must be greater than the warp barrier if any
sn > expected_warp_block & &
// If we know a highest block, snapshot must be recent enough
self . highest_block . map_or ( true , | highest | {
highest < sn | | ( highest - sn ) < = SNAPSHOT_RESTORE_THRESHOLD
} )
) )
. filter_map ( | ( p , peer ) | peer . snapshot_hash . map ( | hash | ( p , hash . clone ( ) ) ) )
. filter ( | & ( _ , ref hash ) | ! self . snapshot . is_known_bad ( hash ) ) ;
let mut snapshot_peers = HashMap ::new ( ) ;
let mut max_peers : usize = 0 ;
let mut best_hash = None ;
for ( p , hash ) in snapshots {
let peers = snapshot_peers . entry ( hash ) . or_insert_with ( Vec ::new ) ;
peers . push ( * p ) ;
if peers . len ( ) > max_peers {
max_peers = peers . len ( ) ;
best_hash = Some ( hash ) ;
}
}
( best_hash , max_peers , snapshot_peers )
} ;
let timeout = ( self . state = = SyncState ::WaitingPeers ) & & self . sync_start_time . map_or ( false , | t | t . elapsed ( ) > WAIT_PEERS_TIMEOUT ) ;
if let ( Some ( hash ) , Some ( peers ) ) = ( best_hash , best_hash . map_or ( None , | h | snapshot_peers . get ( & h ) ) ) {
if max_peers > = SNAPSHOT_MIN_PEERS {
trace! ( target : " sync " , " Starting confirmed snapshot sync {:?} with {:?} " , hash , peers ) ;
self . start_snapshot_sync ( io , peers ) ;
} else if timeout {
trace! ( target : " sync " , " Starting unconfirmed snapshot sync {:?} with {:?} " , hash , peers ) ;
self . start_snapshot_sync ( io , peers ) ;
}
} else if timeout & & ! self . warp_sync . is_warp_only ( ) {
trace! ( target : " sync " , " No snapshots found, starting full sync " ) ;
self . state = SyncState ::Idle ;
self . continue_sync ( io ) ;
}
}
fn start_snapshot_sync ( & mut self , io : & mut SyncIo , peers : & [ PeerId ] ) {
if ! self . snapshot . have_manifest ( ) {
for p in peers {
if self . peers . get ( p ) . map_or ( false , | p | p . asking = = PeerAsking ::Nothing ) {
SyncRequester ::request_snapshot_manifest ( self , io , * p ) ;
}
}
self . state = SyncState ::SnapshotManifest ;
trace! ( target : " sync " , " New snapshot sync with {:?} " , peers ) ;
} else {
self . state = SyncState ::SnapshotData ;
trace! ( target : " sync " , " Resumed snapshot sync with {:?} " , peers ) ;
}
}
/// Restart sync disregarding the block queue status. May end up re-downloading up to QUEUE_SIZE blocks
pub fn restart ( & mut self , io : & mut SyncIo ) {
self . update_targets ( io . chain ( ) ) ;
self . reset_and_continue ( io ) ;
}
/// Update sync after the blockchain has been changed externally.
pub fn update_targets ( & mut self , chain : & BlockChainClient ) {
// Do not assume that the block queue/chain still has our last_imported_block
let chain = chain . chain_info ( ) ;
self . new_blocks = BlockDownloader ::new ( false , & chain . best_block_hash , chain . best_block_number ) ;
self . old_blocks = None ;
if self . download_old_blocks {
if let ( Some ( ancient_block_hash ) , Some ( ancient_block_number ) ) = ( chain . ancient_block_hash , chain . ancient_block_number ) {
trace! ( target : " sync " , " Downloading old blocks from {:?} (#{}) till {:?} (#{:?}) " , ancient_block_hash , ancient_block_number , chain . first_block_hash , chain . first_block_number ) ;
let mut downloader = BlockDownloader ::with_unlimited_reorg ( true , & ancient_block_hash , ancient_block_number ) ;
if let Some ( hash ) = chain . first_block_hash {
trace! ( target : " sync " , " Downloader target set to {:?} " , hash ) ;
downloader . set_target ( & hash ) ;
}
self . old_blocks = Some ( downloader ) ;
}
}
}
2018-05-16 22:01:55 +02:00
/// Resume downloading
2018-05-09 12:05:34 +02:00
fn continue_sync ( & mut self , io : & mut SyncIo ) {
// Collect active peers that can sync
let confirmed_peers : Vec < ( PeerId , u8 ) > = self . peers . iter ( ) . filter_map ( | ( peer_id , peer ) |
if peer . can_sync ( ) {
Some ( ( * peer_id , peer . protocol_version ) )
} else {
None
}
) . collect ( ) ;
let mut peers : Vec < ( PeerId , u8 ) > = confirmed_peers . iter ( ) . filter ( | & & ( peer_id , _ ) |
self . active_peers . contains ( & peer_id )
) . map ( | v | * v ) . collect ( ) ;
random ::new ( ) . shuffle ( & mut peers ) ; //TODO: sort by rating
// prefer peers with higher protocol version
peers . sort_by ( | & ( _ , ref v1 ) , & ( _ , ref v2 ) | v1 . cmp ( v2 ) ) ;
trace! (
target : " sync " ,
" Syncing with peers: {} active, {} confirmed, {} total " ,
self . active_peers . len ( ) , confirmed_peers . len ( ) , self . peers . len ( )
) ;
for ( peer_id , _ ) in peers {
self . sync_peer ( io , peer_id , false ) ;
}
if
( self . state = = SyncState ::Blocks | | self . state = = SyncState ::NewBlocks ) & &
! self . peers . values ( ) . any ( | p | p . asking ! = PeerAsking ::Nothing & & p . block_set ! = Some ( BlockSet ::OldBlocks ) & & p . can_sync ( ) )
{
self . complete_sync ( io ) ;
}
}
/// Called after all blocks have been downloaded
fn complete_sync ( & mut self , io : & mut SyncIo ) {
trace! ( target : " sync " , " Sync complete " ) ;
self . reset ( io ) ;
}
/// Enter waiting state
fn pause_sync ( & mut self ) {
trace! ( target : " sync " , " Block queue full, pausing sync " ) ;
self . state = SyncState ::Waiting ;
}
/// Find something to do for a peer. Called for a new peer or when a peer is done with its task.
fn sync_peer ( & mut self , io : & mut SyncIo , peer_id : PeerId , force : bool ) {
if ! self . active_peers . contains ( & peer_id ) {
trace! ( target : " sync " , " Skipping deactivated peer {} " , peer_id ) ;
return ;
}
let ( peer_latest , peer_difficulty , peer_snapshot_number , peer_snapshot_hash ) = {
if let Some ( peer ) = self . peers . get_mut ( & peer_id ) {
if peer . asking ! = PeerAsking ::Nothing | | ! peer . can_sync ( ) {
trace! ( target : " sync " , " Skipping busy peer {} " , peer_id ) ;
return ;
}
if self . state = = SyncState ::Waiting {
trace! ( target : " sync " , " Waiting for the block queue " ) ;
return ;
}
if self . state = = SyncState ::SnapshotWaiting {
trace! ( target : " sync " , " Waiting for the snapshot restoration " ) ;
return ;
}
( peer . latest_hash . clone ( ) , peer . difficulty . clone ( ) , peer . snapshot_number . as_ref ( ) . cloned ( ) . unwrap_or ( 0 ) , peer . snapshot_hash . as_ref ( ) . cloned ( ) )
} else {
return ;
}
} ;
let chain_info = io . chain ( ) . chain_info ( ) ;
let syncing_difficulty = chain_info . pending_total_difficulty ;
let num_active_peers = self . peers . values ( ) . filter ( | p | p . asking ! = PeerAsking ::Nothing ) . count ( ) ;
let higher_difficulty = peer_difficulty . map_or ( true , | pd | pd > syncing_difficulty ) ;
if force | | higher_difficulty | | self . old_blocks . is_some ( ) {
match self . state {
SyncState ::WaitingPeers = > {
trace! (
target : " sync " ,
" Checking snapshot sync: {} vs {} (peer: {}) " ,
peer_snapshot_number ,
chain_info . best_block_number ,
peer_id
) ;
self . maybe_start_snapshot_sync ( io ) ;
} ,
SyncState ::Idle | SyncState ::Blocks | SyncState ::NewBlocks = > {
if io . chain ( ) . queue_info ( ) . is_full ( ) {
self . pause_sync ( ) ;
return ;
}
let have_latest = io . chain ( ) . block_status ( BlockId ::Hash ( peer_latest ) ) ! = BlockStatus ::Unknown ;
trace! ( target : " sync " , " Considering peer {}, force={}, td={:?}, our td={}, latest={}, have_latest={}, state={:?} " , peer_id , force , peer_difficulty , syncing_difficulty , peer_latest , have_latest , self . state ) ;
if ! have_latest & & ( higher_difficulty | | force | | self . state = = SyncState ::NewBlocks ) {
// check if got new blocks to download
trace! ( target : " sync " , " Syncing with peer {}, force={}, td={:?}, our td={}, state={:?} " , peer_id , force , peer_difficulty , syncing_difficulty , self . state ) ;
if let Some ( request ) = self . new_blocks . request_blocks ( io , num_active_peers ) {
SyncRequester ::request_blocks ( self , io , peer_id , request , BlockSet ::NewBlocks ) ;
if self . state = = SyncState ::Idle {
self . state = SyncState ::Blocks ;
}
return ;
}
}
2018-05-16 22:01:55 +02:00
// Only ask for old blocks if the peer has a higher difficulty
if force | | higher_difficulty {
if let Some ( request ) = self . old_blocks . as_mut ( ) . and_then ( | d | d . request_blocks ( io , num_active_peers ) ) {
SyncRequester ::request_blocks ( self , io , peer_id , request , BlockSet ::OldBlocks ) ;
return ;
}
} else {
trace! ( target : " sync " , " peer {} is not suitable for asking old blocks " , peer_id ) ;
self . deactivate_peer ( io , peer_id ) ;
2018-05-09 12:05:34 +02:00
}
} ,
SyncState ::SnapshotData = > {
2018-05-16 22:01:55 +02:00
match io . snapshot_service ( ) . status ( ) {
RestorationStatus ::Ongoing { state_chunks_done , block_chunks_done , .. } = > {
// Initialize the snapshot if not already done
self . snapshot . initialize ( io . snapshot_service ( ) ) ;
if self . snapshot . done_chunks ( ) - ( state_chunks_done + block_chunks_done ) as usize > MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD {
trace! ( target : " sync " , " Snapshot queue full, pausing sync " ) ;
self . state = SyncState ::SnapshotWaiting ;
return ;
}
} ,
RestorationStatus ::Initializing { .. } = > {
trace! ( target : " warp " , " Snapshot is stil initializing. " ) ;
2018-05-09 12:05:34 +02:00
return ;
2018-05-16 22:01:55 +02:00
} ,
_ = > {
return ;
} ,
2018-05-09 12:05:34 +02:00
}
2018-05-16 22:01:55 +02:00
2018-05-09 12:05:34 +02:00
if peer_snapshot_hash . is_some ( ) & & peer_snapshot_hash = = self . snapshot . snapshot_hash ( ) {
self . clear_peer_download ( peer_id ) ;
SyncRequester ::request_snapshot_data ( self , io , peer_id ) ;
}
} ,
SyncState ::SnapshotManifest | //already downloading from other peer
2018-05-16 22:01:55 +02:00
SyncState ::Waiting |
SyncState ::SnapshotWaiting = > ( )
2018-05-09 12:05:34 +02:00
}
} else {
trace! ( target : " sync " , " Skipping peer {}, force={}, td={:?}, our td={}, state={:?} " , peer_id , force , peer_difficulty , syncing_difficulty , self . state ) ;
}
}
/// Clear all blocks/headers marked as being downloaded by a peer.
fn clear_peer_download ( & mut self , peer_id : PeerId ) {
if let Some ( ref peer ) = self . peers . get ( & peer_id ) {
match peer . asking {
PeerAsking ::BlockHeaders = > {
if let Some ( ref hash ) = peer . asking_hash {
self . new_blocks . clear_header_download ( hash ) ;
if let Some ( ref mut old ) = self . old_blocks {
old . clear_header_download ( hash ) ;
}
}
} ,
PeerAsking ::BlockBodies = > {
self . new_blocks . clear_body_download ( & peer . asking_blocks ) ;
if let Some ( ref mut old ) = self . old_blocks {
old . clear_body_download ( & peer . asking_blocks ) ;
}
} ,
PeerAsking ::BlockReceipts = > {
self . new_blocks . clear_receipt_download ( & peer . asking_blocks ) ;
if let Some ( ref mut old ) = self . old_blocks {
old . clear_receipt_download ( & peer . asking_blocks ) ;
}
} ,
PeerAsking ::SnapshotData = > {
if let Some ( hash ) = peer . asking_snapshot_data {
self . snapshot . clear_chunk_download ( & hash ) ;
}
} ,
_ = > ( ) ,
}
}
}
/// Checks if there are blocks fully downloaded that can be imported into the blockchain and does the import.
fn collect_blocks ( & mut self , io : & mut SyncIo , block_set : BlockSet ) {
match block_set {
BlockSet ::NewBlocks = > {
if self . new_blocks . collect_blocks ( io , self . state = = SyncState ::NewBlocks ) = = Err ( DownloaderImportError ::Invalid ) {
self . restart ( io ) ;
}
} ,
BlockSet ::OldBlocks = > {
if self . old_blocks . as_mut ( ) . map_or ( false , | downloader | { downloader . collect_blocks ( io , false ) = = Err ( DownloaderImportError ::Invalid ) } ) {
self . restart ( io ) ;
} else if self . old_blocks . as_ref ( ) . map_or ( false , | downloader | { downloader . is_complete ( ) } ) {
trace! ( target : " sync " , " Background block download is complete " ) ;
self . old_blocks = None ;
}
}
}
}
/// Reset peer status after request is complete.
fn reset_peer_asking ( & mut self , peer_id : PeerId , asking : PeerAsking ) -> bool {
if let Some ( ref mut peer ) = self . peers . get_mut ( & peer_id ) {
peer . expired = false ;
peer . block_set = None ;
if peer . asking ! = asking {
trace! ( target :" sync " , " Asking {:?} while expected {:?} " , peer . asking , asking ) ;
peer . asking = PeerAsking ::Nothing ;
return false ;
} else {
peer . asking = PeerAsking ::Nothing ;
return true ;
}
}
false
}
/// Send Status message
fn send_status ( & mut self , io : & mut SyncIo , peer : PeerId ) -> Result < ( ) , network ::Error > {
let warp_protocol_version = io . protocol_version ( & WARP_SYNC_PROTOCOL_ID , peer ) ;
let warp_protocol = warp_protocol_version ! = 0 ;
2018-05-14 10:09:05 +02:00
let protocol = if warp_protocol { warp_protocol_version } else { ETH_PROTOCOL_VERSION_63 . 0 } ;
2018-05-09 12:05:34 +02:00
trace! ( target : " sync " , " Sending status to {}, protocol version {} " , peer , protocol ) ;
let mut packet = RlpStream ::new_list ( if warp_protocol { 7 } else { 5 } ) ;
let chain = io . chain ( ) . chain_info ( ) ;
packet . append ( & ( protocol as u32 ) ) ;
packet . append ( & self . network_id ) ;
packet . append ( & chain . total_difficulty ) ;
packet . append ( & chain . best_block_hash ) ;
packet . append ( & chain . genesis_hash ) ;
if warp_protocol {
2018-05-16 22:01:55 +02:00
let manifest = io . snapshot_service ( ) . manifest ( ) ;
2018-05-09 12:05:34 +02:00
let block_number = manifest . as_ref ( ) . map_or ( 0 , | m | m . block_number ) ;
let manifest_hash = manifest . map_or ( H256 ::new ( ) , | m | keccak ( m . into_rlp ( ) ) ) ;
packet . append ( & manifest_hash ) ;
packet . append ( & block_number ) ;
}
io . respond ( STATUS_PACKET , packet . out ( ) )
}
pub fn maintain_peers ( & mut self , io : & mut SyncIo ) {
let tick = Instant ::now ( ) ;
let mut aborting = Vec ::new ( ) ;
for ( peer_id , peer ) in & self . peers {
let elapsed = tick - peer . ask_time ;
let timeout = match peer . asking {
PeerAsking ::BlockHeaders = > elapsed > HEADERS_TIMEOUT ,
PeerAsking ::BlockBodies = > elapsed > BODIES_TIMEOUT ,
PeerAsking ::BlockReceipts = > elapsed > RECEIPTS_TIMEOUT ,
PeerAsking ::Nothing = > false ,
PeerAsking ::ForkHeader = > elapsed > FORK_HEADER_TIMEOUT ,
PeerAsking ::SnapshotManifest = > elapsed > SNAPSHOT_MANIFEST_TIMEOUT ,
PeerAsking ::SnapshotData = > elapsed > SNAPSHOT_DATA_TIMEOUT ,
} ;
if timeout {
debug! ( target :" sync " , " Timeout {} " , peer_id ) ;
io . disconnect_peer ( * peer_id ) ;
aborting . push ( * peer_id ) ;
}
}
for p in aborting {
SyncHandler ::on_peer_aborting ( self , io , p ) ;
}
// Check for handshake timeouts
for ( peer , & ask_time ) in & self . handshaking_peers {
let elapsed = ( tick - ask_time ) / 1_000_000_000 ;
if elapsed > STATUS_TIMEOUT {
trace! ( target :" sync " , " Status timeout {} " , peer ) ;
io . disconnect_peer ( * peer ) ;
}
}
}
fn check_resume ( & mut self , io : & mut SyncIo ) {
2018-05-16 22:01:55 +02:00
match self . state {
SyncState ::Waiting if ! io . chain ( ) . queue_info ( ) . is_full ( ) = > {
self . state = SyncState ::Blocks ;
self . continue_sync ( io ) ;
} ,
SyncState ::SnapshotWaiting = > {
match io . snapshot_service ( ) . status ( ) {
RestorationStatus ::Inactive = > {
trace! ( target :" sync " , " Snapshot restoration is complete " ) ;
self . restart ( io ) ;
} ,
RestorationStatus ::Initializing { .. } = > {
trace! ( target :" sync " , " Snapshot restoration is initializing " ) ;
} ,
RestorationStatus ::Ongoing { state_chunks_done , block_chunks_done , .. } = > {
if ! self . snapshot . is_complete ( ) & & self . snapshot . done_chunks ( ) - ( state_chunks_done + block_chunks_done ) as usize < = MAX_SNAPSHOT_CHUNKS_DOWNLOAD_AHEAD {
trace! ( target :" sync " , " Resuming snapshot sync " ) ;
self . state = SyncState ::SnapshotData ;
self . continue_sync ( io ) ;
}
} ,
RestorationStatus ::Failed = > {
trace! ( target : " sync " , " Snapshot restoration aborted " ) ;
self . state = SyncState ::WaitingPeers ;
self . snapshot . clear ( ) ;
2018-05-09 12:05:34 +02:00
self . continue_sync ( io ) ;
2018-05-16 22:01:55 +02:00
} ,
}
} ,
_ = > ( ) ,
2018-05-09 12:05:34 +02:00
}
}
/// creates rlp to send for the tree defined by 'from' and 'to' hashes
fn create_new_hashes_rlp ( chain : & BlockChainClient , from : & H256 , to : & H256 ) -> Option < Bytes > {
match chain . tree_route ( from , to ) {
Some ( route ) = > {
let uncles = chain . find_uncles ( from ) . unwrap_or_else ( Vec ::new ) ;
match route . blocks . len ( ) {
0 = > None ,
_ = > {
let mut blocks = route . blocks ;
blocks . extend ( uncles ) ;
let mut rlp_stream = RlpStream ::new_list ( blocks . len ( ) ) ;
for block_hash in blocks {
let mut hash_rlp = RlpStream ::new_list ( 2 ) ;
let number = chain . block_header ( BlockId ::Hash ( block_hash . clone ( ) ) )
. expect ( " chain.tree_route and chain.find_uncles only return hahses of blocks that are in the blockchain. qed. " ) . number ( ) ;
hash_rlp . append ( & block_hash ) ;
hash_rlp . append ( & number ) ;
rlp_stream . append_raw ( hash_rlp . as_raw ( ) , 1 ) ;
}
Some ( rlp_stream . out ( ) )
}
}
} ,
None = > None
}
}
/// creates rlp from block bytes and total difficulty
fn create_block_rlp ( bytes : & Bytes , total_difficulty : U256 ) -> Bytes {
let mut rlp_stream = RlpStream ::new_list ( 2 ) ;
rlp_stream . append_raw ( bytes , 1 ) ;
rlp_stream . append ( & total_difficulty ) ;
rlp_stream . out ( )
}
/// creates latest block rlp for the given client
fn create_latest_block_rlp ( chain : & BlockChainClient ) -> Bytes {
ChainSync ::create_block_rlp (
& chain . block ( BlockId ::Hash ( chain . chain_info ( ) . best_block_hash ) )
. expect ( " Best block always exists " ) . into_inner ( ) ,
chain . chain_info ( ) . total_difficulty
)
}
/// creates given hash block rlp for the given client
fn create_new_block_rlp ( chain : & BlockChainClient , hash : & H256 ) -> Bytes {
ChainSync ::create_block_rlp (
& chain . block ( BlockId ::Hash ( hash . clone ( ) ) ) . expect ( " Block has just been sealed; qed " ) . into_inner ( ) ,
chain . block_total_difficulty ( BlockId ::Hash ( hash . clone ( ) ) ) . expect ( " Block has just been sealed; qed. " )
)
}
/// returns peer ids that have different blocks than our chain
fn get_lagging_peers ( & mut self , chain_info : & BlockChainInfo ) -> Vec < PeerId > {
let latest_hash = chain_info . best_block_hash ;
self
. peers
. iter_mut ( )
. filter_map ( | ( & id , ref mut peer_info ) | {
trace! ( target : " sync " , " Checking peer our best {} their best {} " , latest_hash , peer_info . latest_hash ) ;
if peer_info . latest_hash ! = latest_hash {
Some ( id )
} else {
None
}
} )
. collect ::< Vec < _ > > ( )
}
fn select_random_peers ( peers : & [ PeerId ] ) -> Vec < PeerId > {
// take sqrt(x) peers
let mut peers = peers . to_vec ( ) ;
let mut count = ( peers . len ( ) as f64 ) . powf ( 0.5 ) . round ( ) as usize ;
count = cmp ::min ( count , MAX_PEERS_PROPAGATION ) ;
count = cmp ::max ( count , MIN_PEERS_PROPAGATION ) ;
random ::new ( ) . shuffle ( & mut peers ) ;
peers . truncate ( count ) ;
peers
}
fn get_consensus_peers ( & self ) -> Vec < PeerId > {
2018-05-14 10:09:05 +02:00
self . peers . iter ( ) . filter_map ( | ( id , p ) | if p . protocol_version > = PAR_PROTOCOL_VERSION_2 . 0 { Some ( * id ) } else { None } ) . collect ( )
2018-05-09 12:05:34 +02:00
}
fn get_private_transaction_peers ( & self ) -> Vec < PeerId > {
2018-05-14 10:09:05 +02:00
self . peers . iter ( ) . filter_map ( | ( id , p ) | if p . protocol_version > = PAR_PROTOCOL_VERSION_3 . 0 { Some ( * id ) } else { None } ) . collect ( )
2018-05-09 12:05:34 +02:00
}
/// Maintain other peers. Send out any new blocks and transactions
pub fn maintain_sync ( & mut self , io : & mut SyncIo ) {
self . maybe_start_snapshot_sync ( io ) ;
self . check_resume ( io ) ;
}
/// called when block is imported to chain - propagates the blocks and updates transactions sent to peers
pub fn chain_new_blocks ( & mut self , io : & mut SyncIo , _imported : & [ H256 ] , invalid : & [ H256 ] , enacted : & [ H256 ] , _retracted : & [ H256 ] , sealed : & [ H256 ] , proposed : & [ Bytes ] ) {
let queue_info = io . chain ( ) . queue_info ( ) ;
let is_syncing = self . status ( ) . is_syncing ( queue_info ) ;
if ! is_syncing | | ! sealed . is_empty ( ) | | ! proposed . is_empty ( ) {
trace! ( target : " sync " , " Propagating blocks, state={:?} " , self . state ) ;
SyncPropagator ::propagate_latest_blocks ( self , io , sealed ) ;
SyncPropagator ::propagate_proposed_blocks ( self , io , proposed ) ;
}
if ! invalid . is_empty ( ) {
trace! ( target : " sync " , " Bad blocks in the queue, restarting " ) ;
self . restart ( io ) ;
}
if ! is_syncing & & ! enacted . is_empty ( ) & & ! self . peers . is_empty ( ) {
// Select random peer to re-broadcast transactions to.
let peer = random ::new ( ) . gen_range ( 0 , self . peers . len ( ) ) ;
trace! ( target : " sync " , " Re-broadcasting transactions to a random peer. " ) ;
self . peers . values_mut ( ) . nth ( peer ) . map ( | peer_info |
peer_info . last_sent_transactions . clear ( )
) ;
}
}
/// Dispatch incoming requests and responses
pub fn dispatch_packet ( sync : & RwLock < ChainSync > , io : & mut SyncIo , peer : PeerId , packet_id : u8 , data : & [ u8 ] ) {
SyncSupplier ::dispatch_packet ( sync , io , peer , packet_id , data )
}
pub fn on_packet ( & mut self , io : & mut SyncIo , peer : PeerId , packet_id : u8 , data : & [ u8 ] ) {
debug! ( target : " sync " , " {} -> Dispatching packet: {} " , peer , packet_id ) ;
SyncHandler ::on_packet ( self , io , peer , packet_id , data ) ;
}
/// Called when peer sends us new consensus packet
pub fn on_consensus_packet ( io : & mut SyncIo , peer_id : PeerId , r : & Rlp ) -> Result < ( ) , PacketDecodeError > {
SyncHandler ::on_consensus_packet ( io , peer_id , r )
}
/// Called by peer when it is disconnecting
pub fn on_peer_aborting ( & mut self , io : & mut SyncIo , peer : PeerId ) {
SyncHandler ::on_peer_aborting ( self , io , peer ) ;
}
/// Called when a new peer is connected
pub fn on_peer_connected ( & mut self , io : & mut SyncIo , peer : PeerId ) {
SyncHandler ::on_peer_connected ( self , io , peer ) ;
}
/// propagates new transactions to all peers
pub fn propagate_new_transactions ( & mut self , io : & mut SyncIo ) -> usize {
SyncPropagator ::propagate_new_transactions ( self , io )
}
/// Broadcast consensus message to peers.
pub fn propagate_consensus_packet ( & mut self , io : & mut SyncIo , packet : Bytes ) {
SyncPropagator ::propagate_consensus_packet ( self , io , packet ) ;
}
/// Broadcast private transaction message to peers.
pub fn propagate_private_transaction ( & mut self , io : & mut SyncIo , packet : Bytes ) {
SyncPropagator ::propagate_private_transaction ( self , io , packet ) ;
}
/// Broadcast signed private transaction message to peers.
pub fn propagate_signed_private_transaction ( & mut self , io : & mut SyncIo , packet : Bytes ) {
SyncPropagator ::propagate_signed_private_transaction ( self , io , packet ) ;
}
}
#[ cfg(test) ]
pub mod tests {
use std ::collections ::{ HashSet , VecDeque } ;
use ethkey ;
use network ::PeerId ;
use tests ::helpers ::{ TestIo } ;
use tests ::snapshot ::TestSnapshotService ;
use ethereum_types ::{ H256 , U256 , Address } ;
use parking_lot ::RwLock ;
use bytes ::Bytes ;
use rlp ::{ Rlp , RlpStream } ;
use super ::* ;
use ::SyncConfig ;
use super ::{ PeerInfo , PeerAsking } ;
use ethcore ::header ::* ;
use ethcore ::client ::{ BlockChainClient , EachBlockWith , TestBlockChainClient , ChainInfo , BlockInfo } ;
2018-06-12 08:22:54 +02:00
use ethcore ::miner ::{ MinerService , PendingOrdering } ;
2018-05-09 12:05:34 +02:00
use private_tx ::NoopPrivateTxHandler ;
pub fn get_dummy_block ( order : u32 , parent_hash : H256 ) -> Bytes {
let mut header = Header ::new ( ) ;
header . set_gas_limit ( 0. into ( ) ) ;
header . set_difficulty ( ( order * 100 ) . into ( ) ) ;
header . set_timestamp ( ( order * 10 ) as u64 ) ;
header . set_number ( order as u64 ) ;
header . set_parent_hash ( parent_hash ) ;
header . set_state_root ( H256 ::zero ( ) ) ;
let mut rlp = RlpStream ::new_list ( 3 ) ;
rlp . append ( & header ) ;
rlp . append_raw ( & ::rlp ::EMPTY_LIST_RLP , 1 ) ;
rlp . append_raw ( & ::rlp ::EMPTY_LIST_RLP , 1 ) ;
rlp . out ( )
}
pub fn get_dummy_blocks ( order : u32 , parent_hash : H256 ) -> Bytes {
let mut rlp = RlpStream ::new_list ( 1 ) ;
rlp . append_raw ( & get_dummy_block ( order , parent_hash ) , 1 ) ;
let difficulty : U256 = ( 100 * order ) . into ( ) ;
rlp . append ( & difficulty ) ;
rlp . out ( )
}
pub fn get_dummy_hashes ( ) -> Bytes {
let mut rlp = RlpStream ::new_list ( 5 ) ;
for _ in 0 .. 5 {
let mut hash_d_rlp = RlpStream ::new_list ( 2 ) ;
let hash : H256 = H256 ::from ( 0 u64 ) ;
let diff : U256 = U256 ::from ( 1 u64 ) ;
hash_d_rlp . append ( & hash ) ;
hash_d_rlp . append ( & diff ) ;
rlp . append_raw ( & hash_d_rlp . out ( ) , 1 ) ;
}
rlp . out ( )
}
fn queue_info ( unverified : usize , verified : usize ) -> BlockQueueInfo {
BlockQueueInfo {
unverified_queue_size : unverified ,
verified_queue_size : verified ,
verifying_queue_size : 0 ,
max_queue_size : 1000 ,
max_mem_use : 1000 ,
mem_used : 500
}
}
fn sync_status ( state : SyncState ) -> SyncStatus {
SyncStatus {
state : state ,
protocol_version : 0 ,
network_id : 0 ,
start_block_number : 0 ,
last_imported_block_number : None ,
highest_block_number : None ,
blocks_total : 0 ,
blocks_received : 0 ,
num_peers : 0 ,
num_active_peers : 0 ,
mem_used : 0 ,
num_snapshot_chunks : 0 ,
snapshot_chunks_done : 0 ,
last_imported_old_block_number : None ,
}
}
#[ test ]
fn is_still_verifying ( ) {
assert! ( ! sync_status ( SyncState ::Idle ) . is_syncing ( queue_info ( 2 , 1 ) ) ) ;
assert! ( sync_status ( SyncState ::Idle ) . is_syncing ( queue_info ( 2 , 2 ) ) ) ;
}
#[ test ]
fn is_synced_state ( ) {
assert! ( sync_status ( SyncState ::Blocks ) . is_syncing ( queue_info ( 0 , 0 ) ) ) ;
assert! ( ! sync_status ( SyncState ::Idle ) . is_syncing ( queue_info ( 0 , 0 ) ) ) ;
}
pub fn dummy_sync_with_peer ( peer_latest_hash : H256 , client : & BlockChainClient ) -> ChainSync {
let mut sync = ChainSync ::new ( SyncConfig ::default ( ) , client , Arc ::new ( NoopPrivateTxHandler ) ) ;
insert_dummy_peer ( & mut sync , 0 , peer_latest_hash ) ;
sync
}
pub fn insert_dummy_peer ( sync : & mut ChainSync , peer_id : PeerId , peer_latest_hash : H256 ) {
sync . peers . insert ( peer_id ,
PeerInfo {
protocol_version : 0 ,
genesis : H256 ::zero ( ) ,
network_id : 0 ,
latest_hash : peer_latest_hash ,
difficulty : None ,
asking : PeerAsking ::Nothing ,
asking_blocks : Vec ::new ( ) ,
asking_hash : None ,
ask_time : Instant ::now ( ) ,
last_sent_transactions : HashSet ::new ( ) ,
expired : false ,
confirmation : super ::ForkConfirmation ::Confirmed ,
snapshot_number : None ,
snapshot_hash : None ,
asking_snapshot_data : None ,
block_set : None ,
} ) ;
}
#[ test ]
fn finds_lagging_peers ( ) {
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 100 , EachBlockWith ::Uncle ) ;
let mut sync = dummy_sync_with_peer ( client . block_hash_delta_minus ( 10 ) , & client ) ;
let chain_info = client . chain_info ( ) ;
let lagging_peers = sync . get_lagging_peers ( & chain_info ) ;
assert_eq! ( 1 , lagging_peers . len ( ) ) ;
}
#[ test ]
fn calculates_tree_for_lagging_peer ( ) {
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 15 , EachBlockWith ::Uncle ) ;
let start = client . block_hash_delta_minus ( 4 ) ;
let end = client . block_hash_delta_minus ( 2 ) ;
// wrong way end -> start, should be None
let rlp = ChainSync ::create_new_hashes_rlp ( & client , & end , & start ) ;
assert! ( rlp . is_none ( ) ) ;
let rlp = ChainSync ::create_new_hashes_rlp ( & client , & start , & end ) . unwrap ( ) ;
// size of three rlp encoded hash-difficulty
assert_eq! ( 107 , rlp . len ( ) ) ;
}
// idea is that what we produce when propagading latest hashes should be accepted in
// on_peer_new_hashes in our code as well
#[ test ]
fn hashes_rlp_mutually_acceptable ( ) {
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 100 , EachBlockWith ::Uncle ) ;
let queue = RwLock ::new ( VecDeque ::new ( ) ) ;
let mut sync = dummy_sync_with_peer ( client . block_hash_delta_minus ( 5 ) , & client ) ;
let chain_info = client . chain_info ( ) ;
let ss = TestSnapshotService ::new ( ) ;
let mut io = TestIo ::new ( & mut client , & ss , & queue , None ) ;
let peers = sync . get_lagging_peers ( & chain_info ) ;
SyncPropagator ::propagate_new_hashes ( & mut sync , & chain_info , & mut io , & peers ) ;
let data = & io . packets [ 0 ] . data . clone ( ) ;
let result = SyncHandler ::on_peer_new_hashes ( & mut sync , & mut io , 0 , & Rlp ::new ( data ) ) ;
assert! ( result . is_ok ( ) ) ;
}
// idea is that what we produce when propagading latest block should be accepted in
// on_peer_new_block in our code as well
#[ test ]
fn block_rlp_mutually_acceptable ( ) {
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 100 , EachBlockWith ::Uncle ) ;
let queue = RwLock ::new ( VecDeque ::new ( ) ) ;
let mut sync = dummy_sync_with_peer ( client . block_hash_delta_minus ( 5 ) , & client ) ;
let chain_info = client . chain_info ( ) ;
let ss = TestSnapshotService ::new ( ) ;
let mut io = TestIo ::new ( & mut client , & ss , & queue , None ) ;
let peers = sync . get_lagging_peers ( & chain_info ) ;
SyncPropagator ::propagate_blocks ( & mut sync , & chain_info , & mut io , & [ ] , & peers ) ;
let data = & io . packets [ 0 ] . data . clone ( ) ;
let result = SyncHandler ::on_peer_new_block ( & mut sync , & mut io , 0 , & Rlp ::new ( data ) ) ;
assert! ( result . is_ok ( ) ) ;
}
#[ test ]
fn should_add_transactions_to_queue ( ) {
fn sender ( tx : & UnverifiedTransaction ) -> Address {
ethkey ::public_to_address ( & tx . recover_public ( ) . unwrap ( ) )
}
// given
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 98 , EachBlockWith ::Uncle ) ;
client . add_blocks ( 1 , EachBlockWith ::UncleAndTransaction ) ;
client . add_blocks ( 1 , EachBlockWith ::Transaction ) ;
let mut sync = dummy_sync_with_peer ( client . block_hash_delta_minus ( 5 ) , & client ) ;
let good_blocks = vec! [ client . block_hash_delta_minus ( 2 ) ] ;
let retracted_blocks = vec! [ client . block_hash_delta_minus ( 1 ) ] ;
// Add some balance to clients and reset nonces
for h in & [ good_blocks [ 0 ] , retracted_blocks [ 0 ] ] {
let block = client . block ( BlockId ::Hash ( * h ) ) . unwrap ( ) ;
let sender = sender ( & block . transactions ( ) [ 0 ] ) ; ;
client . set_balance ( sender , U256 ::from ( 10_000_000_000_000_000_000 u64 ) ) ;
client . set_nonce ( sender , U256 ::from ( 0 ) ) ;
}
// when
{
let queue = RwLock ::new ( VecDeque ::new ( ) ) ;
let ss = TestSnapshotService ::new ( ) ;
let mut io = TestIo ::new ( & mut client , & ss , & queue , None ) ;
io . chain . miner . chain_new_blocks ( io . chain , & [ ] , & [ ] , & [ ] , & good_blocks , false ) ;
sync . chain_new_blocks ( & mut io , & [ ] , & [ ] , & [ ] , & good_blocks , & [ ] , & [ ] ) ;
2018-06-12 08:22:54 +02:00
assert_eq! ( io . chain . miner . ready_transactions ( io . chain , 10 , PendingOrdering ::Priority ) . len ( ) , 1 ) ;
2018-05-09 12:05:34 +02:00
}
// We need to update nonce status (because we say that the block has been imported)
for h in & [ good_blocks [ 0 ] ] {
let block = client . block ( BlockId ::Hash ( * h ) ) . unwrap ( ) ;
client . set_nonce ( sender ( & block . transactions ( ) [ 0 ] ) , U256 ::from ( 1 ) ) ;
}
{
let queue = RwLock ::new ( VecDeque ::new ( ) ) ;
let ss = TestSnapshotService ::new ( ) ;
let mut io = TestIo ::new ( & client , & ss , & queue , None ) ;
io . chain . miner . chain_new_blocks ( io . chain , & [ ] , & [ ] , & good_blocks , & retracted_blocks , false ) ;
sync . chain_new_blocks ( & mut io , & [ ] , & [ ] , & good_blocks , & retracted_blocks , & [ ] , & [ ] ) ;
}
// then
2018-06-12 08:22:54 +02:00
assert_eq! ( client . miner . ready_transactions ( & client , 10 , PendingOrdering ::Priority ) . len ( ) , 1 ) ;
2018-05-09 12:05:34 +02:00
}
#[ test ]
fn should_not_add_transactions_to_queue_if_not_synced ( ) {
// given
let mut client = TestBlockChainClient ::new ( ) ;
client . add_blocks ( 98 , EachBlockWith ::Uncle ) ;
client . add_blocks ( 1 , EachBlockWith ::UncleAndTransaction ) ;
client . add_blocks ( 1 , EachBlockWith ::Transaction ) ;
let mut sync = dummy_sync_with_peer ( client . block_hash_delta_minus ( 5 ) , & client ) ;
let good_blocks = vec! [ client . block_hash_delta_minus ( 2 ) ] ;
let retracted_blocks = vec! [ client . block_hash_delta_minus ( 1 ) ] ;
let queue = RwLock ::new ( VecDeque ::new ( ) ) ;
let ss = TestSnapshotService ::new ( ) ;
let mut io = TestIo ::new ( & mut client , & ss , & queue , None ) ;
// when
sync . chain_new_blocks ( & mut io , & [ ] , & [ ] , & [ ] , & good_blocks , & [ ] , & [ ] ) ;
assert_eq! ( io . chain . miner . queue_status ( ) . status . transaction_count , 0 ) ;
sync . chain_new_blocks ( & mut io , & [ ] , & [ ] , & good_blocks , & retracted_blocks , & [ ] , & [ ] ) ;
// then
let status = io . chain . miner . queue_status ( ) ;
assert_eq! ( status . status . transaction_count , 0 ) ;
}
}