ancestor search response handler

This commit is contained in:
Robert Habermeier 2016-12-16 14:53:36 +01:00
parent a1f32de2d9
commit 0d7b638a2d
2 changed files with 96 additions and 24 deletions

View File

@ -33,7 +33,7 @@ use ethcore::header::Header;
use light::client::LightChainClient; use light::client::LightChainClient;
use light::net::{ use light::net::{
Announcement, Handler, BasicContext, EventContext, Announcement, Handler, BasicContext, EventContext,
Capabilities, ReqId, Status Capabilities, ReqId, Status,
}; };
use light::request; use light::request;
use network::PeerId; use network::PeerId;
@ -66,10 +66,60 @@ impl Peer {
} }
} }
// Search for a common ancestor with the best chain. // search for a common ancestor with the best chain.
struct AncestorSearch { enum AncestorSearch {
last_batched: u64, Queued(u64), // queued to search for blocks starting from here.
req_id: Option<ReqId>, Awaiting(ReqId, u64, request::Headers), // awaiting response for this request.
Prehistoric, // prehistoric block found. TODO: start to roll back CHTs.
FoundCommon(u64, H256), // common block found.
Genesis, // common ancestor is the genesis.
}
impl AncestorSearch {
fn begin(best_num: u64) -> Self {
match best_num {
0 => AncestorSearch::Genesis,
x => AncestorSearch::Queued(best_num),
}
}
fn process_response<L>(mut self, ctx: &ResponseContext, client: &L) -> AncestorSearch
where L: LightChainClient
{
let first_num = client.chain_info().first_block_number.unwrap_or(0);
match self {
AncestorSearch::Awaiting(id, start, req) => {
if &id == ctx.req_id() {
match response::decode_and_verify(ctx.data(), &req) {
Ok(headers) => {
for header in &headers {
if client.is_known(&header.hash()) {
debug!(target: "sync", "Found common ancestor with best chain");
return AncestorSearch::FoundCommon(header.number(), header.hash());
}
if header.number() <= first_num {
debug!(target: "sync", "Prehistoric common ancestor with best chain.");
return AncestorSearch::Prehistoric;
}
}
AncestorSearch::Queued(start - headers.len() as u64)
}
Err(e) => {
trace!(target: "sync", "Bad headers response from {}: {}", ctx.responder(), e);
ctx.punish_responder();
AncestorSearch::Queued(start)
}
}
} else {
AncestorSearch::Awaiting(id, start, req)
}
}
other => other,
}
}
} }
// synchronization state machine. // synchronization state machine.
@ -218,17 +268,18 @@ impl<L: LightChainClient> Handler for LightSync<L> {
{ {
let mut state = self.state.lock(); let mut state = self.state.lock();
let ctx = ResponseCtx {
peer: ctx.peer(),
req_id: req_id,
ctx: ctx.as_basic(),
data: headers,
};
*state = match mem::replace(&mut *state, SyncState::Idle) { *state = match mem::replace(&mut *state, SyncState::Idle) {
SyncState::Idle => SyncState::Idle, SyncState::Idle => SyncState::Idle,
SyncState::AncestorSearch(search) => SyncState::AncestorSearch(search), SyncState::AncestorSearch(search) =>
SyncState::Rounds(round) => { SyncState::AncestorSearch(search.process_response(&ctx, &*self.client)),
SyncState::Rounds(round.process_response(&ResponseCtx { SyncState::Rounds(round) => SyncState::Rounds(round.process_response(&ctx)),
peer: ctx.peer(),
req_id: req_id,
ctx: ctx.as_basic(),
data: headers,
}))
}
}; };
} }
@ -244,10 +295,17 @@ impl<L: LightChainClient> Handler for LightSync<L> {
impl<L: LightChainClient> LightSync<L> { impl<L: LightChainClient> LightSync<L> {
// Begins a search for the common ancestor and our best block. // Begins a search for the common ancestor and our best block.
// does not lock state, instead has a mutable reference to it passed. // does not lock state, instead has a mutable reference to it passed.
fn begin_search(&self, _state: &mut SyncState) { fn begin_search(&self, state: &mut SyncState) {
self.client.clear_queue(); self.client.clear_queue();
unimplemented!(); let chain_info = self.client.chain_info();
if let None = *self.best_seen.lock() {
// no peers.
*state = SyncState::Idle;
return;
}
*state = SyncState::AncestorSearch(AncestorSearch::begin(chain_info.best_block_number));
} }
fn maintain_sync(&self, ctx: &BasicContext) { fn maintain_sync(&self, ctx: &BasicContext) {
@ -283,7 +341,7 @@ impl<L: LightChainClient> LightSync<L> {
} }
} }
// check for aborted sync round. // handle state transitions.
{ {
match mem::replace(&mut *state, SyncState::Idle) { match mem::replace(&mut *state, SyncState::Idle) {
SyncState::Rounds(SyncRound::Abort(reason)) => { SyncState::Rounds(SyncRound::Abort(reason)) => {
@ -300,11 +358,24 @@ impl<L: LightChainClient> LightSync<L> {
debug!(target: "sync", "Beginning search after aborted sync round"); debug!(target: "sync", "Beginning search after aborted sync round");
self.begin_search(&mut state); self.begin_search(&mut state);
} }
SyncState::AncestorSearch(AncestorSearch::FoundCommon(num, hash)) => {
// TODO: compare to best block and switch to another downloading
// method when close.
*state = SyncState::Rounds(SyncRound::begin(num, hash));
}
SyncState::AncestorSearch(AncestorSearch::Genesis) => {
// Same here.
let g_hash = self.client.chain_info().genesis_hash;
*state = SyncState::Rounds(SyncRound::begin(0, g_hash));
}
SyncState::Idle => self.begin_search(&mut state),
other => *state = other, // restore displaced state. other => *state = other, // restore displaced state.
} }
} }
// allow dispatching of requests. // allow dispatching of requests.
// TODO: maybe wait until the amount of cumulative requests remaining is high enough
// to avoid pumping the failure rate.
{ {
*state = match mem::replace(&mut *state, SyncState::Idle) { *state = match mem::replace(&mut *state, SyncState::Idle) {
SyncState::Rounds(round) SyncState::Rounds(round)

View File

@ -299,9 +299,7 @@ impl RoundStart {
} }
// called on failed attempt. may trigger a transition after a number of attempts. // called on failed attempt. may trigger a transition after a number of attempts.
// a failed attempt is defined as: // a failed attempt is defined as any time a peer returns invalid or incomplete response
// - any time we try to make a request to a peer and fail
// - any time a peer returns invalid or incomplete response
fn failed_attempt(mut self) -> SyncRound { fn failed_attempt(mut self) -> SyncRound {
self.attempt += 1; self.attempt += 1;
@ -371,7 +369,6 @@ impl RoundStart {
where D: Fn(HeadersRequest) -> Option<ReqId> where D: Fn(HeadersRequest) -> Option<ReqId>
{ {
if self.pending_req.is_none() { if self.pending_req.is_none() {
// beginning offset + first block expected after last header we have. // beginning offset + first block expected after last header we have.
let start = (self.start_block.0 + 1) let start = (self.start_block.0 + 1)
+ self.sparse_headers.len() as u64 * (ROUND_SKIP + 1); + self.sparse_headers.len() as u64 * (ROUND_SKIP + 1);
@ -383,9 +380,8 @@ impl RoundStart {
reverse: false, reverse: false,
}; };
match dispatcher(headers_request.clone()) { if let Some(req_id) = dispatcher(headers_request.clone()) {
Some(req_id) => self.pending_req = Some((req_id, headers_request)), self.pending_req = Some((req_id, headers_request));
None => return self.failed_attempt(),
} }
} }
@ -410,6 +406,11 @@ impl SyncRound {
SyncRound::Abort(reason) SyncRound::Abort(reason)
} }
/// Begin sync rounds from a starting block.
pub fn begin(num: u64, hash: H256) -> Self {
SyncRound::Start(RoundStart::new((num, hash)))
}
/// Process an answer to a request. Unknown requests will be ignored. /// Process an answer to a request. Unknown requests will be ignored.
pub fn process_response<R: ResponseContext>(self, ctx: &R) -> Self { pub fn process_response<R: ResponseContext>(self, ctx: &R) -> Self {
match self { match self {