Files
grin-node/servers/src/grin/sync/header_sync.rs
T
Antioch Peverell 1baa59c44d prefer outbound peers when syncing (consistently) (#3521)
but use inbound peer for header and body sync if necessary
sync state from inbound peer if no outbound peers to sync from
2020-12-15 19:11:51 +00:00

265 lines
7.8 KiB
Rust

// Copyright 2020 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use chrono::prelude::{DateTime, Utc};
use chrono::Duration;
use std::sync::Arc;
use crate::chain::{self, SyncState, SyncStatus};
use crate::common::types::Error;
use crate::core::core::hash::{Hash, Hashed};
use crate::core::pow::Difficulty;
use crate::p2p::{self, types::ReasonForBan, Capabilities, Peer};
pub struct HeaderSync {
sync_state: Arc<SyncState>,
peers: Arc<p2p::Peers>,
chain: Arc<chain::Chain>,
prev_header_sync: (DateTime<Utc>, u64, u64),
syncing_peer: Option<Arc<Peer>>,
stalling_ts: Option<DateTime<Utc>>,
}
impl HeaderSync {
pub fn new(
sync_state: Arc<SyncState>,
peers: Arc<p2p::Peers>,
chain: Arc<chain::Chain>,
) -> HeaderSync {
HeaderSync {
sync_state,
peers,
chain,
prev_header_sync: (Utc::now(), 0, 0),
syncing_peer: None,
stalling_ts: None,
}
}
pub fn check_run(
&mut self,
header_head: &chain::Tip,
highest_height: u64,
) -> Result<bool, chain::Error> {
if !self.header_sync_due(header_head) {
return Ok(false);
}
let enable_header_sync = match self.sync_state.status() {
SyncStatus::BodySync { .. }
| SyncStatus::HeaderSync { .. }
| SyncStatus::TxHashsetDone => true,
SyncStatus::NoSync | SyncStatus::Initial | SyncStatus::AwaitingPeers(_) => {
let sync_head = self.chain.get_sync_head()?;
debug!(
"sync: initial transition to HeaderSync. sync_head: {} at {}, resetting to: {} at {}",
sync_head.hash(),
sync_head.height,
header_head.hash(),
header_head.height,
);
// Reset sync_head to header_head on transition to HeaderSync,
// but ONLY on initial transition to HeaderSync state.
//
// The header_head and sync_head may diverge here in the presence of a fork
// in the header chain. Ensure we track the new advertised header chain here
// correctly, so reset any previous (and potentially stale) sync_head to match
// our last known "good" header_head.
//
self.chain.rebuild_sync_mmr(&header_head)?;
true
}
_ => false,
};
if enable_header_sync {
self.sync_state.update(SyncStatus::HeaderSync {
current_height: header_head.height,
highest_height: highest_height,
});
self.syncing_peer = self.header_sync();
return Ok(true);
}
Ok(false)
}
fn header_sync_due(&mut self, header_head: &chain::Tip) -> bool {
let now = Utc::now();
let (timeout, latest_height, prev_height) = self.prev_header_sync;
// received all necessary headers, can ask for more
let all_headers_received =
header_head.height >= prev_height + (p2p::MAX_BLOCK_HEADERS as u64) - 4;
// no headers processed and we're past timeout, need to ask for more
let stalling = header_head.height <= latest_height && now > timeout;
// always enable header sync on initial state transition from NoSync / Initial
let force_sync = match self.sync_state.status() {
SyncStatus::NoSync | SyncStatus::Initial | SyncStatus::AwaitingPeers(_) => true,
_ => false,
};
if force_sync || all_headers_received || stalling {
self.prev_header_sync = (
now + Duration::seconds(10),
header_head.height,
header_head.height,
);
// save the stalling start time
if stalling {
if self.stalling_ts.is_none() {
self.stalling_ts = Some(now);
}
} else {
self.stalling_ts = None;
}
if all_headers_received {
// reset the stalling start time if syncing goes well
self.stalling_ts = None;
} else if let Some(ref stalling_ts) = self.stalling_ts {
if let Some(ref peer) = self.syncing_peer {
match self.sync_state.status() {
SyncStatus::HeaderSync { .. } | SyncStatus::BodySync { .. } => {
// Ban this fraud peer which claims a higher work but can't send us the real headers
if now > *stalling_ts + Duration::seconds(120)
&& header_head.total_difficulty < peer.info.total_difficulty()
{
if let Err(e) = self
.peers
.ban_peer(peer.info.addr, ReasonForBan::FraudHeight)
{
error!("failed to ban peer {}: {:?}", peer.info.addr, e);
}
info!(
"sync: ban a fraud peer: {}, claimed height: {}, total difficulty: {}",
peer.info.addr,
peer.info.height(),
peer.info.total_difficulty(),
);
}
}
_ => (),
}
}
}
self.syncing_peer = None;
true
} else {
// resetting the timeout as long as we progress
if header_head.height > latest_height {
self.prev_header_sync =
(now + Duration::seconds(2), header_head.height, prev_height);
}
false
}
}
fn header_sync(&mut self) -> Option<Arc<Peer>> {
if let Ok(header_head) = self.chain.header_head() {
let peers_iter = || {
self.peers
.iter()
.with_capabilities(Capabilities::HEADER_HIST)
.connected()
};
// Filter peers further based on max difficulty.
let max_diff = peers_iter().max_difficulty().unwrap_or(Difficulty::zero());
let peers_iter = || peers_iter().with_difficulty(|x| x >= max_diff);
// Choose a random "most work" peer, preferring outbound if at all possible.
let peer = peers_iter().outbound().choose_random().or_else(|| {
warn!("no suitable outbound peer for header sync, considering inbound");
peers_iter().inbound().choose_random()
});
if let Some(peer) = peer {
if peer.info.total_difficulty() > header_head.total_difficulty {
return self.request_headers(peer);
}
}
}
return None;
}
/// Request some block headers from a peer to advance us.
fn request_headers(&mut self, peer: Arc<Peer>) -> Option<Arc<Peer>> {
if let Ok(locator) = self.get_locator() {
debug!(
"sync: request_headers: asking {} for headers, {:?}",
peer.info.addr, locator,
);
let _ = peer.send_header_request(locator);
return Some(peer);
}
return None;
}
/// We build a locator based on sync_head.
/// Even if sync_head is significantly out of date we will "reset" it once we
/// start getting headers back from a peer.
fn get_locator(&mut self) -> Result<Vec<Hash>, Error> {
let tip = self.chain.get_sync_head()?;
let heights = get_locator_heights(tip.height);
let locator = self.chain.get_locator_hashes(&heights)?;
Ok(locator)
}
}
// current height back to 0 decreasing in powers of 2
fn get_locator_heights(height: u64) -> Vec<u64> {
let mut current = height;
let mut heights = vec![];
while current > 0 {
heights.push(current);
if heights.len() >= (p2p::MAX_LOCATORS as usize) - 1 {
break;
}
let next = 2u64.pow(heights.len() as u32);
current = if current > next { current - next } else { 0 }
}
heights.push(0);
heights
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_get_locator_heights() {
assert_eq!(get_locator_heights(0), vec![0]);
assert_eq!(get_locator_heights(1), vec![1, 0]);
assert_eq!(get_locator_heights(2), vec![2, 0]);
assert_eq!(get_locator_heights(3), vec![3, 1, 0]);
assert_eq!(get_locator_heights(10), vec![10, 8, 4, 0]);
assert_eq!(get_locator_heights(100), vec![100, 98, 94, 86, 70, 38, 0]);
assert_eq!(
get_locator_heights(1000),
vec![1000, 998, 994, 986, 970, 938, 874, 746, 490, 0]
);
// check the locator is still a manageable length, even for large numbers of
// headers
assert_eq!(
get_locator_heights(10000),
vec![10000, 9998, 9994, 9986, 9970, 9938, 9874, 9746, 9490, 8978, 7954, 5906, 1810, 0,]
);
}
}