Big folder refactor
This commit is contained in:
820
crates/ethcore/sync/src/api.rs
Normal file
820
crates/ethcore/sync/src/api.rs
Normal file
@@ -0,0 +1,820 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::Bytes;
|
||||
use devp2p::NetworkService;
|
||||
use network::{
|
||||
client_version::ClientVersion, ConnectionFilter, Error, ErrorKind,
|
||||
NetworkConfiguration as BasicNetworkConfiguration, NetworkContext, NetworkProtocolHandler,
|
||||
NonReservedPeerMode, PeerId, ProtocolId,
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
io,
|
||||
ops::RangeInclusive,
|
||||
sync::{atomic, mpsc, Arc},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use chain::{
|
||||
fork_filter::ForkFilterApi, ChainSyncApi, SyncState, SyncStatus as EthSyncStatus,
|
||||
ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64, PAR_PROTOCOL_VERSION_1,
|
||||
PAR_PROTOCOL_VERSION_2,
|
||||
};
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, ChainMessageType, ChainNotify, NewBlocks},
|
||||
snapshot::SnapshotService,
|
||||
};
|
||||
use ethereum_types::{H256, H512, U256};
|
||||
use ethkey::Secret;
|
||||
use io::TimerToken;
|
||||
use network::IpFilter;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use stats::{prometheus, prometheus_counter, prometheus_gauge, PrometheusMetrics};
|
||||
|
||||
use std::{
|
||||
net::{AddrParseError, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
use sync_io::NetSyncIo;
|
||||
use types::{
|
||||
creation_status::CreationStatus, restoration_status::RestorationStatus,
|
||||
transaction::UnverifiedTransaction, BlockNumber,
|
||||
};
|
||||
|
||||
/// OpenEthereum sync protocol
|
||||
pub const PAR_PROTOCOL: ProtocolId = *b"par";
|
||||
/// Ethereum sync protocol
|
||||
pub const ETH_PROTOCOL: ProtocolId = *b"eth";
|
||||
|
||||
/// Determine warp sync status.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WarpSync {
|
||||
/// Warp sync is enabled.
|
||||
Enabled,
|
||||
/// Warp sync is disabled.
|
||||
Disabled,
|
||||
/// Only warp sync is allowed (no regular sync) and only after given block number.
|
||||
OnlyAndAfter(BlockNumber),
|
||||
}
|
||||
|
||||
impl WarpSync {
|
||||
/// Returns true if warp sync is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
match *self {
|
||||
WarpSync::Enabled => true,
|
||||
WarpSync::OnlyAndAfter(_) => true,
|
||||
WarpSync::Disabled => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if we are in warp-only mode.
|
||||
///
|
||||
/// i.e. we will never fall back to regular sync
|
||||
/// until given block number is reached by
|
||||
/// successfuly finding and restoring from a snapshot.
|
||||
pub fn is_warp_only(&self) -> bool {
|
||||
if let WarpSync::OnlyAndAfter(_) = *self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync configuration
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SyncConfig {
|
||||
/// Max blocks to download ahead
|
||||
pub max_download_ahead_blocks: usize,
|
||||
/// Enable ancient block download.
|
||||
pub download_old_blocks: bool,
|
||||
/// Network ID
|
||||
pub network_id: u64,
|
||||
/// Main "eth" subprotocol name.
|
||||
pub subprotocol_name: [u8; 3],
|
||||
/// Fork block to check
|
||||
pub fork_block: Option<(BlockNumber, H256)>,
|
||||
/// Enable snapshot sync
|
||||
pub warp_sync: WarpSync,
|
||||
}
|
||||
|
||||
impl Default for SyncConfig {
|
||||
fn default() -> SyncConfig {
|
||||
SyncConfig {
|
||||
max_download_ahead_blocks: 20000,
|
||||
download_old_blocks: true,
|
||||
network_id: 1,
|
||||
subprotocol_name: ETH_PROTOCOL,
|
||||
fork_block: None,
|
||||
warp_sync: WarpSync::Disabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Current sync status
|
||||
pub trait SyncProvider: Send + Sync + PrometheusMetrics {
|
||||
/// Get sync status
|
||||
fn status(&self) -> EthSyncStatus;
|
||||
|
||||
/// Get peers information
|
||||
fn peers(&self) -> Vec<PeerInfo>;
|
||||
|
||||
/// Get the enode if available.
|
||||
fn enode(&self) -> Option<String>;
|
||||
|
||||
/// Returns propagation count for pending transactions.
|
||||
fn transactions_stats(&self) -> BTreeMap<H256, TransactionStats>;
|
||||
}
|
||||
|
||||
/// Transaction stats
|
||||
#[derive(Debug)]
|
||||
pub struct TransactionStats {
|
||||
/// Block number where this TX was first seen.
|
||||
pub first_seen: u64,
|
||||
/// Peers it was propagated to.
|
||||
pub propagated_to: BTreeMap<H512, usize>,
|
||||
}
|
||||
|
||||
/// Peer connection information
|
||||
#[derive(Debug)]
|
||||
pub struct PeerInfo {
|
||||
/// Public node id
|
||||
pub id: Option<String>,
|
||||
/// Node client ID
|
||||
pub client_version: ClientVersion,
|
||||
/// Capabilities
|
||||
pub capabilities: Vec<String>,
|
||||
/// Remote endpoint address
|
||||
pub remote_address: String,
|
||||
/// Local endpoint address
|
||||
pub local_address: String,
|
||||
/// Eth protocol info.
|
||||
pub eth_info: Option<EthProtocolInfo>,
|
||||
}
|
||||
|
||||
/// Ethereum protocol info.
|
||||
#[derive(Debug)]
|
||||
pub struct EthProtocolInfo {
|
||||
/// Protocol version
|
||||
pub version: u32,
|
||||
/// SHA3 of peer best block hash
|
||||
pub head: H256,
|
||||
/// Peer total difficulty if known
|
||||
pub difficulty: Option<U256>,
|
||||
}
|
||||
|
||||
/// A prioritized tasks run in a specialised timer.
|
||||
/// Every task should be completed within a hard deadline,
|
||||
/// if it's not it's either cancelled or split into multiple tasks.
|
||||
/// NOTE These tasks might not complete at all, so anything
|
||||
/// that happens here should work even if the task is cancelled.
|
||||
#[derive(Debug)]
|
||||
pub enum PriorityTask {
|
||||
/// Propagate given block
|
||||
PropagateBlock {
|
||||
/// When the task was initiated
|
||||
started: ::std::time::Instant,
|
||||
/// Raw block RLP to propagate
|
||||
block: Bytes,
|
||||
/// Block hash
|
||||
hash: H256,
|
||||
/// Blocks difficulty
|
||||
difficulty: U256,
|
||||
},
|
||||
/// Propagate a list of transactions
|
||||
PropagateTransactions(::std::time::Instant, Arc<atomic::AtomicBool>),
|
||||
}
|
||||
impl PriorityTask {
|
||||
/// Mark the task as being processed, right after it's retrieved from the queue.
|
||||
pub fn starting(&self) {
|
||||
match *self {
|
||||
PriorityTask::PropagateTransactions(_, ref is_ready) => {
|
||||
is_ready.store(true, atomic::Ordering::SeqCst)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// EthSync initialization parameters.
|
||||
pub struct Params {
|
||||
/// Configuration.
|
||||
pub config: SyncConfig,
|
||||
/// Blockchain client.
|
||||
pub chain: Arc<dyn BlockChainClient>,
|
||||
/// Forks.
|
||||
pub forks: BTreeSet<BlockNumber>,
|
||||
/// Snapshot service.
|
||||
pub snapshot_service: Arc<dyn SnapshotService>,
|
||||
/// Network layer configuration.
|
||||
pub network_config: NetworkConfiguration,
|
||||
}
|
||||
|
||||
/// Ethereum network protocol handler
|
||||
pub struct EthSync {
|
||||
/// Network service
|
||||
network: NetworkService,
|
||||
/// Main (eth/par) protocol handler
|
||||
eth_handler: Arc<SyncProtocolHandler>,
|
||||
/// The main subprotocol name
|
||||
subprotocol_name: [u8; 3],
|
||||
/// Priority tasks notification channel
|
||||
priority_tasks: Mutex<mpsc::Sender<PriorityTask>>,
|
||||
}
|
||||
|
||||
impl EthSync {
|
||||
/// Creates and register protocol with the network service
|
||||
pub fn new(
|
||||
params: Params,
|
||||
connection_filter: Option<Arc<dyn ConnectionFilter>>,
|
||||
) -> Result<Arc<EthSync>, Error> {
|
||||
let (priority_tasks_tx, priority_tasks_rx) = mpsc::channel();
|
||||
let fork_filter = ForkFilterApi::new(&*params.chain, params.forks);
|
||||
|
||||
let sync = ChainSyncApi::new(
|
||||
params.config,
|
||||
&*params.chain,
|
||||
fork_filter,
|
||||
priority_tasks_rx,
|
||||
);
|
||||
let service = NetworkService::new(
|
||||
params.network_config.clone().into_basic()?,
|
||||
connection_filter,
|
||||
)?;
|
||||
|
||||
let sync = Arc::new(EthSync {
|
||||
network: service,
|
||||
eth_handler: Arc::new(SyncProtocolHandler {
|
||||
sync,
|
||||
chain: params.chain,
|
||||
snapshot_service: params.snapshot_service,
|
||||
overlay: RwLock::new(HashMap::new()),
|
||||
}),
|
||||
subprotocol_name: params.config.subprotocol_name,
|
||||
priority_tasks: Mutex::new(priority_tasks_tx),
|
||||
});
|
||||
|
||||
Ok(sync)
|
||||
}
|
||||
|
||||
/// Priority tasks producer
|
||||
pub fn priority_tasks(&self) -> mpsc::Sender<PriorityTask> {
|
||||
self.priority_tasks.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncProvider for EthSync {
|
||||
/// Get sync status
|
||||
fn status(&self) -> EthSyncStatus {
|
||||
self.eth_handler.sync.status()
|
||||
}
|
||||
|
||||
/// Get sync peers
|
||||
fn peers(&self) -> Vec<PeerInfo> {
|
||||
self.network
|
||||
.with_context_eval(self.subprotocol_name, |ctx| {
|
||||
let peer_ids = self.network.connected_peers();
|
||||
|
||||
let peer_info = self.eth_handler.sync.peer_info(&peer_ids);
|
||||
peer_ids
|
||||
.into_iter()
|
||||
.zip(peer_info)
|
||||
.filter_map(|(peer_id, peer_info)| {
|
||||
let session_info = match ctx.session_info(peer_id) {
|
||||
None => return None,
|
||||
Some(info) => info,
|
||||
};
|
||||
|
||||
Some(PeerInfo {
|
||||
id: session_info.id.map(|id| format!("{:x}", id)),
|
||||
client_version: session_info.client_version,
|
||||
capabilities: session_info
|
||||
.peer_capabilities
|
||||
.into_iter()
|
||||
.map(|c| c.to_string())
|
||||
.collect(),
|
||||
remote_address: session_info.remote_address,
|
||||
local_address: session_info.local_address,
|
||||
eth_info: peer_info,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(Vec::new)
|
||||
}
|
||||
|
||||
fn enode(&self) -> Option<String> {
|
||||
self.network.external_url()
|
||||
}
|
||||
|
||||
fn transactions_stats(&self) -> BTreeMap<H256, TransactionStats> {
|
||||
self.eth_handler.sync.transactions_stats()
|
||||
}
|
||||
}
|
||||
|
||||
impl PrometheusMetrics for EthSync {
|
||||
fn prometheus_metrics(&self, r: &mut prometheus::Registry) {
|
||||
let scalar = |b| if b { 1i64 } else { 0i64 };
|
||||
let sync_status = self.status();
|
||||
|
||||
prometheus_gauge(r,
|
||||
"sync_status",
|
||||
"WaitingPeers(0), SnapshotManifest(1), SnapshotData(2), SnapshotWaiting(3), Blocks(4), Idle(5), Waiting(6), NewBlocks(7)",
|
||||
match self.eth_handler.sync.status().state {
|
||||
SyncState::WaitingPeers => 0,
|
||||
SyncState::SnapshotManifest => 1,
|
||||
SyncState::SnapshotData => 2,
|
||||
SyncState::SnapshotWaiting => 3,
|
||||
SyncState::Blocks => 4,
|
||||
SyncState::Idle => 5,
|
||||
SyncState::Waiting => 6,
|
||||
SyncState::NewBlocks => 7,
|
||||
});
|
||||
|
||||
for (key, value) in sync_status.item_sizes.iter() {
|
||||
prometheus_gauge(
|
||||
r,
|
||||
&key,
|
||||
format!("Total item number of {}", key).as_str(),
|
||||
*value as i64,
|
||||
);
|
||||
}
|
||||
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"net_peers",
|
||||
"Total number of connected peers",
|
||||
sync_status.num_peers as i64,
|
||||
);
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"net_active_peers",
|
||||
"Total number of active peers",
|
||||
sync_status.num_active_peers as i64,
|
||||
);
|
||||
prometheus_counter(
|
||||
r,
|
||||
"sync_blocks_recieved",
|
||||
"Number of blocks downloaded so far",
|
||||
sync_status.blocks_received as i64,
|
||||
);
|
||||
prometheus_counter(
|
||||
r,
|
||||
"sync_blocks_total",
|
||||
"Total number of blocks for the sync process",
|
||||
sync_status.blocks_total as i64,
|
||||
);
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"sync_blocks_highest",
|
||||
"Highest block number in the download queue",
|
||||
sync_status.highest_block_number.unwrap_or(0) as i64,
|
||||
);
|
||||
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"snapshot_download_active",
|
||||
"1 if downloading snapshots",
|
||||
scalar(sync_status.is_snapshot_syncing()),
|
||||
);
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"snapshot_download_chunks",
|
||||
"Snapshot chunks",
|
||||
sync_status.num_snapshot_chunks as i64,
|
||||
);
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"snapshot_download_chunks_done",
|
||||
"Snapshot chunks downloaded",
|
||||
sync_status.snapshot_chunks_done as i64,
|
||||
);
|
||||
|
||||
let restoration = self.eth_handler.snapshot_service.restoration_status();
|
||||
let creation = self.eth_handler.snapshot_service.creation_status();
|
||||
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"snapshot_create_block",
|
||||
"First block of the current snapshot creation",
|
||||
if let CreationStatus::Ongoing { block_number } = creation {
|
||||
block_number as i64
|
||||
} else {
|
||||
0
|
||||
},
|
||||
);
|
||||
prometheus_gauge(
|
||||
r,
|
||||
"snapshot_restore_block",
|
||||
"First block of the current snapshot restoration",
|
||||
if let RestorationStatus::Ongoing { block_number, .. } = restoration {
|
||||
block_number as i64
|
||||
} else {
|
||||
0
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const PEERS_TIMER: TimerToken = 0;
|
||||
const MAINTAIN_SYNC_TIMER: TimerToken = 1;
|
||||
const CONTINUE_SYNC_TIMER: TimerToken = 2;
|
||||
const TX_TIMER: TimerToken = 3;
|
||||
const PRIORITY_TIMER: TimerToken = 4;
|
||||
const DELAYED_PROCESSING_TIMER: TimerToken = 5;
|
||||
|
||||
pub(crate) const PRIORITY_TIMER_INTERVAL: Duration = Duration::from_millis(250);
|
||||
|
||||
struct SyncProtocolHandler {
|
||||
/// Shared blockchain client.
|
||||
chain: Arc<dyn BlockChainClient>,
|
||||
/// Shared snapshot service.
|
||||
snapshot_service: Arc<dyn SnapshotService>,
|
||||
/// Sync strategy
|
||||
sync: ChainSyncApi,
|
||||
/// Chain overlay used to cache data such as fork block.
|
||||
overlay: RwLock<HashMap<BlockNumber, Bytes>>,
|
||||
}
|
||||
|
||||
impl NetworkProtocolHandler for SyncProtocolHandler {
|
||||
fn initialize(&self, io: &dyn NetworkContext) {
|
||||
if io.subprotocol_name() != PAR_PROTOCOL {
|
||||
io.register_timer(PEERS_TIMER, Duration::from_millis(700))
|
||||
.expect("Error registering peers timer");
|
||||
io.register_timer(MAINTAIN_SYNC_TIMER, Duration::from_millis(1100))
|
||||
.expect("Error registering sync timer");
|
||||
io.register_timer(CONTINUE_SYNC_TIMER, Duration::from_millis(2500))
|
||||
.expect("Error registering sync timer");
|
||||
io.register_timer(TX_TIMER, Duration::from_millis(1300))
|
||||
.expect("Error registering transactions timer");
|
||||
io.register_timer(DELAYED_PROCESSING_TIMER, Duration::from_millis(2100))
|
||||
.expect("Error registering delayed processing timer");
|
||||
|
||||
io.register_timer(PRIORITY_TIMER, PRIORITY_TIMER_INTERVAL)
|
||||
.expect("Error registering peers timer");
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, io: &dyn NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
self.sync.dispatch_packet(
|
||||
&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay),
|
||||
*peer,
|
||||
packet_id,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
fn connected(&self, io: &dyn NetworkContext, peer: &PeerId) {
|
||||
trace_time!("sync::connected");
|
||||
// If warp protocol is supported only allow warp handshake
|
||||
let warp_protocol = io.protocol_version(PAR_PROTOCOL, *peer).unwrap_or(0) != 0;
|
||||
let warp_context = io.subprotocol_name() == PAR_PROTOCOL;
|
||||
if warp_protocol == warp_context {
|
||||
self.sync.write().on_peer_connected(
|
||||
&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay),
|
||||
*peer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnected(&self, io: &dyn NetworkContext, peer: &PeerId) {
|
||||
trace_time!("sync::disconnected");
|
||||
if io.subprotocol_name() != PAR_PROTOCOL {
|
||||
self.sync.write().on_peer_aborting(
|
||||
&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay),
|
||||
*peer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn timeout(&self, io: &dyn NetworkContext, timer: TimerToken) {
|
||||
trace_time!("sync::timeout");
|
||||
let mut io = NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay);
|
||||
match timer {
|
||||
PEERS_TIMER => self.sync.write().maintain_peers(&mut io),
|
||||
MAINTAIN_SYNC_TIMER => self.sync.write().maintain_sync(&mut io),
|
||||
CONTINUE_SYNC_TIMER => self.sync.write().continue_sync(&mut io),
|
||||
TX_TIMER => self.sync.write().propagate_new_transactions(&mut io),
|
||||
PRIORITY_TIMER => self.sync.process_priority_queue(&mut io),
|
||||
DELAYED_PROCESSING_TIMER => self.sync.process_delayed_requests(&mut io),
|
||||
_ => warn!("Unknown timer {} triggered.", timer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainNotify for EthSync {
|
||||
fn block_pre_import(&self, bytes: &Bytes, hash: &H256, difficulty: &U256) {
|
||||
let task = PriorityTask::PropagateBlock {
|
||||
started: ::std::time::Instant::now(),
|
||||
block: bytes.clone(),
|
||||
hash: *hash,
|
||||
difficulty: *difficulty,
|
||||
};
|
||||
if let Err(e) = self.priority_tasks.lock().send(task) {
|
||||
warn!(target: "sync", "Unexpected error during priority block propagation: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// t_nb 11.4
|
||||
fn new_blocks(&self, new_blocks: NewBlocks) {
|
||||
if new_blocks.has_more_blocks_to_import {
|
||||
return;
|
||||
}
|
||||
self.network.with_context(self.subprotocol_name, |context| {
|
||||
let mut sync_io = NetSyncIo::new(
|
||||
context,
|
||||
&*self.eth_handler.chain,
|
||||
&*self.eth_handler.snapshot_service,
|
||||
&self.eth_handler.overlay,
|
||||
);
|
||||
self.eth_handler.sync.write().chain_new_blocks(
|
||||
&mut sync_io,
|
||||
&new_blocks.imported,
|
||||
&new_blocks.invalid,
|
||||
new_blocks.route.enacted(),
|
||||
new_blocks.route.retracted(),
|
||||
&new_blocks.sealed,
|
||||
&new_blocks.proposed,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
match self.network.start() {
|
||||
Err((err, listen_address)) => match err.into() {
|
||||
ErrorKind::Io(ref e) if e.kind() == io::ErrorKind::AddrInUse => {
|
||||
warn!("Network port {:?} is already in use, make sure that another instance of an Ethereum client is not running or change the port using the --port option.", listen_address.expect("Listen address is not set."))
|
||||
}
|
||||
err => warn!("Error starting network: {}", err),
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.network
|
||||
.register_protocol(
|
||||
self.eth_handler.clone(),
|
||||
self.subprotocol_name,
|
||||
&[ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64],
|
||||
)
|
||||
.unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e));
|
||||
// register the warp sync subprotocol
|
||||
self.network
|
||||
.register_protocol(
|
||||
self.eth_handler.clone(),
|
||||
PAR_PROTOCOL,
|
||||
&[PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2],
|
||||
)
|
||||
.unwrap_or_else(|e| warn!("Error registering snapshot sync protocol: {:?}", e));
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
self.eth_handler.snapshot_service.abort_restore();
|
||||
self.network.stop();
|
||||
}
|
||||
|
||||
fn broadcast(&self, message_type: ChainMessageType) {
|
||||
self.network.with_context(PAR_PROTOCOL, |context| {
|
||||
let mut sync_io = NetSyncIo::new(
|
||||
context,
|
||||
&*self.eth_handler.chain,
|
||||
&*self.eth_handler.snapshot_service,
|
||||
&self.eth_handler.overlay,
|
||||
);
|
||||
match message_type {
|
||||
ChainMessageType::Consensus(message) => self
|
||||
.eth_handler
|
||||
.sync
|
||||
.write()
|
||||
.propagate_consensus_packet(&mut sync_io, message),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn transactions_received(&self, txs: &[UnverifiedTransaction], peer_id: PeerId) {
|
||||
let mut sync = self.eth_handler.sync.write();
|
||||
sync.transactions_received(txs, peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for managing network
|
||||
pub trait ManageNetwork: Send + Sync {
|
||||
/// Set to allow unreserved peers to connect
|
||||
fn accept_unreserved_peers(&self);
|
||||
/// Set to deny unreserved peers to connect
|
||||
fn deny_unreserved_peers(&self);
|
||||
/// Remove reservation for the peer
|
||||
fn remove_reserved_peer(&self, peer: String) -> Result<(), String>;
|
||||
/// Add reserved peer
|
||||
fn add_reserved_peer(&self, peer: String) -> Result<(), String>;
|
||||
/// Start network
|
||||
fn start_network(&self);
|
||||
/// Stop network
|
||||
fn stop_network(&self);
|
||||
/// Returns the minimum and maximum peers.
|
||||
fn num_peers_range(&self) -> RangeInclusive<u32>;
|
||||
/// Get network context for protocol.
|
||||
fn with_proto_context(&self, proto: ProtocolId, f: &mut dyn FnMut(&dyn NetworkContext));
|
||||
}
|
||||
|
||||
impl ManageNetwork for EthSync {
|
||||
fn accept_unreserved_peers(&self) {
|
||||
self.network
|
||||
.set_non_reserved_mode(NonReservedPeerMode::Accept);
|
||||
}
|
||||
|
||||
fn deny_unreserved_peers(&self) {
|
||||
self.network
|
||||
.set_non_reserved_mode(NonReservedPeerMode::Deny);
|
||||
}
|
||||
|
||||
fn remove_reserved_peer(&self, peer: String) -> Result<(), String> {
|
||||
self.network
|
||||
.remove_reserved_peer(&peer)
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn add_reserved_peer(&self, peer: String) -> Result<(), String> {
|
||||
self.network
|
||||
.add_reserved_peer(&peer)
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
fn start_network(&self) {
|
||||
self.start();
|
||||
}
|
||||
|
||||
fn stop_network(&self) {
|
||||
self.network.with_context(self.subprotocol_name, |context| {
|
||||
let mut sync_io = NetSyncIo::new(
|
||||
context,
|
||||
&*self.eth_handler.chain,
|
||||
&*self.eth_handler.snapshot_service,
|
||||
&self.eth_handler.overlay,
|
||||
);
|
||||
self.eth_handler.sync.write().abort(&mut sync_io);
|
||||
});
|
||||
|
||||
self.stop();
|
||||
}
|
||||
|
||||
fn num_peers_range(&self) -> RangeInclusive<u32> {
|
||||
self.network.num_peers_range()
|
||||
}
|
||||
|
||||
fn with_proto_context(&self, proto: ProtocolId, f: &mut dyn FnMut(&dyn NetworkContext)) {
|
||||
self.network.with_context_eval(proto, f);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Network service configuration
|
||||
pub struct NetworkConfiguration {
|
||||
/// Directory path to store general network configuration. None means nothing will be saved
|
||||
pub config_path: Option<String>,
|
||||
/// Directory path to store network-specific configuration. None means nothing will be saved
|
||||
pub net_config_path: Option<String>,
|
||||
/// IP address to listen for incoming connections. Listen to all connections by default
|
||||
pub listen_address: Option<String>,
|
||||
/// IP address to advertise. Detected automatically if none.
|
||||
pub public_address: Option<String>,
|
||||
/// Port for UDP connections, same as TCP by default
|
||||
pub udp_port: Option<u16>,
|
||||
/// Enable NAT configuration
|
||||
pub nat_enabled: bool,
|
||||
/// Enable discovery
|
||||
pub discovery_enabled: bool,
|
||||
/// List of initial node addresses
|
||||
pub boot_nodes: Vec<String>,
|
||||
/// Use provided node key instead of default
|
||||
pub use_secret: Option<Secret>,
|
||||
/// Max number of connected peers to maintain
|
||||
pub max_peers: u32,
|
||||
/// Min number of connected peers to maintain
|
||||
pub min_peers: u32,
|
||||
/// Max pending peers.
|
||||
pub max_pending_peers: u32,
|
||||
/// Reserved snapshot sync peers.
|
||||
pub snapshot_peers: u32,
|
||||
/// List of reserved node addresses.
|
||||
pub reserved_nodes: Vec<String>,
|
||||
/// The non-reserved peer mode.
|
||||
pub allow_non_reserved: bool,
|
||||
/// IP Filtering
|
||||
pub ip_filter: IpFilter,
|
||||
/// Client version string
|
||||
pub client_version: String,
|
||||
}
|
||||
|
||||
impl NetworkConfiguration {
|
||||
/// Create a new default config.
|
||||
pub fn new() -> Self {
|
||||
From::from(BasicNetworkConfiguration::new())
|
||||
}
|
||||
|
||||
/// Create a new local config.
|
||||
pub fn new_local() -> Self {
|
||||
From::from(BasicNetworkConfiguration::new_local())
|
||||
}
|
||||
|
||||
/// Attempt to convert this config into a BasicNetworkConfiguration.
|
||||
pub fn into_basic(self) -> Result<BasicNetworkConfiguration, AddrParseError> {
|
||||
Ok(BasicNetworkConfiguration {
|
||||
config_path: self.config_path,
|
||||
net_config_path: self.net_config_path,
|
||||
listen_address: match self.listen_address {
|
||||
None => None,
|
||||
Some(addr) => Some(SocketAddr::from_str(&addr)?),
|
||||
},
|
||||
public_address: match self.public_address {
|
||||
None => None,
|
||||
Some(addr) => Some(SocketAddr::from_str(&addr)?),
|
||||
},
|
||||
udp_port: self.udp_port,
|
||||
nat_enabled: self.nat_enabled,
|
||||
discovery_enabled: self.discovery_enabled,
|
||||
boot_nodes: self.boot_nodes,
|
||||
use_secret: self.use_secret,
|
||||
max_peers: self.max_peers,
|
||||
min_peers: self.min_peers,
|
||||
max_handshakes: self.max_pending_peers,
|
||||
reserved_protocols: hash_map![PAR_PROTOCOL => self.snapshot_peers],
|
||||
reserved_nodes: self.reserved_nodes,
|
||||
ip_filter: self.ip_filter,
|
||||
non_reserved_mode: if self.allow_non_reserved {
|
||||
NonReservedPeerMode::Accept
|
||||
} else {
|
||||
NonReservedPeerMode::Deny
|
||||
},
|
||||
client_version: self.client_version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BasicNetworkConfiguration> for NetworkConfiguration {
|
||||
fn from(other: BasicNetworkConfiguration) -> Self {
|
||||
NetworkConfiguration {
|
||||
config_path: other.config_path,
|
||||
net_config_path: other.net_config_path,
|
||||
listen_address: other
|
||||
.listen_address
|
||||
.and_then(|addr| Some(format!("{}", addr))),
|
||||
public_address: other
|
||||
.public_address
|
||||
.and_then(|addr| Some(format!("{}", addr))),
|
||||
udp_port: other.udp_port,
|
||||
nat_enabled: other.nat_enabled,
|
||||
discovery_enabled: other.discovery_enabled,
|
||||
boot_nodes: other.boot_nodes,
|
||||
use_secret: other.use_secret,
|
||||
max_peers: other.max_peers,
|
||||
min_peers: other.min_peers,
|
||||
max_pending_peers: other.max_handshakes,
|
||||
snapshot_peers: *other.reserved_protocols.get(&PAR_PROTOCOL).unwrap_or(&0),
|
||||
reserved_nodes: other.reserved_nodes,
|
||||
ip_filter: other.ip_filter,
|
||||
allow_non_reserved: match other.non_reserved_mode {
|
||||
NonReservedPeerMode::Accept => true,
|
||||
_ => false,
|
||||
},
|
||||
client_version: other.client_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for IPC service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServiceConfiguration {
|
||||
/// Sync config.
|
||||
pub sync: SyncConfig,
|
||||
/// Network configuration.
|
||||
pub net: NetworkConfiguration,
|
||||
/// IPC path.
|
||||
pub io_path: String,
|
||||
}
|
||||
|
||||
/// Numbers of peers (max, min, active).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PeerNumbers {
|
||||
/// Number of connected peers.
|
||||
pub connected: usize,
|
||||
/// Number of active peers.
|
||||
pub active: usize,
|
||||
/// Max peers.
|
||||
pub max: usize,
|
||||
/// Min peers.
|
||||
pub min: usize,
|
||||
}
|
||||
1177
crates/ethcore/sync/src/block_sync.rs
Normal file
1177
crates/ethcore/sync/src/block_sync.rs
Normal file
File diff suppressed because it is too large
Load Diff
825
crates/ethcore/sync/src/blocks.rs
Normal file
825
crates/ethcore/sync/src/blocks.rs
Normal file
@@ -0,0 +1,825 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::Bytes;
|
||||
use ethcore::verification::queue::kind::blocks::Unverified;
|
||||
use ethereum_types::H256;
|
||||
use hash::{keccak, KECCAK_EMPTY_LIST_RLP, KECCAK_NULL_RLP};
|
||||
use heapsize::HeapSizeOf;
|
||||
use network;
|
||||
use rlp::{DecoderError, Rlp, RlpStream};
|
||||
use std::collections::{hash_map, BTreeMap, HashMap, HashSet};
|
||||
use triehash_ethereum::ordered_trie_root;
|
||||
use types::{
|
||||
header::Header as BlockHeader,
|
||||
transaction::{TypedTransaction, UnverifiedTransaction},
|
||||
};
|
||||
|
||||
known_heap_size!(0, HeaderId);
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct SyncHeader {
|
||||
pub bytes: Bytes,
|
||||
pub header: BlockHeader,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for SyncHeader {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.bytes.heap_size_of_children() + self.header.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncHeader {
|
||||
pub fn from_rlp(bytes: Bytes) -> Result<Self, DecoderError> {
|
||||
let result = SyncHeader {
|
||||
header: ::rlp::decode(&bytes)?,
|
||||
bytes,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SyncBody {
|
||||
pub transactions_bytes: Bytes,
|
||||
pub transactions: Vec<UnverifiedTransaction>,
|
||||
pub uncles_bytes: Bytes,
|
||||
pub uncles: Vec<BlockHeader>,
|
||||
}
|
||||
|
||||
impl SyncBody {
|
||||
pub fn from_rlp(bytes: &[u8]) -> Result<Self, DecoderError> {
|
||||
let rlp = Rlp::new(bytes);
|
||||
let transactions_rlp = rlp.at(0)?;
|
||||
let uncles_rlp = rlp.at(1)?;
|
||||
|
||||
let result = SyncBody {
|
||||
transactions_bytes: transactions_rlp.as_raw().to_vec(),
|
||||
transactions: TypedTransaction::decode_rlp_list(&transactions_rlp)?,
|
||||
uncles_bytes: uncles_rlp.as_raw().to_vec(),
|
||||
uncles: uncles_rlp.as_list()?,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn empty_body() -> Self {
|
||||
SyncBody {
|
||||
transactions_bytes: ::rlp::EMPTY_LIST_RLP.to_vec(),
|
||||
transactions: Vec::with_capacity(0),
|
||||
uncles_bytes: ::rlp::EMPTY_LIST_RLP.to_vec(),
|
||||
uncles: Vec::with_capacity(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for SyncBody {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.transactions_bytes.heap_size_of_children()
|
||||
+ self.transactions.heap_size_of_children()
|
||||
+ self.uncles_bytes.heap_size_of_children()
|
||||
+ self.uncles.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
/// Block data with optional body.
|
||||
struct SyncBlock {
|
||||
header: SyncHeader,
|
||||
body: Option<SyncBody>,
|
||||
receipts: Option<Bytes>,
|
||||
receipts_root: H256,
|
||||
}
|
||||
|
||||
impl HeapSizeOf for SyncBlock {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.header.heap_size_of_children() + self.body.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
fn unverified_from_sync(header: SyncHeader, body: Option<SyncBody>) -> Unverified {
|
||||
let mut stream = RlpStream::new_list(3);
|
||||
stream.append_raw(&header.bytes, 1);
|
||||
let body = body.unwrap_or_else(SyncBody::empty_body);
|
||||
stream.append_raw(&body.transactions_bytes, 1);
|
||||
stream.append_raw(&body.uncles_bytes, 1);
|
||||
|
||||
Unverified {
|
||||
header: header.header,
|
||||
transactions: body.transactions,
|
||||
uncles: body.uncles,
|
||||
bytes: stream.out().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Block with optional receipt
|
||||
pub struct BlockAndReceipts {
|
||||
/// Block data.
|
||||
pub block: Unverified,
|
||||
/// Block receipts RLP list.
|
||||
pub receipts: Option<Bytes>,
|
||||
}
|
||||
|
||||
/// Used to identify header by transactions and uncles hashes
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
struct HeaderId {
|
||||
transactions_root: H256,
|
||||
uncles: H256,
|
||||
}
|
||||
|
||||
/// A collection of blocks and subchain pointers being downloaded. This keeps track of
|
||||
/// which headers/bodies need to be downloaded, which are being downloaded and also holds
|
||||
/// the downloaded blocks.
|
||||
#[derive(Default)]
|
||||
pub struct BlockCollection {
|
||||
/// Does this collection need block receipts.
|
||||
need_receipts: bool,
|
||||
/// Heads of subchains to download
|
||||
heads: Vec<H256>,
|
||||
/// Downloaded blocks.
|
||||
blocks: HashMap<H256, SyncBlock>,
|
||||
/// Downloaded blocks by parent.
|
||||
parents: HashMap<H256, H256>,
|
||||
/// Used to map body to header.
|
||||
header_ids: HashMap<HeaderId, H256>,
|
||||
/// Used to map receipts root to headers.
|
||||
receipt_ids: HashMap<H256, Vec<H256>>,
|
||||
/// First block in `blocks`.
|
||||
head: Option<H256>,
|
||||
/// Set of block header hashes being downloaded
|
||||
downloading_headers: HashSet<H256>,
|
||||
/// Set of block bodies being downloaded identified by block hash.
|
||||
downloading_bodies: HashSet<H256>,
|
||||
/// Set of block receipts being downloaded identified by receipt root.
|
||||
downloading_receipts: HashSet<H256>,
|
||||
}
|
||||
|
||||
impl BlockCollection {
|
||||
/// Create a new instance.
|
||||
pub fn new(download_receipts: bool) -> BlockCollection {
|
||||
BlockCollection {
|
||||
need_receipts: download_receipts,
|
||||
blocks: HashMap::new(),
|
||||
header_ids: HashMap::new(),
|
||||
receipt_ids: HashMap::new(),
|
||||
heads: Vec::new(),
|
||||
parents: HashMap::new(),
|
||||
head: None,
|
||||
downloading_headers: HashSet::new(),
|
||||
downloading_bodies: HashSet::new(),
|
||||
downloading_receipts: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear everything.
|
||||
pub fn clear(&mut self) {
|
||||
self.blocks.clear();
|
||||
self.parents.clear();
|
||||
self.header_ids.clear();
|
||||
self.receipt_ids.clear();
|
||||
self.heads.clear();
|
||||
self.head = None;
|
||||
self.downloading_headers.clear();
|
||||
self.downloading_bodies.clear();
|
||||
self.downloading_receipts.clear();
|
||||
}
|
||||
|
||||
/// Reset collection for a new sync round with given subchain block hashes.
|
||||
pub fn reset_to(&mut self, hashes: Vec<H256>) {
|
||||
self.clear();
|
||||
self.heads = hashes;
|
||||
}
|
||||
|
||||
/// Insert a set of headers into collection and advance subchain head pointers.
|
||||
pub fn insert_headers(&mut self, headers: Vec<SyncHeader>) {
|
||||
for h in headers {
|
||||
if let Err(e) = self.insert_header(h) {
|
||||
trace!(target: "sync", "Ignored invalid header: {:?}", e);
|
||||
}
|
||||
}
|
||||
self.update_heads();
|
||||
}
|
||||
|
||||
/// Insert a collection of block bodies for previously downloaded headers.
|
||||
pub fn insert_bodies(&mut self, bodies: Vec<SyncBody>) -> Vec<H256> {
|
||||
bodies
|
||||
.into_iter()
|
||||
.filter_map(|b| {
|
||||
self.insert_body(b)
|
||||
.map_err(|e| trace!(target: "sync", "Ignored invalid body: {:?}", e))
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Insert a collection of block receipts for previously downloaded headers.
|
||||
pub fn insert_receipts(&mut self, receipts: Vec<Bytes>) -> Vec<Vec<H256>> {
|
||||
if !self.need_receipts {
|
||||
return Vec::new();
|
||||
}
|
||||
receipts
|
||||
.into_iter()
|
||||
.filter_map(|r| {
|
||||
self.insert_receipt(r)
|
||||
.map_err(|e| trace!(target: "sync", "Ignored invalid receipt: {:?}", e))
|
||||
.ok()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a set of block hashes that require a body download. The returned set is marked as being downloaded.
|
||||
pub fn needed_bodies(&mut self, count: usize, _ignore_downloading: bool) -> Vec<H256> {
|
||||
if self.head.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut needed_bodies: Vec<H256> = Vec::new();
|
||||
let mut head = self.head;
|
||||
while head.is_some() && needed_bodies.len() < count {
|
||||
head = self.parents.get(&head.unwrap()).cloned();
|
||||
if let Some(head) = head {
|
||||
match self.blocks.get(&head) {
|
||||
Some(block)
|
||||
if block.body.is_none() && !self.downloading_bodies.contains(&head) =>
|
||||
{
|
||||
self.downloading_bodies.insert(head.clone());
|
||||
needed_bodies.push(head.clone());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
for h in self.header_ids.values() {
|
||||
if needed_bodies.len() >= count {
|
||||
break;
|
||||
}
|
||||
if !self.downloading_bodies.contains(h) {
|
||||
needed_bodies.push(h.clone());
|
||||
self.downloading_bodies.insert(h.clone());
|
||||
}
|
||||
}
|
||||
needed_bodies
|
||||
}
|
||||
|
||||
/// Returns a set of block hashes that require a receipt download. The returned set is marked as being downloaded.
|
||||
pub fn needed_receipts(&mut self, count: usize, _ignore_downloading: bool) -> Vec<H256> {
|
||||
if self.head.is_none() || !self.need_receipts {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut needed_receipts: Vec<H256> = Vec::new();
|
||||
let mut head = self.head;
|
||||
while head.is_some() && needed_receipts.len() < count {
|
||||
head = self.parents.get(&head.unwrap()).cloned();
|
||||
if let Some(head) = head {
|
||||
match self.blocks.get(&head) {
|
||||
Some(block) => {
|
||||
if block.receipts.is_none()
|
||||
&& !self.downloading_receipts.contains(&block.receipts_root)
|
||||
{
|
||||
self.downloading_receipts.insert(block.receipts_root);
|
||||
needed_receipts.push(head.clone());
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there are multiple blocks per receipt, only request one of them.
|
||||
for (root, h) in self
|
||||
.receipt_ids
|
||||
.iter()
|
||||
.map(|(root, hashes)| (root, hashes[0]))
|
||||
{
|
||||
if needed_receipts.len() >= count {
|
||||
break;
|
||||
}
|
||||
if !self.downloading_receipts.contains(root) {
|
||||
needed_receipts.push(h.clone());
|
||||
self.downloading_receipts.insert(*root);
|
||||
}
|
||||
}
|
||||
needed_receipts
|
||||
}
|
||||
|
||||
/// Returns a set of block hashes that require a header download. The returned set is marked as being downloaded.
|
||||
pub fn needed_headers(
|
||||
&mut self,
|
||||
count: usize,
|
||||
ignore_downloading: bool,
|
||||
) -> Option<(H256, usize)> {
|
||||
// find subchain to download
|
||||
let mut download = None;
|
||||
{
|
||||
for h in &self.heads {
|
||||
if ignore_downloading || !self.downloading_headers.contains(h) {
|
||||
self.downloading_headers.insert(h.clone());
|
||||
download = Some(h.clone());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
download.map(|h| (h, count))
|
||||
}
|
||||
|
||||
/// Unmark header as being downloaded.
|
||||
pub fn clear_header_download(&mut self, hash: &H256) {
|
||||
self.downloading_headers.remove(hash);
|
||||
}
|
||||
|
||||
/// Unmark block body as being downloaded.
|
||||
pub fn clear_body_download(&mut self, hashes: &[H256]) {
|
||||
for h in hashes {
|
||||
self.downloading_bodies.remove(h);
|
||||
}
|
||||
}
|
||||
|
||||
/// Unmark block receipt as being downloaded.
|
||||
pub fn clear_receipt_download(&mut self, hashes: &[H256]) {
|
||||
for h in hashes {
|
||||
if let Some(ref block) = self.blocks.get(h) {
|
||||
self.downloading_receipts.remove(&block.receipts_root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a valid chain of blocks ordered in ascending order and ready for importing into blockchain.
|
||||
pub fn drain(&mut self) -> Vec<BlockAndReceipts> {
|
||||
if self.blocks.is_empty() || self.head.is_none() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut drained = Vec::new();
|
||||
let mut hashes = Vec::new();
|
||||
{
|
||||
let mut blocks = Vec::new();
|
||||
let mut head = self.head;
|
||||
while let Some(h) = head {
|
||||
head = self.parents.get(&h).cloned();
|
||||
if let Some(head) = head {
|
||||
match self.blocks.remove(&head) {
|
||||
Some(block) => {
|
||||
if block.body.is_some()
|
||||
&& (!self.need_receipts || block.receipts.is_some())
|
||||
{
|
||||
blocks.push(block);
|
||||
hashes.push(head);
|
||||
self.head = Some(head);
|
||||
} else {
|
||||
self.blocks.insert(head, block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for block in blocks.into_iter() {
|
||||
let unverified = unverified_from_sync(block.header, block.body);
|
||||
drained.push(BlockAndReceipts {
|
||||
block: unverified,
|
||||
receipts: block.receipts.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
trace!(target: "sync", "Drained {} blocks, new head :{:?}", drained.len(), self.head);
|
||||
drained
|
||||
}
|
||||
|
||||
/// Check if the collection is empty. We consider the syncing round complete once
|
||||
/// there is no block data left and only a single or none head pointer remains.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.heads.len() == 0
|
||||
|| (self.heads.len() == 1 && self.head.map_or(false, |h| h == self.heads[0]))
|
||||
}
|
||||
|
||||
/// Check if collection contains a block header.
|
||||
pub fn contains(&self, hash: &H256) -> bool {
|
||||
self.blocks.contains_key(hash)
|
||||
}
|
||||
|
||||
/// Check the number of heads
|
||||
pub fn heads_len(&self) -> usize {
|
||||
self.heads.len()
|
||||
}
|
||||
|
||||
/// Return number of items size.
|
||||
pub fn get_sizes(&self, sizes: &mut BTreeMap<String, usize>, insert_prefix: &str) {
|
||||
sizes.insert(format!("{}{}", insert_prefix, "heads"), self.heads.len());
|
||||
sizes.insert(format!("{}{}", insert_prefix, "blocks"), self.blocks.len());
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "parents_len"),
|
||||
self.parents.len(),
|
||||
);
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "header_ids_len"),
|
||||
self.header_ids.len(),
|
||||
);
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "downloading_headers_len"),
|
||||
self.downloading_headers.len(),
|
||||
);
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "downloading_bodies_len"),
|
||||
self.downloading_bodies.len(),
|
||||
);
|
||||
|
||||
if self.need_receipts {
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "downloading_receipts_len"),
|
||||
self.downloading_receipts.len(),
|
||||
);
|
||||
sizes.insert(
|
||||
format!("{}{}", insert_prefix, "receipt_ids_len"),
|
||||
self.receipt_ids.len(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if given block hash is marked as being downloaded.
|
||||
pub fn is_downloading(&self, hash: &H256) -> bool {
|
||||
self.downloading_headers.contains(hash) || self.downloading_bodies.contains(hash)
|
||||
}
|
||||
|
||||
fn insert_body(&mut self, body: SyncBody) -> Result<H256, network::Error> {
|
||||
let header_id = {
|
||||
let tx_root = ordered_trie_root(Rlp::new(&body.transactions_bytes).iter().map(|r| {
|
||||
if r.is_list() {
|
||||
r.as_raw()
|
||||
} else {
|
||||
// this list is already decoded and passed validation, for this we are okay to expect proper data
|
||||
r.data().expect("Expect raw transaction list to be valid")
|
||||
}
|
||||
}));
|
||||
let uncles = keccak(&body.uncles_bytes);
|
||||
HeaderId {
|
||||
transactions_root: tx_root,
|
||||
uncles: uncles,
|
||||
}
|
||||
};
|
||||
|
||||
match self.header_ids.remove(&header_id) {
|
||||
Some(h) => {
|
||||
self.downloading_bodies.remove(&h);
|
||||
match self.blocks.get_mut(&h) {
|
||||
Some(ref mut block) => {
|
||||
trace!(target: "sync", "Got body {}", h);
|
||||
block.body = Some(body);
|
||||
Ok(h)
|
||||
}
|
||||
None => {
|
||||
warn!("Got body with no header {}", h);
|
||||
Err(network::ErrorKind::BadProtocol.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
trace!(target: "sync", "Ignored unknown/stale block body. tx_root = {:?}, uncles = {:?}", header_id.transactions_root, header_id.uncles);
|
||||
Err(network::ErrorKind::BadProtocol.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_receipt(&mut self, r: Bytes) -> Result<Vec<H256>, network::Error> {
|
||||
let receipt_root = {
|
||||
let receipts = Rlp::new(&r);
|
||||
//check receipts data before calculating trie root
|
||||
let mut temp_receipts: Vec<&[u8]> = Vec::new();
|
||||
for receipt_byte in receipts.iter() {
|
||||
if receipt_byte.is_list() {
|
||||
temp_receipts.push(receipt_byte.as_raw())
|
||||
} else {
|
||||
temp_receipts.push(
|
||||
receipt_byte
|
||||
.data()
|
||||
.map_err(|e| network::ErrorKind::Rlp(e))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate trie root and use it as hash
|
||||
ordered_trie_root(temp_receipts.iter())
|
||||
};
|
||||
self.downloading_receipts.remove(&receipt_root);
|
||||
match self.receipt_ids.entry(receipt_root) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
let block_hashes = entry.remove();
|
||||
for h in block_hashes.iter() {
|
||||
match self.blocks.get_mut(&h) {
|
||||
Some(ref mut block) => {
|
||||
trace!(target: "sync", "Got receipt {}", h);
|
||||
block.receipts = Some(r.clone());
|
||||
}
|
||||
None => {
|
||||
warn!("Got receipt with no header {}", h);
|
||||
return Err(network::ErrorKind::BadProtocol.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(block_hashes)
|
||||
}
|
||||
hash_map::Entry::Vacant(_) => {
|
||||
trace!(target: "sync", "Ignored unknown/stale block receipt {:?}", receipt_root);
|
||||
Err(network::ErrorKind::BadProtocol.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_header(&mut self, info: SyncHeader) -> Result<H256, DecoderError> {
|
||||
let hash = info.header.hash();
|
||||
if self.blocks.contains_key(&hash) {
|
||||
return Ok(hash);
|
||||
}
|
||||
|
||||
match self.head {
|
||||
None if hash == self.heads[0] => {
|
||||
trace!(target: "sync", "New head {}", hash);
|
||||
self.head = Some(info.header.parent_hash().clone());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let header_id = HeaderId {
|
||||
transactions_root: *info.header.transactions_root(),
|
||||
uncles: *info.header.uncles_hash(),
|
||||
};
|
||||
|
||||
let body = if header_id.transactions_root == KECCAK_NULL_RLP
|
||||
&& header_id.uncles == KECCAK_EMPTY_LIST_RLP
|
||||
{
|
||||
// empty body, just mark as downloaded
|
||||
Some(SyncBody::empty_body())
|
||||
} else {
|
||||
trace!(
|
||||
"Queueing body tx_root = {:?}, uncles = {:?}, block = {:?}, number = {}",
|
||||
header_id.transactions_root,
|
||||
header_id.uncles,
|
||||
hash,
|
||||
info.header.number()
|
||||
);
|
||||
self.header_ids.insert(header_id, hash);
|
||||
None
|
||||
};
|
||||
|
||||
let (receipts, receipts_root) = if self.need_receipts {
|
||||
let receipt_root = *info.header.receipts_root();
|
||||
if receipt_root == KECCAK_NULL_RLP {
|
||||
let receipts_stream = RlpStream::new_list(0);
|
||||
(Some(receipts_stream.out()), receipt_root)
|
||||
} else {
|
||||
self.receipt_ids
|
||||
.entry(receipt_root)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(hash);
|
||||
(None, receipt_root)
|
||||
}
|
||||
} else {
|
||||
(None, H256::new())
|
||||
};
|
||||
|
||||
self.parents.insert(*info.header.parent_hash(), hash);
|
||||
|
||||
let block = SyncBlock {
|
||||
header: info,
|
||||
body,
|
||||
receipts,
|
||||
receipts_root,
|
||||
};
|
||||
|
||||
self.blocks.insert(hash, block);
|
||||
trace!(target: "sync", "New header: {:x}", hash);
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
// update subchain headers
|
||||
fn update_heads(&mut self) {
|
||||
let mut new_heads = Vec::new();
|
||||
let old_subchains: HashSet<_> = { self.heads.iter().cloned().collect() };
|
||||
for s in self.heads.drain(..) {
|
||||
let mut h = s.clone();
|
||||
if !self.blocks.contains_key(&h) {
|
||||
new_heads.push(h);
|
||||
continue;
|
||||
}
|
||||
loop {
|
||||
match self.parents.get(&h) {
|
||||
Some(next) => {
|
||||
h = next.clone();
|
||||
if old_subchains.contains(&h) {
|
||||
trace!(target: "sync", "Completed subchain {:?}", s);
|
||||
break; // reached head of the other subchain, merge by not adding
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
new_heads.push(h);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.heads = new_heads;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{BlockCollection, SyncHeader};
|
||||
use ethcore::{
|
||||
client::{BlockChainClient, BlockId, EachBlockWith, TestBlockChainClient},
|
||||
verification::queue::kind::blocks::Unverified,
|
||||
};
|
||||
use rlp::*;
|
||||
use types::BlockNumber;
|
||||
|
||||
fn is_empty(bc: &BlockCollection) -> bool {
|
||||
bc.heads.is_empty()
|
||||
&& bc.blocks.is_empty()
|
||||
&& bc.parents.is_empty()
|
||||
&& bc.header_ids.is_empty()
|
||||
&& bc.head.is_none()
|
||||
&& bc.downloading_headers.is_empty()
|
||||
&& bc.downloading_bodies.is_empty()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_clear() {
|
||||
let mut bc = BlockCollection::new(false);
|
||||
assert!(is_empty(&bc));
|
||||
let client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Nothing);
|
||||
let hashes = (0..100)
|
||||
.map(|i| {
|
||||
(&client as &dyn BlockChainClient)
|
||||
.block_hash(BlockId::Number(i))
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
bc.reset_to(hashes);
|
||||
assert!(!is_empty(&bc));
|
||||
bc.clear();
|
||||
assert!(is_empty(&bc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_headers() {
|
||||
let mut bc = BlockCollection::new(false);
|
||||
assert!(is_empty(&bc));
|
||||
let client = TestBlockChainClient::new();
|
||||
let nblocks = 200;
|
||||
client.add_blocks(nblocks, EachBlockWith::Nothing);
|
||||
let blocks: Vec<_> = (0..nblocks)
|
||||
.map(|i| {
|
||||
(&client as &dyn BlockChainClient)
|
||||
.block(BlockId::Number(i as BlockNumber))
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
})
|
||||
.collect();
|
||||
let headers: Vec<_> = blocks
|
||||
.iter()
|
||||
.map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap())
|
||||
.collect();
|
||||
let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect();
|
||||
let heads: Vec<_> = hashes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, h)| if i % 20 == 0 { Some(*h) } else { None })
|
||||
.collect();
|
||||
bc.reset_to(heads);
|
||||
assert!(!bc.is_empty());
|
||||
assert_eq!(hashes[0], bc.heads[0]);
|
||||
assert!(bc.needed_bodies(1, false).is_empty());
|
||||
assert!(!bc.contains(&hashes[0]));
|
||||
assert!(!bc.is_downloading(&hashes[0]));
|
||||
|
||||
let (h, n) = bc.needed_headers(6, false).unwrap();
|
||||
assert!(bc.is_downloading(&hashes[0]));
|
||||
assert_eq!(hashes[0], h);
|
||||
assert_eq!(n, 6);
|
||||
assert_eq!(bc.downloading_headers.len(), 1);
|
||||
assert!(bc.drain().is_empty());
|
||||
|
||||
bc.insert_headers(headers[0..6].into_iter().map(Clone::clone).collect());
|
||||
assert_eq!(hashes[5], bc.heads[0]);
|
||||
for h in &hashes[0..6] {
|
||||
bc.clear_header_download(h)
|
||||
}
|
||||
assert_eq!(bc.downloading_headers.len(), 0);
|
||||
assert!(!bc.is_downloading(&hashes[0]));
|
||||
assert!(bc.contains(&hashes[0]));
|
||||
|
||||
assert_eq!(
|
||||
bc.drain().into_iter().map(|b| b.block).collect::<Vec<_>>(),
|
||||
blocks[0..6]
|
||||
.iter()
|
||||
.map(|b| Unverified::from_rlp(b.to_vec()).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert!(!bc.contains(&hashes[0]));
|
||||
assert_eq!(hashes[5], bc.head.unwrap());
|
||||
|
||||
let (h, _) = bc.needed_headers(6, false).unwrap();
|
||||
assert_eq!(hashes[5], h);
|
||||
let (h, _) = bc.needed_headers(6, false).unwrap();
|
||||
assert_eq!(hashes[20], h);
|
||||
bc.insert_headers(headers[10..16].into_iter().map(Clone::clone).collect());
|
||||
assert!(bc.drain().is_empty());
|
||||
bc.insert_headers(headers[5..10].into_iter().map(Clone::clone).collect());
|
||||
assert_eq!(
|
||||
bc.drain().into_iter().map(|b| b.block).collect::<Vec<_>>(),
|
||||
blocks[6..16]
|
||||
.iter()
|
||||
.map(|b| Unverified::from_rlp(b.to_vec()).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
assert_eq!(hashes[15], bc.heads[0]);
|
||||
|
||||
bc.insert_headers(headers[15..].into_iter().map(Clone::clone).collect());
|
||||
bc.drain();
|
||||
assert!(bc.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_headers_with_gap() {
|
||||
let mut bc = BlockCollection::new(false);
|
||||
assert!(is_empty(&bc));
|
||||
let client = TestBlockChainClient::new();
|
||||
let nblocks = 200;
|
||||
client.add_blocks(nblocks, EachBlockWith::Nothing);
|
||||
let blocks: Vec<_> = (0..nblocks)
|
||||
.map(|i| {
|
||||
(&client as &dyn BlockChainClient)
|
||||
.block(BlockId::Number(i as BlockNumber))
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
})
|
||||
.collect();
|
||||
let headers: Vec<_> = blocks
|
||||
.iter()
|
||||
.map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap())
|
||||
.collect();
|
||||
let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect();
|
||||
let heads: Vec<_> = hashes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, h)| if i % 20 == 0 { Some(*h) } else { None })
|
||||
.collect();
|
||||
bc.reset_to(heads);
|
||||
|
||||
bc.insert_headers(headers[2..22].into_iter().map(Clone::clone).collect());
|
||||
assert_eq!(hashes[0], bc.heads[0]);
|
||||
assert_eq!(hashes[21], bc.heads[1]);
|
||||
assert!(bc.head.is_none());
|
||||
bc.insert_headers(headers[0..2].into_iter().map(Clone::clone).collect());
|
||||
assert!(bc.head.is_some());
|
||||
assert_eq!(hashes[21], bc.heads[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_headers_no_gap() {
|
||||
let mut bc = BlockCollection::new(false);
|
||||
assert!(is_empty(&bc));
|
||||
let client = TestBlockChainClient::new();
|
||||
let nblocks = 200;
|
||||
client.add_blocks(nblocks, EachBlockWith::Nothing);
|
||||
let blocks: Vec<_> = (0..nblocks)
|
||||
.map(|i| {
|
||||
(&client as &dyn BlockChainClient)
|
||||
.block(BlockId::Number(i as BlockNumber))
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
})
|
||||
.collect();
|
||||
let headers: Vec<_> = blocks
|
||||
.iter()
|
||||
.map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap())
|
||||
.collect();
|
||||
let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect();
|
||||
let heads: Vec<_> = hashes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, h)| if i % 20 == 0 { Some(*h) } else { None })
|
||||
.collect();
|
||||
bc.reset_to(heads);
|
||||
|
||||
bc.insert_headers(headers[1..2].into_iter().map(Clone::clone).collect());
|
||||
assert!(bc.drain().is_empty());
|
||||
bc.insert_headers(headers[0..1].into_iter().map(Clone::clone).collect());
|
||||
assert_eq!(bc.drain().len(), 2);
|
||||
}
|
||||
}
|
||||
114
crates/ethcore/sync/src/chain/fork_filter.rs
Normal file
114
crates/ethcore/sync/src/chain/fork_filter.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! This module contains a wrapper that connects this codebase with `ethereum-forkid` crate which provides `FORK_ID`
|
||||
//! to support Ethereum network protocol, version 64 and above.
|
||||
|
||||
// Re-export ethereum-forkid crate contents here.
|
||||
pub use ethereum_forkid::{BlockNumber, ForkId, RejectReason};
|
||||
|
||||
use ethcore::client::ChainInfo;
|
||||
use ethereum_forkid::ForkFilter;
|
||||
|
||||
/// Wrapper around fork filter that provides integration with `ForkFilter`.
|
||||
pub struct ForkFilterApi {
|
||||
inner: ForkFilter,
|
||||
}
|
||||
|
||||
impl ForkFilterApi {
|
||||
/// Create `ForkFilterApi` from `ChainInfo` and an `Iterator` over the hard forks.
|
||||
pub fn new<C: ?Sized + ChainInfo, I: IntoIterator<Item = BlockNumber>>(
|
||||
client: &C,
|
||||
forks: I,
|
||||
) -> Self {
|
||||
let chain_info = client.chain_info();
|
||||
let genesis_hash = primitive_types07::H256::from_slice(&chain_info.genesis_hash.0);
|
||||
Self {
|
||||
inner: ForkFilter::new(chain_info.best_block_number, genesis_hash, forks),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Dummy version of ForkFilterApi with no forks.
|
||||
pub fn new_dummy<C: ?Sized + ChainInfo>(client: &C) -> Self {
|
||||
let chain_info = client.chain_info();
|
||||
Self {
|
||||
inner: ForkFilter::new(
|
||||
chain_info.best_block_number,
|
||||
primitive_types07::H256::from_slice(&chain_info.genesis_hash.0),
|
||||
vec![],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_head<C: ?Sized + ChainInfo>(&mut self, client: &C) {
|
||||
self.inner.set_head(client.chain_info().best_block_number);
|
||||
}
|
||||
|
||||
/// Wrapper for `ForkFilter::current`
|
||||
pub fn current<C: ?Sized + ChainInfo>(&mut self, client: &C) -> ForkId {
|
||||
self.update_head(client);
|
||||
self.inner.current()
|
||||
}
|
||||
|
||||
/// Wrapper for `ForkFilter::is_compatible`
|
||||
pub fn is_compatible<C: ?Sized + ChainInfo>(
|
||||
&mut self,
|
||||
client: &C,
|
||||
fork_id: ForkId,
|
||||
) -> Result<(), RejectReason> {
|
||||
self.update_head(client);
|
||||
self.inner.is_compatible(fork_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ethcore::{client::TestBlockChainClient, ethereum, spec::Spec};
|
||||
|
||||
fn test_spec<F: Fn() -> Spec>(spec_builder: F, forks: Vec<BlockNumber>) {
|
||||
let spec = (spec_builder)();
|
||||
let genesis_hash = spec.genesis_header().hash();
|
||||
let spec_forks = spec.hard_forks.clone();
|
||||
let client = TestBlockChainClient::new_with_spec(spec);
|
||||
|
||||
assert_eq!(
|
||||
ForkFilterApi::new(&client, spec_forks).inner,
|
||||
ForkFilter::new(
|
||||
0,
|
||||
primitive_types07::H256::from_slice(&genesis_hash.0),
|
||||
forks
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ethereum_spec() {
|
||||
test_spec(
|
||||
|| ethereum::new_foundation(&String::new()),
|
||||
vec![
|
||||
1_150_000, 1_920_000, 2_463_000, 2_675_000, 4_370_000, 7_280_000, 9_069_000,
|
||||
9_200_000,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ropsten_spec() {
|
||||
test_spec(
|
||||
|| ethereum::new_ropsten(&String::new()),
|
||||
vec![10, 1_700_000, 4_230_000, 4_939_394, 6_485_846, 7_117_117],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rinkeby_spec() {
|
||||
test_spec(
|
||||
|| ethereum::new_rinkeby(&String::new()),
|
||||
vec![1, 2, 3, 1_035_301, 3_660_663, 4_321_234, 5_435_345],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goerli_spec() {
|
||||
test_spec(|| ethereum::new_goerli(&String::new()), vec![1_561_651])
|
||||
}
|
||||
}
|
||||
953
crates/ethcore/sync/src/chain/handler.rs
Normal file
953
crates/ethcore/sync/src/chain/handler.rs
Normal file
@@ -0,0 +1,953 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use api::{ETH_PROTOCOL, PAR_PROTOCOL};
|
||||
use block_sync::{BlockDownloaderImportError as DownloaderImportError, DownloadAction};
|
||||
use bytes::Bytes;
|
||||
use enum_primitive::FromPrimitive;
|
||||
use ethcore::{
|
||||
error::{BlockError, Error as EthcoreError, ErrorKind as EthcoreErrorKind, ImportErrorKind},
|
||||
snapshot::{ManifestData, RestorationStatus},
|
||||
verification::queue::kind::blocks::Unverified,
|
||||
};
|
||||
use ethereum_types::{H256, U256};
|
||||
use hash::keccak;
|
||||
use network::{client_version::ClientVersion, PeerId};
|
||||
use rlp::Rlp;
|
||||
use snapshot::ChunkType;
|
||||
use std::{cmp, mem, time::Instant};
|
||||
use sync_io::SyncIo;
|
||||
use types::{block_status::BlockStatus, ids::BlockId, BlockNumber};
|
||||
|
||||
use super::sync_packet::{
|
||||
PacketInfo, SyncPacket,
|
||||
SyncPacket::{
|
||||
BlockBodiesPacket, BlockHeadersPacket, NewBlockHashesPacket, NewBlockPacket,
|
||||
ReceiptsPacket, SnapshotDataPacket, SnapshotManifestPacket, StatusPacket,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
BlockSet, ChainSync, ForkConfirmation, PacketProcessError, PeerAsking, PeerInfo, SyncRequester,
|
||||
SyncState, ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_64, MAX_NEW_BLOCK_AGE, MAX_NEW_HASHES,
|
||||
PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2,
|
||||
};
|
||||
|
||||
/// The Chain Sync Handler: handles responses from peers
|
||||
pub struct SyncHandler;
|
||||
|
||||
impl SyncHandler {
|
||||
/// Handle incoming packet from peer
|
||||
pub fn on_packet(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer: PeerId,
|
||||
packet_id: u8,
|
||||
data: &[u8],
|
||||
) {
|
||||
let rlp = Rlp::new(data);
|
||||
if let Some(packet_id) = SyncPacket::from_u8(packet_id) {
|
||||
let result = match packet_id {
|
||||
StatusPacket => SyncHandler::on_peer_status(sync, io, peer, &rlp),
|
||||
BlockHeadersPacket => SyncHandler::on_peer_block_headers(sync, io, peer, &rlp),
|
||||
BlockBodiesPacket => SyncHandler::on_peer_block_bodies(sync, io, peer, &rlp),
|
||||
ReceiptsPacket => SyncHandler::on_peer_block_receipts(sync, io, peer, &rlp),
|
||||
NewBlockPacket => SyncHandler::on_peer_new_block(sync, io, peer, &rlp),
|
||||
NewBlockHashesPacket => SyncHandler::on_peer_new_hashes(sync, io, peer, &rlp),
|
||||
SnapshotManifestPacket => SyncHandler::on_snapshot_manifest(sync, io, peer, &rlp),
|
||||
SnapshotDataPacket => SyncHandler::on_snapshot_data(sync, io, peer, &rlp),
|
||||
_ => {
|
||||
debug!(target: "sync", "{}: Unknown packet {}", peer, packet_id.id());
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(DownloaderImportError::Invalid) => {
|
||||
debug!(target:"sync", "{} -> Invalid packet {}", peer, packet_id.id());
|
||||
io.disable_peer(peer);
|
||||
sync.deactivate_peer(io, peer);
|
||||
}
|
||||
Err(DownloaderImportError::Useless) => {
|
||||
sync.deactivate_peer(io, peer);
|
||||
}
|
||||
Ok(()) => {
|
||||
// give a task to the same peer first
|
||||
sync.sync_peer(io, peer, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!(target: "sync", "{}: Unknown packet {}", peer, packet_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when peer sends us new consensus packet
|
||||
pub fn on_consensus_packet(io: &mut dyn SyncIo, peer_id: PeerId, r: &Rlp) {
|
||||
trace!(target: "sync", "Received consensus packet from {:?}", peer_id);
|
||||
io.chain().queue_consensus_message(r.as_raw().to_vec());
|
||||
}
|
||||
|
||||
/// Called by peer when it is disconnecting
|
||||
pub fn on_peer_aborting(sync: &mut ChainSync, io: &mut dyn SyncIo, peer_id: PeerId) {
|
||||
trace!(target: "sync", "== Disconnecting {}: {}", peer_id, io.peer_version(peer_id));
|
||||
sync.handshaking_peers.remove(&peer_id);
|
||||
if sync.peers.contains_key(&peer_id) {
|
||||
debug!(target: "sync", "Disconnected {}", peer_id);
|
||||
sync.clear_peer_download(peer_id);
|
||||
sync.peers.remove(&peer_id);
|
||||
sync.delayed_requests
|
||||
.retain(|(request_peer_id, _, _)| *request_peer_id != peer_id);
|
||||
sync.active_peers.remove(&peer_id);
|
||||
|
||||
if sync.state == SyncState::SnapshotManifest {
|
||||
// Check if we are asking other peers for
|
||||
// the snapshot manifest as well.
|
||||
// If not, return to initial state
|
||||
let still_asking_manifest = sync
|
||||
.peers
|
||||
.iter()
|
||||
.filter(|&(id, p)| {
|
||||
sync.active_peers.contains(id) && p.asking == PeerAsking::SnapshotManifest
|
||||
})
|
||||
.next()
|
||||
.is_none();
|
||||
|
||||
if still_asking_manifest {
|
||||
sync.state = ChainSync::get_init_state(sync.warp_sync, io.chain());
|
||||
}
|
||||
}
|
||||
sync.continue_sync(io);
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when a new peer is connected
|
||||
pub fn on_peer_connected(sync: &mut ChainSync, io: &mut dyn SyncIo, peer: PeerId) {
|
||||
trace!(target: "sync", "== Connected {}: {}", peer, io.peer_version(peer));
|
||||
if let Err(e) = sync.send_status(io, peer) {
|
||||
debug!(target:"sync", "Error sending status request: {:?}", e);
|
||||
io.disconnect_peer(peer);
|
||||
} else {
|
||||
sync.handshaking_peers.insert(peer, Instant::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by peer once it has new block bodies
|
||||
pub fn on_peer_new_block(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
if !sync.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
|
||||
trace!(target: "sync", "Ignoring new block from unconfirmed peer {}", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
// t_nb 1.0 decode RLP
|
||||
let block = Unverified::from_rlp(r.at(0)?.as_raw().to_vec())?;
|
||||
let hash = block.header.hash();
|
||||
let number = block.header.number();
|
||||
trace!(target: "sync", "{} -> NewBlock ({})", peer_id, hash);
|
||||
if number > sync.highest_block.unwrap_or(0) {
|
||||
sync.highest_block = Some(number);
|
||||
}
|
||||
let parent_hash = block.header.parent_hash();
|
||||
let difficulty: U256 = r.val_at(1)?;
|
||||
// Most probably the sent block is being imported by peer right now
|
||||
// Use td and hash, that peer must have for now
|
||||
// t_nb 1.1 check new block diffuculty it can be found as second item in RLP and update peer diffuculty
|
||||
let parent_td = difficulty.checked_sub(*block.header.difficulty());
|
||||
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
if peer
|
||||
.difficulty
|
||||
.map_or(true, |pd| parent_td.map_or(false, |td| td > pd))
|
||||
{
|
||||
peer.difficulty = parent_td;
|
||||
}
|
||||
}
|
||||
let mut unknown = false;
|
||||
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
peer.latest_hash = *parent_hash;
|
||||
}
|
||||
|
||||
// t_nb 1.2 if block number is to older then 20 dont process it
|
||||
let last_imported_number = sync.new_blocks.last_imported_block_number();
|
||||
if last_imported_number > number && last_imported_number - number > MAX_NEW_BLOCK_AGE {
|
||||
trace!(target: "sync", "Ignored ancient new block {:?}", hash);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
match io.chain().import_block(block) {
|
||||
Err(EthcoreError(EthcoreErrorKind::Import(ImportErrorKind::AlreadyInChain), _)) => {
|
||||
trace!(target: "sync", "New block already in chain {:?}", hash);
|
||||
}
|
||||
Err(EthcoreError(EthcoreErrorKind::Import(ImportErrorKind::AlreadyQueued), _)) => {
|
||||
trace!(target: "sync", "New block already queued {:?}", hash);
|
||||
}
|
||||
Ok(_) => {
|
||||
// abort current download of the same block
|
||||
sync.complete_sync(io);
|
||||
sync.new_blocks.mark_as_known(&hash, number);
|
||||
trace!(target: "sync", "New block queued {:?} ({})", hash, number);
|
||||
}
|
||||
Err(EthcoreError(EthcoreErrorKind::Block(BlockError::UnknownParent(p)), _)) => {
|
||||
unknown = true;
|
||||
trace!(target: "sync", "New block with unknown parent ({:?}) {:?}", p, hash);
|
||||
}
|
||||
Err(e) => {
|
||||
debug!(target: "sync", "Bad new block {:?} : {:?}", hash, e);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
};
|
||||
if unknown {
|
||||
if sync.state != SyncState::Idle {
|
||||
trace!(target: "sync", "NewBlock ignored while seeking");
|
||||
} else {
|
||||
trace!(target: "sync", "New unknown block {:?}", hash);
|
||||
//TODO: handle too many unknown blocks
|
||||
sync.sync_peer(io, peer_id, true);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles `NewHashes` packet. Initiates headers download for any unknown hashes.
|
||||
pub fn on_peer_new_hashes(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
if !sync.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
|
||||
trace!(target: "sync", "Ignoring new hashes from unconfirmed peer {}", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
let hashes: Vec<_> = r
|
||||
.iter()
|
||||
.take(MAX_NEW_HASHES)
|
||||
.map(|item| (item.val_at::<H256>(0), item.val_at::<BlockNumber>(1)))
|
||||
.collect();
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
// Peer has new blocks with unknown difficulty
|
||||
peer.difficulty = None;
|
||||
if let Some(&(Ok(ref h), _)) = hashes.last() {
|
||||
peer.latest_hash = h.clone();
|
||||
}
|
||||
}
|
||||
if sync.state != SyncState::Idle {
|
||||
trace!(target: "sync", "Ignoring new hashes since we're already downloading.");
|
||||
let max = r
|
||||
.iter()
|
||||
.take(MAX_NEW_HASHES)
|
||||
.map(|item| item.val_at::<BlockNumber>(1).unwrap_or(0))
|
||||
.fold(0u64, cmp::max);
|
||||
if max > sync.highest_block.unwrap_or(0) {
|
||||
sync.highest_block = Some(max);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
trace!(target: "sync", "{} -> NewHashes ({} entries)", peer_id, r.item_count()?);
|
||||
let mut max_height: BlockNumber = 0;
|
||||
let mut new_hashes = Vec::new();
|
||||
let last_imported_number = sync.new_blocks.last_imported_block_number();
|
||||
for (rh, rn) in hashes {
|
||||
let hash = rh?;
|
||||
let number = rn?;
|
||||
if number > sync.highest_block.unwrap_or(0) {
|
||||
sync.highest_block = Some(number);
|
||||
}
|
||||
if sync.new_blocks.is_downloading(&hash) {
|
||||
continue;
|
||||
}
|
||||
if last_imported_number > number && last_imported_number - number > MAX_NEW_BLOCK_AGE {
|
||||
trace!(target: "sync", "Ignored ancient new block hash {:?}", hash);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
match io.chain().block_status(BlockId::Hash(hash.clone())) {
|
||||
BlockStatus::InChain => {
|
||||
trace!(target: "sync", "New block hash already in chain {:?}", hash);
|
||||
}
|
||||
BlockStatus::Queued => {
|
||||
trace!(target: "sync", "New hash block already queued {:?}", hash);
|
||||
}
|
||||
BlockStatus::Unknown => {
|
||||
new_hashes.push(hash.clone());
|
||||
if number > max_height {
|
||||
trace!(target: "sync", "New unknown block hash {:?}", hash);
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
peer.latest_hash = hash.clone();
|
||||
}
|
||||
max_height = number;
|
||||
}
|
||||
}
|
||||
BlockStatus::Bad => {
|
||||
debug!(target: "sync", "Bad new block hash {:?}", hash);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
if max_height != 0 {
|
||||
trace!(target: "sync", "Downloading blocks for new hashes");
|
||||
sync.new_blocks.reset_to(new_hashes);
|
||||
sync.state = SyncState::NewBlocks;
|
||||
sync.sync_peer(io, peer_id, true);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called by peer once it has new block bodies
|
||||
fn on_peer_block_bodies(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
sync.clear_peer_download(peer_id);
|
||||
let block_set = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.and_then(|p| p.block_set)
|
||||
.unwrap_or(BlockSet::NewBlocks);
|
||||
let allowed = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.map(|p| p.is_allowed())
|
||||
.unwrap_or(false);
|
||||
|
||||
if !sync.reset_peer_asking(peer_id, PeerAsking::BlockBodies) || !allowed {
|
||||
trace!(target: "sync", "{}: Ignored unexpected bodies", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
let expected_blocks = match sync.peers.get_mut(&peer_id) {
|
||||
Some(peer) => mem::replace(&mut peer.asking_blocks, Vec::new()),
|
||||
None => {
|
||||
trace!(target: "sync", "{}: Ignored unexpected bodies (peer not found)", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let item_count = r.item_count()?;
|
||||
trace!(target: "sync", "{} -> BlockBodies ({} entries), set = {:?}", peer_id, item_count, block_set);
|
||||
if item_count == 0 {
|
||||
Err(DownloaderImportError::Useless)
|
||||
} else if sync.state == SyncState::Waiting {
|
||||
trace!(target: "sync", "Ignored block bodies while waiting");
|
||||
Ok(())
|
||||
} else {
|
||||
{
|
||||
let downloader = match block_set {
|
||||
BlockSet::NewBlocks => &mut sync.new_blocks,
|
||||
BlockSet::OldBlocks => match sync.old_blocks {
|
||||
None => {
|
||||
trace!(target: "sync", "Ignored block headers while block download is inactive");
|
||||
return Ok(());
|
||||
}
|
||||
Some(ref mut blocks) => blocks,
|
||||
},
|
||||
};
|
||||
downloader.import_bodies(r, expected_blocks.as_slice())?;
|
||||
}
|
||||
sync.collect_blocks(io, block_set);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn on_peer_fork_header(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
{
|
||||
let peer = sync
|
||||
.peers
|
||||
.get_mut(&peer_id)
|
||||
.expect("Is only called when peer is present in peers");
|
||||
peer.asking = PeerAsking::Nothing;
|
||||
let item_count = r.item_count()?;
|
||||
let (fork_number, fork_hash) = sync
|
||||
.fork_block
|
||||
.expect("ForkHeader request is sent only fork block is Some; qed")
|
||||
.clone();
|
||||
|
||||
if item_count == 0 || item_count != 1 {
|
||||
trace!(target: "sync", "{}: Chain is too short to confirm the block", peer_id);
|
||||
peer.confirmation = ForkConfirmation::TooShort;
|
||||
} else {
|
||||
let header = r.at(0)?.as_raw();
|
||||
if keccak(&header) != fork_hash {
|
||||
trace!(target: "sync", "{}: Fork mismatch", peer_id);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
|
||||
trace!(target: "sync", "{}: Confirmed peer", peer_id);
|
||||
peer.confirmation = ForkConfirmation::Confirmed;
|
||||
|
||||
if !io.chain_overlay().read().contains_key(&fork_number) {
|
||||
trace!(target: "sync", "Inserting (fork) block {} header", fork_number);
|
||||
io.chain_overlay()
|
||||
.write()
|
||||
.insert(fork_number, header.to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
/// Called by peer once it has new block headers during sync
|
||||
fn on_peer_block_headers(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
let is_fork_header_request = match sync.peers.get(&peer_id) {
|
||||
Some(peer) if peer.asking == PeerAsking::ForkHeader => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_fork_header_request {
|
||||
return SyncHandler::on_peer_fork_header(sync, io, peer_id, r);
|
||||
}
|
||||
|
||||
sync.clear_peer_download(peer_id);
|
||||
let expected_hash = sync.peers.get(&peer_id).and_then(|p| p.asking_hash);
|
||||
let allowed = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.map(|p| p.is_allowed())
|
||||
.unwrap_or(false);
|
||||
let block_set = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.and_then(|p| p.block_set)
|
||||
.unwrap_or(BlockSet::NewBlocks);
|
||||
|
||||
if !sync.reset_peer_asking(peer_id, PeerAsking::BlockHeaders) {
|
||||
debug!(target: "sync", "{}: Ignored unexpected headers", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
let expected_hash = match expected_hash {
|
||||
Some(hash) => hash,
|
||||
None => {
|
||||
debug!(target: "sync", "{}: Ignored unexpected headers (expected_hash is None)", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
if !allowed {
|
||||
debug!(target: "sync", "{}: Ignored unexpected headers (peer not allowed)", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let item_count = r.item_count()?;
|
||||
trace!(target: "sync", "{} -> BlockHeaders ({} entries), state = {:?}, set = {:?}", peer_id, item_count, sync.state, block_set);
|
||||
if (sync.state == SyncState::Idle || sync.state == SyncState::WaitingPeers)
|
||||
&& sync.old_blocks.is_none()
|
||||
{
|
||||
trace!(target: "sync", "Ignored unexpected block headers");
|
||||
return Ok(());
|
||||
}
|
||||
if sync.state == SyncState::Waiting {
|
||||
trace!(target: "sync", "Ignored block headers while waiting");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let result = {
|
||||
let downloader = match block_set {
|
||||
BlockSet::NewBlocks => &mut sync.new_blocks,
|
||||
BlockSet::OldBlocks => match sync.old_blocks {
|
||||
None => {
|
||||
trace!(target: "sync", "Ignored block headers while block download is inactive");
|
||||
return Ok(());
|
||||
}
|
||||
Some(ref mut blocks) => blocks,
|
||||
},
|
||||
};
|
||||
downloader.import_headers(io, r, expected_hash)?
|
||||
};
|
||||
|
||||
if result == DownloadAction::Reset {
|
||||
sync.reset_downloads(block_set);
|
||||
}
|
||||
|
||||
sync.collect_blocks(io, block_set);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called by peer once it has new block receipts
|
||||
fn on_peer_block_receipts(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
sync.clear_peer_download(peer_id);
|
||||
let block_set = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.and_then(|p| p.block_set)
|
||||
.unwrap_or(BlockSet::NewBlocks);
|
||||
let allowed = sync
|
||||
.peers
|
||||
.get(&peer_id)
|
||||
.map(|p| p.is_allowed())
|
||||
.unwrap_or(false);
|
||||
if !sync.reset_peer_asking(peer_id, PeerAsking::BlockReceipts) || !allowed {
|
||||
trace!(target: "sync", "{}: Ignored unexpected receipts", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
let expected_blocks = match sync.peers.get_mut(&peer_id) {
|
||||
Some(peer) => mem::replace(&mut peer.asking_blocks, Vec::new()),
|
||||
None => {
|
||||
trace!(target: "sync", "{}: Ignored unexpected bodies (peer not found)", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let item_count = r.item_count()?;
|
||||
trace!(target: "sync", "{} -> BlockReceipts ({} entries)", peer_id, item_count);
|
||||
if item_count == 0 {
|
||||
Err(DownloaderImportError::Useless)
|
||||
} else if sync.state == SyncState::Waiting {
|
||||
trace!(target: "sync", "Ignored block receipts while waiting");
|
||||
Ok(())
|
||||
} else {
|
||||
{
|
||||
let downloader = match block_set {
|
||||
BlockSet::NewBlocks => &mut sync.new_blocks,
|
||||
BlockSet::OldBlocks => match sync.old_blocks {
|
||||
None => {
|
||||
trace!(target: "sync", "Ignored block headers while block download is inactive");
|
||||
return Ok(());
|
||||
}
|
||||
Some(ref mut blocks) => blocks,
|
||||
},
|
||||
};
|
||||
downloader.import_receipts(r, expected_blocks.as_slice())?;
|
||||
}
|
||||
sync.collect_blocks(io, block_set);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when snapshot manifest is downloaded from a peer.
|
||||
fn on_snapshot_manifest(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
if !sync.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
|
||||
trace!(target: "sync", "Ignoring snapshot manifest from unconfirmed peer {}", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
sync.clear_peer_download(peer_id);
|
||||
if !sync.reset_peer_asking(peer_id, PeerAsking::SnapshotManifest)
|
||||
|| sync.state != SyncState::SnapshotManifest
|
||||
{
|
||||
trace!(target: "sync", "{}: Ignored unexpected/expired manifest", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let manifest_rlp = r.at(0)?;
|
||||
let manifest = ManifestData::from_rlp(manifest_rlp.as_raw())?;
|
||||
|
||||
let is_supported_version = io
|
||||
.snapshot_service()
|
||||
.supported_versions()
|
||||
.map_or(false, |(l, h)| {
|
||||
manifest.version >= l && manifest.version <= h
|
||||
});
|
||||
|
||||
if !is_supported_version {
|
||||
trace!(target: "sync", "{}: Snapshot manifest version not supported: {}", peer_id, manifest.version);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
sync.snapshot
|
||||
.reset_to(&manifest, &keccak(manifest_rlp.as_raw()));
|
||||
io.snapshot_service().begin_restore(manifest);
|
||||
sync.state = SyncState::SnapshotData;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called when snapshot data is downloaded from a peer.
|
||||
fn on_snapshot_data(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
if !sync.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
|
||||
trace!(target: "sync", "Ignoring snapshot data from unconfirmed peer {}", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
sync.clear_peer_download(peer_id);
|
||||
if !sync.reset_peer_asking(peer_id, PeerAsking::SnapshotData)
|
||||
|| (sync.state != SyncState::SnapshotData && sync.state != SyncState::SnapshotWaiting)
|
||||
{
|
||||
trace!(target: "sync", "{}: Ignored unexpected snapshot data", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// check service status
|
||||
let status = io.snapshot_service().restoration_status();
|
||||
match status {
|
||||
RestorationStatus::Inactive | RestorationStatus::Failed => {
|
||||
trace!(target: "sync", "{}: Snapshot restoration aborted", peer_id);
|
||||
sync.state = SyncState::WaitingPeers;
|
||||
|
||||
// only note bad if restoration failed.
|
||||
if let (Some(hash), RestorationStatus::Failed) =
|
||||
(sync.snapshot.snapshot_hash(), status)
|
||||
{
|
||||
trace!(target: "sync", "Noting snapshot hash {} as bad", hash);
|
||||
sync.snapshot.note_bad(hash);
|
||||
}
|
||||
|
||||
sync.snapshot.clear();
|
||||
return Ok(());
|
||||
}
|
||||
RestorationStatus::Initializing { .. } => {
|
||||
trace!(target: "warp", "{}: Snapshot restoration is initializing", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
RestorationStatus::Ongoing { .. } => {
|
||||
trace!(target: "sync", "{}: Snapshot restoration is ongoing", peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
let snapshot_data: Bytes = r.val_at(0)?;
|
||||
match sync.snapshot.validate_chunk(&snapshot_data) {
|
||||
Ok(ChunkType::Block(hash)) => {
|
||||
trace!(target: "sync", "{}: Processing block chunk", peer_id);
|
||||
io.snapshot_service()
|
||||
.restore_block_chunk(hash, snapshot_data);
|
||||
}
|
||||
Ok(ChunkType::State(hash)) => {
|
||||
trace!(target: "sync", "{}: Processing state chunk", peer_id);
|
||||
io.snapshot_service()
|
||||
.restore_state_chunk(hash, snapshot_data);
|
||||
}
|
||||
Err(()) => {
|
||||
trace!(target: "sync", "{}: Got bad snapshot chunk", peer_id);
|
||||
io.disconnect_peer(peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if sync.snapshot.is_complete() {
|
||||
// wait for snapshot restoration process to complete
|
||||
sync.state = SyncState::SnapshotWaiting;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called by peer to report status
|
||||
fn on_peer_status(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), DownloaderImportError> {
|
||||
let mut r_iter = r.iter();
|
||||
sync.handshaking_peers.remove(&peer_id);
|
||||
let protocol_version: u8 = r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?;
|
||||
let eth_protocol_version = io.protocol_version(Ð_PROTOCOL, peer_id);
|
||||
let warp_protocol_version = io.protocol_version(&PAR_PROTOCOL, peer_id);
|
||||
let warp_protocol = warp_protocol_version != 0;
|
||||
|
||||
let network_id = r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?;
|
||||
let difficulty = Some(
|
||||
r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?,
|
||||
);
|
||||
let latest_hash = r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?;
|
||||
let genesis = r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?;
|
||||
let forkid_validation_error = {
|
||||
if eth_protocol_version >= ETH_PROTOCOL_VERSION_64.0 {
|
||||
let fork_id = rlp04::Rlp::new(r.as_raw()).val_at(5)?;
|
||||
r_iter.next().ok_or(rlp::DecoderError::RlpIsTooShort)?;
|
||||
sync.fork_filter
|
||||
.is_compatible(io.chain(), fork_id)
|
||||
.err()
|
||||
.map(|e| (fork_id, e))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let snapshot_hash = if warp_protocol {
|
||||
Some(
|
||||
r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let snapshot_number = if warp_protocol {
|
||||
Some(
|
||||
r_iter
|
||||
.next()
|
||||
.ok_or(rlp::DecoderError::RlpIsTooShort)?
|
||||
.as_val()?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let peer = PeerInfo {
|
||||
protocol_version,
|
||||
network_id,
|
||||
difficulty,
|
||||
latest_hash,
|
||||
genesis,
|
||||
asking: PeerAsking::Nothing,
|
||||
asking_blocks: Vec::new(),
|
||||
asking_hash: None,
|
||||
ask_time: Instant::now(),
|
||||
last_sent_transactions: Default::default(),
|
||||
expired: false,
|
||||
confirmation: if sync.fork_block.is_none() {
|
||||
ForkConfirmation::Confirmed
|
||||
} else {
|
||||
ForkConfirmation::Unconfirmed
|
||||
},
|
||||
asking_snapshot_data: None,
|
||||
snapshot_hash,
|
||||
snapshot_number,
|
||||
block_set: None,
|
||||
client_version: ClientVersion::from(io.peer_version(peer_id)),
|
||||
};
|
||||
|
||||
trace!(target: "sync", "New peer {} (\
|
||||
protocol: {}, \
|
||||
network: {:?}, \
|
||||
difficulty: {:?}, \
|
||||
latest:{}, \
|
||||
genesis:{}, \
|
||||
snapshot:{:?})",
|
||||
peer_id,
|
||||
peer.protocol_version,
|
||||
peer.network_id,
|
||||
peer.difficulty,
|
||||
peer.latest_hash,
|
||||
peer.genesis,
|
||||
peer.snapshot_number
|
||||
);
|
||||
if io.is_expired() {
|
||||
trace!(target: "sync", "Status packet from expired session {}:{}", peer_id, io.peer_version(peer_id));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if sync.peers.contains_key(&peer_id) {
|
||||
debug!(target: "sync", "Unexpected status packet from {}:{}", peer_id, io.peer_version(peer_id));
|
||||
return Ok(());
|
||||
}
|
||||
let chain_info = io.chain().chain_info();
|
||||
if peer.genesis != chain_info.genesis_hash {
|
||||
trace!(target: "sync", "Peer {} genesis hash mismatch (ours: {}, theirs: {})", peer_id, chain_info.genesis_hash, peer.genesis);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
if peer.network_id != sync.network_id {
|
||||
trace!(target: "sync", "Peer {} network id mismatch (ours: {}, theirs: {})", peer_id, sync.network_id, peer.network_id);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
|
||||
if let Some((fork_id, reason)) = forkid_validation_error {
|
||||
trace!(target: "sync", "Peer {} incompatible fork id (fork id: {:#x}/{}, error: {:?})", peer_id, fork_id.hash.0, fork_id.next, reason);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
|
||||
if false
|
||||
|| (warp_protocol
|
||||
&& (peer.protocol_version < PAR_PROTOCOL_VERSION_1.0
|
||||
|| peer.protocol_version > PAR_PROTOCOL_VERSION_2.0))
|
||||
|| (!warp_protocol
|
||||
&& (peer.protocol_version < ETH_PROTOCOL_VERSION_63.0
|
||||
|| peer.protocol_version > ETH_PROTOCOL_VERSION_64.0))
|
||||
{
|
||||
trace!(target: "sync", "Peer {} unsupported eth protocol ({})", peer_id, peer.protocol_version);
|
||||
return Err(DownloaderImportError::Invalid);
|
||||
}
|
||||
|
||||
if sync.sync_start_time.is_none() {
|
||||
sync.sync_start_time = Some(Instant::now());
|
||||
}
|
||||
|
||||
sync.peers.insert(peer_id.clone(), peer);
|
||||
// Don't activate peer immediatelly when searching for common block.
|
||||
// Let the current sync round complete first.
|
||||
sync.active_peers.insert(peer_id.clone());
|
||||
debug!(target: "sync", "Connected {}:{}", peer_id, io.peer_version(peer_id));
|
||||
|
||||
if let Some((fork_block, _)) = sync.fork_block {
|
||||
SyncRequester::request_fork_header(sync, io, peer_id, fork_block);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called when peer sends us new transactions
|
||||
pub fn on_peer_transactions(
|
||||
sync: &ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
r: &Rlp,
|
||||
) -> Result<(), PacketProcessError> {
|
||||
// Accept transactions only when fully synced
|
||||
if !io.is_chain_queue_empty()
|
||||
|| (sync.state != SyncState::Idle && sync.state != SyncState::NewBlocks)
|
||||
{
|
||||
trace!(target: "sync", "{} Ignoring transactions while syncing", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
if !sync.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
|
||||
trace!(target: "sync", "{} Ignoring transactions from unconfirmed/unknown peer", peer_id);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let item_count = r.item_count()?;
|
||||
trace!(target: "sync", "{:02} -> Transactions ({} entries)", peer_id, item_count);
|
||||
let mut transactions = Vec::with_capacity(item_count);
|
||||
for i in r.iter() {
|
||||
let tx = if i.is_list() {
|
||||
i.as_raw().to_vec() // legacy transaction. just add it raw
|
||||
} else {
|
||||
i.data()?.to_vec() // typed transaction. remove header from start and send only payload.
|
||||
};
|
||||
transactions.push(tx);
|
||||
}
|
||||
io.chain().queue_transactions(transactions, peer_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethcore::client::{ChainInfo, EachBlockWith, TestBlockChainClient};
|
||||
use parking_lot::RwLock;
|
||||
use rlp::Rlp;
|
||||
use std::collections::VecDeque;
|
||||
use tests::{helpers::TestIo, snapshot::TestSnapshotService};
|
||||
|
||||
use super::{
|
||||
super::tests::{dummy_sync_with_peer, get_dummy_block, get_dummy_blocks, get_dummy_hashes},
|
||||
*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn handles_peer_new_hashes() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(10, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let hashes_data = get_dummy_hashes();
|
||||
let hashes_rlp = Rlp::new(&hashes_data);
|
||||
|
||||
let result = SyncHandler::on_peer_new_hashes(&mut sync, &mut io, 0, &hashes_rlp);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_peer_new_block_malformed() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(10, EachBlockWith::Uncle);
|
||||
|
||||
let block_data = get_dummy_block(11, client.chain_info().best_block_hash);
|
||||
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
|
||||
//sync.have_common_block = true;
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let block = Rlp::new(&block_data);
|
||||
|
||||
let result = SyncHandler::on_peer_new_block(&mut sync, &mut io, 0, &block);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_peer_new_block() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(10, EachBlockWith::Uncle);
|
||||
|
||||
let block_data = get_dummy_blocks(11, client.chain_info().best_block_hash);
|
||||
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let block = Rlp::new(&block_data);
|
||||
|
||||
SyncHandler::on_peer_new_block(&mut sync, &mut io, 0, &block).expect("result to be ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_peer_new_block_empty() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(10, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let empty_data = vec![];
|
||||
let block = Rlp::new(&empty_data);
|
||||
|
||||
let result = SyncHandler::on_peer_new_block(&mut sync, &mut io, 0, &block);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_peer_new_hashes_empty() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(10, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let empty_hashes_data = vec![];
|
||||
let hashes_rlp = Rlp::new(&empty_hashes_data);
|
||||
|
||||
let result = SyncHandler::on_peer_new_hashes(&mut sync, &mut io, 0, &hashes_rlp);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
1804
crates/ethcore/sync/src/chain/mod.rs
Normal file
1804
crates/ethcore/sync/src/chain/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
715
crates/ethcore/sync/src/chain/propagator.rs
Normal file
715
crates/ethcore/sync/src/chain/propagator.rs
Normal file
@@ -0,0 +1,715 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{cmp, collections::HashSet};
|
||||
|
||||
use bytes::Bytes;
|
||||
use ethereum_types::H256;
|
||||
use fastmap::H256FastSet;
|
||||
use network::{client_version::ClientCapabilities, PeerId};
|
||||
use rand::Rng;
|
||||
use rlp::RlpStream;
|
||||
use sync_io::SyncIo;
|
||||
use types::{blockchain_info::BlockChainInfo, transaction::SignedTransaction, BlockNumber};
|
||||
|
||||
use super::sync_packet::{
|
||||
SyncPacket,
|
||||
SyncPacket::{ConsensusDataPacket, NewBlockHashesPacket, NewBlockPacket, TransactionsPacket},
|
||||
};
|
||||
|
||||
use super::{
|
||||
random, ChainSync, MAX_PEERS_PROPAGATION, MAX_PEER_LAG_PROPAGATION,
|
||||
MAX_TRANSACTION_PACKET_SIZE, MIN_PEERS_PROPAGATION,
|
||||
};
|
||||
|
||||
/// The Chain Sync Propagator: propagates data to peers
|
||||
pub struct SyncPropagator;
|
||||
|
||||
impl SyncPropagator {
|
||||
// t_nb 11.4.3 propagates latest block to a set of peers
|
||||
pub fn propagate_blocks(
|
||||
sync: &mut ChainSync,
|
||||
chain_info: &BlockChainInfo,
|
||||
io: &mut dyn SyncIo,
|
||||
blocks: &[H256],
|
||||
peers: &[PeerId],
|
||||
) -> usize {
|
||||
trace!(target: "sync", "Sending NewBlocks to {:?}", peers);
|
||||
let sent = peers.len();
|
||||
let mut send_packet = |io: &mut dyn SyncIo, rlp: Bytes| {
|
||||
for peer_id in peers {
|
||||
SyncPropagator::send_packet(io, *peer_id, NewBlockPacket, rlp.clone());
|
||||
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(peer_id) {
|
||||
peer.latest_hash = chain_info.best_block_hash.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if blocks.is_empty() {
|
||||
let rlp = ChainSync::create_latest_block_rlp(io.chain());
|
||||
send_packet(io, rlp);
|
||||
} else {
|
||||
for h in blocks {
|
||||
let rlp = ChainSync::create_new_block_rlp(io.chain(), h);
|
||||
send_packet(io, rlp);
|
||||
}
|
||||
}
|
||||
|
||||
sent
|
||||
}
|
||||
|
||||
// t_nb 11.4.2 propagates new known hashes to all peers
|
||||
pub fn propagate_new_hashes(
|
||||
sync: &mut ChainSync,
|
||||
chain_info: &BlockChainInfo,
|
||||
io: &mut dyn SyncIo,
|
||||
peers: &[PeerId],
|
||||
) -> usize {
|
||||
trace!(target: "sync", "Sending NewHashes to {:?}", peers);
|
||||
let last_parent = *io.chain().best_block_header().parent_hash();
|
||||
let best_block_hash = chain_info.best_block_hash;
|
||||
let rlp = match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &best_block_hash)
|
||||
{
|
||||
Some(rlp) => rlp,
|
||||
None => return 0,
|
||||
};
|
||||
|
||||
let sent = peers.len();
|
||||
for peer_id in peers {
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(peer_id) {
|
||||
peer.latest_hash = best_block_hash;
|
||||
}
|
||||
SyncPropagator::send_packet(io, *peer_id, NewBlockHashesPacket, rlp.clone());
|
||||
}
|
||||
sent
|
||||
}
|
||||
|
||||
/// propagates new transactions to all peers
|
||||
pub fn propagate_new_transactions<F: FnMut() -> bool>(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
mut should_continue: F,
|
||||
) -> usize {
|
||||
// Early out if nobody to send to.
|
||||
if sync.peers.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let transactions = io.chain().transactions_to_propagate();
|
||||
if transactions.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if !should_continue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let (transactions, service_transactions): (Vec<_>, Vec<_>) = transactions
|
||||
.iter()
|
||||
.map(|tx| tx.signed())
|
||||
.partition(|tx| !tx.tx().gas_price.is_zero());
|
||||
|
||||
// usual transactions could be propagated to all peers
|
||||
let mut affected_peers = HashSet::new();
|
||||
if !transactions.is_empty() {
|
||||
let peers = SyncPropagator::select_peers_for_transactions(sync, |_| true);
|
||||
affected_peers = SyncPropagator::propagate_transactions_to_peers(
|
||||
sync,
|
||||
io,
|
||||
peers,
|
||||
transactions,
|
||||
&mut should_continue,
|
||||
);
|
||||
}
|
||||
|
||||
// most of times service_transactions will be empty
|
||||
// => there's no need to merge packets
|
||||
if !service_transactions.is_empty() {
|
||||
let service_transactions_peers =
|
||||
SyncPropagator::select_peers_for_transactions(sync, |peer_id| {
|
||||
io.peer_version(*peer_id).accepts_service_transaction()
|
||||
});
|
||||
let service_transactions_affected_peers =
|
||||
SyncPropagator::propagate_transactions_to_peers(
|
||||
sync,
|
||||
io,
|
||||
service_transactions_peers,
|
||||
service_transactions,
|
||||
&mut should_continue,
|
||||
);
|
||||
affected_peers.extend(&service_transactions_affected_peers);
|
||||
}
|
||||
|
||||
affected_peers.len()
|
||||
}
|
||||
|
||||
fn propagate_transactions_to_peers<F: FnMut() -> bool>(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peers: Vec<PeerId>,
|
||||
transactions: Vec<&SignedTransaction>,
|
||||
mut should_continue: F,
|
||||
) -> HashSet<PeerId> {
|
||||
let all_transactions_hashes = transactions
|
||||
.iter()
|
||||
.map(|tx| tx.hash())
|
||||
.collect::<H256FastSet>();
|
||||
let all_transactions_rlp = {
|
||||
let mut packet = RlpStream::new_list(transactions.len());
|
||||
for tx in &transactions {
|
||||
tx.rlp_append(&mut packet);
|
||||
}
|
||||
packet.out()
|
||||
};
|
||||
|
||||
// Clear old transactions from stats
|
||||
sync.transactions_stats.retain(&all_transactions_hashes);
|
||||
|
||||
let send_packet = |io: &mut dyn SyncIo, peer_id: PeerId, sent: usize, rlp: Bytes| {
|
||||
let size = rlp.len();
|
||||
SyncPropagator::send_packet(io, peer_id, TransactionsPacket, rlp);
|
||||
trace!(target: "sync", "{:02} <- Transactions ({} entries; {} bytes)", peer_id, sent, size);
|
||||
};
|
||||
|
||||
let block_number = io.chain().chain_info().best_block_number;
|
||||
let mut sent_to_peers = HashSet::new();
|
||||
let mut max_sent = 0;
|
||||
|
||||
// for every peer construct and send transactions packet
|
||||
for peer_id in peers {
|
||||
if !should_continue() {
|
||||
debug!(target: "sync", "Sent up to {} transactions to {} peers.", max_sent, sent_to_peers.len());
|
||||
return sent_to_peers;
|
||||
}
|
||||
|
||||
let stats = &mut sync.transactions_stats;
|
||||
let peer_info = sync.peers.get_mut(&peer_id)
|
||||
.expect("peer_id is form peers; peers is result of select_peers_for_transactions; select_peers_for_transactions selects peers from self.peers; qed");
|
||||
|
||||
// Send all transactions, if the peer doesn't know about anything
|
||||
if peer_info.last_sent_transactions.is_empty() {
|
||||
// update stats
|
||||
for hash in &all_transactions_hashes {
|
||||
let id = io.peer_session_info(peer_id).and_then(|info| info.id);
|
||||
stats.propagated(hash, id, block_number);
|
||||
}
|
||||
peer_info.last_sent_transactions = all_transactions_hashes.clone();
|
||||
|
||||
send_packet(
|
||||
io,
|
||||
peer_id,
|
||||
all_transactions_hashes.len(),
|
||||
all_transactions_rlp.clone(),
|
||||
);
|
||||
sent_to_peers.insert(peer_id);
|
||||
max_sent = cmp::max(max_sent, all_transactions_hashes.len());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get hashes of all transactions to send to this peer
|
||||
let to_send = all_transactions_hashes
|
||||
.difference(&peer_info.last_sent_transactions)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
if to_send.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Construct RLP
|
||||
let (packet, to_send) = {
|
||||
let mut to_send = to_send;
|
||||
let mut packet = RlpStream::new();
|
||||
packet.begin_unbounded_list();
|
||||
let mut pushed = 0;
|
||||
for tx in &transactions {
|
||||
let hash = tx.hash();
|
||||
if to_send.contains(&hash) {
|
||||
let appended =
|
||||
packet.append_raw_checked(&tx.encode(), 1, MAX_TRANSACTION_PACKET_SIZE);
|
||||
if !appended {
|
||||
// Maximal packet size reached just proceed with sending
|
||||
debug!(target: "sync", "Transaction packet size limit reached. Sending incomplete set of {}/{} transactions.", pushed, to_send.len());
|
||||
to_send = to_send.into_iter().take(pushed).collect();
|
||||
break;
|
||||
}
|
||||
pushed += 1;
|
||||
}
|
||||
}
|
||||
packet.complete_unbounded_list();
|
||||
(packet, to_send)
|
||||
};
|
||||
|
||||
// Update stats
|
||||
let id = io.peer_session_info(peer_id).and_then(|info| info.id);
|
||||
for hash in &to_send {
|
||||
// update stats
|
||||
stats.propagated(hash, id, block_number);
|
||||
}
|
||||
|
||||
peer_info.last_sent_transactions = all_transactions_hashes
|
||||
.intersection(&peer_info.last_sent_transactions)
|
||||
.chain(&to_send)
|
||||
.cloned()
|
||||
.collect();
|
||||
send_packet(io, peer_id, to_send.len(), packet.out());
|
||||
sent_to_peers.insert(peer_id);
|
||||
max_sent = cmp::max(max_sent, to_send.len());
|
||||
}
|
||||
|
||||
debug!(target: "sync", "Sent up to {} transactions to {} peers.", max_sent, sent_to_peers.len());
|
||||
sent_to_peers
|
||||
}
|
||||
|
||||
// t_nb 11.4.1 propagate latest blocks to peers
|
||||
pub fn propagate_latest_blocks(sync: &mut ChainSync, io: &mut dyn SyncIo, sealed: &[H256]) {
|
||||
let chain_info = io.chain().chain_info();
|
||||
if (((chain_info.best_block_number as i64) - (sync.last_sent_block_number as i64)).abs()
|
||||
as BlockNumber)
|
||||
< MAX_PEER_LAG_PROPAGATION
|
||||
{
|
||||
let peers = sync.get_lagging_peers(&chain_info);
|
||||
if sealed.is_empty() {
|
||||
// t_nb 11.4.2
|
||||
let hashes = SyncPropagator::propagate_new_hashes(sync, &chain_info, io, &peers);
|
||||
let peers = ChainSync::select_random_peers(&peers);
|
||||
// t_nb 11.4.3
|
||||
let blocks =
|
||||
SyncPropagator::propagate_blocks(sync, &chain_info, io, sealed, &peers);
|
||||
if blocks != 0 || hashes != 0 {
|
||||
trace!(target: "sync", "Sent latest {} blocks and {} hashes to peers.", blocks, hashes);
|
||||
}
|
||||
} else {
|
||||
// t_nb 11.4.3
|
||||
SyncPropagator::propagate_blocks(sync, &chain_info, io, sealed, &peers);
|
||||
// t_nb 11.4.2
|
||||
SyncPropagator::propagate_new_hashes(sync, &chain_info, io, &peers);
|
||||
trace!(target: "sync", "Sent sealed block to all peers");
|
||||
};
|
||||
}
|
||||
sync.last_sent_block_number = chain_info.best_block_number;
|
||||
}
|
||||
|
||||
// t_nb 11.4.4 Distribute valid proposed blocks to subset of current peers. (if there is any proposed)
|
||||
pub fn propagate_proposed_blocks(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
proposed: &[Bytes],
|
||||
) {
|
||||
let peers = sync.get_consensus_peers();
|
||||
trace!(target: "sync", "Sending proposed blocks to {:?}", peers);
|
||||
for block in proposed {
|
||||
let rlp = ChainSync::create_block_rlp(block, io.chain().chain_info().total_difficulty);
|
||||
for peer_id in &peers {
|
||||
SyncPropagator::send_packet(io, *peer_id, NewBlockPacket, rlp.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Broadcast consensus message to peers.
|
||||
pub fn propagate_consensus_packet(sync: &mut ChainSync, io: &mut dyn SyncIo, packet: Bytes) {
|
||||
let lucky_peers = ChainSync::select_random_peers(&sync.get_consensus_peers());
|
||||
trace!(target: "sync", "Sending consensus packet to {:?}", lucky_peers);
|
||||
for peer_id in lucky_peers {
|
||||
SyncPropagator::send_packet(io, peer_id, ConsensusDataPacket, packet.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn select_peers_for_transactions<F>(sync: &ChainSync, filter: F) -> Vec<PeerId>
|
||||
where
|
||||
F: Fn(&PeerId) -> bool,
|
||||
{
|
||||
// sqrt(x)/x scaled to max u32
|
||||
let fraction =
|
||||
((sync.peers.len() as f64).powf(-0.5) * (u32::max_value() as f64).round()) as u32;
|
||||
let small = sync.peers.len() < MIN_PEERS_PROPAGATION;
|
||||
|
||||
let mut random = random::new();
|
||||
sync.peers
|
||||
.keys()
|
||||
.cloned()
|
||||
.filter(filter)
|
||||
.filter(|_| small || random.next_u32() < fraction)
|
||||
.take(MAX_PEERS_PROPAGATION)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Generic packet sender
|
||||
pub fn send_packet(
|
||||
sync: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
packet_id: SyncPacket,
|
||||
packet: Bytes,
|
||||
) {
|
||||
if let Err(e) = sync.send(peer_id, packet_id, packet) {
|
||||
debug!(target:"sync", "Error sending packet: {:?}", e);
|
||||
sync.disconnect_peer(peer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ethcore::client::{BlockInfo, ChainInfo, EachBlockWith, TestBlockChainClient};
|
||||
use parking_lot::RwLock;
|
||||
use rlp::Rlp;
|
||||
use std::collections::VecDeque;
|
||||
use tests::{helpers::TestIo, snapshot::TestSnapshotService};
|
||||
use types::transaction::TypedTransaction;
|
||||
|
||||
use super::{
|
||||
super::{tests::*, *},
|
||||
*,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn sends_new_hashes_to_lagging_peer() {
|
||||
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);
|
||||
let peer_count =
|
||||
SyncPropagator::propagate_new_hashes(&mut sync, &chain_info, &mut io, &peers);
|
||||
|
||||
// 1 message should be send
|
||||
assert_eq!(1, io.packets.len());
|
||||
// 1 peer should be updated
|
||||
assert_eq!(1, peer_count);
|
||||
// NEW_BLOCK_HASHES_PACKET
|
||||
assert_eq!(0x01, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sends_latest_block_to_lagging_peer() {
|
||||
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);
|
||||
let peer_count =
|
||||
SyncPropagator::propagate_blocks(&mut sync, &chain_info, &mut io, &[], &peers);
|
||||
|
||||
// 1 message should be send
|
||||
assert_eq!(1, io.packets.len());
|
||||
// 1 peer should be updated
|
||||
assert_eq!(1, peer_count);
|
||||
// NEW_BLOCK_PACKET
|
||||
assert_eq!(0x07, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sends_sealed_block() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let hash = client.block_hash(BlockId::Number(99)).unwrap();
|
||||
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);
|
||||
let peer_count = SyncPropagator::propagate_blocks(
|
||||
&mut sync,
|
||||
&chain_info,
|
||||
&mut io,
|
||||
&[hash.clone()],
|
||||
&peers,
|
||||
);
|
||||
|
||||
// 1 message should be send
|
||||
assert_eq!(1, io.packets.len());
|
||||
// 1 peer should be updated
|
||||
assert_eq!(1, peer_count);
|
||||
// NEW_BLOCK_PACKET
|
||||
assert_eq!(0x07, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sends_proposed_block() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(2, EachBlockWith::Uncle);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let block = client.block(BlockId::Latest).unwrap().into_inner();
|
||||
let mut sync = ChainSync::new(
|
||||
SyncConfig::default(),
|
||||
&client,
|
||||
ForkFilterApi::new_dummy(&client),
|
||||
);
|
||||
sync.peers.insert(
|
||||
0,
|
||||
PeerInfo {
|
||||
// Messaging protocol
|
||||
protocol_version: 2,
|
||||
genesis: H256::zero(),
|
||||
network_id: 0,
|
||||
latest_hash: client.block_hash_delta_minus(1),
|
||||
difficulty: None,
|
||||
asking: PeerAsking::Nothing,
|
||||
asking_blocks: Vec::new(),
|
||||
asking_hash: None,
|
||||
ask_time: Instant::now(),
|
||||
last_sent_transactions: Default::default(),
|
||||
expired: false,
|
||||
confirmation: ForkConfirmation::Confirmed,
|
||||
snapshot_number: None,
|
||||
snapshot_hash: None,
|
||||
asking_snapshot_data: None,
|
||||
block_set: None,
|
||||
client_version: ClientVersion::from(""),
|
||||
},
|
||||
);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
SyncPropagator::propagate_proposed_blocks(&mut sync, &mut io, &[block]);
|
||||
|
||||
// 1 message should be sent
|
||||
assert_eq!(1, io.packets.len());
|
||||
// NEW_BLOCK_PACKET
|
||||
assert_eq!(0x07, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagates_transactions() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
let peer_count = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
// Try to propagate same transactions for the second time
|
||||
let peer_count2 = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
// Even after new block transactions should not be propagated twice
|
||||
sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]);
|
||||
// Try to propagate same transactions for the third time
|
||||
let peer_count3 = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
|
||||
// 1 message should be send
|
||||
assert_eq!(1, io.packets.len());
|
||||
// 1 peer should be updated but only once
|
||||
assert_eq!(1, peer_count);
|
||||
assert_eq!(0, peer_count2);
|
||||
assert_eq!(0, peer_count3);
|
||||
// TRANSACTIONS_PACKET
|
||||
assert_eq!(0x02, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_propagate_new_transactions_after_new_block() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
let peer_count = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
io.chain.insert_transaction_to_queue();
|
||||
// New block import should not trigger propagation.
|
||||
// (we only propagate on timeout)
|
||||
sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]);
|
||||
|
||||
// 2 message should be send
|
||||
assert_eq!(1, io.packets.len());
|
||||
// 1 peer should receive the message
|
||||
assert_eq!(1, peer_count);
|
||||
// TRANSACTIONS_PACKET
|
||||
assert_eq!(0x02, io.packets[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_fail_for_no_peers() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
// Sync with no peers
|
||||
let mut sync = ChainSync::new(
|
||||
SyncConfig::default(),
|
||||
&client,
|
||||
ForkFilterApi::new_dummy(&client),
|
||||
);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
let peer_count = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[], &[]);
|
||||
// Try to propagate same transactions for the second time
|
||||
let peer_count2 = SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
|
||||
assert_eq!(0, io.packets.len());
|
||||
assert_eq!(0, peer_count);
|
||||
assert_eq!(0, peer_count2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagates_transactions_without_alternating() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
// should sent some
|
||||
{
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
let peer_count =
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
assert_eq!(1, io.packets.len());
|
||||
assert_eq!(1, peer_count);
|
||||
}
|
||||
// Insert some more
|
||||
client.insert_transaction_to_queue();
|
||||
let (peer_count2, peer_count3) = {
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
// Propagate new transactions
|
||||
let peer_count2 =
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
// And now the peer should have all transactions
|
||||
let peer_count3 =
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
(peer_count2, peer_count3)
|
||||
};
|
||||
|
||||
// 2 message should be send (in total)
|
||||
assert_eq!(2, queue.read().len());
|
||||
// 1 peer should be updated but only once after inserting new transaction
|
||||
assert_eq!(1, peer_count2);
|
||||
assert_eq!(0, peer_count3);
|
||||
// TRANSACTIONS_PACKET
|
||||
assert_eq!(0x02, queue.read()[0].packet_id);
|
||||
assert_eq!(0x02, queue.read()[1].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_maintain_transations_propagation_stats() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Uncle);
|
||||
client.insert_transaction_to_queue();
|
||||
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
|
||||
let stats = sync.transactions_stats();
|
||||
assert_eq!(
|
||||
stats.len(),
|
||||
1,
|
||||
"Should maintain stats for single transaction."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_propagate_service_transaction_to_selected_peers_only() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.insert_transaction_with_gas_price_to_queue(U256::zero());
|
||||
let block_hash = client.block_hash_delta_minus(1);
|
||||
let mut sync = ChainSync::new(
|
||||
SyncConfig::default(),
|
||||
&client,
|
||||
ForkFilterApi::new_dummy(&client),
|
||||
);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
// when peer#1 is Geth
|
||||
insert_dummy_peer(&mut sync, 1, block_hash);
|
||||
io.peers_info.insert(1, "Geth".to_owned());
|
||||
// and peer#2 is OpenEthereum, accepting service transactions
|
||||
insert_dummy_peer(&mut sync, 2, block_hash);
|
||||
io.peers_info
|
||||
.insert(2, "OpenEthereum/v2.6.0/linux/rustc".to_owned());
|
||||
// and peer#3 is OpenEthereum, accepting service transactions
|
||||
insert_dummy_peer(&mut sync, 3, block_hash);
|
||||
io.peers_info
|
||||
.insert(3, "OpenEthereum/ABCDEFGH/v2.7.3/linux/rustc".to_owned());
|
||||
|
||||
// and new service transaction is propagated to peers
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
|
||||
// peer#2 && peer#3 are receiving service transaction
|
||||
assert!(io
|
||||
.packets
|
||||
.iter()
|
||||
.any(|p| p.packet_id == 0x02 && p.recipient == 2)); // TRANSACTIONS_PACKET
|
||||
assert!(io
|
||||
.packets
|
||||
.iter()
|
||||
.any(|p| p.packet_id == 0x02 && p.recipient == 3)); // TRANSACTIONS_PACKET
|
||||
assert_eq!(io.packets.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_propagate_service_transaction_is_sent_as_separate_message() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
let tx1_hash = client.insert_transaction_to_queue();
|
||||
let tx2_hash = client.insert_transaction_with_gas_price_to_queue(U256::zero());
|
||||
let block_hash = client.block_hash_delta_minus(1);
|
||||
let mut sync = ChainSync::new(
|
||||
SyncConfig::default(),
|
||||
&client,
|
||||
ForkFilterApi::new_dummy(&client),
|
||||
);
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
// when peer#1 is OpenEthereum, accepting service transactions
|
||||
insert_dummy_peer(&mut sync, 1, block_hash);
|
||||
io.peers_info
|
||||
.insert(1, "OpenEthereum/v2.6.0/linux/rustc".to_owned());
|
||||
|
||||
// and service + non-service transactions are propagated to peers
|
||||
SyncPropagator::propagate_new_transactions(&mut sync, &mut io, || true);
|
||||
|
||||
// two separate packets for peer are queued:
|
||||
// 1) with non-service-transaction
|
||||
// 2) with service transaction
|
||||
let sent_transactions: Vec<UnverifiedTransaction> = io
|
||||
.packets
|
||||
.iter()
|
||||
.filter_map(|p| {
|
||||
if p.packet_id != 0x02 || p.recipient != 1 {
|
||||
// TRANSACTIONS_PACKET
|
||||
return None;
|
||||
}
|
||||
|
||||
let rlp = Rlp::new(&*p.data);
|
||||
let item_count = rlp.item_count().unwrap_or(0);
|
||||
if item_count != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
rlp.at(0)
|
||||
.ok()
|
||||
.and_then(|r| TypedTransaction::decode_rlp(&r).ok())
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(sent_transactions.len(), 2);
|
||||
assert!(sent_transactions.iter().any(|tx| tx.hash() == tx1_hash));
|
||||
assert!(sent_transactions.iter().any(|tx| tx.hash() == tx2_hash));
|
||||
}
|
||||
}
|
||||
237
crates/ethcore/sync/src/chain/requester.rs
Normal file
237
crates/ethcore/sync/src/chain/requester.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use block_sync::BlockRequest;
|
||||
use bytes::Bytes;
|
||||
use ethereum_types::H256;
|
||||
use network::PeerId;
|
||||
use rlp::RlpStream;
|
||||
use std::time::Instant;
|
||||
use sync_io::SyncIo;
|
||||
use types::BlockNumber;
|
||||
|
||||
use super::sync_packet::{
|
||||
SyncPacket,
|
||||
SyncPacket::{
|
||||
GetBlockBodiesPacket, GetBlockHeadersPacket, GetReceiptsPacket, GetSnapshotDataPacket,
|
||||
GetSnapshotManifestPacket,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{BlockSet, ChainSync, PeerAsking};
|
||||
|
||||
/// The Chain Sync Requester: requesting data to other peers
|
||||
pub struct SyncRequester;
|
||||
|
||||
impl SyncRequester {
|
||||
/// Perform block download request`
|
||||
pub fn request_blocks(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
request: BlockRequest,
|
||||
block_set: BlockSet,
|
||||
) {
|
||||
match request {
|
||||
BlockRequest::Headers { start, count, skip } => {
|
||||
SyncRequester::request_headers_by_hash(
|
||||
sync, io, peer_id, &start, count, skip, false, block_set,
|
||||
);
|
||||
}
|
||||
BlockRequest::Bodies { hashes } => {
|
||||
SyncRequester::request_bodies(sync, io, peer_id, hashes, block_set);
|
||||
}
|
||||
BlockRequest::Receipts { hashes } => {
|
||||
SyncRequester::request_receipts(sync, io, peer_id, hashes, block_set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request block bodies from a peer
|
||||
fn request_bodies(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
hashes: Vec<H256>,
|
||||
set: BlockSet,
|
||||
) {
|
||||
let mut rlp = RlpStream::new_list(hashes.len());
|
||||
trace!(target: "sync", "{} <- GetBlockBodies: {} entries starting from {:?}, set = {:?}", peer_id, hashes.len(), hashes.first(), set);
|
||||
for h in &hashes {
|
||||
rlp.append(&h.clone());
|
||||
}
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::BlockBodies,
|
||||
GetBlockBodiesPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
let peer = sync.peers.get_mut(&peer_id).expect("peer_id may originate either from on_packet, where it is already validated or from enumerating self.peers. qed");
|
||||
peer.asking_blocks = hashes;
|
||||
peer.block_set = Some(set);
|
||||
}
|
||||
|
||||
/// Request headers from a peer by block number
|
||||
pub fn request_fork_header(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
n: BlockNumber,
|
||||
) {
|
||||
trace!(target: "sync", "{} <- GetForkHeader: at {}", peer_id, n);
|
||||
let mut rlp = RlpStream::new_list(4);
|
||||
rlp.append(&n);
|
||||
rlp.append(&1u32);
|
||||
rlp.append(&0u32);
|
||||
rlp.append(&0u32);
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::ForkHeader,
|
||||
GetBlockHeadersPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Find some headers or blocks to download for a peer.
|
||||
pub fn request_snapshot_data(sync: &mut ChainSync, io: &mut dyn SyncIo, peer_id: PeerId) {
|
||||
// find chunk data to download
|
||||
if let Some(hash) = sync.snapshot.needed_chunk() {
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
peer.asking_snapshot_data = Some(hash.clone());
|
||||
}
|
||||
SyncRequester::request_snapshot_chunk(sync, io, peer_id, &hash);
|
||||
}
|
||||
}
|
||||
|
||||
/// Request snapshot manifest from a peer.
|
||||
pub fn request_snapshot_manifest(sync: &mut ChainSync, io: &mut dyn SyncIo, peer_id: PeerId) {
|
||||
trace!(target: "sync", "{} <- GetSnapshotManifest", peer_id);
|
||||
let rlp = RlpStream::new_list(0);
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::SnapshotManifest,
|
||||
GetSnapshotManifestPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Request headers from a peer by block hash
|
||||
fn request_headers_by_hash(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
h: &H256,
|
||||
count: u64,
|
||||
skip: u64,
|
||||
reverse: bool,
|
||||
set: BlockSet,
|
||||
) {
|
||||
trace!(target: "sync", "{} <- GetBlockHeaders: {} entries starting from {}, set = {:?}", peer_id, count, h, set);
|
||||
let mut rlp = RlpStream::new_list(4);
|
||||
rlp.append(h);
|
||||
rlp.append(&count);
|
||||
rlp.append(&skip);
|
||||
rlp.append(&if reverse { 1u32 } else { 0u32 });
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::BlockHeaders,
|
||||
GetBlockHeadersPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
let peer = sync.peers.get_mut(&peer_id).expect("peer_id may originate either from on_packet, where it is already validated or from enumerating self.peers. qed");
|
||||
peer.asking_hash = Some(h.clone());
|
||||
peer.block_set = Some(set);
|
||||
}
|
||||
|
||||
/// Request block receipts from a peer
|
||||
fn request_receipts(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
hashes: Vec<H256>,
|
||||
set: BlockSet,
|
||||
) {
|
||||
let mut rlp = RlpStream::new_list(hashes.len());
|
||||
trace!(target: "sync", "{} <- GetBlockReceipts: {} entries starting from {:?}, set = {:?}", peer_id, hashes.len(), hashes.first(), set);
|
||||
for h in &hashes {
|
||||
rlp.append(&h.clone());
|
||||
}
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::BlockReceipts,
|
||||
GetReceiptsPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
let peer = sync.peers.get_mut(&peer_id).expect("peer_id may originate either from on_packet, where it is already validated or from enumerating self.peers. qed");
|
||||
peer.asking_blocks = hashes;
|
||||
peer.block_set = Some(set);
|
||||
}
|
||||
|
||||
/// Request snapshot chunk from a peer.
|
||||
fn request_snapshot_chunk(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
chunk: &H256,
|
||||
) {
|
||||
trace!(target: "sync", "{} <- GetSnapshotData {:?}", peer_id, chunk);
|
||||
let mut rlp = RlpStream::new_list(1);
|
||||
rlp.append(chunk);
|
||||
SyncRequester::send_request(
|
||||
sync,
|
||||
io,
|
||||
peer_id,
|
||||
PeerAsking::SnapshotData,
|
||||
GetSnapshotDataPacket,
|
||||
rlp.out(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Generic request sender
|
||||
fn send_request(
|
||||
sync: &mut ChainSync,
|
||||
io: &mut dyn SyncIo,
|
||||
peer_id: PeerId,
|
||||
asking: PeerAsking,
|
||||
packet_id: SyncPacket,
|
||||
packet: Bytes,
|
||||
) {
|
||||
if let Some(ref mut peer) = sync.peers.get_mut(&peer_id) {
|
||||
if peer.asking != PeerAsking::Nothing {
|
||||
warn!(target:"sync", "Asking {:?} while requesting {:?}", peer.asking, asking);
|
||||
}
|
||||
peer.asking = asking;
|
||||
peer.ask_time = Instant::now();
|
||||
|
||||
let result = io.send(peer_id, packet_id, packet);
|
||||
|
||||
if let Err(e) = result {
|
||||
debug!(target:"sync", "Error sending request: {:?}", e);
|
||||
io.disconnect_peer(peer_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
655
crates/ethcore/sync/src/chain/supplier.rs
Normal file
655
crates/ethcore/sync/src/chain/supplier.rs
Normal file
@@ -0,0 +1,655 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
#[cfg(not(test))]
|
||||
use devp2p::PAYLOAD_SOFT_LIMIT;
|
||||
#[cfg(test)]
|
||||
pub const PAYLOAD_SOFT_LIMIT: usize = 100_000;
|
||||
|
||||
use enum_primitive::FromPrimitive;
|
||||
use ethereum_types::H256;
|
||||
use network::{self, PeerId};
|
||||
use parking_lot::RwLock;
|
||||
use rlp::{Rlp, RlpStream};
|
||||
use std::cmp;
|
||||
use types::{ids::BlockId, BlockNumber};
|
||||
|
||||
use sync_io::SyncIo;
|
||||
|
||||
use super::sync_packet::{
|
||||
PacketInfo, SyncPacket,
|
||||
SyncPacket::{
|
||||
BlockBodiesPacket, BlockHeadersPacket, ConsensusDataPacket, GetBlockBodiesPacket,
|
||||
GetBlockHeadersPacket, GetReceiptsPacket, GetSnapshotDataPacket, GetSnapshotManifestPacket,
|
||||
ReceiptsPacket, SnapshotDataPacket, SnapshotManifestPacket, StatusPacket,
|
||||
TransactionsPacket,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
ChainSync, PacketProcessError, RlpResponseResult, SyncHandler, MAX_BODIES_TO_SEND,
|
||||
MAX_HEADERS_TO_SEND, MAX_RECEIPTS_HEADERS_TO_SEND,
|
||||
};
|
||||
|
||||
/// The Chain Sync Supplier: answers requests from peers with available data
|
||||
pub struct SyncSupplier;
|
||||
|
||||
impl SyncSupplier {
|
||||
/// Dispatch incoming requests and responses
|
||||
// Take a u8 and not a SyncPacketId because this is the entry point
|
||||
// to chain sync from the outside world.
|
||||
pub fn dispatch_packet(
|
||||
sync: &RwLock<ChainSync>,
|
||||
io: &mut dyn SyncIo,
|
||||
peer: PeerId,
|
||||
packet_id: u8,
|
||||
data: &[u8],
|
||||
) {
|
||||
let rlp = Rlp::new(data);
|
||||
|
||||
if let Some(id) = SyncPacket::from_u8(packet_id) {
|
||||
let result = match id {
|
||||
GetBlockBodiesPacket => SyncSupplier::return_rlp(
|
||||
io,
|
||||
&rlp,
|
||||
peer,
|
||||
SyncSupplier::return_block_bodies,
|
||||
|e| format!("Error sending block bodies: {:?}", e),
|
||||
),
|
||||
|
||||
GetBlockHeadersPacket => SyncSupplier::return_rlp(
|
||||
io,
|
||||
&rlp,
|
||||
peer,
|
||||
SyncSupplier::return_block_headers,
|
||||
|e| format!("Error sending block headers: {:?}", e),
|
||||
),
|
||||
|
||||
GetReceiptsPacket => {
|
||||
SyncSupplier::return_rlp(io, &rlp, peer, SyncSupplier::return_receipts, |e| {
|
||||
format!("Error sending receipts: {:?}", e)
|
||||
})
|
||||
}
|
||||
GetSnapshotManifestPacket => SyncSupplier::return_rlp(
|
||||
io,
|
||||
&rlp,
|
||||
peer,
|
||||
SyncSupplier::return_snapshot_manifest,
|
||||
|e| format!("Error sending snapshot manifest: {:?}", e),
|
||||
),
|
||||
|
||||
GetSnapshotDataPacket => SyncSupplier::return_rlp(
|
||||
io,
|
||||
&rlp,
|
||||
peer,
|
||||
SyncSupplier::return_snapshot_data,
|
||||
|e| format!("Error sending snapshot data: {:?}", e),
|
||||
),
|
||||
|
||||
StatusPacket => {
|
||||
sync.write().on_packet(io, peer, packet_id, data);
|
||||
Ok(())
|
||||
}
|
||||
// Packets that require the peer to be confirmed
|
||||
_ => {
|
||||
if !sync.read().peers.contains_key(&peer) {
|
||||
debug!(target:"sync", "Unexpected packet {} from unregistered peer: {}:{}", packet_id, peer, io.peer_version(peer));
|
||||
return;
|
||||
}
|
||||
debug!(target: "sync", "{} -> Dispatching packet: {}", peer, packet_id);
|
||||
|
||||
match id {
|
||||
ConsensusDataPacket => SyncHandler::on_consensus_packet(io, peer, &rlp),
|
||||
TransactionsPacket => {
|
||||
let res = {
|
||||
let sync_ro = sync.read();
|
||||
SyncHandler::on_peer_transactions(&*sync_ro, io, peer, &rlp)
|
||||
};
|
||||
if res.is_err() {
|
||||
// peer sent invalid data, disconnect.
|
||||
io.disable_peer(peer);
|
||||
sync.write().deactivate_peer(io, peer);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
sync.write().on_packet(io, peer, packet_id, data);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(PacketProcessError::Decoder(e)) => {
|
||||
debug!(target:"sync", "{} -> Malformed packet {} : {}", peer, packet_id, e)
|
||||
}
|
||||
Err(PacketProcessError::ClientBusy) => {
|
||||
sync.write().add_delayed_request(peer, packet_id, data)
|
||||
}
|
||||
Ok(()) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch delayed request
|
||||
/// The main difference with dispatch packet is the direct send of the responses to the peer
|
||||
pub fn dispatch_delayed_request(
|
||||
sync: &RwLock<ChainSync>,
|
||||
io: &mut dyn SyncIo,
|
||||
peer: PeerId,
|
||||
packet_id: u8,
|
||||
data: &[u8],
|
||||
) {
|
||||
let rlp = Rlp::new(data);
|
||||
|
||||
if let Some(id) = SyncPacket::from_u8(packet_id) {
|
||||
let result = match id {
|
||||
GetBlockHeadersPacket => SyncSupplier::send_rlp(
|
||||
io,
|
||||
&rlp,
|
||||
peer,
|
||||
SyncSupplier::return_block_headers,
|
||||
|e| format!("Error sending block headers: {:?}", e),
|
||||
),
|
||||
|
||||
_ => {
|
||||
debug!(target:"sync", "Unexpected packet {} was dispatched for delayed processing", packet_id);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(PacketProcessError::Decoder(e)) => {
|
||||
debug!(target:"sync", "{} -> Malformed packet {} : {}", peer, packet_id, e)
|
||||
}
|
||||
Err(PacketProcessError::ClientBusy) => {
|
||||
sync.write().add_delayed_request(peer, packet_id, data)
|
||||
}
|
||||
Ok(()) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Respond to GetBlockHeaders request
|
||||
fn return_block_headers(io: &dyn SyncIo, r: &Rlp, peer_id: PeerId) -> RlpResponseResult {
|
||||
// Cannot return blocks, if forks processing is in progress,
|
||||
// The request should be postponed for later processing
|
||||
if io.chain().is_processing_fork() {
|
||||
return Err(PacketProcessError::ClientBusy);
|
||||
}
|
||||
// Packet layout:
|
||||
// [ block: { P , B_32 }, maxHeaders: P, skip: P, reverse: P in { 0 , 1 } ]
|
||||
let max_headers: usize = r.val_at(1)?;
|
||||
let skip: usize = r.val_at(2)?;
|
||||
let reverse: bool = r.val_at(3)?;
|
||||
let last = io.chain().chain_info().best_block_number;
|
||||
let number = if r.at(0)?.size() == 32 {
|
||||
// id is a hash
|
||||
let hash: H256 = r.val_at(0)?;
|
||||
trace!(target: "sync", "{} -> GetBlockHeaders (hash: {}, max: {}, skip: {}, reverse:{})", peer_id, hash, max_headers, skip, reverse);
|
||||
match io.chain().block_header(BlockId::Hash(hash)) {
|
||||
Some(hdr) => {
|
||||
let number = hdr.number().into();
|
||||
debug_assert_eq!(hdr.hash(), hash);
|
||||
|
||||
if max_headers == 1
|
||||
|| io.chain().block_hash(BlockId::Number(number)) != Some(hash)
|
||||
{
|
||||
// Non canonical header or single header requested
|
||||
// TODO: handle single-step reverse hashchains of non-canon hashes
|
||||
trace!(target:"sync", "Returning single header: {:?}", hash);
|
||||
let mut rlp = RlpStream::new_list(1);
|
||||
rlp.append_raw(&hdr.into_inner(), 1);
|
||||
return Ok(Some((BlockHeadersPacket, rlp)));
|
||||
}
|
||||
number
|
||||
}
|
||||
None => return Ok(Some((BlockHeadersPacket, RlpStream::new_list(0)))), //no such header, return nothing
|
||||
}
|
||||
} else {
|
||||
let number = r.val_at::<BlockNumber>(0)?;
|
||||
trace!(target: "sync", "{} -> GetBlockHeaders (number: {}, max: {}, skip: {}, reverse:{})", peer_id, number, max_headers, skip, reverse);
|
||||
number
|
||||
};
|
||||
|
||||
let mut number = if reverse {
|
||||
cmp::min(last, number)
|
||||
} else {
|
||||
cmp::max(0, number)
|
||||
};
|
||||
let max_count = cmp::min(MAX_HEADERS_TO_SEND, max_headers);
|
||||
let mut count = 0;
|
||||
let mut data = Bytes::new();
|
||||
let inc = skip.saturating_add(1) as BlockNumber;
|
||||
let overlay = io.chain_overlay().read();
|
||||
|
||||
// We are checking the `overlay` as well since it's where the ForkBlock
|
||||
// header is cached : so peers can confirm we are on the right fork,
|
||||
// even if we are not synced until the fork block
|
||||
while (number <= last || overlay.contains_key(&number)) && count < max_count {
|
||||
if let Some(hdr) = overlay.get(&number) {
|
||||
trace!(target: "sync", "{}: Returning cached fork header", peer_id);
|
||||
data.extend_from_slice(hdr);
|
||||
count += 1;
|
||||
} else if let Some(hdr) = io.chain().block_header(BlockId::Number(number)) {
|
||||
data.append(&mut hdr.into_inner());
|
||||
count += 1;
|
||||
// Check that the packet won't be oversized
|
||||
if data.len() > PAYLOAD_SOFT_LIMIT {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No required block.
|
||||
break;
|
||||
}
|
||||
if reverse {
|
||||
if number <= inc || number == 0 {
|
||||
break;
|
||||
}
|
||||
number = number.saturating_sub(inc);
|
||||
} else {
|
||||
number = number.saturating_add(inc);
|
||||
}
|
||||
}
|
||||
let mut rlp = RlpStream::new_list(count as usize);
|
||||
rlp.append_raw(&data, count as usize);
|
||||
trace!(target: "sync", "{} -> GetBlockHeaders: returned {} entries", peer_id, count);
|
||||
Ok(Some((BlockHeadersPacket, rlp)))
|
||||
}
|
||||
|
||||
/// Respond to GetBlockBodies request
|
||||
fn return_block_bodies(io: &dyn SyncIo, r: &Rlp, peer_id: PeerId) -> RlpResponseResult {
|
||||
let mut count = r.item_count().unwrap_or(0);
|
||||
if count == 0 {
|
||||
debug!(target: "sync", "Empty GetBlockBodies request, ignoring.");
|
||||
return Ok(None);
|
||||
}
|
||||
count = cmp::min(count, MAX_BODIES_TO_SEND);
|
||||
let mut added = 0usize;
|
||||
let mut data = Bytes::new();
|
||||
for i in 0..count {
|
||||
if let Some(body) = io.chain().block_body(BlockId::Hash(r.val_at::<H256>(i)?)) {
|
||||
data.append(&mut body.into_inner());
|
||||
added += 1;
|
||||
// Check that the packet won't be oversized
|
||||
if data.len() > PAYLOAD_SOFT_LIMIT {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut rlp = RlpStream::new_list(added);
|
||||
rlp.append_raw(&data, added);
|
||||
trace!(target: "sync", "{} -> GetBlockBodies: returned {} entries", peer_id, added);
|
||||
Ok(Some((BlockBodiesPacket, rlp)))
|
||||
}
|
||||
|
||||
fn return_receipts(io: &dyn SyncIo, rlp: &Rlp, peer_id: PeerId) -> RlpResponseResult {
|
||||
let mut count = rlp.item_count().unwrap_or(0);
|
||||
trace!(target: "sync", "{} -> GetReceipts: {} entries", peer_id, count);
|
||||
if count == 0 {
|
||||
debug!(target: "sync", "Empty GetReceipts request, ignoring.");
|
||||
return Ok(None);
|
||||
}
|
||||
count = cmp::min(count, MAX_RECEIPTS_HEADERS_TO_SEND);
|
||||
let mut added_headers = 0usize;
|
||||
let mut data = Bytes::new();
|
||||
let mut total_bytes = 0;
|
||||
for i in 0..count {
|
||||
if let Some(receipts) = io.chain().block_receipts(&rlp.val_at::<H256>(i)?) {
|
||||
let mut receipts_bytes = ::rlp::encode(&receipts);
|
||||
total_bytes += receipts_bytes.len();
|
||||
if total_bytes > PAYLOAD_SOFT_LIMIT {
|
||||
break;
|
||||
}
|
||||
data.append(&mut receipts_bytes);
|
||||
added_headers += 1;
|
||||
}
|
||||
}
|
||||
let mut rlp_result = RlpStream::new_list(added_headers);
|
||||
rlp_result.append_raw(&data, added_headers);
|
||||
Ok(Some((ReceiptsPacket, rlp_result)))
|
||||
}
|
||||
|
||||
/// Respond to GetSnapshotManifest request
|
||||
fn return_snapshot_manifest(io: &dyn SyncIo, r: &Rlp, peer_id: PeerId) -> RlpResponseResult {
|
||||
let count = r.item_count().unwrap_or(0);
|
||||
trace!(target: "warp", "{} -> GetSnapshotManifest", peer_id);
|
||||
if count != 0 {
|
||||
debug!(target: "warp", "Invalid GetSnapshotManifest request, ignoring.");
|
||||
return Ok(None);
|
||||
}
|
||||
let rlp = match io.snapshot_service().manifest() {
|
||||
Some(manifest) => {
|
||||
trace!(target: "warp", "{} <- SnapshotManifest", peer_id);
|
||||
let mut rlp = RlpStream::new_list(1);
|
||||
rlp.append_raw(&manifest.into_rlp(), 1);
|
||||
rlp
|
||||
}
|
||||
None => {
|
||||
trace!(target: "warp", "{}: No snapshot manifest to return", peer_id);
|
||||
RlpStream::new_list(0)
|
||||
}
|
||||
};
|
||||
Ok(Some((SnapshotManifestPacket, rlp)))
|
||||
}
|
||||
|
||||
/// Respond to GetSnapshotData request
|
||||
fn return_snapshot_data(io: &dyn SyncIo, r: &Rlp, peer_id: PeerId) -> RlpResponseResult {
|
||||
let hash: H256 = r.val_at(0)?;
|
||||
trace!(target: "warp", "{} -> GetSnapshotData {:?}", peer_id, hash);
|
||||
let rlp = match io.snapshot_service().chunk(hash) {
|
||||
Some(data) => {
|
||||
let mut rlp = RlpStream::new_list(1);
|
||||
trace!(target: "warp", "{} <- SnapshotData", peer_id);
|
||||
rlp.append(&data);
|
||||
rlp
|
||||
}
|
||||
None => {
|
||||
trace!(target: "warp", "{}: No snapshot data to return", peer_id);
|
||||
RlpStream::new_list(0)
|
||||
}
|
||||
};
|
||||
Ok(Some((SnapshotDataPacket, rlp)))
|
||||
}
|
||||
|
||||
fn return_rlp<FRlp, FError>(
|
||||
io: &mut dyn SyncIo,
|
||||
rlp: &Rlp,
|
||||
peer: PeerId,
|
||||
rlp_func: FRlp,
|
||||
error_func: FError,
|
||||
) -> Result<(), PacketProcessError>
|
||||
where
|
||||
FRlp: Fn(&dyn SyncIo, &Rlp, PeerId) -> RlpResponseResult,
|
||||
FError: FnOnce(network::Error) -> String,
|
||||
{
|
||||
let response = rlp_func(io, rlp, peer);
|
||||
if let Some((packet_id, rlp_stream)) = response? {
|
||||
io.respond(packet_id.id(), rlp_stream.out())
|
||||
.unwrap_or_else(|e| debug!(target: "sync", "{:?}", error_func(e)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_rlp<FRlp, FError>(
|
||||
io: &mut dyn SyncIo,
|
||||
rlp: &Rlp,
|
||||
peer: PeerId,
|
||||
rlp_func: FRlp,
|
||||
error_func: FError,
|
||||
) -> Result<(), PacketProcessError>
|
||||
where
|
||||
FRlp: Fn(&dyn SyncIo, &Rlp, PeerId) -> RlpResponseResult,
|
||||
FError: FnOnce(network::Error) -> String,
|
||||
{
|
||||
let response = rlp_func(io, rlp, peer);
|
||||
match response {
|
||||
Err(e) => Err(e),
|
||||
Ok(Some((packet_id, rlp_stream))) => {
|
||||
io.send(peer, packet_id, rlp_stream.out())
|
||||
.unwrap_or_else(|e| debug!(target: "sync", "{:?}", error_func(e)));
|
||||
Ok(())
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{super::tests::*, *};
|
||||
use blocks::SyncHeader;
|
||||
use bytes::Bytes;
|
||||
use ethcore::client::{BlockChainClient, EachBlockWith, TestBlockChainClient};
|
||||
use ethereum_types::H256;
|
||||
use parking_lot::RwLock;
|
||||
use rlp::{Rlp, RlpStream};
|
||||
use std::collections::VecDeque;
|
||||
use tests::{helpers::TestIo, snapshot::TestSnapshotService};
|
||||
|
||||
#[test]
|
||||
fn return_block_headers() {
|
||||
fn make_hash_req(h: &H256, count: usize, skip: usize, reverse: bool) -> Bytes {
|
||||
let mut rlp = RlpStream::new_list(4);
|
||||
rlp.append(h);
|
||||
rlp.append(&count);
|
||||
rlp.append(&skip);
|
||||
rlp.append(&if reverse { 1u32 } else { 0u32 });
|
||||
rlp.out()
|
||||
}
|
||||
|
||||
fn make_num_req(n: usize, count: usize, skip: usize, reverse: bool) -> Bytes {
|
||||
let mut rlp = RlpStream::new_list(4);
|
||||
rlp.append(&n);
|
||||
rlp.append(&count);
|
||||
rlp.append(&skip);
|
||||
rlp.append(&if reverse { 1u32 } else { 0u32 });
|
||||
rlp.out()
|
||||
}
|
||||
fn to_header_vec(rlp: ::chain::RlpResponseResult) -> Vec<SyncHeader> {
|
||||
Rlp::new(&rlp.unwrap().unwrap().1.out())
|
||||
.iter()
|
||||
.map(|r| SyncHeader::from_rlp(r.as_raw().to_vec()).unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(100, EachBlockWith::Nothing);
|
||||
let blocks: Vec<_> = (0..100)
|
||||
.map(|i| {
|
||||
(&client as &dyn BlockChainClient)
|
||||
.block(BlockId::Number(i as BlockNumber))
|
||||
.map(|b| b.into_inner())
|
||||
.unwrap()
|
||||
})
|
||||
.collect();
|
||||
let headers: Vec<_> = blocks
|
||||
.iter()
|
||||
.map(|b| SyncHeader::from_rlp(Rlp::new(b).at(0).unwrap().as_raw().to_vec()).unwrap())
|
||||
.collect();
|
||||
let hashes: Vec<_> = headers.iter().map(|h| h.header.hash()).collect();
|
||||
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let unknown: H256 = H256::new();
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&unknown, 1, 0, false)),
|
||||
0,
|
||||
);
|
||||
assert!(to_header_vec(result).is_empty());
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&unknown, 1, 0, true)),
|
||||
0,
|
||||
);
|
||||
assert!(to_header_vec(result).is_empty());
|
||||
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&hashes[2], 1, 0, true)),
|
||||
0,
|
||||
);
|
||||
assert_eq!(to_header_vec(result), vec![headers[2].clone()]);
|
||||
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&hashes[2], 1, 0, false)),
|
||||
0,
|
||||
);
|
||||
assert_eq!(to_header_vec(result), vec![headers[2].clone()]);
|
||||
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&hashes[50], 3, 5, false)),
|
||||
0,
|
||||
);
|
||||
assert_eq!(
|
||||
to_header_vec(result),
|
||||
vec![
|
||||
headers[50].clone(),
|
||||
headers[56].clone(),
|
||||
headers[62].clone()
|
||||
]
|
||||
);
|
||||
|
||||
let result = SyncSupplier::return_block_headers(
|
||||
&io,
|
||||
&Rlp::new(&make_hash_req(&hashes[50], 3, 5, true)),
|
||||
0,
|
||||
);
|
||||
assert_eq!(
|
||||
to_header_vec(result),
|
||||
vec![
|
||||
headers[50].clone(),
|
||||
headers[44].clone(),
|
||||
headers[38].clone()
|
||||
]
|
||||
);
|
||||
|
||||
let result =
|
||||
SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(2, 1, 0, true)), 0);
|
||||
assert_eq!(to_header_vec(result), vec![headers[2].clone()]);
|
||||
|
||||
let result =
|
||||
SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(2, 1, 0, false)), 0);
|
||||
assert_eq!(to_header_vec(result), vec![headers[2].clone()]);
|
||||
|
||||
let result =
|
||||
SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(50, 3, 5, false)), 0);
|
||||
assert_eq!(
|
||||
to_header_vec(result),
|
||||
vec![
|
||||
headers[50].clone(),
|
||||
headers[56].clone(),
|
||||
headers[62].clone()
|
||||
]
|
||||
);
|
||||
|
||||
let result =
|
||||
SyncSupplier::return_block_headers(&io, &Rlp::new(&make_num_req(50, 3, 5, true)), 0);
|
||||
assert_eq!(
|
||||
to_header_vec(result),
|
||||
vec![
|
||||
headers[50].clone(),
|
||||
headers[44].clone(),
|
||||
headers[38].clone()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respect_packet_limit() {
|
||||
let small_num_blocks = 10;
|
||||
let large_num_blocks = 50;
|
||||
let tx_per_block = 100;
|
||||
|
||||
let mut client = TestBlockChainClient::new();
|
||||
client.add_blocks(large_num_blocks, EachBlockWith::Transactions(tx_per_block));
|
||||
|
||||
let mut small_rlp_request = RlpStream::new_list(small_num_blocks);
|
||||
let mut large_rlp_request = RlpStream::new_list(large_num_blocks);
|
||||
|
||||
for i in 0..small_num_blocks {
|
||||
let hash: H256 = client.block_hash(BlockId::Number(i as u64)).unwrap();
|
||||
small_rlp_request.append(&hash);
|
||||
large_rlp_request.append(&hash);
|
||||
}
|
||||
|
||||
for i in small_num_blocks..large_num_blocks {
|
||||
let hash: H256 = client.block_hash(BlockId::Number(i as u64)).unwrap();
|
||||
large_rlp_request.append(&hash);
|
||||
}
|
||||
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let small_result =
|
||||
SyncSupplier::return_block_bodies(&io, &Rlp::new(&small_rlp_request.out()), 0);
|
||||
let small_result = small_result.unwrap().unwrap().1;
|
||||
assert_eq!(
|
||||
Rlp::new(&small_result.out()).item_count().unwrap(),
|
||||
small_num_blocks
|
||||
);
|
||||
|
||||
let large_result =
|
||||
SyncSupplier::return_block_bodies(&io, &Rlp::new(&large_rlp_request.out()), 0);
|
||||
let large_result = large_result.unwrap().unwrap().1;
|
||||
assert!(Rlp::new(&large_result.out()).item_count().unwrap() < large_num_blocks);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_receipts_empty() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let ss = TestSnapshotService::new();
|
||||
let io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let result = SyncSupplier::return_receipts(&io, &Rlp::new(&[0xc0]), 0);
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_receipts() {
|
||||
let mut client = TestBlockChainClient::new();
|
||||
let queue = RwLock::new(VecDeque::new());
|
||||
let sync = dummy_sync_with_peer(H256::new(), &client);
|
||||
let ss = TestSnapshotService::new();
|
||||
let mut io = TestIo::new(&mut client, &ss, &queue, None);
|
||||
|
||||
let mut receipt_list = RlpStream::new_list(4);
|
||||
receipt_list.append(&H256::from(
|
||||
"0000000000000000000000000000000000000000000000005555555555555555",
|
||||
));
|
||||
receipt_list.append(&H256::from(
|
||||
"ff00000000000000000000000000000000000000000000000000000000000000",
|
||||
));
|
||||
receipt_list.append(&H256::from(
|
||||
"fff0000000000000000000000000000000000000000000000000000000000000",
|
||||
));
|
||||
receipt_list.append(&H256::from(
|
||||
"aff0000000000000000000000000000000000000000000000000000000000000",
|
||||
));
|
||||
|
||||
let receipts_request = receipt_list.out();
|
||||
// it returns rlp ONLY for hashes started with "f"
|
||||
let result = SyncSupplier::return_receipts(&io, &Rlp::new(&receipts_request.clone()), 0);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let rlp_result = result.unwrap();
|
||||
assert!(rlp_result.is_some());
|
||||
|
||||
// the length of two rlp-encoded receipts
|
||||
assert_eq!(603, rlp_result.unwrap().1.out().len());
|
||||
|
||||
io.sender = Some(2usize);
|
||||
SyncSupplier::dispatch_packet(
|
||||
&RwLock::new(sync),
|
||||
&mut io,
|
||||
0usize,
|
||||
GetReceiptsPacket.id(),
|
||||
&receipts_request,
|
||||
);
|
||||
assert_eq!(1, io.packets.len());
|
||||
}
|
||||
}
|
||||
133
crates/ethcore/sync/src/chain/sync_packet.rs
Normal file
133
crates/ethcore/sync/src/chain/sync_packet.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! When sending packets over p2p we specify both which subprotocol
|
||||
//! to use and what kind of packet we are sending (through a packet id).
|
||||
//! Likewise when receiving packets from other peers we decode the
|
||||
//! subprotocol and the packet id. This module helps coupling both
|
||||
//! pieces of information together and provides an easy mechanism
|
||||
//! to convert to/from the packet id values transmitted over the
|
||||
//! wire.
|
||||
|
||||
#![allow(unused_doc_comments)]
|
||||
|
||||
use api::{ETH_PROTOCOL, PAR_PROTOCOL};
|
||||
use network::{PacketId, ProtocolId};
|
||||
|
||||
// An enum that defines all known packet ids in the context of
|
||||
// synchronization and provides a mechanism to convert from
|
||||
// packet ids (of type PacketId or u8) directly read from the network
|
||||
// to enum variants. This implicitly provides a mechanism to
|
||||
// check whether a given packet id is known, and to prevent
|
||||
// packet id clashes when defining new ids.
|
||||
enum_from_primitive! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum SyncPacket {
|
||||
StatusPacket = 0x00,
|
||||
NewBlockHashesPacket = 0x01,
|
||||
TransactionsPacket = 0x02,
|
||||
GetBlockHeadersPacket = 0x03,
|
||||
BlockHeadersPacket = 0x04,
|
||||
GetBlockBodiesPacket = 0x05,
|
||||
BlockBodiesPacket = 0x06,
|
||||
NewBlockPacket = 0x07,
|
||||
|
||||
//GetNodeDataPacket = 0x0d,
|
||||
//NodeDataPacket = 0x0e,
|
||||
GetReceiptsPacket = 0x0f,
|
||||
ReceiptsPacket = 0x10,
|
||||
|
||||
GetSnapshotManifestPacket = 0x11,
|
||||
SnapshotManifestPacket = 0x12,
|
||||
GetSnapshotDataPacket = 0x13,
|
||||
SnapshotDataPacket = 0x14,
|
||||
ConsensusDataPacket = 0x15,
|
||||
}
|
||||
}
|
||||
|
||||
use self::SyncPacket::*;
|
||||
|
||||
/// Provide both subprotocol and packet id information within the
|
||||
/// same object.
|
||||
pub trait PacketInfo {
|
||||
fn id(&self) -> PacketId;
|
||||
fn protocol(&self) -> ProtocolId;
|
||||
}
|
||||
|
||||
// The mechanism to match packet ids and protocol may be improved
|
||||
// through some macro magic, but for now this works.
|
||||
impl PacketInfo for SyncPacket {
|
||||
fn protocol(&self) -> ProtocolId {
|
||||
match self {
|
||||
StatusPacket
|
||||
| NewBlockHashesPacket
|
||||
| TransactionsPacket
|
||||
| GetBlockHeadersPacket
|
||||
| BlockHeadersPacket
|
||||
| GetBlockBodiesPacket
|
||||
| BlockBodiesPacket
|
||||
| NewBlockPacket
|
||||
//| GetNodeDataPacket
|
||||
//| NodeDataPacket
|
||||
| GetReceiptsPacket
|
||||
| ReceiptsPacket => ETH_PROTOCOL,
|
||||
|
||||
GetSnapshotManifestPacket
|
||||
| SnapshotManifestPacket
|
||||
| GetSnapshotDataPacket
|
||||
| SnapshotDataPacket
|
||||
| ConsensusDataPacket => PAR_PROTOCOL,
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> PacketId {
|
||||
(*self) as PacketId
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use enum_primitive::FromPrimitive;
|
||||
|
||||
#[test]
|
||||
fn packet_ids_from_u8_when_from_primitive_zero_then_equals_status_packet() {
|
||||
assert_eq!(SyncPacket::from_u8(0x00), Some(StatusPacket));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_ids_from_u8_when_from_primitive_eleven_then_equals_get_snapshot_manifest_packet() {
|
||||
assert_eq!(SyncPacket::from_u8(0x11), Some(GetSnapshotManifestPacket));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn packet_ids_from_u8_when_invalid_packet_id_then_none() {
|
||||
assert!(SyncPacket::from_u8(0x99).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_status_packet_then_id_and_protocol_match() {
|
||||
assert_eq!(StatusPacket.id(), StatusPacket as PacketId);
|
||||
assert_eq!(StatusPacket.protocol(), ETH_PROTOCOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_consensus_data_packet_then_id_and_protocol_match() {
|
||||
assert_eq!(ConsensusDataPacket.id(), ConsensusDataPacket as PacketId);
|
||||
assert_eq!(ConsensusDataPacket.protocol(), PAR_PROTOCOL);
|
||||
}
|
||||
}
|
||||
78
crates/ethcore/sync/src/lib.rs
Normal file
78
crates/ethcore/sync/src/lib.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Blockchain sync module
|
||||
//! Implements ethereum protocol version 63 as specified here:
|
||||
//! https://github.com/ethereum/wiki/wiki/Ethereum-Wire-Protocol
|
||||
//!
|
||||
|
||||
extern crate common_types as types;
|
||||
extern crate derive_more;
|
||||
extern crate ethcore;
|
||||
extern crate ethcore_io as io;
|
||||
extern crate ethcore_network as network;
|
||||
extern crate ethcore_network_devp2p as devp2p;
|
||||
extern crate ethereum_forkid;
|
||||
extern crate ethereum_types;
|
||||
extern crate ethkey;
|
||||
extern crate ethstore;
|
||||
extern crate fastmap;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate parity_bytes as bytes;
|
||||
extern crate parking_lot;
|
||||
extern crate primitive_types07;
|
||||
extern crate rand;
|
||||
extern crate rlp;
|
||||
extern crate rlp04;
|
||||
extern crate stats;
|
||||
extern crate triehash_ethereum;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
#[cfg(test)]
|
||||
extern crate kvdb_memorydb;
|
||||
#[cfg(test)]
|
||||
extern crate rustc_hex;
|
||||
|
||||
#[macro_use]
|
||||
extern crate enum_primitive;
|
||||
#[macro_use]
|
||||
extern crate macros;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate heapsize;
|
||||
#[macro_use]
|
||||
extern crate trace_time;
|
||||
|
||||
mod block_sync;
|
||||
mod blocks;
|
||||
mod chain;
|
||||
mod snapshot;
|
||||
mod sync_io;
|
||||
mod transactions_stats;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod api;
|
||||
|
||||
pub use api::*;
|
||||
pub use chain::{SyncState, SyncStatus};
|
||||
pub use devp2p::validate_node_url;
|
||||
pub use network::{ConnectionDirection, ConnectionFilter, Error, ErrorKind, NonReservedPeerMode};
|
||||
32
crates/ethcore/sync/src/res/private_spec.json
Normal file
32
crates/ethcore/sync/src/res/private_spec.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "PrivateTransactions",
|
||||
"engine": {
|
||||
"instantSeal": {
|
||||
"params": {}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"accountStartNonce": "0x0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x11"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"generic": "0x0"
|
||||
},
|
||||
"difficulty": "0x20000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x989680"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }
|
||||
}
|
||||
}
|
||||
285
crates/ethcore/sync/src/snapshot.rs
Normal file
285
crates/ethcore/sync/src/snapshot.rs
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use ethcore::snapshot::{ManifestData, SnapshotService};
|
||||
use ethereum_types::H256;
|
||||
use hash::keccak;
|
||||
|
||||
use std::{collections::HashSet, iter::FromIterator};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum ChunkType {
|
||||
State(H256),
|
||||
Block(H256),
|
||||
}
|
||||
|
||||
pub struct Snapshot {
|
||||
pending_state_chunks: Vec<H256>,
|
||||
pending_block_chunks: Vec<H256>,
|
||||
downloading_chunks: HashSet<H256>,
|
||||
completed_chunks: HashSet<H256>,
|
||||
snapshot_hash: Option<H256>,
|
||||
bad_hashes: HashSet<H256>,
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl Snapshot {
|
||||
/// Create a new instance.
|
||||
pub fn new() -> Snapshot {
|
||||
Snapshot {
|
||||
pending_state_chunks: Vec::new(),
|
||||
pending_block_chunks: Vec::new(),
|
||||
downloading_chunks: HashSet::new(),
|
||||
completed_chunks: HashSet::new(),
|
||||
snapshot_hash: None,
|
||||
bad_hashes: HashSet::new(),
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sync the Snapshot completed chunks with the Snapshot Service
|
||||
pub fn initialize(&mut self, snapshot_service: &dyn SnapshotService) {
|
||||
if self.initialized {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(completed_chunks) = snapshot_service.completed_chunks() {
|
||||
self.completed_chunks = HashSet::from_iter(completed_chunks);
|
||||
}
|
||||
|
||||
trace!(
|
||||
target: "snapshot",
|
||||
"Snapshot is now initialized with {} completed chunks.",
|
||||
self.completed_chunks.len(),
|
||||
);
|
||||
|
||||
self.initialized = true;
|
||||
}
|
||||
|
||||
/// Clear everything.
|
||||
pub fn clear(&mut self) {
|
||||
self.pending_state_chunks.clear();
|
||||
self.pending_block_chunks.clear();
|
||||
self.downloading_chunks.clear();
|
||||
self.completed_chunks.clear();
|
||||
self.snapshot_hash = None;
|
||||
self.initialized = false;
|
||||
}
|
||||
|
||||
/// Check if currently downloading a snapshot.
|
||||
pub fn have_manifest(&self) -> bool {
|
||||
self.snapshot_hash.is_some()
|
||||
}
|
||||
|
||||
/// Reset collection for a manifest RLP
|
||||
pub fn reset_to(&mut self, manifest: &ManifestData, hash: &H256) {
|
||||
self.clear();
|
||||
self.pending_state_chunks = manifest.state_hashes.clone();
|
||||
self.pending_block_chunks = manifest.block_hashes.clone();
|
||||
self.snapshot_hash = Some(hash.clone());
|
||||
}
|
||||
|
||||
/// Validate chunk and mark it as downloaded
|
||||
pub fn validate_chunk(&mut self, chunk: &[u8]) -> Result<ChunkType, ()> {
|
||||
let hash = keccak(chunk);
|
||||
if self.completed_chunks.contains(&hash) {
|
||||
trace!(target: "sync", "Ignored proccessed chunk: {:x}", hash);
|
||||
return Err(());
|
||||
}
|
||||
self.downloading_chunks.remove(&hash);
|
||||
if self.pending_block_chunks.iter().any(|h| h == &hash) {
|
||||
self.completed_chunks.insert(hash.clone());
|
||||
return Ok(ChunkType::Block(hash));
|
||||
}
|
||||
if self.pending_state_chunks.iter().any(|h| h == &hash) {
|
||||
self.completed_chunks.insert(hash.clone());
|
||||
return Ok(ChunkType::State(hash));
|
||||
}
|
||||
trace!(target: "sync", "Ignored unknown chunk: {:x}", hash);
|
||||
Err(())
|
||||
}
|
||||
|
||||
/// Find a chunk to download
|
||||
pub fn needed_chunk(&mut self) -> Option<H256> {
|
||||
// Find next needed chunk: first block, then state chunks
|
||||
let chunk = {
|
||||
let chunk_filter =
|
||||
|h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h);
|
||||
|
||||
let needed_block_chunk = self
|
||||
.pending_block_chunks
|
||||
.iter()
|
||||
.filter(|&h| chunk_filter(h))
|
||||
.map(|h| *h)
|
||||
.next();
|
||||
|
||||
// If no block chunks to download, get the state chunks
|
||||
if needed_block_chunk.is_none() {
|
||||
self.pending_state_chunks
|
||||
.iter()
|
||||
.filter(|&h| chunk_filter(h))
|
||||
.map(|h| *h)
|
||||
.next()
|
||||
} else {
|
||||
needed_block_chunk
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(hash) = chunk {
|
||||
self.downloading_chunks.insert(hash.clone());
|
||||
}
|
||||
chunk
|
||||
}
|
||||
|
||||
pub fn clear_chunk_download(&mut self, hash: &H256) {
|
||||
self.downloading_chunks.remove(hash);
|
||||
}
|
||||
|
||||
// note snapshot hash as bad.
|
||||
pub fn note_bad(&mut self, hash: H256) {
|
||||
self.bad_hashes.insert(hash);
|
||||
}
|
||||
|
||||
// whether snapshot hash is known to be bad.
|
||||
pub fn is_known_bad(&self, hash: &H256) -> bool {
|
||||
self.bad_hashes.contains(hash)
|
||||
}
|
||||
|
||||
pub fn snapshot_hash(&self) -> Option<H256> {
|
||||
self.snapshot_hash
|
||||
}
|
||||
|
||||
pub fn total_chunks(&self) -> usize {
|
||||
self.pending_block_chunks.len() + self.pending_state_chunks.len()
|
||||
}
|
||||
|
||||
pub fn done_chunks(&self) -> usize {
|
||||
self.completed_chunks.len()
|
||||
}
|
||||
|
||||
pub fn is_complete(&self) -> bool {
|
||||
self.total_chunks() == self.completed_chunks.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
use ethcore::snapshot::ManifestData;
|
||||
use hash::keccak;
|
||||
|
||||
fn is_empty(snapshot: &Snapshot) -> bool {
|
||||
snapshot.pending_block_chunks.is_empty()
|
||||
&& snapshot.pending_state_chunks.is_empty()
|
||||
&& snapshot.completed_chunks.is_empty()
|
||||
&& snapshot.downloading_chunks.is_empty()
|
||||
&& snapshot.snapshot_hash.is_none()
|
||||
}
|
||||
|
||||
fn test_manifest() -> (ManifestData, H256, Vec<Bytes>, Vec<Bytes>) {
|
||||
let state_chunks: Vec<Bytes> = (0..20).map(|_| H256::random().to_vec()).collect();
|
||||
let block_chunks: Vec<Bytes> = (0..20).map(|_| H256::random().to_vec()).collect();
|
||||
let manifest = ManifestData {
|
||||
version: 2,
|
||||
state_hashes: state_chunks.iter().map(|data| keccak(data)).collect(),
|
||||
block_hashes: block_chunks.iter().map(|data| keccak(data)).collect(),
|
||||
state_root: H256::new(),
|
||||
block_number: 42,
|
||||
block_hash: H256::new(),
|
||||
};
|
||||
let mhash = keccak(manifest.clone().into_rlp());
|
||||
(manifest, mhash, state_chunks, block_chunks)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_clear() {
|
||||
let mut snapshot = Snapshot::new();
|
||||
assert!(is_empty(&snapshot));
|
||||
let (manifest, mhash, _, _) = test_manifest();
|
||||
snapshot.reset_to(&manifest, &mhash);
|
||||
assert!(!is_empty(&snapshot));
|
||||
snapshot.clear();
|
||||
assert!(is_empty(&snapshot));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_chunks() {
|
||||
let mut snapshot = Snapshot::new();
|
||||
let (manifest, mhash, state_chunks, block_chunks) = test_manifest();
|
||||
snapshot.reset_to(&manifest, &mhash);
|
||||
assert_eq!(snapshot.done_chunks(), 0);
|
||||
assert!(snapshot.validate_chunk(&H256::random().to_vec()).is_err());
|
||||
|
||||
let requested: Vec<H256> = (0..40).map(|_| snapshot.needed_chunk().unwrap()).collect();
|
||||
assert!(snapshot.needed_chunk().is_none());
|
||||
|
||||
let requested_all_block_chunks = manifest
|
||||
.block_hashes
|
||||
.iter()
|
||||
.all(|h| requested.iter().any(|rh| rh == h));
|
||||
assert!(requested_all_block_chunks);
|
||||
|
||||
let requested_all_state_chunks = manifest
|
||||
.state_hashes
|
||||
.iter()
|
||||
.all(|h| requested.iter().any(|rh| rh == h));
|
||||
assert!(requested_all_state_chunks);
|
||||
|
||||
assert_eq!(snapshot.downloading_chunks.len(), 40);
|
||||
|
||||
assert_eq!(
|
||||
snapshot.validate_chunk(&state_chunks[4]),
|
||||
Ok(ChunkType::State(manifest.state_hashes[4].clone()))
|
||||
);
|
||||
assert_eq!(snapshot.completed_chunks.len(), 1);
|
||||
assert_eq!(snapshot.downloading_chunks.len(), 39);
|
||||
|
||||
assert_eq!(
|
||||
snapshot.validate_chunk(&block_chunks[10]),
|
||||
Ok(ChunkType::Block(manifest.block_hashes[10].clone()))
|
||||
);
|
||||
assert_eq!(snapshot.completed_chunks.len(), 2);
|
||||
assert_eq!(snapshot.downloading_chunks.len(), 38);
|
||||
|
||||
for (i, data) in state_chunks.iter().enumerate() {
|
||||
if i != 4 {
|
||||
assert!(snapshot.validate_chunk(data).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
for (i, data) in block_chunks.iter().enumerate() {
|
||||
if i != 10 {
|
||||
assert!(snapshot.validate_chunk(data).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
assert!(snapshot.is_complete());
|
||||
assert_eq!(snapshot.done_chunks(), 40);
|
||||
assert_eq!(snapshot.done_chunks(), snapshot.total_chunks());
|
||||
assert_eq!(snapshot.snapshot_hash(), Some(keccak(manifest.into_rlp())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracks_known_bad() {
|
||||
let mut snapshot = Snapshot::new();
|
||||
let hash = H256::random();
|
||||
|
||||
assert_eq!(snapshot.is_known_bad(&hash), false);
|
||||
snapshot.note_bad(hash);
|
||||
assert_eq!(snapshot.is_known_bad(&hash), true);
|
||||
}
|
||||
}
|
||||
133
crates/ethcore/sync/src/sync_io.rs
Normal file
133
crates/ethcore/sync/src/sync_io.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use bytes::Bytes;
|
||||
use chain::sync_packet::{PacketInfo, SyncPacket};
|
||||
use ethcore::{client::BlockChainClient, snapshot::SnapshotService};
|
||||
use network::{
|
||||
client_version::ClientVersion, Error, NetworkContext, PacketId, PeerId, ProtocolId, SessionInfo,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use types::BlockNumber;
|
||||
|
||||
/// IO interface for the syncing handler.
|
||||
/// Provides peer connection management and an interface to the blockchain client.
|
||||
// TODO: ratings
|
||||
pub trait SyncIo {
|
||||
/// Disable a peer
|
||||
fn disable_peer(&mut self, peer_id: PeerId);
|
||||
/// Disconnect peer
|
||||
fn disconnect_peer(&mut self, peer_id: PeerId);
|
||||
/// Respond to current request with a packet. Can be called from an IO handler for incoming packet.
|
||||
fn respond(&mut self, packet_id: PacketId, data: Vec<u8>) -> Result<(), Error>;
|
||||
/// Send a packet to a peer using specified protocol.
|
||||
fn send(&mut self, peer_id: PeerId, packet_id: SyncPacket, data: Vec<u8>) -> Result<(), Error>;
|
||||
/// Get the blockchain
|
||||
fn chain(&self) -> &dyn BlockChainClient;
|
||||
/// Get the snapshot service.
|
||||
fn snapshot_service(&self) -> &dyn SnapshotService;
|
||||
/// Returns peer version identifier
|
||||
fn peer_version(&self, peer_id: PeerId) -> ClientVersion {
|
||||
ClientVersion::from(peer_id.to_string())
|
||||
}
|
||||
/// Returns information on p2p session
|
||||
fn peer_session_info(&self, peer_id: PeerId) -> Option<SessionInfo>;
|
||||
/// Maximum mutually supported version of a gien protocol.
|
||||
fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8;
|
||||
/// Returns if the chain block queue empty
|
||||
fn is_chain_queue_empty(&self) -> bool {
|
||||
self.chain().is_queue_empty()
|
||||
}
|
||||
/// Check if the session is expired
|
||||
fn is_expired(&self) -> bool;
|
||||
/// Return sync overlay
|
||||
fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>>;
|
||||
}
|
||||
|
||||
/// Wraps `NetworkContext` and the blockchain client
|
||||
pub struct NetSyncIo<'s> {
|
||||
network: &'s dyn NetworkContext,
|
||||
chain: &'s dyn BlockChainClient,
|
||||
snapshot_service: &'s dyn SnapshotService,
|
||||
chain_overlay: &'s RwLock<HashMap<BlockNumber, Bytes>>,
|
||||
}
|
||||
|
||||
impl<'s> NetSyncIo<'s> {
|
||||
/// Creates a new instance from the `NetworkContext` and the blockchain client reference.
|
||||
pub fn new(
|
||||
network: &'s dyn NetworkContext,
|
||||
chain: &'s dyn BlockChainClient,
|
||||
snapshot_service: &'s dyn SnapshotService,
|
||||
chain_overlay: &'s RwLock<HashMap<BlockNumber, Bytes>>,
|
||||
) -> NetSyncIo<'s> {
|
||||
NetSyncIo {
|
||||
network: network,
|
||||
chain: chain,
|
||||
snapshot_service: snapshot_service,
|
||||
chain_overlay: chain_overlay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> SyncIo for NetSyncIo<'s> {
|
||||
fn disable_peer(&mut self, peer_id: PeerId) {
|
||||
self.network.disable_peer(peer_id);
|
||||
}
|
||||
|
||||
fn disconnect_peer(&mut self, peer_id: PeerId) {
|
||||
self.network.disconnect_peer(peer_id);
|
||||
}
|
||||
|
||||
fn respond(&mut self, packet_id: PacketId, data: Vec<u8>) -> Result<(), Error> {
|
||||
self.network.respond(packet_id, data)
|
||||
}
|
||||
|
||||
fn send(&mut self, peer_id: PeerId, packet_id: SyncPacket, data: Vec<u8>) -> Result<(), Error> {
|
||||
self.network
|
||||
.send_protocol(packet_id.protocol(), peer_id, packet_id.id(), data)
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn BlockChainClient {
|
||||
self.chain
|
||||
}
|
||||
|
||||
fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>> {
|
||||
self.chain_overlay
|
||||
}
|
||||
|
||||
fn snapshot_service(&self) -> &dyn SnapshotService {
|
||||
self.snapshot_service
|
||||
}
|
||||
|
||||
fn peer_session_info(&self, peer_id: PeerId) -> Option<SessionInfo> {
|
||||
self.network.session_info(peer_id)
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
self.network.is_expired()
|
||||
}
|
||||
|
||||
fn protocol_version(&self, protocol: &ProtocolId, peer_id: PeerId) -> u8 {
|
||||
self.network
|
||||
.protocol_version(*protocol, peer_id)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
fn peer_version(&self, peer_id: PeerId) -> ClientVersion {
|
||||
self.network.peer_client_version(peer_id)
|
||||
}
|
||||
}
|
||||
301
crates/ethcore/sync/src/tests/chain.rs
Normal file
301
crates/ethcore/sync/src/tests/chain.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::helpers::*;
|
||||
use chain::SyncState;
|
||||
use ethcore::client::{
|
||||
BlockChainClient, BlockId, BlockInfo, ChainInfo, EachBlockWith, TestBlockChainClient,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use SyncConfig;
|
||||
use WarpSync;
|
||||
|
||||
#[test]
|
||||
fn two_peers() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
assert!(net.peer(0).chain.block(BlockId::Number(1000)).is_some());
|
||||
assert_eq!(
|
||||
*net.peer(0).chain.blocks.read(),
|
||||
*net.peer(1).chain.blocks.read()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn long_chain() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(1).chain.add_blocks(50000, EachBlockWith::Nothing);
|
||||
net.sync();
|
||||
assert!(net.peer(0).chain.block(BlockId::Number(50000)).is_some());
|
||||
assert_eq!(
|
||||
*net.peer(0).chain.blocks.read(),
|
||||
*net.peer(1).chain.blocks.read()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_after_sync() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
let status = net.peer(0).sync.read().status();
|
||||
assert_eq!(status.state, SyncState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_few_steps() {
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(1).chain.add_blocks(100, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(100, EachBlockWith::Uncle);
|
||||
let total_steps = net.sync();
|
||||
assert!(total_steps < 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_blocks() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
for n in 0..200 {
|
||||
let with = if n % 2 == 0 {
|
||||
EachBlockWith::Nothing
|
||||
} else {
|
||||
EachBlockWith::Uncle
|
||||
};
|
||||
net.peer(1).chain.add_blocks(5, with.clone());
|
||||
net.peer(2).chain.add_blocks(5, with);
|
||||
}
|
||||
net.sync();
|
||||
assert!(net.peer(0).chain.block(BlockId::Number(1000)).is_some());
|
||||
assert_eq!(
|
||||
*net.peer(0).chain.blocks.read(),
|
||||
*net.peer(1).chain.blocks.read()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forked() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(0).chain.add_blocks(30, EachBlockWith::Uncle);
|
||||
net.peer(1).chain.add_blocks(30, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(30, EachBlockWith::Uncle);
|
||||
net.peer(0).chain.add_blocks(10, EachBlockWith::Nothing); //fork
|
||||
net.peer(1).chain.add_blocks(20, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle);
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle); //fork between 1 and 2
|
||||
net.peer(2).chain.add_blocks(1, EachBlockWith::Nothing);
|
||||
// peer 1 has the best chain of 601 blocks
|
||||
let peer1_chain = net.peer(1).chain.numbers.read().clone();
|
||||
net.sync();
|
||||
assert_eq!(
|
||||
*net.peer(0).chain.difficulty.read(),
|
||||
*net.peer(1).chain.difficulty.read()
|
||||
);
|
||||
assert_eq!(&*net.peer(0).chain.numbers.read(), &peer1_chain);
|
||||
assert_eq!(&*net.peer(1).chain.numbers.read(), &peer1_chain);
|
||||
assert_eq!(&*net.peer(2).chain.numbers.read(), &peer1_chain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forked_with_misbehaving_peer() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
|
||||
let mut alt_spec = ::ethcore::spec::Spec::new_test();
|
||||
alt_spec.extra_data = b"fork".to_vec();
|
||||
// peer 0 is on a totally different chain with higher total difficulty
|
||||
net.peer_mut(0).chain = Arc::new(TestBlockChainClient::new_with_spec(alt_spec));
|
||||
net.peer(0).chain.add_blocks(50, EachBlockWith::Nothing);
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing);
|
||||
net.peer(2).chain.add_blocks(10, EachBlockWith::Nothing);
|
||||
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing);
|
||||
net.peer(2).chain.add_blocks(20, EachBlockWith::Uncle);
|
||||
// peer 1 should sync to peer 2, others should not change
|
||||
let peer0_chain = net.peer(0).chain.numbers.read().clone();
|
||||
let peer2_chain = net.peer(2).chain.numbers.read().clone();
|
||||
net.sync();
|
||||
assert_eq!(&*net.peer(0).chain.numbers.read(), &peer0_chain);
|
||||
assert_eq!(&*net.peer(1).chain.numbers.read(), &peer2_chain);
|
||||
assert_eq!(&*net.peer(2).chain.numbers.read(), &peer2_chain);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn net_hard_fork() {
|
||||
::env_logger::try_init().ok();
|
||||
let ref_client = TestBlockChainClient::new();
|
||||
ref_client.add_blocks(50, EachBlockWith::Uncle);
|
||||
{
|
||||
let mut net = TestNet::new_with_fork(
|
||||
2,
|
||||
Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap())),
|
||||
);
|
||||
net.peer(0).chain.add_blocks(100, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 100);
|
||||
}
|
||||
{
|
||||
let mut net = TestNet::new_with_fork(
|
||||
2,
|
||||
Some((50, ref_client.block_hash(BlockId::Number(50)).unwrap())),
|
||||
);
|
||||
net.peer(0).chain.add_blocks(100, EachBlockWith::Nothing);
|
||||
net.sync();
|
||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(3);
|
||||
net.peer(1).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
net.peer(2).chain.add_blocks(1000, EachBlockWith::Uncle);
|
||||
|
||||
net.sync();
|
||||
|
||||
// make sure that sync has actually happened
|
||||
assert!(net.peer(0).chain.chain_info().best_block_number > 100);
|
||||
net.restart_peer(0);
|
||||
|
||||
let status = net.peer(0).sync.read().status();
|
||||
assert_eq!(status.state, SyncState::Idle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_empty() {
|
||||
let net = TestNet::new(2);
|
||||
assert_eq!(net.peer(0).sync.read().status().state, SyncState::Idle);
|
||||
let mut config = SyncConfig::default();
|
||||
config.warp_sync = WarpSync::Enabled;
|
||||
let net = TestNet::new_with_config(2, config);
|
||||
assert_eq!(
|
||||
net.peer(0).sync.read().status().state,
|
||||
SyncState::WaitingPeers
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_packet() {
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(0).chain.add_blocks(100, EachBlockWith::Uncle);
|
||||
net.peer(1).chain.add_blocks(1, EachBlockWith::Uncle);
|
||||
|
||||
net.start();
|
||||
|
||||
net.sync_step_peer(0);
|
||||
|
||||
assert_eq!(1, net.peer(0).queue.read().len());
|
||||
assert_eq!(0x00, net.peer(0).queue.read()[0].packet_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagate_hashes() {
|
||||
let mut net = TestNet::new(6);
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
|
||||
net.peer(0).chain.add_blocks(10, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
net.trigger_chain_new_blocks(0); //first event just sets the marker
|
||||
net.trigger_chain_new_blocks(0);
|
||||
|
||||
// 5 peers with NewHahses, 4 with blocks
|
||||
assert_eq!(9, net.peer(0).queue.read().len());
|
||||
let mut hashes = 0;
|
||||
let mut blocks = 0;
|
||||
for i in 0..net.peer(0).queue.read().len() {
|
||||
if net.peer(0).queue.read()[i].packet_id == 0x1 {
|
||||
hashes += 1;
|
||||
}
|
||||
if net.peer(0).queue.read()[i].packet_id == 0x7 {
|
||||
blocks += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(blocks, 4);
|
||||
assert_eq!(hashes, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propagate_blocks() {
|
||||
let mut net = TestNet::new(20);
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Uncle);
|
||||
net.sync();
|
||||
|
||||
net.peer(0).chain.add_blocks(10, EachBlockWith::Uncle);
|
||||
net.trigger_chain_new_blocks(0); //first event just sets the marker
|
||||
net.trigger_chain_new_blocks(0);
|
||||
|
||||
assert!(!net.peer(0).queue.read().is_empty());
|
||||
// NEW_BLOCK_PACKET
|
||||
let blocks = net
|
||||
.peer(0)
|
||||
.queue
|
||||
.read()
|
||||
.iter()
|
||||
.filter(|p| p.packet_id == 0x7)
|
||||
.count();
|
||||
assert!(blocks > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restart_on_malformed_block() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(1).chain.add_blocks(5, EachBlockWith::Nothing);
|
||||
net.peer(1)
|
||||
.chain
|
||||
.add_block(EachBlockWith::Nothing, |mut header| {
|
||||
header
|
||||
.set_extra_data(b"This extra data is way too long to be considered valid".to_vec());
|
||||
header
|
||||
});
|
||||
net.sync_steps(20);
|
||||
|
||||
// This gets accepted just fine since the TestBlockChainClient performs no validation.
|
||||
// Probably remove this test?
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_on_broken_chain() {
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(1).chain.add_blocks(10, EachBlockWith::Nothing);
|
||||
net.peer(1).chain.corrupt_block_parent(6);
|
||||
net.sync_steps(20);
|
||||
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disconnect_on_unrelated_chain() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut net = TestNet::new(2);
|
||||
net.peer(0).chain.set_history(Some(20));
|
||||
net.peer(1).chain.set_history(Some(20));
|
||||
net.restart_peer(0);
|
||||
net.restart_peer(1);
|
||||
net.peer(0).chain.add_blocks(500, EachBlockWith::Uncle);
|
||||
net.peer(1).chain.add_blocks(300, EachBlockWith::Nothing);
|
||||
net.sync();
|
||||
assert_eq!(net.disconnect_events, vec![(0, 0)]);
|
||||
}
|
||||
174
crates/ethcore/sync/src/tests/consensus.rs
Normal file
174
crates/ethcore/sync/src/tests/consensus.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::helpers::*;
|
||||
use ethcore::{
|
||||
client::{ChainInfo, ClientIoMessage},
|
||||
engines,
|
||||
miner::{self, MinerService},
|
||||
spec::Spec,
|
||||
};
|
||||
use ethereum_types::{Address, U256};
|
||||
use ethkey::{KeyPair, Secret};
|
||||
use hash::keccak;
|
||||
use io::{IoChannel, IoHandler};
|
||||
use std::sync::Arc;
|
||||
use types::transaction::{Action, PendingTransaction, Transaction, TypedTransaction};
|
||||
use SyncConfig;
|
||||
|
||||
fn new_tx(secret: &Secret, nonce: U256, chain_id: u64) -> PendingTransaction {
|
||||
let signed = TypedTransaction::Legacy(Transaction {
|
||||
nonce: nonce.into(),
|
||||
gas_price: 0.into(),
|
||||
gas: 21000.into(),
|
||||
action: Action::Call(Address::default()),
|
||||
value: 0.into(),
|
||||
data: Vec::new(),
|
||||
})
|
||||
.sign(secret, Some(chain_id));
|
||||
PendingTransaction::new(signed, None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authority_round() {
|
||||
let s0 = KeyPair::from_secret_slice(&keccak("1")).unwrap();
|
||||
let s1 = KeyPair::from_secret_slice(&keccak("0")).unwrap();
|
||||
|
||||
let chain_id = Spec::new_test_round().chain_id();
|
||||
let mut net = TestNet::with_spec(2, SyncConfig::default(), Spec::new_test_round);
|
||||
let io_handler0: Arc<dyn IoHandler<ClientIoMessage>> =
|
||||
Arc::new(TestIoHandler::new(net.peer(0).chain.clone()));
|
||||
let io_handler1: Arc<dyn IoHandler<ClientIoMessage>> =
|
||||
Arc::new(TestIoHandler::new(net.peer(1).chain.clone()));
|
||||
// Push transaction to both clients. Only one of them gets lucky to produce a block.
|
||||
net.peer(0)
|
||||
.miner
|
||||
.set_author(miner::Author::Sealer(engines::signer::from_keypair(
|
||||
s0.clone(),
|
||||
)));
|
||||
net.peer(1)
|
||||
.miner
|
||||
.set_author(miner::Author::Sealer(engines::signer::from_keypair(
|
||||
s1.clone(),
|
||||
)));
|
||||
net.peer(0)
|
||||
.chain
|
||||
.engine()
|
||||
.register_client(Arc::downgrade(&net.peer(0).chain) as _);
|
||||
net.peer(1)
|
||||
.chain
|
||||
.engine()
|
||||
.register_client(Arc::downgrade(&net.peer(1).chain) as _);
|
||||
net.peer(0)
|
||||
.chain
|
||||
.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
|
||||
net.peer(1)
|
||||
.chain
|
||||
.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
|
||||
// exchange statuses
|
||||
net.sync();
|
||||
// Trigger block proposal
|
||||
net.peer(0)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id))
|
||||
.unwrap();
|
||||
net.peer(1)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id))
|
||||
.unwrap();
|
||||
// Sync a block
|
||||
net.sync();
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1);
|
||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1);
|
||||
|
||||
net.peer(0)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id))
|
||||
.unwrap();
|
||||
net.peer(1)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id))
|
||||
.unwrap();
|
||||
// Move to next proposer step.
|
||||
net.peer(0).chain.engine().step();
|
||||
net.peer(1).chain.engine().step();
|
||||
net.sync();
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2);
|
||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2);
|
||||
|
||||
// Fork the network with equal height.
|
||||
net.peer(0)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id))
|
||||
.unwrap();
|
||||
net.peer(1)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id))
|
||||
.unwrap();
|
||||
// Let both nodes build one block.
|
||||
net.peer(0).chain.engine().step();
|
||||
let early_hash = net.peer(0).chain.chain_info().best_block_hash;
|
||||
net.peer(1).chain.engine().step();
|
||||
net.peer(0).chain.engine().step();
|
||||
net.peer(1).chain.engine().step();
|
||||
let ci0 = net.peer(0).chain.chain_info();
|
||||
let ci1 = net.peer(1).chain.chain_info();
|
||||
assert_eq!(ci0.best_block_number, 3);
|
||||
assert_eq!(ci1.best_block_number, 3);
|
||||
assert!(ci0.best_block_hash != ci1.best_block_hash);
|
||||
// Reorg to the chain with earlier view.
|
||||
net.sync();
|
||||
let ci0 = net.peer(0).chain.chain_info();
|
||||
let ci1 = net.peer(1).chain.chain_info();
|
||||
assert_eq!(ci0.best_block_number, 3);
|
||||
assert_eq!(ci1.best_block_number, 3);
|
||||
assert_eq!(ci0.best_block_hash, ci1.best_block_hash);
|
||||
assert_eq!(ci1.best_block_hash, early_hash);
|
||||
|
||||
// Selfish miner
|
||||
net.peer(0)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 3.into(), chain_id))
|
||||
.unwrap();
|
||||
net.peer(1)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 3.into(), chain_id))
|
||||
.unwrap();
|
||||
// Node 0 is an earlier primary.
|
||||
net.peer(0).chain.engine().step();
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 4);
|
||||
net.peer(0).chain.engine().step();
|
||||
net.peer(0).chain.engine().step();
|
||||
net.peer(0).chain.engine().step();
|
||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 4);
|
||||
// Node 1 makes 2 blocks, but is a later primary on the first one.
|
||||
net.peer(1).chain.engine().step();
|
||||
net.peer(1).chain.engine().step();
|
||||
net.peer(1)
|
||||
.miner
|
||||
.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 4.into(), chain_id))
|
||||
.unwrap();
|
||||
net.peer(1).chain.engine().step();
|
||||
net.peer(1).chain.engine().step();
|
||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 5);
|
||||
// Reorg to the longest chain one not ealier view one.
|
||||
net.sync();
|
||||
let ci0 = net.peer(0).chain.chain_info();
|
||||
let ci1 = net.peer(1).chain.chain_info();
|
||||
assert_eq!(ci0.best_block_number, 5);
|
||||
assert_eq!(ci1.best_block_number, 5);
|
||||
assert_eq!(ci0.best_block_hash, ci1.best_block_hash);
|
||||
}
|
||||
618
crates/ethcore/sync/src/tests/helpers.rs
Normal file
618
crates/ethcore/sync/src/tests/helpers.rs
Normal file
@@ -0,0 +1,618 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use api::PAR_PROTOCOL;
|
||||
use bytes::Bytes;
|
||||
use chain::{
|
||||
sync_packet::{PacketInfo, SyncPacket},
|
||||
ChainSync, ForkFilterApi, SyncSupplier, ETH_PROTOCOL_VERSION_64, PAR_PROTOCOL_VERSION_2,
|
||||
};
|
||||
use ethcore::{
|
||||
client::{
|
||||
BlockChainClient, ChainMessageType, ChainNotify, Client as EthcoreClient, ClientConfig,
|
||||
ClientIoMessage, NewBlocks, TestBlockChainClient,
|
||||
},
|
||||
miner::Miner,
|
||||
snapshot::SnapshotService,
|
||||
spec::Spec,
|
||||
test_helpers,
|
||||
};
|
||||
|
||||
use ethereum_types::H256;
|
||||
use io::{IoChannel, IoContext, IoHandler};
|
||||
use network::{self, client_version::ClientVersion, PacketId, PeerId, ProtocolId, SessionInfo};
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
sync::Arc,
|
||||
};
|
||||
use sync_io::SyncIo;
|
||||
use tests::snapshot::*;
|
||||
|
||||
use types::BlockNumber;
|
||||
use SyncConfig;
|
||||
|
||||
pub trait FlushingBlockChainClient: BlockChainClient {
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
impl FlushingBlockChainClient for EthcoreClient {
|
||||
fn flush(&self) {
|
||||
self.flush_queue();
|
||||
}
|
||||
}
|
||||
|
||||
impl FlushingBlockChainClient for TestBlockChainClient {}
|
||||
|
||||
pub struct TestIo<'p, C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
C: 'p,
|
||||
{
|
||||
pub chain: &'p C,
|
||||
pub snapshot_service: &'p TestSnapshotService,
|
||||
pub queue: &'p RwLock<VecDeque<TestPacket>>,
|
||||
pub sender: Option<PeerId>,
|
||||
pub to_disconnect: HashSet<PeerId>,
|
||||
pub packets: Vec<TestPacket>,
|
||||
pub peers_info: HashMap<PeerId, String>,
|
||||
overlay: RwLock<HashMap<BlockNumber, Bytes>>,
|
||||
}
|
||||
|
||||
impl<'p, C> TestIo<'p, C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
C: 'p,
|
||||
{
|
||||
pub fn new(
|
||||
chain: &'p C,
|
||||
ss: &'p TestSnapshotService,
|
||||
queue: &'p RwLock<VecDeque<TestPacket>>,
|
||||
sender: Option<PeerId>,
|
||||
) -> TestIo<'p, C> {
|
||||
TestIo {
|
||||
chain: chain,
|
||||
snapshot_service: ss,
|
||||
queue: queue,
|
||||
sender: sender,
|
||||
to_disconnect: HashSet::new(),
|
||||
overlay: RwLock::new(HashMap::new()),
|
||||
packets: Vec::new(),
|
||||
peers_info: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p, C> Drop for TestIo<'p, C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
C: 'p,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
self.queue.write().extend(self.packets.drain(..));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'p, C> SyncIo for TestIo<'p, C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
C: 'p,
|
||||
{
|
||||
fn disable_peer(&mut self, peer_id: PeerId) {
|
||||
self.disconnect_peer(peer_id);
|
||||
}
|
||||
|
||||
fn disconnect_peer(&mut self, peer_id: PeerId) {
|
||||
self.to_disconnect.insert(peer_id);
|
||||
}
|
||||
|
||||
fn is_expired(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn respond(&mut self, packet_id: PacketId, data: Vec<u8>) -> Result<(), network::Error> {
|
||||
self.packets.push(TestPacket {
|
||||
data: data,
|
||||
packet_id: packet_id,
|
||||
recipient: self.sender.unwrap(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send(
|
||||
&mut self,
|
||||
peer_id: PeerId,
|
||||
packet_id: SyncPacket,
|
||||
data: Vec<u8>,
|
||||
) -> Result<(), network::Error> {
|
||||
self.packets.push(TestPacket {
|
||||
data: data,
|
||||
packet_id: packet_id.id(),
|
||||
recipient: peer_id,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn chain(&self) -> &dyn BlockChainClient {
|
||||
&*self.chain
|
||||
}
|
||||
|
||||
fn peer_version(&self, peer_id: PeerId) -> ClientVersion {
|
||||
let client_id = self
|
||||
.peers_info
|
||||
.get(&peer_id)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| peer_id.to_string());
|
||||
|
||||
ClientVersion::from(client_id)
|
||||
}
|
||||
|
||||
fn snapshot_service(&self) -> &dyn SnapshotService {
|
||||
self.snapshot_service
|
||||
}
|
||||
|
||||
fn peer_session_info(&self, _peer_id: PeerId) -> Option<SessionInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
fn protocol_version(&self, protocol: &ProtocolId, _peer_id: PeerId) -> u8 {
|
||||
if protocol == &PAR_PROTOCOL {
|
||||
PAR_PROTOCOL_VERSION_2.0
|
||||
} else {
|
||||
ETH_PROTOCOL_VERSION_64.0
|
||||
}
|
||||
}
|
||||
|
||||
fn chain_overlay(&self) -> &RwLock<HashMap<BlockNumber, Bytes>> {
|
||||
&self.overlay
|
||||
}
|
||||
}
|
||||
|
||||
/// Mock for emulution of async run of new blocks
|
||||
struct NewBlockMessage {
|
||||
imported: Vec<H256>,
|
||||
invalid: Vec<H256>,
|
||||
enacted: Vec<H256>,
|
||||
retracted: Vec<H256>,
|
||||
sealed: Vec<H256>,
|
||||
proposed: Vec<Bytes>,
|
||||
}
|
||||
|
||||
/// Abstract messages between peers.
|
||||
pub trait Message {
|
||||
/// The intended recipient of this message.
|
||||
fn recipient(&self) -> PeerId;
|
||||
}
|
||||
|
||||
/// Mock subprotocol packet
|
||||
pub struct TestPacket {
|
||||
pub data: Bytes,
|
||||
pub packet_id: PacketId,
|
||||
pub recipient: PeerId,
|
||||
}
|
||||
|
||||
impl Message for TestPacket {
|
||||
fn recipient(&self) -> PeerId {
|
||||
self.recipient
|
||||
}
|
||||
}
|
||||
|
||||
/// A peer which can be a member of the `TestNet`.
|
||||
pub trait Peer {
|
||||
type Message: Message;
|
||||
|
||||
/// Called on connection to other indicated peer.
|
||||
fn on_connect(&self, other: PeerId);
|
||||
|
||||
/// Called on disconnect from other indicated peer.
|
||||
fn on_disconnect(&self, other: PeerId);
|
||||
|
||||
/// Receive a message from another peer. Return a set of peers to disconnect.
|
||||
fn receive_message(&self, from: PeerId, msg: Self::Message) -> HashSet<PeerId>;
|
||||
|
||||
/// Produce the next pending message to send to another peer.
|
||||
fn pending_message(&self) -> Option<Self::Message>;
|
||||
|
||||
/// Whether this peer is done syncing (has no messages to send).
|
||||
fn is_done(&self) -> bool;
|
||||
|
||||
/// Execute a "sync step". This is called for each peer after it sends a packet.
|
||||
fn sync_step(&self);
|
||||
|
||||
/// Restart sync for a peer.
|
||||
fn restart_sync(&self);
|
||||
|
||||
/// Process the queue of pending io messages
|
||||
fn process_all_io_messages(&self);
|
||||
|
||||
/// Process the queue of new block messages
|
||||
fn process_all_new_block_messages(&self);
|
||||
}
|
||||
|
||||
pub struct EthPeer<C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
{
|
||||
pub chain: Arc<C>,
|
||||
pub miner: Arc<Miner>,
|
||||
pub snapshot_service: Arc<TestSnapshotService>,
|
||||
pub sync: RwLock<ChainSync>,
|
||||
pub queue: RwLock<VecDeque<TestPacket>>,
|
||||
pub io_queue: RwLock<VecDeque<ChainMessageType>>,
|
||||
new_blocks_queue: RwLock<VecDeque<NewBlockMessage>>,
|
||||
}
|
||||
|
||||
impl<C> EthPeer<C>
|
||||
where
|
||||
C: FlushingBlockChainClient,
|
||||
{
|
||||
fn is_io_queue_empty(&self) -> bool {
|
||||
self.io_queue.read().is_empty()
|
||||
}
|
||||
|
||||
fn is_new_blocks_queue_empty(&self) -> bool {
|
||||
self.new_blocks_queue.read().is_empty()
|
||||
}
|
||||
|
||||
fn process_io_message(&self, message: ChainMessageType) {
|
||||
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None);
|
||||
match message {
|
||||
ChainMessageType::Consensus(data) => {
|
||||
self.sync.write().propagate_consensus_packet(&mut io, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_new_block_message(&self, message: NewBlockMessage) {
|
||||
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None);
|
||||
self.sync.write().chain_new_blocks(
|
||||
&mut io,
|
||||
&message.imported,
|
||||
&message.invalid,
|
||||
&message.enacted,
|
||||
&message.retracted,
|
||||
&message.sealed,
|
||||
&message.proposed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: FlushingBlockChainClient> Peer for EthPeer<C> {
|
||||
type Message = TestPacket;
|
||||
|
||||
fn on_connect(&self, other: PeerId) {
|
||||
self.sync.write().update_targets(&*self.chain);
|
||||
self.sync.write().on_peer_connected(
|
||||
&mut TestIo::new(
|
||||
&*self.chain,
|
||||
&self.snapshot_service,
|
||||
&self.queue,
|
||||
Some(other),
|
||||
),
|
||||
other,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_disconnect(&self, other: PeerId) {
|
||||
let mut io = TestIo::new(
|
||||
&*self.chain,
|
||||
&self.snapshot_service,
|
||||
&self.queue,
|
||||
Some(other),
|
||||
);
|
||||
self.sync.write().on_peer_aborting(&mut io, other);
|
||||
}
|
||||
|
||||
fn receive_message(&self, from: PeerId, msg: TestPacket) -> HashSet<PeerId> {
|
||||
let mut io = TestIo::new(
|
||||
&*self.chain,
|
||||
&self.snapshot_service,
|
||||
&self.queue,
|
||||
Some(from),
|
||||
);
|
||||
SyncSupplier::dispatch_packet(&self.sync, &mut io, from, msg.packet_id, &msg.data);
|
||||
self.chain.flush();
|
||||
io.to_disconnect.clone()
|
||||
}
|
||||
|
||||
fn pending_message(&self) -> Option<TestPacket> {
|
||||
self.chain.flush();
|
||||
self.queue.write().pop_front()
|
||||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
self.queue.read().is_empty() && self.is_io_queue_empty() && self.is_new_blocks_queue_empty()
|
||||
}
|
||||
|
||||
fn sync_step(&self) {
|
||||
let mut io = TestIo::new(&*self.chain, &self.snapshot_service, &self.queue, None);
|
||||
self.chain.flush();
|
||||
self.sync.write().maintain_peers(&mut io);
|
||||
self.sync.write().maintain_sync(&mut io);
|
||||
self.sync.write().continue_sync(&mut io);
|
||||
self.sync.write().propagate_new_transactions(&mut io);
|
||||
}
|
||||
|
||||
fn restart_sync(&self) {
|
||||
self.sync.write().restart(&mut TestIo::new(
|
||||
&*self.chain,
|
||||
&self.snapshot_service,
|
||||
&self.queue,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
fn process_all_io_messages(&self) {
|
||||
if !self.is_io_queue_empty() {
|
||||
while let Some(message) = self.io_queue.write().pop_front() {
|
||||
self.process_io_message(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_all_new_block_messages(&self) {
|
||||
if !self.is_new_blocks_queue_empty() {
|
||||
while let Some(message) = self.new_blocks_queue.write().pop_front() {
|
||||
self.process_new_block_message(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestNet<P> {
|
||||
pub peers: Vec<Arc<P>>,
|
||||
pub started: bool,
|
||||
pub disconnect_events: Vec<(PeerId, PeerId)>, //disconnected (initiated by, to)
|
||||
}
|
||||
|
||||
impl TestNet<EthPeer<TestBlockChainClient>> {
|
||||
pub fn new(n: usize) -> Self {
|
||||
Self::new_with_config(n, SyncConfig::default())
|
||||
}
|
||||
|
||||
pub fn new_with_fork(n: usize, fork: Option<(BlockNumber, H256)>) -> Self {
|
||||
let mut config = SyncConfig::default();
|
||||
config.fork_block = fork;
|
||||
Self::new_with_config(n, config)
|
||||
}
|
||||
|
||||
pub fn new_with_config(n: usize, config: SyncConfig) -> Self {
|
||||
let mut net = TestNet {
|
||||
peers: Vec::new(),
|
||||
started: false,
|
||||
disconnect_events: Vec::new(),
|
||||
};
|
||||
for _ in 0..n {
|
||||
let chain = TestBlockChainClient::new();
|
||||
let ss = Arc::new(TestSnapshotService::new());
|
||||
let sync = ChainSync::new(config.clone(), &chain, ForkFilterApi::new_dummy(&chain));
|
||||
net.peers.push(Arc::new(EthPeer {
|
||||
sync: RwLock::new(sync),
|
||||
snapshot_service: ss,
|
||||
chain: Arc::new(chain),
|
||||
miner: Arc::new(Miner::new_for_tests(&Spec::new_test(), None)),
|
||||
queue: RwLock::new(VecDeque::new()),
|
||||
io_queue: RwLock::new(VecDeque::new()),
|
||||
new_blocks_queue: RwLock::new(VecDeque::new()),
|
||||
}));
|
||||
}
|
||||
net
|
||||
}
|
||||
|
||||
// relies on Arc uniqueness, which is only true when we haven't registered a ChainNotify.
|
||||
pub fn peer_mut(&mut self, i: usize) -> &mut EthPeer<TestBlockChainClient> {
|
||||
Arc::get_mut(&mut self.peers[i]).expect("Arc never exposed externally")
|
||||
}
|
||||
}
|
||||
|
||||
impl TestNet<EthPeer<EthcoreClient>> {
|
||||
pub fn with_spec<F>(n: usize, config: SyncConfig, spec_factory: F) -> Self
|
||||
where
|
||||
F: Fn() -> Spec,
|
||||
{
|
||||
let mut net = TestNet {
|
||||
peers: Vec::new(),
|
||||
started: false,
|
||||
disconnect_events: Vec::new(),
|
||||
};
|
||||
for _ in 0..n {
|
||||
net.add_peer_with_private_config(config.clone(), spec_factory());
|
||||
}
|
||||
net
|
||||
}
|
||||
|
||||
pub fn add_peer_with_private_config(&mut self, config: SyncConfig, spec: Spec) {
|
||||
let channel = IoChannel::disconnected();
|
||||
let miner = Arc::new(Miner::new_for_tests(&spec, None));
|
||||
let client = EthcoreClient::new(
|
||||
ClientConfig::default(),
|
||||
&spec,
|
||||
test_helpers::new_db(),
|
||||
miner.clone(),
|
||||
channel.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ss = Arc::new(TestSnapshotService::new());
|
||||
let sync = ChainSync::new(config, &*client, ForkFilterApi::new_dummy(&*client));
|
||||
let peer = Arc::new(EthPeer {
|
||||
sync: RwLock::new(sync),
|
||||
snapshot_service: ss,
|
||||
chain: client,
|
||||
miner,
|
||||
queue: RwLock::new(VecDeque::new()),
|
||||
io_queue: RwLock::new(VecDeque::new()),
|
||||
new_blocks_queue: RwLock::new(VecDeque::new()),
|
||||
});
|
||||
peer.chain.add_notify(peer.clone());
|
||||
//private_provider.add_notify(peer.clone());
|
||||
self.peers.push(peer);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> TestNet<P>
|
||||
where
|
||||
P: Peer,
|
||||
{
|
||||
pub fn peer(&self, i: usize) -> &P {
|
||||
&self.peers[i]
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
if self.started {
|
||||
return;
|
||||
}
|
||||
for peer in 0..self.peers.len() {
|
||||
for client in 0..self.peers.len() {
|
||||
if peer != client {
|
||||
self.peers[peer].on_connect(client as PeerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.started = true;
|
||||
}
|
||||
|
||||
pub fn sync_step(&mut self) {
|
||||
for peer in 0..self.peers.len() {
|
||||
let packet = self.peers[peer].pending_message();
|
||||
if let Some(packet) = packet {
|
||||
let disconnecting = {
|
||||
let recipient = packet.recipient();
|
||||
trace!("--- {} -> {} ---", peer, recipient);
|
||||
let to_disconnect =
|
||||
self.peers[recipient].receive_message(peer as PeerId, packet);
|
||||
for d in &to_disconnect {
|
||||
// notify this that disconnecting peers are disconnecting
|
||||
self.peers[recipient].on_disconnect(*d as PeerId);
|
||||
self.disconnect_events.push((peer, *d));
|
||||
}
|
||||
to_disconnect
|
||||
};
|
||||
for d in &disconnecting {
|
||||
// notify other peers that this peer is disconnecting
|
||||
self.peers[*d].on_disconnect(peer as PeerId);
|
||||
}
|
||||
}
|
||||
|
||||
self.sync_step_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sync_step_peer(&mut self, peer_num: usize) {
|
||||
self.peers[peer_num].sync_step();
|
||||
}
|
||||
|
||||
pub fn restart_peer(&mut self, i: usize) {
|
||||
self.peers[i].restart_sync();
|
||||
}
|
||||
|
||||
pub fn sync(&mut self) -> u32 {
|
||||
self.start();
|
||||
let mut total_steps = 0;
|
||||
while !self.done() {
|
||||
self.sync_step();
|
||||
self.deliver_io_messages();
|
||||
self.deliver_new_block_messages();
|
||||
total_steps += 1;
|
||||
}
|
||||
total_steps
|
||||
}
|
||||
|
||||
pub fn sync_steps(&mut self, count: usize) {
|
||||
self.start();
|
||||
for _ in 0..count {
|
||||
self.sync_step();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deliver_io_messages(&mut self) {
|
||||
for peer in self.peers.iter() {
|
||||
peer.process_all_io_messages();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deliver_new_block_messages(&mut self) {
|
||||
for peer in self.peers.iter() {
|
||||
peer.process_all_new_block_messages();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn done(&self) -> bool {
|
||||
self.peers.iter().all(|p| p.is_done())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: FlushingBlockChainClient> TestNet<EthPeer<C>> {
|
||||
pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) {
|
||||
let peer = &mut self.peers[peer_id];
|
||||
peer.sync.write().chain_new_blocks(
|
||||
&mut TestIo::new(&*peer.chain, &peer.snapshot_service, &peer.queue, None),
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestIoHandler {
|
||||
pub client: Arc<EthcoreClient>,
|
||||
}
|
||||
|
||||
impl TestIoHandler {
|
||||
pub fn new(client: Arc<EthcoreClient>) -> Self {
|
||||
TestIoHandler { client }
|
||||
}
|
||||
}
|
||||
|
||||
impl IoHandler<ClientIoMessage> for TestIoHandler {
|
||||
fn message(&self, _io: &IoContext<ClientIoMessage>, net_message: &ClientIoMessage) {
|
||||
match *net_message {
|
||||
ClientIoMessage::Execute(ref exec) => {
|
||||
(*exec.0)(&self.client);
|
||||
}
|
||||
_ => {} // ignore other messages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChainNotify for EthPeer<EthcoreClient> {
|
||||
fn new_blocks(&self, new_blocks: NewBlocks) {
|
||||
if new_blocks.has_more_blocks_to_import {
|
||||
return;
|
||||
}
|
||||
let (enacted, retracted) = new_blocks.route.into_enacted_retracted();
|
||||
|
||||
self.new_blocks_queue.write().push_back(NewBlockMessage {
|
||||
imported: new_blocks.imported,
|
||||
invalid: new_blocks.invalid,
|
||||
enacted,
|
||||
retracted,
|
||||
sealed: new_blocks.sealed,
|
||||
proposed: new_blocks.proposed,
|
||||
});
|
||||
}
|
||||
|
||||
fn start(&self) {}
|
||||
|
||||
fn stop(&self) {}
|
||||
|
||||
fn broadcast(&self, message_type: ChainMessageType) {
|
||||
self.io_queue.write().push_back(message_type)
|
||||
}
|
||||
}
|
||||
23
crates/ethcore/sync/src/tests/mod.rs
Normal file
23
crates/ethcore/sync/src/tests/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod chain;
|
||||
mod consensus;
|
||||
pub mod helpers;
|
||||
pub mod snapshot;
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
mod rpc;
|
||||
29
crates/ethcore/sync/src/tests/rpc.rs
Normal file
29
crates/ethcore/sync/src/tests/rpc.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::super::NetworkConfiguration;
|
||||
use ipc::binary::{deserialize, serialize};
|
||||
use network::NetworkConfiguration as BasicNetworkConfiguration;
|
||||
use std::convert::From;
|
||||
|
||||
#[test]
|
||||
fn network_settings_serialize() {
|
||||
let net_cfg = NetworkConfiguration::from(BasicNetworkConfiguration::new_local());
|
||||
let serialized = serialize(&net_cfg).unwrap();
|
||||
let deserialized = deserialize::<NetworkConfiguration>(&serialized).unwrap();
|
||||
|
||||
assert_eq!(net_cfg.udp_port, deserialized.udp_port);
|
||||
}
|
||||
221
crates/ethcore/sync/src/tests/snapshot.rs
Normal file
221
crates/ethcore/sync/src/tests/snapshot.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use super::helpers::*;
|
||||
use bytes::Bytes;
|
||||
use ethcore::{
|
||||
client::EachBlockWith,
|
||||
snapshot::{CreationStatus, ManifestData, RestorationStatus, SnapshotService},
|
||||
};
|
||||
use ethereum_types::H256;
|
||||
use hash::keccak;
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use types::BlockNumber;
|
||||
use SyncConfig;
|
||||
use WarpSync;
|
||||
|
||||
pub struct TestSnapshotService {
|
||||
manifest: Option<ManifestData>,
|
||||
chunks: HashMap<H256, Bytes>,
|
||||
|
||||
restoration_manifest: Mutex<Option<ManifestData>>,
|
||||
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
||||
block_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
||||
}
|
||||
|
||||
impl TestSnapshotService {
|
||||
pub fn new() -> TestSnapshotService {
|
||||
TestSnapshotService {
|
||||
manifest: None,
|
||||
chunks: HashMap::new(),
|
||||
restoration_manifest: Mutex::new(None),
|
||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_snapshot(
|
||||
num_chunks: usize,
|
||||
block_hash: H256,
|
||||
block_number: BlockNumber,
|
||||
) -> TestSnapshotService {
|
||||
let num_state_chunks = num_chunks / 2;
|
||||
let num_block_chunks = num_chunks - num_state_chunks;
|
||||
let state_chunks: Vec<Bytes> = (0..num_state_chunks)
|
||||
.map(|_| H256::random().to_vec())
|
||||
.collect();
|
||||
let block_chunks: Vec<Bytes> = (0..num_block_chunks)
|
||||
.map(|_| H256::random().to_vec())
|
||||
.collect();
|
||||
let manifest = ManifestData {
|
||||
version: 2,
|
||||
state_hashes: state_chunks.iter().map(|data| keccak(data)).collect(),
|
||||
block_hashes: block_chunks.iter().map(|data| keccak(data)).collect(),
|
||||
state_root: H256::new(),
|
||||
block_number: block_number,
|
||||
block_hash: block_hash,
|
||||
};
|
||||
let mut chunks: HashMap<H256, Bytes> = state_chunks
|
||||
.into_iter()
|
||||
.map(|data| (keccak(&data), data))
|
||||
.collect();
|
||||
chunks.extend(block_chunks.into_iter().map(|data| (keccak(&data), data)));
|
||||
TestSnapshotService {
|
||||
manifest: Some(manifest),
|
||||
chunks: chunks,
|
||||
restoration_manifest: Mutex::new(None),
|
||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapshotService for TestSnapshotService {
|
||||
fn manifest(&self) -> Option<ManifestData> {
|
||||
self.manifest.as_ref().cloned()
|
||||
}
|
||||
|
||||
fn supported_versions(&self) -> Option<(u64, u64)> {
|
||||
Some((1, 2))
|
||||
}
|
||||
|
||||
fn completed_chunks(&self) -> Option<Vec<H256>> {
|
||||
Some(vec![])
|
||||
}
|
||||
|
||||
fn chunk(&self, hash: H256) -> Option<Bytes> {
|
||||
self.chunks.get(&hash).cloned()
|
||||
}
|
||||
|
||||
fn creation_status(&self) -> CreationStatus {
|
||||
CreationStatus::Inactive
|
||||
}
|
||||
|
||||
fn restoration_status(&self) -> RestorationStatus {
|
||||
match *self.restoration_manifest.lock() {
|
||||
Some(ref manifest)
|
||||
if self.state_restoration_chunks.lock().len() == manifest.state_hashes.len()
|
||||
&& self.block_restoration_chunks.lock().len()
|
||||
== manifest.block_hashes.len() =>
|
||||
{
|
||||
RestorationStatus::Inactive
|
||||
}
|
||||
Some(ref manifest) => RestorationStatus::Ongoing {
|
||||
block_number: 0,
|
||||
state_chunks: manifest.state_hashes.len() as u32,
|
||||
block_chunks: manifest.block_hashes.len() as u32,
|
||||
state_chunks_done: self.state_restoration_chunks.lock().len() as u32,
|
||||
block_chunks_done: self.block_restoration_chunks.lock().len() as u32,
|
||||
},
|
||||
None => RestorationStatus::Inactive,
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_restore(&self, manifest: ManifestData) {
|
||||
let mut restoration_manifest = self.restoration_manifest.lock();
|
||||
|
||||
if let Some(ref c_manifest) = *restoration_manifest {
|
||||
if c_manifest.state_root == manifest.state_root {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
*restoration_manifest = Some(manifest);
|
||||
self.state_restoration_chunks.lock().clear();
|
||||
self.block_restoration_chunks.lock().clear();
|
||||
}
|
||||
|
||||
fn abort_restore(&self) {
|
||||
*self.restoration_manifest.lock() = None;
|
||||
self.state_restoration_chunks.lock().clear();
|
||||
self.block_restoration_chunks.lock().clear();
|
||||
}
|
||||
|
||||
fn abort_snapshot(&self) {}
|
||||
|
||||
fn restore_state_chunk(&self, hash: H256, chunk: Bytes) {
|
||||
if self
|
||||
.restoration_manifest
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.state_hashes.iter().any(|h| h == &hash))
|
||||
{
|
||||
self.state_restoration_chunks.lock().insert(hash, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_block_chunk(&self, hash: H256, chunk: Bytes) {
|
||||
if self
|
||||
.restoration_manifest
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map_or(false, |m| m.block_hashes.iter().any(|h| h == &hash))
|
||||
{
|
||||
self.block_restoration_chunks.lock().insert(hash, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
fn shutdown(&self) {
|
||||
self.abort_restore();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_sync() {
|
||||
::env_logger::try_init().ok();
|
||||
let mut config = SyncConfig::default();
|
||||
config.warp_sync = WarpSync::Enabled;
|
||||
let mut net = TestNet::new_with_config(5, config);
|
||||
let snapshot_service = Arc::new(TestSnapshotService::new_with_snapshot(
|
||||
16,
|
||||
H256::new(),
|
||||
500000,
|
||||
));
|
||||
for i in 0..4 {
|
||||
net.peer_mut(i).snapshot_service = snapshot_service.clone();
|
||||
net.peer(i).chain.add_blocks(1, EachBlockWith::Nothing);
|
||||
}
|
||||
net.sync_steps(50);
|
||||
assert_eq!(
|
||||
net.peer(4)
|
||||
.snapshot_service
|
||||
.state_restoration_chunks
|
||||
.lock()
|
||||
.len(),
|
||||
net.peer(0)
|
||||
.snapshot_service
|
||||
.manifest
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.state_hashes
|
||||
.len()
|
||||
);
|
||||
assert_eq!(
|
||||
net.peer(4)
|
||||
.snapshot_service
|
||||
.block_restoration_chunks
|
||||
.lock()
|
||||
.len(),
|
||||
net.peer(0)
|
||||
.snapshot_service
|
||||
.manifest
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.block_hashes
|
||||
.len()
|
||||
);
|
||||
}
|
||||
151
crates/ethcore/sync/src/transactions_stats.rs
Normal file
151
crates/ethcore/sync/src/transactions_stats.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2015-2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of OpenEthereum.
|
||||
|
||||
// OpenEthereum 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.
|
||||
|
||||
// OpenEthereum 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 OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use api::TransactionStats;
|
||||
use ethereum_types::{H256, H512};
|
||||
use fastmap::H256FastMap;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::BuildHasher,
|
||||
};
|
||||
use types::BlockNumber;
|
||||
|
||||
type NodeId = H512;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Stats {
|
||||
first_seen: BlockNumber,
|
||||
propagated_to: HashMap<NodeId, usize>,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new(number: BlockNumber) -> Self {
|
||||
Stats {
|
||||
first_seen: number,
|
||||
propagated_to: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Stats> for TransactionStats {
|
||||
fn from(other: &'a Stats) -> Self {
|
||||
TransactionStats {
|
||||
first_seen: other.first_seen,
|
||||
propagated_to: other
|
||||
.propagated_to
|
||||
.iter()
|
||||
.map(|(hash, size)| (*hash, *size))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TransactionsStats {
|
||||
pending_transactions: H256FastMap<Stats>,
|
||||
}
|
||||
|
||||
impl TransactionsStats {
|
||||
/// Increases number of propagations to given `enodeid`.
|
||||
pub fn propagated(
|
||||
&mut self,
|
||||
hash: &H256,
|
||||
enode_id: Option<NodeId>,
|
||||
current_block_num: BlockNumber,
|
||||
) {
|
||||
let enode_id = enode_id.unwrap_or_default();
|
||||
let stats = self
|
||||
.pending_transactions
|
||||
.entry(*hash)
|
||||
.or_insert_with(|| Stats::new(current_block_num));
|
||||
let count = stats.propagated_to.entry(enode_id).or_insert(0);
|
||||
*count = count.saturating_add(1);
|
||||
}
|
||||
|
||||
/// Returns propagation stats for given hash or `None` if hash is not known.
|
||||
#[cfg(test)]
|
||||
pub fn get(&self, hash: &H256) -> Option<&Stats> {
|
||||
self.pending_transactions.get(hash)
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> &H256FastMap<Stats> {
|
||||
&self.pending_transactions
|
||||
}
|
||||
|
||||
/// Retains only transactions present in given `HashSet`.
|
||||
pub fn retain<S: BuildHasher>(&mut self, hashes: &HashSet<H256, S>) {
|
||||
let to_remove = self
|
||||
.pending_transactions
|
||||
.keys()
|
||||
.filter(|hash| !hashes.contains(hash))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for hash in to_remove {
|
||||
self.pending_transactions.remove(&hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::{Stats, TransactionsStats};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[test]
|
||||
fn should_keep_track_of_propagations() {
|
||||
// given
|
||||
let mut stats = TransactionsStats::default();
|
||||
let hash = 5.into();
|
||||
let enodeid1 = 2.into();
|
||||
let enodeid2 = 5.into();
|
||||
|
||||
// when
|
||||
stats.propagated(&hash, Some(enodeid1), 5);
|
||||
stats.propagated(&hash, Some(enodeid1), 10);
|
||||
stats.propagated(&hash, Some(enodeid2), 15);
|
||||
|
||||
// then
|
||||
let stats = stats.get(&hash);
|
||||
assert_eq!(
|
||||
stats,
|
||||
Some(&Stats {
|
||||
first_seen: 5,
|
||||
propagated_to: hash_map![
|
||||
enodeid1 => 2,
|
||||
enodeid2 => 1
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_remove_hash_from_tracking() {
|
||||
// given
|
||||
let mut stats = TransactionsStats::default();
|
||||
let hash = 5.into();
|
||||
let enodeid1 = 5.into();
|
||||
stats.propagated(&hash, Some(enodeid1), 10);
|
||||
|
||||
// when
|
||||
stats.retain(&HashSet::new());
|
||||
|
||||
// then
|
||||
let stats = stats.get(&hash);
|
||||
assert_eq!(stats, None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user