Improve block and transaction propagation (#9954)
* Refactor sync to add priority tasks. * Send priority tasks notifications. * Propagate blocks, optimize transactions. * Implement transaction propagation. Use sync_channel. * Tone down info. * Prevent deadlock by not waiting forever for sync lock. * Fix lock order. * Don't use sync_channel to prevent deadlocks. * Fix tests.
This commit is contained in:
committed by
Afri Schoedon
parent
14c9cbd40e
commit
0b5bbf6048
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, mpsc, atomic};
|
||||
use std::collections::{HashMap, BTreeMap};
|
||||
use std::io;
|
||||
use std::ops::Range;
|
||||
@@ -33,10 +33,10 @@ use ethcore::client::{BlockChainClient, ChainNotify, ChainRoute, ChainMessageTyp
|
||||
use ethcore::snapshot::SnapshotService;
|
||||
use ethcore::header::BlockNumber;
|
||||
use sync_io::NetSyncIo;
|
||||
use chain::{ChainSync, SyncStatus as EthSyncStatus};
|
||||
use chain::{ChainSyncApi, SyncStatus as EthSyncStatus};
|
||||
use std::net::{SocketAddr, AddrParseError};
|
||||
use std::str::FromStr;
|
||||
use parking_lot::RwLock;
|
||||
use parking_lot::{RwLock, Mutex};
|
||||
use chain::{ETH_PROTOCOL_VERSION_63, ETH_PROTOCOL_VERSION_62,
|
||||
PAR_PROTOCOL_VERSION_1, PAR_PROTOCOL_VERSION_2, PAR_PROTOCOL_VERSION_3,
|
||||
PRIVATE_TRANSACTION_PACKET, SIGNED_PRIVATE_TRANSACTION_PACKET};
|
||||
@@ -228,6 +228,37 @@ impl AttachedProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -260,6 +291,8 @@ pub struct EthSync {
|
||||
subprotocol_name: [u8; 3],
|
||||
/// Light subprotocol name.
|
||||
light_subprotocol_name: [u8; 3],
|
||||
/// Priority tasks notification channel
|
||||
priority_tasks: Mutex<mpsc::Sender<PriorityTask>>,
|
||||
}
|
||||
|
||||
fn light_params(
|
||||
@@ -312,13 +345,19 @@ impl EthSync {
|
||||
})
|
||||
};
|
||||
|
||||
let chain_sync = ChainSync::new(params.config, &*params.chain, params.private_tx_handler.clone());
|
||||
let (priority_tasks_tx, priority_tasks_rx) = mpsc::channel();
|
||||
let sync = ChainSyncApi::new(
|
||||
params.config,
|
||||
&*params.chain,
|
||||
params.private_tx_handler.clone(),
|
||||
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: RwLock::new(chain_sync),
|
||||
sync,
|
||||
chain: params.chain,
|
||||
snapshot_service: params.snapshot_service,
|
||||
overlay: RwLock::new(HashMap::new()),
|
||||
@@ -327,26 +366,32 @@ impl EthSync {
|
||||
subprotocol_name: params.config.subprotocol_name,
|
||||
light_subprotocol_name: params.config.light_subprotocol_name,
|
||||
attached_protos: params.attached_protos,
|
||||
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.read().status()
|
||||
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 eth_sync = self.eth_handler.sync.read();
|
||||
let light_proto = self.light_proto.as_ref();
|
||||
|
||||
peer_ids.into_iter().filter_map(|peer_id| {
|
||||
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,
|
||||
@@ -358,7 +403,7 @@ impl SyncProvider for EthSync {
|
||||
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: eth_sync.peer_info(&peer_id),
|
||||
eth_info: peer_info,
|
||||
pip_info: light_proto.as_ref().and_then(|lp| lp.peer_status(peer_id)).map(Into::into),
|
||||
})
|
||||
}).collect()
|
||||
@@ -370,17 +415,16 @@ impl SyncProvider for EthSync {
|
||||
}
|
||||
|
||||
fn transactions_stats(&self) -> BTreeMap<H256, TransactionStats> {
|
||||
let sync = self.eth_handler.sync.read();
|
||||
sync.transactions_stats()
|
||||
.iter()
|
||||
.map(|(hash, stats)| (*hash, stats.into()))
|
||||
.collect()
|
||||
self.eth_handler.sync.transactions_stats()
|
||||
}
|
||||
}
|
||||
|
||||
const PEERS_TIMER: TimerToken = 0;
|
||||
const SYNC_TIMER: TimerToken = 1;
|
||||
const TX_TIMER: TimerToken = 2;
|
||||
const PRIORITY_TIMER: TimerToken = 3;
|
||||
|
||||
pub(crate) const PRIORITY_TIMER_INTERVAL: Duration = Duration::from_millis(250);
|
||||
|
||||
struct SyncProtocolHandler {
|
||||
/// Shared blockchain client.
|
||||
@@ -388,7 +432,7 @@ struct SyncProtocolHandler {
|
||||
/// Shared snapshot service.
|
||||
snapshot_service: Arc<SnapshotService>,
|
||||
/// Sync strategy
|
||||
sync: RwLock<ChainSync>,
|
||||
sync: ChainSyncApi,
|
||||
/// Chain overlay used to cache data such as fork block.
|
||||
overlay: RwLock<HashMap<BlockNumber, Bytes>>,
|
||||
}
|
||||
@@ -399,11 +443,13 @@ impl NetworkProtocolHandler for SyncProtocolHandler {
|
||||
io.register_timer(PEERS_TIMER, Duration::from_millis(700)).expect("Error registering peers timer");
|
||||
io.register_timer(SYNC_TIMER, Duration::from_millis(1100)).expect("Error registering sync timer");
|
||||
io.register_timer(TX_TIMER, Duration::from_millis(1300)).expect("Error registering transactions timer");
|
||||
|
||||
io.register_timer(PRIORITY_TIMER, PRIORITY_TIMER_INTERVAL).expect("Error registering peers timer");
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
ChainSync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer, packet_id, data);
|
||||
self.sync.dispatch_packet(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service, &self.overlay), *peer, packet_id, data);
|
||||
}
|
||||
|
||||
fn connected(&self, io: &NetworkContext, peer: &PeerId) {
|
||||
@@ -429,15 +475,26 @@ impl NetworkProtocolHandler for SyncProtocolHandler {
|
||||
match timer {
|
||||
PEERS_TIMER => self.sync.write().maintain_peers(&mut io),
|
||||
SYNC_TIMER => self.sync.write().maintain_sync(&mut io),
|
||||
TX_TIMER => {
|
||||
self.sync.write().propagate_new_transactions(&mut io);
|
||||
},
|
||||
TX_TIMER => self.sync.write().propagate_new_transactions(&mut io),
|
||||
PRIORITY_TIMER => self.sync.process_priority_queue(&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);
|
||||
}
|
||||
}
|
||||
|
||||
fn new_blocks(&self,
|
||||
imported: Vec<H256>,
|
||||
invalid: Vec<H256>,
|
||||
|
||||
Reference in New Issue
Block a user