mirror of
https://github.com/mimblewimble/grin.git
synced 2025-02-08 04:11:08 +03:00
Added full node sync mode. Follows closely the bitcoin header-first sync. Related p2p messages and protocol.
This commit is contained in:
parent
f6114231ae
commit
4af049a887
15 changed files with 643 additions and 70 deletions
|
@ -12,12 +12,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use chain::{self, ChainAdapter};
|
use chain::{self, ChainAdapter};
|
||||||
use core::core;
|
use core::core;
|
||||||
use p2p::{NetAdapter, Server};
|
use core::core::hash::{Hash, Hashed};
|
||||||
|
use core::core::target::Difficulty;
|
||||||
|
use p2p::{self, NetAdapter, Server};
|
||||||
use util::OneTime;
|
use util::OneTime;
|
||||||
|
use sync;
|
||||||
|
|
||||||
/// Implementation of the NetAdapter for the blockchain. Gets notified when new
|
/// Implementation of the NetAdapter for the blockchain. Gets notified when new
|
||||||
/// blocks and transactions are received and forwards to the chain and pool
|
/// blocks and transactions are received and forwards to the chain and pool
|
||||||
|
@ -27,23 +32,32 @@ pub struct NetToChainAdapter {
|
||||||
chain_head: Arc<Mutex<chain::Tip>>,
|
chain_head: Arc<Mutex<chain::Tip>>,
|
||||||
chain_store: Arc<chain::ChainStore>,
|
chain_store: Arc<chain::ChainStore>,
|
||||||
chain_adapter: Arc<ChainToNetAdapter>,
|
chain_adapter: Arc<ChainToNetAdapter>,
|
||||||
|
|
||||||
|
syncer: OneTime<Arc<sync::Syncer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetAdapter for NetToChainAdapter {
|
impl NetAdapter for NetToChainAdapter {
|
||||||
fn height(&self) -> u64 {
|
fn total_difficulty(&self) -> Difficulty {
|
||||||
self.chain_head.lock().unwrap().height
|
self.chain_head.lock().unwrap().clone().total_difficulty
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transaction_received(&self, tx: core::Transaction) {
|
fn transaction_received(&self, tx: core::Transaction) {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_received(&self, b: core::Block) {
|
fn block_received(&self, b: core::Block) {
|
||||||
// TODO delegate to a separate thread to avoid holding up the caller
|
|
||||||
debug!("Received block {} from network, going to process.",
|
debug!("Received block {} from network, going to process.",
|
||||||
b.hash());
|
b.hash());
|
||||||
|
|
||||||
// pushing the new block through the chain pipeline
|
// pushing the new block through the chain pipeline
|
||||||
let store = self.chain_store.clone();
|
let store = self.chain_store.clone();
|
||||||
let chain_adapter = self.chain_adapter.clone();
|
let chain_adapter = self.chain_adapter.clone();
|
||||||
let res = chain::process_block(&b, store, chain_adapter, chain::NONE);
|
let opts = if self.syncer.borrow().syncing() {
|
||||||
|
chain::SYNC
|
||||||
|
} else {
|
||||||
|
chain::NONE
|
||||||
|
};
|
||||||
|
let res = chain::process_block(&b, store, chain_adapter, opts);
|
||||||
|
|
||||||
// log errors and update the shared head reference on success
|
// log errors and update the shared head reference on success
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
|
@ -53,6 +67,94 @@ impl NetAdapter for NetToChainAdapter {
|
||||||
let mut head = chain_head.lock().unwrap();
|
let mut head = chain_head.lock().unwrap();
|
||||||
*head = tip;
|
*head = tip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.syncer.borrow().syncing() {
|
||||||
|
self.syncer.borrow().block_received(b.hash());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn headers_received(&self, bhs: Vec<core::BlockHeader>) {
|
||||||
|
let opts = if self.syncer.borrow().syncing() {
|
||||||
|
chain::SYNC
|
||||||
|
} else {
|
||||||
|
chain::NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
// try to add each header to our header chain
|
||||||
|
let mut added_hs = vec![];
|
||||||
|
for bh in bhs {
|
||||||
|
let store = self.chain_store.clone();
|
||||||
|
let chain_adapter = self.chain_adapter.clone();
|
||||||
|
|
||||||
|
let res = chain::process_block_header(&bh, store, chain_adapter, opts);
|
||||||
|
match res {
|
||||||
|
Ok(_) => {
|
||||||
|
added_hs.push(bh.hash());
|
||||||
|
}
|
||||||
|
Err(chain::Error::Unfit(_)) => {
|
||||||
|
info!("Received unfit block header {} at {}.",
|
||||||
|
bh.hash(),
|
||||||
|
bh.height);
|
||||||
|
}
|
||||||
|
Err(chain::Error::StoreErr(e)) => {
|
||||||
|
error!("Store error processing block header {}: {:?}", bh.hash(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("Invalid block header {}: {:?}.", bh.hash(), e);
|
||||||
|
// TODO penalize peer somehow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!("Added {} headers to the header chain.", added_hs.len());
|
||||||
|
|
||||||
|
if self.syncer.borrow().syncing() {
|
||||||
|
self.syncer.borrow().headers_received(added_hs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn locate_headers(&self, locator: Vec<Hash>) -> Vec<core::BlockHeader> {
|
||||||
|
if locator.len() == 0 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// go through the locator vector and check if we know any of these headers
|
||||||
|
let known = self.chain_store.get_block_header(&locator[0]);
|
||||||
|
let header = match known {
|
||||||
|
Ok(header) => header,
|
||||||
|
Err(chain::types::Error::NotFoundErr) => {
|
||||||
|
return self.locate_headers(locator[1..].to_vec());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not build header locator: {:?}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// looks like we know one, getting as many following headers as allowed
|
||||||
|
let hh = header.height;
|
||||||
|
let mut headers = vec![];
|
||||||
|
for h in (hh + 1)..(hh + (p2p::MAX_BLOCK_HEADERS as u64)) {
|
||||||
|
let header = self.chain_store.get_header_by_height(h);
|
||||||
|
match header {
|
||||||
|
Ok(head) => headers.push(head),
|
||||||
|
Err(chain::types::Error::NotFoundErr) => break,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Could not build header locator: {:?}", e);
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_block(&self, h: Hash) -> Option<core::Block> {
|
||||||
|
let store = self.chain_store.clone();
|
||||||
|
let b = store.get_block(&h);
|
||||||
|
match b {
|
||||||
|
Ok(b) => Some(b),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,8 +167,17 @@ impl NetToChainAdapter {
|
||||||
chain_head: chain_head,
|
chain_head: chain_head,
|
||||||
chain_store: chain_store,
|
chain_store: chain_store,
|
||||||
chain_adapter: chain_adapter,
|
chain_adapter: chain_adapter,
|
||||||
|
syncer: OneTime::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_sync(&self, sync: sync::Syncer) {
|
||||||
|
let arc_sync = Arc::new(sync);
|
||||||
|
self.syncer.init(arc_sync.clone());
|
||||||
|
thread::Builder::new().name("syncer".to_string()).spawn(move || {
|
||||||
|
arc_sync.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implementation of the ChainAdapter for the network. Gets notified when the
|
/// Implementation of the ChainAdapter for the network. Gets notified when the
|
||||||
|
|
|
@ -39,5 +39,6 @@ extern crate secp256k1zkp as secp;
|
||||||
mod adapters;
|
mod adapters;
|
||||||
mod miner;
|
mod miner;
|
||||||
mod server;
|
mod server;
|
||||||
|
mod sync;
|
||||||
|
|
||||||
pub use server::{Server, ServerConfig};
|
pub use server::{Server, ServerConfig};
|
||||||
|
|
|
@ -23,8 +23,6 @@ use adapters::ChainToNetAdapter;
|
||||||
use core::consensus;
|
use core::consensus;
|
||||||
use core::core;
|
use core::core;
|
||||||
use core::core::hash::{Hash, Hashed};
|
use core::core::hash::{Hash, Hashed};
|
||||||
use core::core::target::Difficulty;
|
|
||||||
use core::pow;
|
|
||||||
use core::pow::cuckoo;
|
use core::pow::cuckoo;
|
||||||
use chain;
|
use chain;
|
||||||
use secp;
|
use secp;
|
||||||
|
|
|
@ -29,6 +29,7 @@ use chain::ChainStore;
|
||||||
use core;
|
use core;
|
||||||
use miner;
|
use miner;
|
||||||
use p2p;
|
use p2p;
|
||||||
|
use sync;
|
||||||
|
|
||||||
/// Errors than can be reported by a server implementation, mostly wraps
|
/// Errors than can be reported by a server implementation, mostly wraps
|
||||||
/// underlying components errors.
|
/// underlying components errors.
|
||||||
|
@ -89,9 +90,12 @@ impl Server {
|
||||||
let net_adapter = Arc::new(NetToChainAdapter::new(shared_head.clone(),
|
let net_adapter = Arc::new(NetToChainAdapter::new(shared_head.clone(),
|
||||||
chain_store.clone(),
|
chain_store.clone(),
|
||||||
chain_adapter.clone()));
|
chain_adapter.clone()));
|
||||||
let server = Arc::new(p2p::Server::new(config.p2p_config, net_adapter));
|
let server = Arc::new(p2p::Server::new(config.p2p_config, net_adapter.clone()));
|
||||||
chain_adapter.init(server.clone());
|
chain_adapter.init(server.clone());
|
||||||
|
|
||||||
|
let sync = sync::Syncer::new(chain_store.clone(), server.clone());
|
||||||
|
net_adapter.start_sync(sync);
|
||||||
|
|
||||||
let mut evtlp = reactor::Core::new().unwrap();
|
let mut evtlp = reactor::Core::new().unwrap();
|
||||||
let handle = evtlp.handle();
|
let handle = evtlp.handle();
|
||||||
evtlp.run(server.start(handle.clone())).unwrap();
|
evtlp.run(server.start(handle.clone())).unwrap();
|
||||||
|
@ -116,9 +120,12 @@ impl Server {
|
||||||
let net_adapter = Arc::new(NetToChainAdapter::new(shared_head.clone(),
|
let net_adapter = Arc::new(NetToChainAdapter::new(shared_head.clone(),
|
||||||
chain_store.clone(),
|
chain_store.clone(),
|
||||||
chain_adapter.clone()));
|
chain_adapter.clone()));
|
||||||
let server = Arc::new(p2p::Server::new(config.p2p_config, net_adapter));
|
let server = Arc::new(p2p::Server::new(config.p2p_config, net_adapter.clone()));
|
||||||
chain_adapter.init(server.clone());
|
chain_adapter.init(server.clone());
|
||||||
|
|
||||||
|
let sync = sync::Syncer::new(chain_store.clone(), server.clone());
|
||||||
|
net_adapter.start_sync(sync);
|
||||||
|
|
||||||
evt_handle.spawn(server.start(evt_handle.clone()).map_err(|_| ()));
|
evt_handle.spawn(server.start(evt_handle.clone()).map_err(|_| ()));
|
||||||
|
|
||||||
warn!("Grin server started.");
|
warn!("Grin server started.");
|
||||||
|
@ -173,7 +180,7 @@ fn store_head(config: &ServerConfig)
|
||||||
if config.cuckoo_size > 0 {
|
if config.cuckoo_size > 0 {
|
||||||
gen.header.cuckoo_len = config.cuckoo_size;
|
gen.header.cuckoo_len = config.cuckoo_size;
|
||||||
let diff = gen.header.difficulty.clone();
|
let diff = gen.header.difficulty.clone();
|
||||||
core::pow::pow(&mut gen, diff).unwrap();
|
core::pow::pow(&mut gen.header, diff).unwrap();
|
||||||
}
|
}
|
||||||
try!(chain_store.save_block(&gen).map_err(&Error::StoreErr));
|
try!(chain_store.save_block(&gen).map_err(&Error::StoreErr));
|
||||||
let tip = chain::types::Tip::new(gen.hash());
|
let tip = chain::types::Tip::new(gen.hash());
|
||||||
|
|
233
grin/src/sync.rs
Normal file
233
grin/src/sync.rs
Normal file
|
@ -0,0 +1,233 @@
|
||||||
|
// Copyright 2016 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.
|
||||||
|
|
||||||
|
//! Synchronization of the local blockchain with the rest of the network. Used
|
||||||
|
//! either on a brand new node or when a node is late based on others' heads.
|
||||||
|
//! Always starts by downloading the header chain before asking either for full
|
||||||
|
//! blocks or a full UTXO set with related information.
|
||||||
|
|
||||||
|
/// How many block bodies to download in parallel
|
||||||
|
const MAX_BODY_DOWNLOADS: usize = 8;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Instant, Duration};
|
||||||
|
|
||||||
|
use core::core::hash::{Hash, Hashed};
|
||||||
|
use chain;
|
||||||
|
use p2p;
|
||||||
|
|
||||||
|
pub struct Syncer {
|
||||||
|
chain_store: Arc<chain::ChainStore>,
|
||||||
|
p2p: Arc<p2p::Server>,
|
||||||
|
|
||||||
|
sync: Mutex<bool>,
|
||||||
|
last_header_req: Mutex<Instant>,
|
||||||
|
blocks_to_download: Mutex<Vec<Hash>>,
|
||||||
|
blocks_downloading: Mutex<Vec<(Hash, Instant)>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Syncer {
|
||||||
|
pub fn new(chain_store: Arc<chain::ChainStore>, p2p: Arc<p2p::Server>) -> Syncer {
|
||||||
|
Syncer {
|
||||||
|
chain_store: chain_store,
|
||||||
|
p2p: p2p,
|
||||||
|
sync: Mutex::new(true),
|
||||||
|
last_header_req: Mutex::new(Instant::now() - Duration::from_secs(2)),
|
||||||
|
blocks_to_download: Mutex::new(vec![]),
|
||||||
|
blocks_downloading: Mutex::new(vec![]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn syncing(&self) -> bool {
|
||||||
|
*self.sync.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the local chain state, comparing it with our peers and triggers
|
||||||
|
/// syncing if required.
|
||||||
|
pub fn run(&self) -> Result<(), chain::Error> {
|
||||||
|
debug!("Starting syncer.");
|
||||||
|
let start = Instant::now();
|
||||||
|
loop {
|
||||||
|
let pc = self.p2p.peer_count();
|
||||||
|
if pc > 3 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if pc > 0 && (Instant::now() - start > Duration::from_secs(15)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
thread::sleep(Duration::from_millis(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have missing full blocks for which we already have a header
|
||||||
|
self.init_download()?;
|
||||||
|
|
||||||
|
// main syncing loop, requests more headers and bodies periodically as long
|
||||||
|
// as a peer with higher difficulty exists and we're not fully caught up
|
||||||
|
info!("Starting sync loop.");
|
||||||
|
loop {
|
||||||
|
let tip = self.chain_store.get_header_head()?;
|
||||||
|
// TODO do something better (like trying to get more) if we lose peers
|
||||||
|
let peer = self.p2p.most_work_peer().unwrap();
|
||||||
|
|
||||||
|
let more_headers = peer.info.total_difficulty > tip.total_difficulty;
|
||||||
|
let more_bodies = {
|
||||||
|
let blocks_to_download = self.blocks_to_download.lock().unwrap();
|
||||||
|
let blocks_downloading = self.blocks_downloading.lock().unwrap();
|
||||||
|
blocks_to_download.len() > 0 || blocks_downloading.len() > 0
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let last_header_req = self.last_header_req.lock().unwrap().clone();
|
||||||
|
if more_headers && (Instant::now() - Duration::from_secs(2) > last_header_req) {
|
||||||
|
self.request_headers()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if more_bodies {
|
||||||
|
self.request_bodies();
|
||||||
|
}
|
||||||
|
if !more_headers && !more_bodies {
|
||||||
|
// TODO check we haven't been lied to on the total work
|
||||||
|
let mut sync = self.sync.lock().unwrap();
|
||||||
|
*sync = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
}
|
||||||
|
info!("Sync done.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the gap between the header chain and the full block chain and
|
||||||
|
/// initializes the blocks_to_download structure with the missing full
|
||||||
|
/// blocks
|
||||||
|
fn init_download(&self) -> Result<(), chain::Error> {
|
||||||
|
// compare the header's head to the full one to see what we're missing
|
||||||
|
let header_head = self.chain_store.get_header_head()?;
|
||||||
|
let full_head = self.chain_store.head()?;
|
||||||
|
let mut blocks_to_download = self.blocks_to_download.lock().unwrap();
|
||||||
|
|
||||||
|
// go back the chain and insert for download all blocks we only have the
|
||||||
|
// head for
|
||||||
|
let mut prev_h = header_head.last_block_h;
|
||||||
|
while prev_h != full_head.last_block_h {
|
||||||
|
let header = self.chain_store.get_block_header(&prev_h)?;
|
||||||
|
blocks_to_download.push(header.hash());
|
||||||
|
prev_h = header.previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Added {} full block hashes to download.",
|
||||||
|
blocks_to_download.len());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asks for the blocks we haven't downloaded yet and place them in the
|
||||||
|
/// downloading structure.
|
||||||
|
fn request_bodies(&self) {
|
||||||
|
let mut blocks_downloading = self.blocks_downloading.lock().unwrap();
|
||||||
|
if blocks_downloading.len() > MAX_BODY_DOWNLOADS {
|
||||||
|
// clean up potentially dead downloads
|
||||||
|
let twenty_sec_ago = Instant::now() - Duration::from_secs(20);
|
||||||
|
blocks_downloading.iter()
|
||||||
|
.position(|&h| h.1 < twenty_sec_ago)
|
||||||
|
.map(|n| blocks_downloading.remove(n));
|
||||||
|
} else {
|
||||||
|
// consume hashes from blocks to download, place them in downloading and
|
||||||
|
// request them from the network
|
||||||
|
let mut blocks_to_download = self.blocks_to_download.lock().unwrap();
|
||||||
|
while blocks_to_download.len() > 0 && blocks_downloading.len() < MAX_BODY_DOWNLOADS {
|
||||||
|
let h = blocks_to_download.pop().unwrap();
|
||||||
|
let peer = self.p2p.random_peer().unwrap();
|
||||||
|
peer.send_block_request(h);
|
||||||
|
blocks_downloading.push((h, Instant::now()));
|
||||||
|
}
|
||||||
|
debug!("Requesting more full block hashes to download, total: {}.",
|
||||||
|
blocks_to_download.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We added a block, clean up the downloading structure
|
||||||
|
pub fn block_received(&self, bh: Hash) {
|
||||||
|
// just clean up the downloading list
|
||||||
|
let mut bds = self.blocks_downloading.lock().unwrap();
|
||||||
|
bds.iter().position(|&h| h.0 == bh).map(|n| bds.remove(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request some block headers from a peer to advance us
|
||||||
|
fn request_headers(&self) -> Result<(), chain::Error> {
|
||||||
|
{
|
||||||
|
let mut last_header_req = self.last_header_req.lock().unwrap();
|
||||||
|
*last_header_req = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tip = self.chain_store.get_header_head()?;
|
||||||
|
let peer = self.p2p.most_work_peer();
|
||||||
|
let locator = self.get_locator(&tip)?;
|
||||||
|
if let Some(p) = peer {
|
||||||
|
debug!("Asking peer {} for more block headers.", p.info.addr);
|
||||||
|
p.send_header_request(locator)?;
|
||||||
|
} else {
|
||||||
|
warn!("Could not get most worked peer to request headers.");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We added a header, add it to the full block download list
|
||||||
|
pub fn headers_received(&self, bhs: Vec<Hash>) {
|
||||||
|
let mut blocks_to_download = self.blocks_to_download.lock().unwrap();
|
||||||
|
let hs_len = bhs.len();
|
||||||
|
for h in bhs {
|
||||||
|
// enlist for full block download
|
||||||
|
blocks_to_download.insert(0, h);
|
||||||
|
}
|
||||||
|
// ask for more headers if we got as many as required
|
||||||
|
if hs_len == (p2p::MAX_BLOCK_HEADERS as usize) {
|
||||||
|
self.request_headers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a vector of block hashes that should help the remote peer sending
|
||||||
|
/// us the right block headers.
|
||||||
|
fn get_locator(&self, tip: &chain::Tip) -> Result<Vec<Hash>, chain::Error> {
|
||||||
|
// Prepare the heights we want as the latests height minus increasing powers
|
||||||
|
// of 2 up to max.
|
||||||
|
let mut heights = vec![tip.height];
|
||||||
|
let mut tail = (1..p2p::MAX_LOCATORS)
|
||||||
|
.map(|n| 2u64.pow(n))
|
||||||
|
.filter_map(|n| if n > tip.height {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(tip.height - n)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
heights.append(&mut tail);
|
||||||
|
|
||||||
|
// Iteratively travel the header chain back from our head and retain the
|
||||||
|
// headers at the wanted heights.
|
||||||
|
let mut header = self.chain_store.get_block_header(&tip.last_block_h)?;
|
||||||
|
let mut locator = vec![];
|
||||||
|
while heights.len() > 0 {
|
||||||
|
if header.height == heights[0] {
|
||||||
|
heights = heights[1..].to_vec();
|
||||||
|
locator.push(header.hash());
|
||||||
|
}
|
||||||
|
if header.height > 0 {
|
||||||
|
header = self.chain_store.get_block_header(&header.previous)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(locator)
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,8 +30,8 @@ use futures::task::park;
|
||||||
use tokio_core::reactor;
|
use tokio_core::reactor;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simulate_servers() {
|
fn simulate_block_propagation() {
|
||||||
env_logger::init().unwrap();
|
env_logger::init();
|
||||||
|
|
||||||
let mut evtlp = reactor::Core::new().unwrap();
|
let mut evtlp = reactor::Core::new().unwrap();
|
||||||
let handle = evtlp.handle();
|
let handle = evtlp.handle();
|
||||||
|
@ -41,7 +41,7 @@ fn simulate_servers() {
|
||||||
for n in 0..5 {
|
for n in 0..5 {
|
||||||
let s = grin::Server::future(
|
let s = grin::Server::future(
|
||||||
grin::ServerConfig{
|
grin::ServerConfig{
|
||||||
db_root: format!("target/grin-{}", n),
|
db_root: format!("target/grin-prop-{}", n),
|
||||||
cuckoo_size: 12,
|
cuckoo_size: 12,
|
||||||
p2p_config: p2p::P2PConfig{port: 10000+n, ..p2p::P2PConfig::default()}
|
p2p_config: p2p::P2PConfig{port: 10000+n, ..p2p::P2PConfig::default()}
|
||||||
}, &handle).unwrap();
|
}, &handle).unwrap();
|
||||||
|
@ -69,6 +69,37 @@ fn simulate_servers() {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn simulate_full_sync() {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let mut evtlp = reactor::Core::new().unwrap();
|
||||||
|
let handle = evtlp.handle();
|
||||||
|
|
||||||
|
// instantiates 2 servers on different ports
|
||||||
|
let mut servers = vec![];
|
||||||
|
for n in 0..2 {
|
||||||
|
let s = grin::Server::future(
|
||||||
|
grin::ServerConfig{
|
||||||
|
db_root: format!("target/grin-sync-{}", n),
|
||||||
|
cuckoo_size: 12,
|
||||||
|
p2p_config: p2p::P2PConfig{port: 11000+n, ..p2p::P2PConfig::default()}
|
||||||
|
}, &handle).unwrap();
|
||||||
|
servers.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// mine a few blocks on server 1
|
||||||
|
servers[0].start_miner();
|
||||||
|
thread::sleep(time::Duration::from_secs(15));
|
||||||
|
|
||||||
|
// connect 1 and 2
|
||||||
|
let addr = format!("{}:{}", "127.0.0.1", 11001);
|
||||||
|
servers[0].connect_peer(addr.parse().unwrap()).unwrap();
|
||||||
|
|
||||||
|
// 2 should get blocks
|
||||||
|
evtlp.run(change(&servers[1]));
|
||||||
|
}
|
||||||
|
|
||||||
// Builds the change future, monitoring for a change of head on the provided server
|
// Builds the change future, monitoring for a change of head on the provided server
|
||||||
fn change<'a>(s: &'a grin::Server) -> HeadChange<'a> {
|
fn change<'a>(s: &'a grin::Server) -> HeadChange<'a> {
|
||||||
let start_head = s.head();
|
let start_head = s.head();
|
||||||
|
|
|
@ -223,7 +223,7 @@ impl Connection {
|
||||||
pub struct TimeoutConnection {
|
pub struct TimeoutConnection {
|
||||||
underlying: Connection,
|
underlying: Connection,
|
||||||
|
|
||||||
expected_responses: Arc<Mutex<Vec<(Type, Hash, Instant)>>>,
|
expected_responses: Arc<Mutex<Vec<(Type, Hash, Option<bool>, Instant)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TimeoutConnection {
|
impl TimeoutConnection {
|
||||||
|
@ -244,11 +244,15 @@ impl TimeoutConnection {
|
||||||
let recv_h = try!(handler.handle(sender, header, data));
|
let recv_h = try!(handler.handle(sender, header, data));
|
||||||
|
|
||||||
let mut expects = exp.lock().unwrap();
|
let mut expects = exp.lock().unwrap();
|
||||||
|
println!("EXP1 {}", expects.len());
|
||||||
let filtered = expects.iter()
|
let filtered = expects.iter()
|
||||||
.filter(|&&(typ, h, _)| msg_type != typ || recv_h.is_some() && recv_h.unwrap() != h)
|
.filter(|&&(typ, h, _, _)| {
|
||||||
|
msg_type != typ || recv_h.is_some() && recv_h.unwrap() != h
|
||||||
|
})
|
||||||
.map(|&x| x)
|
.map(|&x| x)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
*expects = filtered;
|
*expects = filtered;
|
||||||
|
println!("EXP2 {}", expects.len());
|
||||||
|
|
||||||
Ok(recv_h)
|
Ok(recv_h)
|
||||||
});
|
});
|
||||||
|
@ -259,7 +263,7 @@ impl TimeoutConnection {
|
||||||
.interval(Duration::new(2, 0))
|
.interval(Duration::new(2, 0))
|
||||||
.fold((), move |_, _| {
|
.fold((), move |_, _| {
|
||||||
let exp = exp.lock().unwrap();
|
let exp = exp.lock().unwrap();
|
||||||
for &(_, _, t) in exp.deref() {
|
for &(_, _, _, t) in exp.deref() {
|
||||||
if Instant::now() - t > Duration::new(2, 0) {
|
if Instant::now() - t > Duration::new(2, 0) {
|
||||||
return Err(TimerError::TooLong);
|
return Err(TimerError::TooLong);
|
||||||
}
|
}
|
||||||
|
@ -280,15 +284,15 @@ impl TimeoutConnection {
|
||||||
pub fn send_request(&self,
|
pub fn send_request(&self,
|
||||||
t: Type,
|
t: Type,
|
||||||
body: &ser::Writeable,
|
body: &ser::Writeable,
|
||||||
expect_h: Option<Hash>)
|
expect_h: Option<(Type, Hash)>)
|
||||||
-> Result<(), ser::Error> {
|
-> Result<(), ser::Error> {
|
||||||
let sent = try!(self.underlying.send_msg(t, body));
|
let sent = try!(self.underlying.send_msg(t, body));
|
||||||
|
|
||||||
let mut expects = self.expected_responses.lock().unwrap();
|
let mut expects = self.expected_responses.lock().unwrap();
|
||||||
if let Some(h) = expect_h {
|
if let Some((rt, h)) = expect_h {
|
||||||
expects.push((t, h, Instant::now()));
|
expects.push((rt, h, None, Instant::now()));
|
||||||
} else {
|
} else {
|
||||||
expects.push((t, ZERO_HASH, Instant::now()));
|
expects.push((t, ZERO_HASH, None, Instant::now()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ use rand::os::OsRng;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
|
|
||||||
use core::ser::Error;
|
use core::ser::Error;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
use msg::*;
|
use msg::*;
|
||||||
use types::*;
|
use types::*;
|
||||||
use protocol::ProtocolV1;
|
use protocol::ProtocolV1;
|
||||||
|
@ -46,7 +47,7 @@ impl Handshake {
|
||||||
|
|
||||||
/// Handles connecting to a new remote peer, starting the version handshake.
|
/// Handles connecting to a new remote peer, starting the version handshake.
|
||||||
pub fn connect(&self,
|
pub fn connect(&self,
|
||||||
height: u64,
|
total_difficulty: Difficulty,
|
||||||
conn: TcpStream)
|
conn: TcpStream)
|
||||||
-> Box<Future<Item = (TcpStream, ProtocolV1, PeerInfo), Error = Error>> {
|
-> Box<Future<Item = (TcpStream, ProtocolV1, PeerInfo), Error = Error>> {
|
||||||
// prepare the first part of the hanshake
|
// prepare the first part of the hanshake
|
||||||
|
@ -55,7 +56,7 @@ impl Handshake {
|
||||||
version: PROTOCOL_VERSION,
|
version: PROTOCOL_VERSION,
|
||||||
capabilities: FULL_SYNC,
|
capabilities: FULL_SYNC,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
height: height,
|
total_difficulty: total_difficulty,
|
||||||
sender_addr: SockAddr(conn.local_addr().unwrap()),
|
sender_addr: SockAddr(conn.local_addr().unwrap()),
|
||||||
receiver_addr: SockAddr(conn.peer_addr().unwrap()),
|
receiver_addr: SockAddr(conn.peer_addr().unwrap()),
|
||||||
user_agent: USER_AGENT.to_string(),
|
user_agent: USER_AGENT.to_string(),
|
||||||
|
@ -76,7 +77,7 @@ impl Handshake {
|
||||||
user_agent: shake.user_agent,
|
user_agent: shake.user_agent,
|
||||||
addr: conn.peer_addr().unwrap(),
|
addr: conn.peer_addr().unwrap(),
|
||||||
version: shake.version,
|
version: shake.version,
|
||||||
height: shake.height,
|
total_difficulty: shake.total_difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Connected to peer {:?}", peer_info);
|
info!("Connected to peer {:?}", peer_info);
|
||||||
|
@ -89,7 +90,7 @@ impl Handshake {
|
||||||
/// Handles receiving a connection from a new remote peer that started the
|
/// Handles receiving a connection from a new remote peer that started the
|
||||||
/// version handshake.
|
/// version handshake.
|
||||||
pub fn handshake(&self,
|
pub fn handshake(&self,
|
||||||
height: u64,
|
total_difficulty: Difficulty,
|
||||||
conn: TcpStream)
|
conn: TcpStream)
|
||||||
-> Box<Future<Item = (TcpStream, ProtocolV1, PeerInfo), Error = Error>> {
|
-> Box<Future<Item = (TcpStream, ProtocolV1, PeerInfo), Error = Error>> {
|
||||||
let nonces = self.nonces.clone();
|
let nonces = self.nonces.clone();
|
||||||
|
@ -117,13 +118,13 @@ impl Handshake {
|
||||||
user_agent: hand.user_agent,
|
user_agent: hand.user_agent,
|
||||||
addr: conn.peer_addr().unwrap(),
|
addr: conn.peer_addr().unwrap(),
|
||||||
version: hand.version,
|
version: hand.version,
|
||||||
height: hand.height,
|
total_difficulty: hand.total_difficulty,
|
||||||
};
|
};
|
||||||
// send our reply with our info
|
// send our reply with our info
|
||||||
let shake = Shake {
|
let shake = Shake {
|
||||||
version: PROTOCOL_VERSION,
|
version: PROTOCOL_VERSION,
|
||||||
capabilities: FULL_SYNC,
|
capabilities: FULL_SYNC,
|
||||||
height: height,
|
total_difficulty: total_difficulty,
|
||||||
user_agent: USER_AGENT.to_string(),
|
user_agent: USER_AGENT.to_string(),
|
||||||
};
|
};
|
||||||
Ok((conn, shake, peer_info))
|
Ok((conn, shake, peer_info))
|
||||||
|
|
|
@ -47,4 +47,4 @@ mod types;
|
||||||
|
|
||||||
pub use server::{Server, DummyAdapter};
|
pub use server::{Server, DummyAdapter};
|
||||||
pub use peer::Peer;
|
pub use peer::Peer;
|
||||||
pub use types::{P2PConfig, NetAdapter};
|
pub use types::{P2PConfig, NetAdapter, MAX_LOCATORS, MAX_BLOCK_HEADERS};
|
||||||
|
|
|
@ -21,8 +21,11 @@ use futures::future::{Future, ok};
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
use tokio_core::io::{write_all, read_exact};
|
use tokio_core::io::{write_all, read_exact};
|
||||||
|
|
||||||
use core::ser::{self, Writeable, Readable, Writer, Reader};
|
|
||||||
use core::consensus::MAX_MSG_LEN;
|
use core::consensus::MAX_MSG_LEN;
|
||||||
|
use core::core::BlockHeader;
|
||||||
|
use core::core::hash::Hash;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
|
use core::ser::{self, Writeable, Readable, Writer, Reader};
|
||||||
|
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
|
@ -53,6 +56,9 @@ enum_from_primitive! {
|
||||||
Pong,
|
Pong,
|
||||||
GetPeerAddrs,
|
GetPeerAddrs,
|
||||||
PeerAddrs,
|
PeerAddrs,
|
||||||
|
GetHeaders,
|
||||||
|
Headers,
|
||||||
|
GetBlock,
|
||||||
Block,
|
Block,
|
||||||
Transaction,
|
Transaction,
|
||||||
}
|
}
|
||||||
|
@ -177,8 +183,10 @@ pub struct Hand {
|
||||||
pub capabilities: Capabilities,
|
pub capabilities: Capabilities,
|
||||||
/// randomly generated for each handshake, helps detect self
|
/// randomly generated for each handshake, helps detect self
|
||||||
pub nonce: u64,
|
pub nonce: u64,
|
||||||
/// current height of the sender, used to check whether sync may be needed
|
/// total difficulty accumulated by the sender, used to check whether sync
|
||||||
pub height: u64,
|
/// may
|
||||||
|
/// be needed
|
||||||
|
pub total_difficulty: Difficulty,
|
||||||
/// network address of the sender
|
/// network address of the sender
|
||||||
pub sender_addr: SockAddr,
|
pub sender_addr: SockAddr,
|
||||||
/// network address of the receiver
|
/// network address of the receiver
|
||||||
|
@ -192,8 +200,8 @@ impl Writeable for Hand {
|
||||||
ser_multiwrite!(writer,
|
ser_multiwrite!(writer,
|
||||||
[write_u32, self.version],
|
[write_u32, self.version],
|
||||||
[write_u32, self.capabilities.bits()],
|
[write_u32, self.capabilities.bits()],
|
||||||
[write_u64, self.nonce],
|
[write_u64, self.nonce]);
|
||||||
[write_u64, self.height]);
|
self.total_difficulty.write(writer);
|
||||||
self.sender_addr.write(writer);
|
self.sender_addr.write(writer);
|
||||||
self.receiver_addr.write(writer);
|
self.receiver_addr.write(writer);
|
||||||
writer.write_bytes(&self.user_agent)
|
writer.write_bytes(&self.user_agent)
|
||||||
|
@ -202,7 +210,8 @@ impl Writeable for Hand {
|
||||||
|
|
||||||
impl Readable<Hand> for Hand {
|
impl Readable<Hand> for Hand {
|
||||||
fn read(reader: &mut Reader) -> Result<Hand, ser::Error> {
|
fn read(reader: &mut Reader) -> Result<Hand, ser::Error> {
|
||||||
let (version, capab, nonce, height) = ser_multiread!(reader, read_u32, read_u32, read_u64, read_u64);
|
let (version, capab, nonce) = ser_multiread!(reader, read_u32, read_u32, read_u64);
|
||||||
|
let total_diff = try!(Difficulty::read(reader));
|
||||||
let sender_addr = try!(SockAddr::read(reader));
|
let sender_addr = try!(SockAddr::read(reader));
|
||||||
let receiver_addr = try!(SockAddr::read(reader));
|
let receiver_addr = try!(SockAddr::read(reader));
|
||||||
let ua = try!(reader.read_vec());
|
let ua = try!(reader.read_vec());
|
||||||
|
@ -212,7 +221,7 @@ impl Readable<Hand> for Hand {
|
||||||
version: version,
|
version: version,
|
||||||
capabilities: capabilities,
|
capabilities: capabilities,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
height: height,
|
total_difficulty: total_diff,
|
||||||
sender_addr: sender_addr,
|
sender_addr: sender_addr,
|
||||||
receiver_addr: receiver_addr,
|
receiver_addr: receiver_addr,
|
||||||
user_agent: user_agent,
|
user_agent: user_agent,
|
||||||
|
@ -227,8 +236,10 @@ pub struct Shake {
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
/// sender capabilities
|
/// sender capabilities
|
||||||
pub capabilities: Capabilities,
|
pub capabilities: Capabilities,
|
||||||
/// current height of the sender, used to check whether sync may be needed
|
/// total difficulty accumulated by the sender, used to check whether sync
|
||||||
pub height: u64,
|
/// may
|
||||||
|
/// be needed
|
||||||
|
pub total_difficulty: Difficulty,
|
||||||
/// name of version of the software
|
/// name of version of the software
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
}
|
}
|
||||||
|
@ -237,22 +248,24 @@ impl Writeable for Shake {
|
||||||
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||||
ser_multiwrite!(writer,
|
ser_multiwrite!(writer,
|
||||||
[write_u32, self.version],
|
[write_u32, self.version],
|
||||||
[write_u32, self.capabilities.bits()],
|
[write_u32, self.capabilities.bits()]);
|
||||||
[write_u64, self.height],
|
self.total_difficulty.write(writer);
|
||||||
[write_bytes, &self.user_agent]);
|
writer.write_bytes(&self.user_agent);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Readable<Shake> for Shake {
|
impl Readable<Shake> for Shake {
|
||||||
fn read(reader: &mut Reader) -> Result<Shake, ser::Error> {
|
fn read(reader: &mut Reader) -> Result<Shake, ser::Error> {
|
||||||
let (version, capab, height, ua) = ser_multiread!(reader, read_u32, read_u32, read_u64, read_vec);
|
let (version, capab) = ser_multiread!(reader, read_u32, read_u32);
|
||||||
|
let total_diff = try!(Difficulty::read(reader));
|
||||||
|
let ua = try!(reader.read_vec());
|
||||||
let user_agent = try!(String::from_utf8(ua).map_err(|_| ser::Error::CorruptedData));
|
let user_agent = try!(String::from_utf8(ua).map_err(|_| ser::Error::CorruptedData));
|
||||||
let capabilities = try!(Capabilities::from_bits(capab).ok_or(ser::Error::CorruptedData));
|
let capabilities = try!(Capabilities::from_bits(capab).ok_or(ser::Error::CorruptedData));
|
||||||
Ok(Shake {
|
Ok(Shake {
|
||||||
version: version,
|
version: version,
|
||||||
capabilities: capabilities,
|
capabilities: capabilities,
|
||||||
height: height,
|
total_difficulty: total_diff,
|
||||||
user_agent: user_agent,
|
user_agent: user_agent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -387,6 +400,58 @@ impl Readable<SockAddr> for SockAddr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Serializable wrapper for the block locator.
|
||||||
|
pub struct Locator {
|
||||||
|
pub hashes: Vec<Hash>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writeable for Locator {
|
||||||
|
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u8(self.hashes.len() as u8)?;
|
||||||
|
for h in &self.hashes {
|
||||||
|
h.write(writer)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable<Locator> for Locator {
|
||||||
|
fn read(reader: &mut Reader) -> Result<Locator, ser::Error> {
|
||||||
|
let len = reader.read_u8()?;
|
||||||
|
let mut hashes = Vec::with_capacity(len as usize);
|
||||||
|
for _ in 0..len {
|
||||||
|
hashes.push(Hash::read(reader)?);
|
||||||
|
}
|
||||||
|
Ok(Locator { hashes: hashes })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serializable wrapper for a list of block headers.
|
||||||
|
pub struct Headers {
|
||||||
|
pub headers: Vec<BlockHeader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Writeable for Headers {
|
||||||
|
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
|
||||||
|
writer.write_u16(self.headers.len() as u16)?;
|
||||||
|
for h in &self.headers {
|
||||||
|
h.write(writer)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Readable<Headers> for Headers {
|
||||||
|
fn read(reader: &mut Reader) -> Result<Headers, ser::Error> {
|
||||||
|
let len = reader.read_u16()?;
|
||||||
|
let mut headers = Vec::with_capacity(len as usize);
|
||||||
|
for _ in 0..len {
|
||||||
|
headers.push(BlockHeader::read(reader)?);
|
||||||
|
}
|
||||||
|
Ok(Headers { headers: headers })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Placeholder for messages like Ping and Pong that don't send anything but
|
/// Placeholder for messages like Ping and Pong that don't send anything but
|
||||||
/// the header.
|
/// the header.
|
||||||
pub struct Empty {}
|
pub struct Empty {}
|
||||||
|
|
|
@ -18,12 +18,14 @@ use futures::Future;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
|
|
||||||
use core::core;
|
use core::core;
|
||||||
|
use core::core::hash::Hash;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
use core::ser::Error;
|
use core::ser::Error;
|
||||||
use handshake::Handshake;
|
use handshake::Handshake;
|
||||||
use types::*;
|
use types::*;
|
||||||
|
|
||||||
pub struct Peer {
|
pub struct Peer {
|
||||||
info: PeerInfo,
|
pub info: PeerInfo,
|
||||||
proto: Box<Protocol>,
|
proto: Box<Protocol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,10 +35,10 @@ unsafe impl Send for Peer {}
|
||||||
impl Peer {
|
impl Peer {
|
||||||
/// Initiates the handshake with another peer.
|
/// Initiates the handshake with another peer.
|
||||||
pub fn connect(conn: TcpStream,
|
pub fn connect(conn: TcpStream,
|
||||||
height: u64,
|
total_difficulty: Difficulty,
|
||||||
hs: &Handshake)
|
hs: &Handshake)
|
||||||
-> Box<Future<Item = (TcpStream, Peer), Error = Error>> {
|
-> Box<Future<Item = (TcpStream, Peer), Error = Error>> {
|
||||||
let connect_peer = hs.connect(height, conn).and_then(|(conn, proto, info)| {
|
let connect_peer = hs.connect(total_difficulty, conn).and_then(|(conn, proto, info)| {
|
||||||
Ok((conn,
|
Ok((conn,
|
||||||
Peer {
|
Peer {
|
||||||
info: info,
|
info: info,
|
||||||
|
@ -48,10 +50,10 @@ impl Peer {
|
||||||
|
|
||||||
/// Accept a handshake initiated by another peer.
|
/// Accept a handshake initiated by another peer.
|
||||||
pub fn accept(conn: TcpStream,
|
pub fn accept(conn: TcpStream,
|
||||||
height: u64,
|
total_difficulty: Difficulty,
|
||||||
hs: &Handshake)
|
hs: &Handshake)
|
||||||
-> Box<Future<Item = (TcpStream, Peer), Error = Error>> {
|
-> Box<Future<Item = (TcpStream, Peer), Error = Error>> {
|
||||||
let hs_peer = hs.handshake(height, conn).and_then(|(conn, proto, info)| {
|
let hs_peer = hs.handshake(total_difficulty, conn).and_then(|(conn, proto, info)| {
|
||||||
Ok((conn,
|
Ok((conn,
|
||||||
Peer {
|
Peer {
|
||||||
info: info,
|
info: info,
|
||||||
|
@ -68,7 +70,11 @@ impl Peer {
|
||||||
na: Arc<NetAdapter>)
|
na: Arc<NetAdapter>)
|
||||||
-> Box<Future<Item = (), Error = Error>> {
|
-> Box<Future<Item = (), Error = Error>> {
|
||||||
|
|
||||||
self.proto.handle(conn, na)
|
let addr = self.info.addr;
|
||||||
|
Box::new(self.proto.handle(conn, na).and_then(move |_| {
|
||||||
|
info!("Client {} disconnected.", addr);
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bytes sent and received by this peer to the remote peer.
|
/// Bytes sent and received by this peer to the remote peer.
|
||||||
|
@ -87,6 +93,15 @@ impl Peer {
|
||||||
self.proto.send_block(b)
|
self.proto.send_block(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_header_request(&self, locator: Vec<Hash>) -> Result<(), Error> {
|
||||||
|
self.proto.send_header_request(locator)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_block_request(&self, h: Hash) -> Result<(), Error> {
|
||||||
|
debug!("Requesting block {} from peer {}.", h, self.info.addr);
|
||||||
|
self.proto.send_block_request(h)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&self) {
|
pub fn stop(&self) {
|
||||||
self.proto.close();
|
self.proto.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,14 @@ impl Protocol for ProtocolV1 {
|
||||||
self.send_msg(Type::Transaction, tx)
|
self.send_msg(Type::Transaction, tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_header_request(&self, locator: Vec<Hash>) -> Result<(), ser::Error> {
|
||||||
|
self.send_request(Type::GetHeaders, &Locator { hashes: locator }, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_block_request(&self, h: Hash) -> Result<(), ser::Error> {
|
||||||
|
self.send_request(Type::GetBlock, &h, Some((Type::Block, h)))
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the connection to the remote peer
|
/// Close the connection to the remote peer
|
||||||
fn close(&self) {
|
fn close(&self) {
|
||||||
// TODO some kind of shutdown signal
|
// TODO some kind of shutdown signal
|
||||||
|
@ -97,15 +105,7 @@ impl ProtocolV1 {
|
||||||
body: &ser::Writeable,
|
body: &ser::Writeable,
|
||||||
expect_resp: Option<(Type, Hash)>)
|
expect_resp: Option<(Type, Hash)>)
|
||||||
-> Result<(), ser::Error> {
|
-> Result<(), ser::Error> {
|
||||||
let sent = self.send_msg(t, body);
|
self.conn.borrow().send_request(t, body, expect_resp)
|
||||||
|
|
||||||
if let Err(e) = sent {
|
|
||||||
warn!("Couldn't send message to remote peer: {}", e);
|
|
||||||
} else if let Some(exp) = expect_resp {
|
|
||||||
let mut expects = self.expected_responses.lock().unwrap();
|
|
||||||
expects.push(exp);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,22 +116,58 @@ fn handle_payload(adapter: &NetAdapter,
|
||||||
-> Result<Option<Hash>, ser::Error> {
|
-> Result<Option<Hash>, ser::Error> {
|
||||||
match header.msg_type {
|
match header.msg_type {
|
||||||
Type::Ping => {
|
Type::Ping => {
|
||||||
let data = try!(ser::ser_vec(&MsgHeader::new(Type::Pong, 0)));
|
let data = ser::ser_vec(&MsgHeader::new(Type::Pong, 0))?;
|
||||||
sender.send(data);
|
sender.send(data);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
Type::Pong => Ok(None),
|
Type::Pong => Ok(None),
|
||||||
Type::Transaction => {
|
Type::Transaction => {
|
||||||
let tx = try!(ser::deserialize::<core::Transaction>(&mut &buf[..]));
|
let tx = ser::deserialize::<core::Transaction>(&mut &buf[..])?;
|
||||||
adapter.transaction_received(tx);
|
adapter.transaction_received(tx);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
Type::GetBlock => {
|
||||||
|
let h = ser::deserialize::<Hash>(&mut &buf[..])?;
|
||||||
|
let bo = adapter.get_block(h);
|
||||||
|
if let Some(b) = bo {
|
||||||
|
// serialize and send the block over
|
||||||
|
let mut body_data = vec![];
|
||||||
|
try!(ser::serialize(&mut body_data, &b));
|
||||||
|
let mut data = vec![];
|
||||||
|
try!(ser::serialize(&mut data,
|
||||||
|
&MsgHeader::new(Type::Block, body_data.len() as u64)));
|
||||||
|
data.append(&mut body_data);
|
||||||
|
sender.send(data);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
Type::Block => {
|
Type::Block => {
|
||||||
let b = try!(ser::deserialize::<core::Block>(&mut &buf[..]));
|
let b = ser::deserialize::<core::Block>(&mut &buf[..])?;
|
||||||
let bh = b.hash();
|
let bh = b.hash();
|
||||||
adapter.block_received(b);
|
adapter.block_received(b);
|
||||||
Ok(Some(bh))
|
Ok(Some(bh))
|
||||||
}
|
}
|
||||||
|
Type::GetHeaders => {
|
||||||
|
// load headers from the locator
|
||||||
|
let loc = ser::deserialize::<Locator>(&mut &buf[..])?;
|
||||||
|
let headers = adapter.locate_headers(loc.hashes);
|
||||||
|
|
||||||
|
// serialize and send all the headers over
|
||||||
|
let mut body_data = vec![];
|
||||||
|
try!(ser::serialize(&mut body_data, &Headers { headers: headers }));
|
||||||
|
let mut data = vec![];
|
||||||
|
try!(ser::serialize(&mut data,
|
||||||
|
&MsgHeader::new(Type::Headers, body_data.len() as u64)));
|
||||||
|
data.append(&mut body_data);
|
||||||
|
sender.send(data);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
Type::Headers => {
|
||||||
|
let headers = ser::deserialize::<Headers>(&mut &buf[..])?;
|
||||||
|
adapter.headers_received(headers.headers);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!("unknown message type {:?}", header.msg_type);
|
debug!("unknown message type {:?}", header.msg_type);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
@ -24,10 +24,13 @@ use std::time::Duration;
|
||||||
use futures;
|
use futures;
|
||||||
use futures::{Future, Stream};
|
use futures::{Future, Stream};
|
||||||
use futures::future::IntoFuture;
|
use futures::future::IntoFuture;
|
||||||
|
use rand::{self, Rng};
|
||||||
use tokio_core::net::{TcpListener, TcpStream};
|
use tokio_core::net::{TcpListener, TcpStream};
|
||||||
use tokio_core::reactor;
|
use tokio_core::reactor;
|
||||||
|
|
||||||
use core::core;
|
use core::core;
|
||||||
|
use core::core::hash::Hash;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
use core::ser::Error;
|
use core::ser::Error;
|
||||||
use handshake::Handshake;
|
use handshake::Handshake;
|
||||||
use peer::Peer;
|
use peer::Peer;
|
||||||
|
@ -36,9 +39,18 @@ use types::*;
|
||||||
/// A no-op network adapter used for testing.
|
/// A no-op network adapter used for testing.
|
||||||
pub struct DummyAdapter {}
|
pub struct DummyAdapter {}
|
||||||
impl NetAdapter for DummyAdapter {
|
impl NetAdapter for DummyAdapter {
|
||||||
fn height(&self) -> u64 { 0 }
|
fn total_difficulty(&self) -> Difficulty {
|
||||||
|
Difficulty::one()
|
||||||
|
}
|
||||||
fn transaction_received(&self, tx: core::Transaction) {}
|
fn transaction_received(&self, tx: core::Transaction) {}
|
||||||
fn block_received(&self, b: core::Block) {}
|
fn block_received(&self, b: core::Block) {}
|
||||||
|
fn headers_received(&self, bh: Vec<core::BlockHeader>) {}
|
||||||
|
fn locate_headers(&self, locator: Vec<Hash>) -> Vec<core::BlockHeader> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
fn get_block(&self, h: Hash) -> Option<core::Block> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// P2P server implementation, handling bootstrapping to find and connect to
|
/// P2P server implementation, handling bootstrapping to find and connect to
|
||||||
|
@ -80,11 +92,11 @@ impl Server {
|
||||||
let hp = h.clone();
|
let hp = h.clone();
|
||||||
let peers = socket.incoming().map_err(|e| Error::IOErr(e)).map(move |(conn, addr)| {
|
let peers = socket.incoming().map_err(|e| Error::IOErr(e)).map(move |(conn, addr)| {
|
||||||
let adapter = adapter.clone();
|
let adapter = adapter.clone();
|
||||||
let height = adapter.height();
|
let total_diff = adapter.total_difficulty();
|
||||||
let peers = peers.clone();
|
let peers = peers.clone();
|
||||||
|
|
||||||
// accept the peer and add it to the server map
|
// accept the peer and add it to the server map
|
||||||
let peer_accept = add_to_peers(peers, Peer::accept(conn, height, &hs.clone()));
|
let peer_accept = add_to_peers(peers, Peer::accept(conn, total_diff, &hs.clone()));
|
||||||
|
|
||||||
// wire in a future to timeout the accept after 5 secs
|
// wire in a future to timeout the accept after 5 secs
|
||||||
let timed_peer = with_timeout(Box::new(peer_accept), &hp);
|
let timed_peer = with_timeout(Box::new(peer_accept), &hp);
|
||||||
|
@ -132,17 +144,45 @@ impl Server {
|
||||||
let socket = TcpStream::connect(&addr, &h).map_err(|e| Error::IOErr(e));
|
let socket = TcpStream::connect(&addr, &h).map_err(|e| Error::IOErr(e));
|
||||||
let request = socket.and_then(move |socket| {
|
let request = socket.and_then(move |socket| {
|
||||||
let peers = peers.clone();
|
let peers = peers.clone();
|
||||||
let height = adapter1.height();
|
let total_diff = adapter1.total_difficulty();
|
||||||
|
|
||||||
// connect to the peer and add it to the server map, wiring it a timeout for
|
// connect to the peer and add it to the server map, wiring it a timeout for
|
||||||
// the handhake
|
// the handhake
|
||||||
let peer_connect = add_to_peers(peers, Peer::connect(socket, height, &Handshake::new()));
|
let peer_connect =
|
||||||
|
add_to_peers(peers, Peer::connect(socket, total_diff, &Handshake::new()));
|
||||||
with_timeout(Box::new(peer_connect), &h)
|
with_timeout(Box::new(peer_connect), &h)
|
||||||
})
|
})
|
||||||
.and_then(move |(socket, peer)| peer.run(socket, adapter2));
|
.and_then(move |(socket, peer)| peer.run(socket, adapter2));
|
||||||
Box::new(request)
|
Box::new(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the peer with the most worked branch, showing the highest total
|
||||||
|
/// difficulty.
|
||||||
|
pub fn most_work_peer(&self) -> Option<Arc<Peer>> {
|
||||||
|
let peers = self.peers.read().unwrap();
|
||||||
|
if peers.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut res = peers[0].clone();
|
||||||
|
for p in peers.deref() {
|
||||||
|
if res.info.total_difficulty < p.info.total_difficulty {
|
||||||
|
res = (*p).clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a random peer we're connected to.
|
||||||
|
pub fn random_peer(&self) -> Option<Arc<Peer>> {
|
||||||
|
let peers = self.peers.read().unwrap();
|
||||||
|
if peers.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let idx = rand::thread_rng().gen_range(0, peers.len());
|
||||||
|
Some(peers[idx].clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Broadcasts the provided block to all our peers. A peer implementation
|
/// Broadcasts the provided block to all our peers. A peer implementation
|
||||||
/// may drop the broadcast request if it knows the remote peer already has
|
/// may drop the broadcast request if it knows the remote peer already has
|
||||||
/// the block.
|
/// the block.
|
||||||
|
@ -156,7 +196,7 @@ impl Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Number of peers we're currently connected to.
|
/// Number of peers we're currently connected to.
|
||||||
pub fn peers_count(&self) -> u32 {
|
pub fn peer_count(&self) -> u32 {
|
||||||
self.peers.read().unwrap().len() as u32
|
self.peers.read().unwrap().len() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,19 @@ use futures::Future;
|
||||||
use tokio_core::net::TcpStream;
|
use tokio_core::net::TcpStream;
|
||||||
|
|
||||||
use core::core;
|
use core::core;
|
||||||
|
use core::core::hash::Hash;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
use core::ser::Error;
|
use core::ser::Error;
|
||||||
|
|
||||||
|
/// Maximum number of hashes in a block header locator request
|
||||||
|
pub const MAX_LOCATORS: u32 = 10;
|
||||||
|
|
||||||
|
/// Maximum number of block headers a peer should ever send
|
||||||
|
pub const MAX_BLOCK_HEADERS: u32 = 512;
|
||||||
|
|
||||||
|
/// Maximum number of block bodies a peer should ever ask for and send
|
||||||
|
pub const MAX_BLOCK_BODIES: u32 = 16;
|
||||||
|
|
||||||
/// Configuration for the peer-to-peer server.
|
/// Configuration for the peer-to-peer server.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct P2PConfig {
|
pub struct P2PConfig {
|
||||||
|
@ -56,7 +67,7 @@ pub struct PeerInfo {
|
||||||
pub user_agent: String,
|
pub user_agent: String,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
pub height: u64,
|
pub total_difficulty: Difficulty,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A given communication protocol agreed upon between 2 peers (usually
|
/// A given communication protocol agreed upon between 2 peers (usually
|
||||||
|
@ -81,6 +92,12 @@ pub trait Protocol {
|
||||||
/// Relays a transaction to the remote peer.
|
/// Relays a transaction to the remote peer.
|
||||||
fn send_transaction(&self, tx: &core::Transaction) -> Result<(), Error>;
|
fn send_transaction(&self, tx: &core::Transaction) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Sends a request for block headers based on the provided block locator.
|
||||||
|
fn send_header_request(&self, locator: Vec<Hash>) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Sends a request for a block from its hash.
|
||||||
|
fn send_block_request(&self, h: Hash) -> Result<(), Error>;
|
||||||
|
|
||||||
/// How many bytes have been sent/received to/from the remote peer.
|
/// How many bytes have been sent/received to/from the remote peer.
|
||||||
fn transmitted_bytes(&self) -> (u64, u64);
|
fn transmitted_bytes(&self) -> (u64, u64);
|
||||||
|
|
||||||
|
@ -92,12 +109,25 @@ pub trait Protocol {
|
||||||
/// forwarding or querying of blocks and transactions from the network among
|
/// forwarding or querying of blocks and transactions from the network among
|
||||||
/// other things.
|
/// other things.
|
||||||
pub trait NetAdapter: Sync + Send {
|
pub trait NetAdapter: Sync + Send {
|
||||||
/// Current height of our chain.
|
/// Current height of our chain.
|
||||||
fn height(&self) -> u64;
|
fn total_difficulty(&self) -> Difficulty;
|
||||||
|
|
||||||
/// A valid transaction has been received from one of our peers
|
/// A valid transaction has been received from one of our peers
|
||||||
fn transaction_received(&self, tx: core::Transaction);
|
fn transaction_received(&self, tx: core::Transaction);
|
||||||
|
|
||||||
/// A block has been received from one of our peers
|
/// A block has been received from one of our peers
|
||||||
fn block_received(&self, b: core::Block);
|
fn block_received(&self, b: core::Block);
|
||||||
|
|
||||||
|
/// A set of block header has been received, typically in response to a
|
||||||
|
/// block
|
||||||
|
/// header request.
|
||||||
|
fn headers_received(&self, bh: Vec<core::BlockHeader>);
|
||||||
|
|
||||||
|
/// Finds a list of block headers based on the provided locator. Tries to
|
||||||
|
/// identify the common chain and gets the headers that follow it
|
||||||
|
/// immediately.
|
||||||
|
fn locate_headers(&self, locator: Vec<Hash>) -> Vec<core::BlockHeader>;
|
||||||
|
|
||||||
|
/// Gets a full block by its hash.
|
||||||
|
fn get_block(&self, h: Hash) -> Option<core::Block>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ use tokio_core::net::TcpStream;
|
||||||
use tokio_core::reactor::{self, Core};
|
use tokio_core::reactor::{self, Core};
|
||||||
|
|
||||||
use core::ser;
|
use core::ser;
|
||||||
|
use core::core::target::Difficulty;
|
||||||
use p2p::Peer;
|
use p2p::Peer;
|
||||||
|
|
||||||
// Starts a server and connects a client peer to it to check handshake, followed by a ping/pong exchange to make sure the connection is live.
|
// Starts a server and connects a client peer to it to check handshake, followed by a ping/pong exchange to make sure the connection is live.
|
||||||
|
@ -50,7 +51,7 @@ fn peer_handshake() {
|
||||||
let addr = SocketAddr::new(p2p_conf.host, p2p_conf.port);
|
let addr = SocketAddr::new(p2p_conf.host, p2p_conf.port);
|
||||||
let socket = TcpStream::connect(&addr, &phandle).map_err(|e| ser::Error::IOErr(e));
|
let socket = TcpStream::connect(&addr, &phandle).map_err(|e| ser::Error::IOErr(e));
|
||||||
socket.and_then(move |socket| {
|
socket.and_then(move |socket| {
|
||||||
Peer::connect(socket, 0, &p2p::handshake::Handshake::new())
|
Peer::connect(socket, Difficulty::one(), &p2p::handshake::Handshake::new())
|
||||||
}).and_then(move |(socket, peer)| {
|
}).and_then(move |(socket, peer)| {
|
||||||
rhandle.spawn(peer.run(socket, net_adapter.clone()).map_err(|e| {
|
rhandle.spawn(peer.run(socket, net_adapter.clone()).map_err(|e| {
|
||||||
panic!("Client run failed: {}", e);
|
panic!("Client run failed: {}", e);
|
||||||
|
@ -63,7 +64,7 @@ fn peer_handshake() {
|
||||||
assert!(recv > 0);
|
assert!(recv > 0);
|
||||||
Ok(())
|
Ok(())
|
||||||
}).and_then(|_| {
|
}).and_then(|_| {
|
||||||
assert!(server.peers_count() > 0);
|
assert!(server.peer_count() > 0);
|
||||||
server.stop();
|
server.stop();
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue