Big folder refactor

This commit is contained in:
Adria Massanet
2021-01-13 17:03:12 +00:00
committed by rakita
parent 0e5d6944b7
commit c46fe330dc
846 changed files with 255 additions and 39400 deletions

View 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,
}

File diff suppressed because it is too large Load Diff

View 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);
}
}

View 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])
}
}

View 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(&ETH_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());
}
}

File diff suppressed because it is too large Load Diff

View 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));
}
}

View 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);
}
}
}
}

View 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());
}
}

View 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);
}
}

View 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};

View 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 } } } }
}
}

View 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);
}
}

View 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)
}
}

View 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)]);
}

View 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);
}

View 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)
}
}

View 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;

View 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);
}

View 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()
);
}

View 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);
}
}