handle events, minimal state machine
This commit is contained in:
		
							parent
							
								
									72f7391551
								
							
						
					
					
						commit
						9c7340307e
					
				| @ -105,6 +105,9 @@ pub trait EventContext: BasicContext { | ||||
| 	/// Get the peer relevant to the event e.g. message sender,
 | ||||
| 	/// disconnected/connected peer.
 | ||||
| 	fn peer(&self) -> PeerId; | ||||
| 
 | ||||
| 	/// Treat the event context as a basic context.
 | ||||
| 	fn as_basic(&self) -> &BasicContext; | ||||
| } | ||||
| 
 | ||||
| /// Basic context.
 | ||||
| @ -182,4 +185,8 @@ impl<'a> EventContext for Ctx<'a> { | ||||
| 	fn peer(&self) -> PeerId { | ||||
| 		self.peer | ||||
| 	} | ||||
| 
 | ||||
| 	fn as_basic(&self) -> &BasicContext { | ||||
| 		&*self | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -54,11 +54,12 @@ extern crate ethcore_ipc as ipc; | ||||
| mod chain; | ||||
| mod blocks; | ||||
| mod block_sync; | ||||
| mod light_sync; | ||||
| mod sync_io; | ||||
| mod snapshot; | ||||
| mod transactions_stats; | ||||
| 
 | ||||
| pub mod light_sync; | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests; | ||||
| 
 | ||||
|  | ||||
| @ -25,18 +25,22 @@ | ||||
| //! in the same binary; unlike a full node which might communicate via IPC.
 | ||||
| 
 | ||||
| use std::collections::HashMap; | ||||
| use std::fmt; | ||||
| use std::mem; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use ethcore::header::Header; | ||||
| 
 | ||||
| use light::client::LightChainClient; | ||||
| use light::net::{Announcement, Error as NetError, Handler, EventContext, Capabilities, ReqId, Status}; | ||||
| use light::net::{ | ||||
| 	Announcement, Handler, BasicContext, EventContext, | ||||
| 	Capabilities, ReqId, Status | ||||
| }; | ||||
| use light::request; | ||||
| use network::PeerId; | ||||
| use rlp::{DecoderError, UntrustedRlp, View}; | ||||
| use util::{Bytes, U256, H256, Mutex, RwLock}; | ||||
| 
 | ||||
| use self::sync_round::{SyncRound, ResponseContext}; | ||||
| 
 | ||||
| mod response; | ||||
| mod sync_round; | ||||
| 
 | ||||
| @ -49,7 +53,6 @@ struct ChainInfo { | ||||
| } | ||||
| 
 | ||||
| struct Peer { | ||||
| 	first_status: ChainInfo, | ||||
| 	status: ChainInfo, | ||||
| } | ||||
| 
 | ||||
| @ -57,17 +60,48 @@ impl Peer { | ||||
| 	/// Create a peer object.
 | ||||
| 	fn new(chain_info: ChainInfo) -> Self { | ||||
| 		Peer { | ||||
| 			first_status: chain_info.clone(), | ||||
| 			status: chain_info.clone(), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Search for a common ancestor.
 | ||||
| struct AncestorSearch { | ||||
| 	last_batched: u64, | ||||
| 	req_id: ReqId, | ||||
| } | ||||
| 
 | ||||
| // synchronization state machine.
 | ||||
| enum SyncState { | ||||
| 	// Idle (waiting for peers)
 | ||||
| 	Idle, | ||||
| 	// searching for common ancestor with best chain.
 | ||||
| 	// queue should be cleared at this phase.
 | ||||
| 	AncestorSearch(AncestorSearch), | ||||
| 	// Doing sync rounds.
 | ||||
| 	Rounds(SyncRound), | ||||
| } | ||||
| 
 | ||||
| struct ResponseCtx<'a> { | ||||
| 	peer: PeerId, | ||||
| 	req_id: ReqId, | ||||
| 	ctx: &'a BasicContext, | ||||
| 	data: &'a [Bytes], | ||||
| } | ||||
| 
 | ||||
| impl<'a> ResponseContext for ResponseCtx<'a> { | ||||
| 	fn responder(&self) -> PeerId { self.peer } | ||||
| 	fn req_id(&self) -> &ReqId { &self.req_id } | ||||
| 	fn data(&self) -> &[Bytes] { self.data } | ||||
| 	fn punish_responder(&self) { self.ctx.disable_peer(self.peer) } | ||||
| } | ||||
| 
 | ||||
| /// Light client synchronization manager. See module docs for more details.
 | ||||
| pub struct LightSync<L: LightChainClient> { | ||||
| 	best_seen: Mutex<Option<(H256, U256)>>, // best seen block on the network.
 | ||||
| 	peers: RwLock<HashMap<PeerId, Mutex<Peer>>>, // peers which are relevant to synchronization.
 | ||||
| 	client: Arc<L>, | ||||
| 	state: Mutex<SyncState>, | ||||
| } | ||||
| 
 | ||||
| impl<L: LightChainClient> Handler for LightSync<L> { | ||||
| @ -75,7 +109,8 @@ impl<L: LightChainClient> Handler for LightSync<L> { | ||||
| 		let our_best = self.client.chain_info().best_block_number; | ||||
| 
 | ||||
| 		if !capabilities.serve_headers || status.head_num <= our_best { | ||||
| 			trace!(target: "sync", "Ignoring irrelevant peer: {}", ctx.peer()); | ||||
| 			trace!(target: "sync", "Disconnecting irrelevant peer: {}", ctx.peer()); | ||||
| 			ctx.disconnect_peer(ctx.peer()); | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| @ -85,27 +120,130 @@ impl<L: LightChainClient> Handler for LightSync<L> { | ||||
| 			head_num: status.head_num, | ||||
| 		}; | ||||
| 
 | ||||
| 		self.peers.write().insert(ctx.peer(), Mutex::new(Peer::new(chain_info))); | ||||
| 		let mut best = self.best_seen.lock(); | ||||
| 		if best.as_ref().map_or(true, |b| status.head_td > b.1) { | ||||
| 			*best = Some((status.head_hash, status.head_td)); | ||||
| 		} | ||||
| 
 | ||||
| 	fn on_disconnect(&self, ctx: &EventContext, _unfulfilled: &[ReqId]) { | ||||
| 		self.peers.write().insert(ctx.peer(), Mutex::new(Peer::new(chain_info))); | ||||
| 		self.maintain_sync(ctx.as_basic()); | ||||
| 	} | ||||
| 
 | ||||
| 	fn on_disconnect(&self, ctx: &EventContext, unfulfilled: &[ReqId]) { | ||||
| 		let peer_id = ctx.peer(); | ||||
| 
 | ||||
| 		let peer = match self.peers.write().remove(&peer_id).map(|p| p.into_inner()) { | ||||
| 			Some(peer) => peer, | ||||
| 			None => return, | ||||
| 		}; | ||||
| 
 | ||||
| 		let new_best = { | ||||
| 			let mut best = self.best_seen.lock(); | ||||
| 			let peer_best = (peer.status.head_hash, peer.status.head_td); | ||||
| 
 | ||||
| 			if best.as_ref().map_or(false, |b| b == &peer_best) { | ||||
| 				// search for next-best block.
 | ||||
| 				let next_best: Option<(H256, U256)> = self.peers.read().values() | ||||
| 					.map(|p| p.lock()) | ||||
| 					.map(|p| (p.status.head_hash, p.status.head_td)) | ||||
| 					.fold(None, |acc, x| match acc { | ||||
| 						Some(acc) => if x.1 > acc.1 { Some(x) } else { Some(acc) }, | ||||
| 						None => Some(x), | ||||
| 					}); | ||||
| 
 | ||||
| 				*best = next_best; | ||||
| 			} | ||||
| 
 | ||||
| 			best.clone() | ||||
| 		}; | ||||
| 
 | ||||
| 		if new_best.is_none() { | ||||
| 			debug!(target: "sync", "No peers remain. Reverting to idle"); | ||||
| 			*self.state.lock() = SyncState::Idle; | ||||
| 		} else { | ||||
| 			let mut state = self.state.lock(); | ||||
| 
 | ||||
| 			*state = match mem::replace(&mut *state, SyncState::Idle) { | ||||
| 				SyncState::Idle => SyncState::Idle, | ||||
| 				SyncState::AncestorSearch(search) => SyncState::AncestorSearch(search), | ||||
| 				SyncState::Rounds(round) => SyncState::Rounds(round.requests_abandoned(unfulfilled)), | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		self.maintain_sync(ctx.as_basic()); | ||||
| 	} | ||||
| 
 | ||||
| 	fn on_announcement(&self, ctx: &EventContext, announcement: &Announcement) { | ||||
| 		// restart search for common ancestor if necessary.
 | ||||
| 		// restart download if necessary.
 | ||||
| 		// if this is a peer we found irrelevant earlier, we may want to
 | ||||
| 		// re-evaluate their usefulness.
 | ||||
| 		if !self.peers.read().contains_key(&ctx.peer()) { return } | ||||
| 		let last_td = { | ||||
| 			let peers = self.peers.read(); | ||||
| 			match peers.get(&ctx.peer()){ | ||||
| 				None => return, | ||||
| 				Some(peer) => { | ||||
| 					let mut peer = peer.lock(); | ||||
| 					let last_td = peer.status.head_td; | ||||
| 					peer.status = ChainInfo { | ||||
| 						head_td: announcement.head_td, | ||||
| 						head_hash: announcement.head_hash, | ||||
| 						head_num: announcement.head_num, | ||||
| 					}; | ||||
| 					last_td | ||||
| 				} | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		trace!(target: "sync", "Announcement from peer {}: new chain head {:?}, reorg depth {}", | ||||
| 			ctx.peer(), (announcement.head_hash, announcement.head_num), announcement.reorg_depth); | ||||
| 
 | ||||
| 		if last_td < announcement.head_td { | ||||
| 			trace!(target: "sync", "Peer {} moved backwards.", ctx.peer()); | ||||
| 			self.peers.write().remove(&ctx.peer()); | ||||
| 			ctx.disconnect_peer(ctx.peer()); | ||||
| 		} | ||||
| 
 | ||||
| 		let mut best = self.best_seen.lock(); | ||||
| 		if best.as_ref().map_or(true, |b| announcement.head_td > b.1) { | ||||
| 			*best = Some((announcement.head_hash, announcement.head_td)); | ||||
| 		} | ||||
| 
 | ||||
| 		self.maintain_sync(ctx.as_basic()); | ||||
| 	} | ||||
| 
 | ||||
| 	fn on_block_headers(&self, ctx: &EventContext, req_id: ReqId, headers: &[Bytes]) { | ||||
| 		let peer_id = ctx.peer(); | ||||
| 		if !self.peers.read().contains_key(&ctx.peer()) { | ||||
| 			return; | ||||
| 		} | ||||
| 
 | ||||
| 		{ | ||||
| 			let mut state = self.state.lock(); | ||||
| 
 | ||||
| 			*state = match mem::replace(&mut *state, SyncState::Idle) { | ||||
| 				SyncState::Idle => SyncState::Idle, | ||||
| 				SyncState::AncestorSearch(search) => SyncState::AncestorSearch(search), | ||||
| 				SyncState::Rounds(round) => { | ||||
| 					SyncState::Rounds(round.process_response(&ResponseCtx { | ||||
| 						peer: ctx.peer(), | ||||
| 						req_id: req_id, | ||||
| 						ctx: ctx.as_basic(), | ||||
| 						data: headers, | ||||
| 					})) | ||||
| 				} | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 		self.maintain_sync(ctx.as_basic()); | ||||
| 	} | ||||
| 
 | ||||
| 	fn tick(&self, ctx: &BasicContext) { | ||||
| 		self.maintain_sync(ctx); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // private helpers
 | ||||
| impl<L: LightChainClient> LightSync<L> { | ||||
| 	fn maintain_sync(&self, ctx: &BasicContext) { | ||||
| 		const DRAIN_AMOUNT: usize = 256; | ||||
| 
 | ||||
| 		unimplemented!() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @ -120,6 +258,7 @@ impl<L: LightChainClient> LightSync<L> { | ||||
| 			best_seen: Mutex::new(None), | ||||
| 			peers: RwLock::new(HashMap::new()), | ||||
| 			client: client, | ||||
| 			state: Mutex::new(SyncState::Idle), | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -18,17 +18,14 @@ | ||||
| 
 | ||||
| use std::cmp::Ordering; | ||||
| use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; | ||||
| use std::mem; | ||||
| 
 | ||||
| use ethcore::header::Header; | ||||
| 
 | ||||
| use light::client::LightChainClient; | ||||
| use light::net::{EventContext, ReqId}; | ||||
| use light::net::ReqId; | ||||
| use light::request::Headers as HeadersRequest; | ||||
| 
 | ||||
| use network::PeerId; | ||||
| use rlp::{UntrustedRlp, View}; | ||||
| use util::{Bytes, H256, Mutex}; | ||||
| use util::{Bytes, H256}; | ||||
| 
 | ||||
| use super::response; | ||||
| 
 | ||||
| @ -47,7 +44,7 @@ pub trait ResponseContext { | ||||
| 	/// Get the peer who sent this response.
 | ||||
| 	fn responder(&self) ->	PeerId; | ||||
| 	/// Get the request ID this response corresponds to.
 | ||||
| 	fn req_id(&self) -> ReqId; | ||||
| 	fn req_id(&self) -> &ReqId; | ||||
| 	/// Get the (unverified) response data.
 | ||||
| 	fn data(&self) -> &[Bytes]; | ||||
| 	/// Punish the responder.
 | ||||
| @ -173,7 +170,7 @@ impl Fetcher { | ||||
| 	} | ||||
| 
 | ||||
| 	fn process_response<R: ResponseContext>(mut self, ctx: &R) -> SyncRound { | ||||
| 		let mut request = match self.pending.remove(&ctx.req_id()) { | ||||
| 		let mut request = match self.pending.remove(ctx.req_id()) { | ||||
| 			Some(request) => request, | ||||
| 			None => return SyncRound::Fetch(self), | ||||
| 		}; | ||||
| @ -267,8 +264,8 @@ impl Fetcher { | ||||
| 		SyncRound::Fetch(self) | ||||
| 	} | ||||
| 
 | ||||
| 	fn drain(mut self, headers: &mut Vec<Header>, max: usize) -> SyncRound { | ||||
| 		let max = ::std::cmp::min(max, self.ready.len()); | ||||
| 	fn drain(mut self, headers: &mut Vec<Header>, max: Option<usize>) -> SyncRound { | ||||
| 		let max = ::std::cmp::min(max.unwrap_or(usize::max_value()), self.ready.len()); | ||||
| 		headers.extend(self.ready.drain(0..max)); | ||||
| 
 | ||||
| 		if self.sparse.is_empty() && self.ready.is_empty() { | ||||
| @ -321,7 +318,7 @@ impl RoundStart { | ||||
| 
 | ||||
| 	fn process_response<R: ResponseContext>(mut self, ctx: &R) -> SyncRound { | ||||
| 		let req = match self.pending_req.take() { | ||||
| 			Some((id, ref req)) if ctx.req_id() == id => { req.clone() } | ||||
| 			Some((id, ref req)) if ctx.req_id() == &id => { req.clone() } | ||||
| 			other => { | ||||
| 				self.pending_req = other; | ||||
| 				return SyncRound::Start(self); | ||||
| @ -445,9 +442,9 @@ impl SyncRound { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/// Drain up to a maximum number of headers (continuous, starting with a child of
 | ||||
| 	/// Drain up to a maximum number (None -> all) of headers (continuous, starting with a child of
 | ||||
| 	/// the round start block) from the round, starting a new one once finished.
 | ||||
| 	pub fn drain(self, v: &mut Vec<Header>, max: usize) -> Self { | ||||
| 	pub fn drain(self, v: &mut Vec<Header>, max: Option<usize>) -> Self { | ||||
| 		match self { | ||||
| 			SyncRound::Fetch(fetcher) => fetcher.drain(v, max), | ||||
| 			other => other, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user