Snapshot sync (#2047)

* PV64 sync

* Tests

* Client DB restore

* Snapshot restoration over IPC

* Upating test

* Minor tweaks

* Upating test
This commit is contained in:
Arkadiy Paronyan 2016-09-06 15:31:13 +02:00 committed by GitHub
parent 9655920896
commit 5c5d9c8ccd
32 changed files with 1258 additions and 420 deletions

View File

@ -19,5 +19,6 @@ extern crate ethcore_ipc_codegen;
fn main() { fn main() {
ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap(); ethcore_ipc_codegen::derive_binary("src/types/mod.rs.in").unwrap();
ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap(); ethcore_ipc_codegen::derive_ipc("src/client/traits.rs").unwrap();
ethcore_ipc_codegen::derive_ipc("src/snapshot/snapshot_service_trait.rs").unwrap();
ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap(); ethcore_ipc_codegen::derive_ipc("src/client/chain_notify.rs").unwrap();
} }

View File

@ -37,7 +37,7 @@ const MIN_MEM_LIMIT: usize = 16384;
const MIN_QUEUE_LIMIT: usize = 512; const MIN_QUEUE_LIMIT: usize = 512;
/// Block queue configuration /// Block queue configuration
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct BlockQueueConfig { pub struct BlockQueueConfig {
/// Maximum number of blocks to keep in unverified queue. /// Maximum number of blocks to keep in unverified queue.
/// When the limit is reached, is_full returns true. /// When the limit is reached, is_full returns true.

View File

@ -17,7 +17,7 @@
//! Blockchain configuration. //! Blockchain configuration.
/// Blockchain configuration. /// Blockchain configuration.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub struct Config { pub struct Config {
/// Preferred cache size in bytes. /// Preferred cache size in bytes.
pub pref_cache_size: usize, pub pref_cache_size: usize,

View File

@ -32,7 +32,7 @@ use util::kvdb::*;
// other // other
use io::*; use io::*;
use views::{BlockView, HeaderView, BodyView}; use views::{BlockView, HeaderView, BodyView};
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult}; use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
use header::BlockNumber; use header::BlockNumber;
use state::State; use state::State;
use spec::Spec; use spec::Spec;
@ -122,11 +122,13 @@ impl SleepState {
/// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue. /// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue.
pub struct Client { pub struct Client {
mode: Mode, mode: Mode,
chain: Arc<BlockChain>, chain: RwLock<Arc<BlockChain>>,
tracedb: Arc<TraceDB<BlockChain>>, tracedb: RwLock<TraceDB<BlockChain>>,
engine: Arc<Engine>, engine: Arc<Engine>,
db: Arc<Database>, config: ClientConfig,
state_db: Mutex<Box<JournalDB>>, db: RwLock<Arc<Database>>,
pruning: journaldb::Algorithm,
state_db: RwLock<Box<JournalDB>>,
block_queue: BlockQueue, block_queue: BlockQueue,
report: RwLock<ClientReport>, report: RwLock<ClientReport>,
import_lock: Mutex<()>, import_lock: Mutex<()>,
@ -168,8 +170,8 @@ impl Client {
db_config.wal = config.db_wal; db_config.wal = config.db_wal;
let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database))); let db = Arc::new(try!(Database::open(&db_config, &path.to_str().unwrap()).map_err(ClientError::Database)));
let chain = Arc::new(BlockChain::new(config.blockchain, &gb, db.clone())); let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone()));
let tracedb = Arc::new(try!(TraceDB::new(config.tracing, db.clone(), chain.clone()))); let tracedb = RwLock::new(try!(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone())));
let mut state_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE); let mut state_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE);
if state_db.is_empty() && try!(spec.ensure_db_good(state_db.as_hashdb_mut())) { if state_db.is_empty() && try!(spec.ensure_db_good(state_db.as_hashdb_mut())) {
@ -184,32 +186,34 @@ impl Client {
let engine = spec.engine.clone(); let engine = spec.engine.clone();
let block_queue = BlockQueue::new(config.queue, engine.clone(), message_channel.clone()); let block_queue = BlockQueue::new(config.queue.clone(), engine.clone(), message_channel.clone());
let panic_handler = PanicHandler::new_in_arc(); let panic_handler = PanicHandler::new_in_arc();
panic_handler.forward_from(&block_queue); panic_handler.forward_from(&block_queue);
let awake = match config.mode { Mode::Dark(..) => false, _ => true }; let awake = match config.mode { Mode::Dark(..) => false, _ => true };
let factories = Factories { let factories = Factories {
vm: EvmFactory::new(config.vm_type), vm: EvmFactory::new(config.vm_type.clone()),
trie: TrieFactory::new(config.trie_spec), trie: TrieFactory::new(config.trie_spec.clone()),
accountdb: Default::default(), accountdb: Default::default(),
}; };
let client = Client { let client = Client {
sleep_state: Mutex::new(SleepState::new(awake)), sleep_state: Mutex::new(SleepState::new(awake)),
liveness: AtomicBool::new(awake), liveness: AtomicBool::new(awake),
mode: config.mode, mode: config.mode.clone(),
chain: chain, chain: RwLock::new(chain),
tracedb: tracedb, tracedb: tracedb,
engine: engine, engine: engine,
db: db, pruning: config.pruning.clone(),
state_db: Mutex::new(state_db), verifier: verification::new(config.verifier_type.clone()),
config: config,
db: RwLock::new(db),
state_db: RwLock::new(state_db),
block_queue: block_queue, block_queue: block_queue,
report: RwLock::new(Default::default()), report: RwLock::new(Default::default()),
import_lock: Mutex::new(()), import_lock: Mutex::new(()),
panic_handler: panic_handler, panic_handler: panic_handler,
verifier: verification::new(config.verifier_type),
miner: miner, miner: miner,
io_channel: message_channel, io_channel: message_channel,
notify: RwLock::new(Vec::new()), notify: RwLock::new(Vec::new()),
@ -253,8 +257,9 @@ impl Client {
let mut last_hashes = LastHashes::new(); let mut last_hashes = LastHashes::new();
last_hashes.resize(256, H256::default()); last_hashes.resize(256, H256::default());
last_hashes[0] = parent_hash; last_hashes[0] = parent_hash;
let chain = self.chain.read();
for i in 0..255 { for i in 0..255 {
match self.chain.block_details(&last_hashes[i]) { match chain.block_details(&last_hashes[i]) {
Some(details) => { Some(details) => {
last_hashes[i + 1] = details.parent.clone(); last_hashes[i + 1] = details.parent.clone();
}, },
@ -270,22 +275,23 @@ impl Client {
let engine = &*self.engine; let engine = &*self.engine;
let header = &block.header; let header = &block.header;
let chain = self.chain.read();
// Check the block isn't so old we won't be able to enact it. // Check the block isn't so old we won't be able to enact it.
let best_block_number = self.chain.best_block_number(); let best_block_number = chain.best_block_number();
if best_block_number >= HISTORY && header.number() <= best_block_number - HISTORY { if best_block_number >= HISTORY && header.number() <= best_block_number - HISTORY {
warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number); warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number);
return Err(()); return Err(());
} }
// Verify Block Family // Verify Block Family
let verify_family_result = self.verifier.verify_block_family(header, &block.bytes, engine, &*self.chain); let verify_family_result = self.verifier.verify_block_family(header, &block.bytes, engine, &**chain);
if let Err(e) = verify_family_result { if let Err(e) = verify_family_result {
warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
return Err(()); return Err(());
}; };
// Check if Parent is in chain // Check if Parent is in chain
let chain_has_parent = self.chain.block_header(header.parent_hash()); let chain_has_parent = chain.block_header(header.parent_hash());
if let None = chain_has_parent { if let None = chain_has_parent {
warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash()); warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash());
return Err(()); return Err(());
@ -294,9 +300,9 @@ impl Client {
// Enact Verified Block // Enact Verified Block
let parent = chain_has_parent.unwrap(); let parent = chain_has_parent.unwrap();
let last_hashes = self.build_last_hashes(header.parent_hash().clone()); let last_hashes = self.build_last_hashes(header.parent_hash().clone());
let db = self.state_db.lock().boxed_clone(); let db = self.state_db.read().boxed_clone();
let enact_result = enact_verified(block, engine, self.tracedb.tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone());
if let Err(e) = enact_result { if let Err(e) = enact_result {
warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e); warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
return Err(()); return Err(());
@ -408,17 +414,18 @@ impl Client {
} }
} }
self.db.flush().expect("DB flush failed."); self.db.read().flush().expect("DB flush failed.");
imported imported
} }
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain { fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain {
let number = block.header().number(); let number = block.header().number();
let parent = block.header().parent_hash().clone(); let parent = block.header().parent_hash().clone();
let chain = self.chain.read();
// Are we committing an era? // Are we committing an era?
let ancient = if number >= HISTORY { let ancient = if number >= HISTORY {
let n = number - HISTORY; let n = number - HISTORY;
Some((n, self.chain.block_hash(n).unwrap())) Some((n, chain.block_hash(n).unwrap()))
} else { } else {
None None
}; };
@ -432,14 +439,14 @@ impl Client {
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new)); //let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
let mut batch = DBTransaction::new(&self.db); let mut batch = DBTransaction::new(&self.db.read());
// CHECK! I *think* this is fine, even if the state_root is equal to another // CHECK! I *think* this is fine, even if the state_root is equal to another
// already-imported block of the same number. // already-imported block of the same number.
// TODO: Prove it with a test. // TODO: Prove it with a test.
block.drain().commit(&mut batch, number, hash, ancient).expect("DB commit failed."); block.drain().commit(&mut batch, number, hash, ancient).expect("DB commit failed.");
let route = self.chain.insert_block(&mut batch, block_data, receipts); let route = chain.insert_block(&mut batch, block_data, receipts);
self.tracedb.import(&mut batch, TraceImportRequest { self.tracedb.read().import(&mut batch, TraceImportRequest {
traces: traces.into(), traces: traces.into(),
block_hash: hash.clone(), block_hash: hash.clone(),
block_number: number, block_number: number,
@ -447,8 +454,8 @@ impl Client {
retracted: route.retracted.len() retracted: route.retracted.len()
}); });
// Final commit to the DB // Final commit to the DB
self.db.write_buffered(batch); self.db.read().write_buffered(batch);
self.chain.commit(); chain.commit();
self.update_last_hashes(&parent, hash); self.update_last_hashes(&parent, hash);
route route
@ -491,10 +498,10 @@ impl Client {
}; };
self.block_header(id).and_then(|header| { self.block_header(id).and_then(|header| {
let db = self.state_db.lock().boxed_clone(); let db = self.state_db.read().boxed_clone();
// early exit for pruned blocks // early exit for pruned blocks
if db.is_pruned() && self.chain.best_block_number() >= block_number + HISTORY { if db.is_pruned() && self.chain.read().best_block_number() >= block_number + HISTORY {
return None; return None;
} }
@ -522,7 +529,7 @@ impl Client {
/// Get a copy of the best block's state. /// Get a copy of the best block's state.
pub fn state(&self) -> State { pub fn state(&self) -> State {
State::from_existing( State::from_existing(
self.state_db.lock().boxed_clone(), self.state_db.read().boxed_clone(),
HeaderView::new(&self.best_block_header()).state_root(), HeaderView::new(&self.best_block_header()).state_root(),
self.engine.account_start_nonce(), self.engine.account_start_nonce(),
self.factories.clone()) self.factories.clone())
@ -531,22 +538,22 @@ impl Client {
/// Get info on the cache. /// Get info on the cache.
pub fn blockchain_cache_info(&self) -> BlockChainCacheSize { pub fn blockchain_cache_info(&self) -> BlockChainCacheSize {
self.chain.cache_size() self.chain.read().cache_size()
} }
/// Get the report. /// Get the report.
pub fn report(&self) -> ClientReport { pub fn report(&self) -> ClientReport {
let mut report = self.report.read().clone(); let mut report = self.report.read().clone();
report.state_db_mem = self.state_db.lock().mem_used(); report.state_db_mem = self.state_db.read().mem_used();
report report
} }
/// Tick the client. /// Tick the client.
// TODO: manage by real events. // TODO: manage by real events.
pub fn tick(&self) { pub fn tick(&self) {
self.chain.collect_garbage(); self.chain.read().collect_garbage();
self.block_queue.collect_garbage(); self.block_queue.collect_garbage();
self.tracedb.collect_garbage(); self.tracedb.read().collect_garbage();
match self.mode { match self.mode {
Mode::Dark(timeout) => { Mode::Dark(timeout) => {
@ -584,16 +591,16 @@ impl Client {
pub fn block_number(&self, id: BlockID) -> Option<BlockNumber> { pub fn block_number(&self, id: BlockID) -> Option<BlockNumber> {
match id { match id {
BlockID::Number(number) => Some(number), BlockID::Number(number) => Some(number),
BlockID::Hash(ref hash) => self.chain.block_number(hash), BlockID::Hash(ref hash) => self.chain.read().block_number(hash),
BlockID::Earliest => Some(0), BlockID::Earliest => Some(0),
BlockID::Latest | BlockID::Pending => Some(self.chain.best_block_number()), BlockID::Latest | BlockID::Pending => Some(self.chain.read().best_block_number()),
} }
} }
/// Take a snapshot at the given block. /// Take a snapshot at the given block.
/// If the ID given is "latest", this will default to 1000 blocks behind. /// If the ID given is "latest", this will default to 1000 blocks behind.
pub fn take_snapshot<W: snapshot_io::SnapshotWriter + Send>(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), ::error::Error> { pub fn take_snapshot<W: snapshot_io::SnapshotWriter + Send>(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), EthcoreError> {
let db = self.state_db.lock().boxed_clone(); let db = self.state_db.read().boxed_clone();
let best_block_number = self.chain_info().best_block_number; let best_block_number = self.chain_info().best_block_number;
let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at))); let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at)));
@ -618,7 +625,7 @@ impl Client {
}, },
}; };
try!(snapshot::take_snapshot(&self.chain, start_hash, db.as_hashdb(), writer, p)); try!(snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p));
Ok(()) Ok(())
} }
@ -634,8 +641,8 @@ impl Client {
fn transaction_address(&self, id: TransactionID) -> Option<TransactionAddress> { fn transaction_address(&self, id: TransactionID) -> Option<TransactionAddress> {
match id { match id {
TransactionID::Hash(ref hash) => self.chain.transaction_address(hash), TransactionID::Hash(ref hash) => self.chain.read().transaction_address(hash),
TransactionID::Location(id, index) => Self::block_hash(&self.chain, id).map(|hash| TransactionAddress { TransactionID::Location(id, index) => Self::block_hash(&self.chain.read(), id).map(|hash| TransactionAddress {
block_hash: hash, block_hash: hash,
index: index, index: index,
}) })
@ -666,6 +673,25 @@ impl Client {
} }
} }
impl snapshot::DatabaseRestore for Client {
/// Restart the client with a new backend
fn restore_db(&self, new_db: &str) -> Result<(), EthcoreError> {
let _import_lock = self.import_lock.lock();
let mut state_db = self.state_db.write();
let mut chain = self.chain.write();
let mut tracedb = self.tracedb.write();
self.miner.clear();
let db = self.db.write();
try!(db.restore(new_db));
*state_db = journaldb::new(db.clone(), self.pruning, ::db::COL_STATE);
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
*tracedb = try!(TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone()).map_err(ClientError::from));
Ok(())
}
}
impl BlockChainClient for Client { impl BlockChainClient for Client {
fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result<Executed, CallError> { fn call(&self, t: &SignedTransaction, block: BlockID, analytics: CallAnalytics) -> Result<Executed, CallError> {
let header = try!(self.block_header(block).ok_or(CallError::StatePruned)); let header = try!(self.block_header(block).ok_or(CallError::StatePruned));
@ -749,15 +775,17 @@ impl BlockChainClient for Client {
} }
fn best_block_header(&self) -> Bytes { fn best_block_header(&self) -> Bytes {
self.chain.best_block_header() self.chain.read().best_block_header()
} }
fn block_header(&self, id: BlockID) -> Option<Bytes> { fn block_header(&self, id: BlockID) -> Option<Bytes> {
Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block_header_data(&hash)) let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_header_data(&hash))
} }
fn block_body(&self, id: BlockID) -> Option<Bytes> { fn block_body(&self, id: BlockID) -> Option<Bytes> {
Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block_body(&hash)) let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_body(&hash))
} }
fn block(&self, id: BlockID) -> Option<Bytes> { fn block(&self, id: BlockID) -> Option<Bytes> {
@ -766,14 +794,16 @@ impl BlockChainClient for Client {
return Some(block.rlp_bytes(Seal::Without)); return Some(block.rlp_bytes(Seal::Without));
} }
} }
Self::block_hash(&self.chain, id).and_then(|hash| { let chain = self.chain.read();
self.chain.block(&hash) Self::block_hash(&chain, id).and_then(|hash| {
chain.block(&hash)
}) })
} }
fn block_status(&self, id: BlockID) -> BlockStatus { fn block_status(&self, id: BlockID) -> BlockStatus {
match Self::block_hash(&self.chain, id) { let chain = self.chain.read();
Some(ref hash) if self.chain.is_known(hash) => BlockStatus::InChain, match Self::block_hash(&chain, id) {
Some(ref hash) if chain.is_known(hash) => BlockStatus::InChain,
Some(hash) => self.block_queue.block_status(&hash), Some(hash) => self.block_queue.block_status(&hash),
None => BlockStatus::Unknown None => BlockStatus::Unknown
} }
@ -785,7 +815,8 @@ impl BlockChainClient for Client {
return Some(*block.header.difficulty() + self.block_total_difficulty(BlockID::Latest).expect("blocks in chain have details; qed")); return Some(*block.header.difficulty() + self.block_total_difficulty(BlockID::Latest).expect("blocks in chain have details; qed"));
} }
} }
Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block_details(&hash)).map(|d| d.total_difficulty) let chain = self.chain.read();
Self::block_hash(&chain, id).and_then(|hash| chain.block_details(&hash)).map(|d| d.total_difficulty)
} }
fn nonce(&self, address: &Address, id: BlockID) -> Option<U256> { fn nonce(&self, address: &Address, id: BlockID) -> Option<U256> {
@ -793,7 +824,8 @@ impl BlockChainClient for Client {
} }
fn block_hash(&self, id: BlockID) -> Option<H256> { fn block_hash(&self, id: BlockID) -> Option<H256> {
Self::block_hash(&self.chain, id) let chain = self.chain.read();
Self::block_hash(&chain, id)
} }
fn code(&self, address: &Address, id: BlockID) -> Option<Option<Bytes>> { fn code(&self, address: &Address, id: BlockID) -> Option<Option<Bytes>> {
@ -809,7 +841,7 @@ impl BlockChainClient for Client {
} }
fn transaction(&self, id: TransactionID) -> Option<LocalizedTransaction> { fn transaction(&self, id: TransactionID) -> Option<LocalizedTransaction> {
self.transaction_address(id).and_then(|address| self.chain.transaction(&address)) self.transaction_address(id).and_then(|address| self.chain.read().transaction(&address))
} }
fn uncle(&self, id: UncleID) -> Option<Bytes> { fn uncle(&self, id: UncleID) -> Option<Bytes> {
@ -818,11 +850,12 @@ impl BlockChainClient for Client {
} }
fn transaction_receipt(&self, id: TransactionID) -> Option<LocalizedReceipt> { fn transaction_receipt(&self, id: TransactionID) -> Option<LocalizedReceipt> {
self.transaction_address(id).and_then(|address| self.chain.block_number(&address.block_hash).and_then(|block_number| { let chain = self.chain.read();
let t = self.chain.block_body(&address.block_hash) self.transaction_address(id).and_then(|address| chain.block_number(&address.block_hash).and_then(|block_number| {
let t = chain.block_body(&address.block_hash)
.and_then(|block| BodyView::new(&block).localized_transaction_at(&address.block_hash, block_number, address.index)); .and_then(|block| BodyView::new(&block).localized_transaction_at(&address.block_hash, block_number, address.index));
match (t, self.chain.transaction_receipt(&address)) { match (t, chain.transaction_receipt(&address)) {
(Some(tx), Some(receipt)) => { (Some(tx), Some(receipt)) => {
let block_hash = tx.block_hash.clone(); let block_hash = tx.block_hash.clone();
let block_number = tx.block_number.clone(); let block_number = tx.block_number.clone();
@ -832,7 +865,7 @@ impl BlockChainClient for Client {
0 => U256::zero(), 0 => U256::zero(),
i => { i => {
let prior_address = TransactionAddress { block_hash: address.block_hash, index: i - 1 }; let prior_address = TransactionAddress { block_hash: address.block_hash, index: i - 1 };
let prior_receipt = self.chain.transaction_receipt(&prior_address).expect("Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed"); let prior_receipt = chain.transaction_receipt(&prior_address).expect("Transaction receipt at `address` exists; `prior_address` has lower index in same block; qed");
prior_receipt.gas_used prior_receipt.gas_used
} }
}; };
@ -863,28 +896,29 @@ impl BlockChainClient for Client {
} }
fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> { fn tree_route(&self, from: &H256, to: &H256) -> Option<TreeRoute> {
match self.chain.is_known(from) && self.chain.is_known(to) { let chain = self.chain.read();
true => Some(self.chain.tree_route(from.clone(), to.clone())), match chain.is_known(from) && chain.is_known(to) {
true => Some(chain.tree_route(from.clone(), to.clone())),
false => None false => None
} }
} }
fn find_uncles(&self, hash: &H256) -> Option<Vec<H256>> { fn find_uncles(&self, hash: &H256) -> Option<Vec<H256>> {
self.chain.find_uncle_hashes(hash, self.engine.maximum_uncle_age()) self.chain.read().find_uncle_hashes(hash, self.engine.maximum_uncle_age())
} }
fn state_data(&self, hash: &H256) -> Option<Bytes> { fn state_data(&self, hash: &H256) -> Option<Bytes> {
self.state_db.lock().state(hash) self.state_db.read().state(hash)
} }
fn block_receipts(&self, hash: &H256) -> Option<Bytes> { fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
self.chain.block_receipts(hash).map(|receipts| ::rlp::encode(&receipts).to_vec()) self.chain.read().block_receipts(hash).map(|receipts| ::rlp::encode(&receipts).to_vec())
} }
fn import_block(&self, bytes: Bytes) -> Result<H256, BlockImportError> { fn import_block(&self, bytes: Bytes) -> Result<H256, BlockImportError> {
{ {
let header = BlockView::new(&bytes).header_view(); let header = BlockView::new(&bytes).header_view();
if self.chain.is_known(&header.sha3()) { if self.chain.read().is_known(&header.sha3()) {
return Err(BlockImportError::Import(ImportError::AlreadyInChain)); return Err(BlockImportError::Import(ImportError::AlreadyInChain));
} }
if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown { if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown {
@ -903,12 +937,13 @@ impl BlockChainClient for Client {
} }
fn chain_info(&self) -> BlockChainInfo { fn chain_info(&self) -> BlockChainInfo {
let chain = self.chain.read();
BlockChainInfo { BlockChainInfo {
total_difficulty: self.chain.best_block_total_difficulty(), total_difficulty: chain.best_block_total_difficulty(),
pending_total_difficulty: self.chain.best_block_total_difficulty(), pending_total_difficulty: chain.best_block_total_difficulty(),
genesis_hash: self.chain.genesis_hash(), genesis_hash: chain.genesis_hash(),
best_block_hash: self.chain.best_block_hash(), best_block_hash: chain.best_block_hash(),
best_block_number: From::from(self.chain.best_block_number()) best_block_number: From::from(chain.best_block_number())
} }
} }
@ -918,7 +953,7 @@ impl BlockChainClient for Client {
fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option<Vec<BlockNumber>> { fn blocks_with_bloom(&self, bloom: &H2048, from_block: BlockID, to_block: BlockID) -> Option<Vec<BlockNumber>> {
match (self.block_number(from_block), self.block_number(to_block)) { match (self.block_number(from_block), self.block_number(to_block)) {
(Some(from), Some(to)) => Some(self.chain.blocks_with_bloom(bloom, from, to)), (Some(from), Some(to)) => Some(self.chain.read().blocks_with_bloom(bloom, from, to)),
_ => None _ => None
} }
} }
@ -936,10 +971,11 @@ impl BlockChainClient for Client {
blocks.sort(); blocks.sort();
let chain = self.chain.read();
blocks.into_iter() blocks.into_iter()
.filter_map(|number| self.chain.block_hash(number).map(|hash| (number, hash))) .filter_map(|number| chain.block_hash(number).map(|hash| (number, hash)))
.filter_map(|(number, hash)| self.chain.block_receipts(&hash).map(|r| (number, hash, r.receipts))) .filter_map(|(number, hash)| chain.block_receipts(&hash).map(|r| (number, hash, r.receipts)))
.filter_map(|(number, hash, receipts)| self.chain.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes()))) .filter_map(|(number, hash, receipts)| chain.block_body(&hash).map(|ref b| (number, hash, receipts, BodyView::new(b).transaction_hashes())))
.flat_map(|(number, hash, receipts, hashes)| { .flat_map(|(number, hash, receipts, hashes)| {
let mut log_index = 0; let mut log_index = 0;
receipts.into_iter() receipts.into_iter()
@ -975,7 +1011,7 @@ impl BlockChainClient for Client {
to_address: From::from(filter.to_address), to_address: From::from(filter.to_address),
}; };
let traces = self.tracedb.filter(&filter); let traces = self.tracedb.read().filter(&filter);
Some(traces) Some(traces)
} else { } else {
None None
@ -987,7 +1023,7 @@ impl BlockChainClient for Client {
self.transaction_address(trace.transaction) self.transaction_address(trace.transaction)
.and_then(|tx_address| { .and_then(|tx_address| {
self.block_number(BlockID::Hash(tx_address.block_hash)) self.block_number(BlockID::Hash(tx_address.block_hash))
.and_then(|number| self.tracedb.trace(number, tx_address.index, trace_address)) .and_then(|number| self.tracedb.read().trace(number, tx_address.index, trace_address))
}) })
} }
@ -995,17 +1031,17 @@ impl BlockChainClient for Client {
self.transaction_address(transaction) self.transaction_address(transaction)
.and_then(|tx_address| { .and_then(|tx_address| {
self.block_number(BlockID::Hash(tx_address.block_hash)) self.block_number(BlockID::Hash(tx_address.block_hash))
.and_then(|number| self.tracedb.transaction_traces(number, tx_address.index)) .and_then(|number| self.tracedb.read().transaction_traces(number, tx_address.index))
}) })
} }
fn block_traces(&self, block: BlockID) -> Option<Vec<LocalizedTrace>> { fn block_traces(&self, block: BlockID) -> Option<Vec<LocalizedTrace>> {
self.block_number(block) self.block_number(block)
.and_then(|number| self.tracedb.block_traces(number)) .and_then(|number| self.tracedb.read().block_traces(number))
} }
fn last_hashes(&self) -> LastHashes { fn last_hashes(&self) -> LastHashes {
(*self.build_last_hashes(self.chain.best_block_hash())).clone() (*self.build_last_hashes(self.chain.read().best_block_hash())).clone()
} }
fn queue_transactions(&self, transactions: Vec<Bytes>) { fn queue_transactions(&self, transactions: Vec<Bytes>) {
@ -1032,14 +1068,15 @@ impl BlockChainClient for Client {
impl MiningBlockChainClient for Client { impl MiningBlockChainClient for Client {
fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock {
let engine = &*self.engine; let engine = &*self.engine;
let h = self.chain.best_block_hash(); let chain = self.chain.read();
let h = chain.best_block_hash();
let mut open_block = OpenBlock::new( let mut open_block = OpenBlock::new(
engine, engine,
self.factories.clone(), self.factories.clone(),
false, // TODO: this will need to be parameterised once we want to do immediate mining insertion. false, // TODO: this will need to be parameterised once we want to do immediate mining insertion.
self.state_db.lock().boxed_clone(), self.state_db.read().boxed_clone(),
&self.chain.block_header(&h).expect("h is best block hash: so its header must exist: qed"), &chain.block_header(&h).expect("h is best block hash: so its header must exist: qed"),
self.build_last_hashes(h.clone()), self.build_last_hashes(h.clone()),
author, author,
gas_range_target, gas_range_target,
@ -1047,7 +1084,7 @@ impl MiningBlockChainClient for Client {
).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed"); ).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed");
// Add uncles // Add uncles
self.chain chain
.find_uncle_headers(&h, engine.maximum_uncle_age()) .find_uncle_headers(&h, engine.maximum_uncle_age())
.unwrap() .unwrap()
.into_iter() .into_iter()
@ -1088,7 +1125,7 @@ impl MiningBlockChainClient for Client {
precise_time_ns() - start, precise_time_ns() - start,
); );
}); });
self.db.flush().expect("DB flush failed."); self.db.read().flush().expect("DB flush failed.");
Ok(h) Ok(h)
} }
} }

View File

@ -62,7 +62,7 @@ impl FromStr for DatabaseCompactionProfile {
} }
/// Operating mode for the client. /// Operating mode for the client.
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq, Clone)]
pub enum Mode { pub enum Mode {
/// Always on. /// Always on.
Active, Active,

View File

@ -227,6 +227,11 @@ impl Miner {
self.options.force_sealing || !self.options.new_work_notify.is_empty() self.options.force_sealing || !self.options.new_work_notify.is_empty()
} }
/// Clear all pending block states
pub fn clear(&self) {
self.sealing_work.lock().queue.reset();
}
/// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing. /// Get `Some` `clone()` of the current pending block's state or `None` if we're not sealing.
pub fn pending_state(&self) -> Option<State> { pub fn pending_state(&self) -> Option<State> {
self.sealing_work.lock().queue.peek_last_ref().map(|b| b.block().fields().state.clone()) self.sealing_work.lock().queue.peek_last_ref().map(|b| b.block().fields().state.clone())

View File

@ -78,7 +78,7 @@ impl ClientService {
let pruning = config.pruning; let pruning = config.pruning;
let client = try!(Client::new(config, &spec, db_path, miner, io_service.channel())); let client = try!(Client::new(config, &spec, db_path, miner, io_service.channel()));
let snapshot = try!(SnapshotService::new(spec, pruning, db_path.into(), io_service.channel())); let snapshot = try!(SnapshotService::new(spec, pruning, db_path.into(), io_service.channel(), client.clone()));
let snapshot = Arc::new(snapshot); let snapshot = Arc::new(snapshot);
@ -90,7 +90,7 @@ impl ClientService {
try!(io_service.register_handler(client_io)); try!(io_service.register_handler(client_io));
let stop_guard = ::devtools::StopGuard::new(); let stop_guard = ::devtools::StopGuard::new();
run_ipc(ipc_path, client.clone(), stop_guard.share()); run_ipc(ipc_path, client.clone(), snapshot.clone(), stop_guard.share());
Ok(ClientService { Ok(ClientService {
io_service: Arc::new(io_service), io_service: Arc::new(io_service),
@ -176,14 +176,27 @@ impl IoHandler<ClientIoMessage> for ClientIoHandler {
} }
#[cfg(feature="ipc")] #[cfg(feature="ipc")]
fn run_ipc(base_path: &Path, client: Arc<Client>, stop: Arc<AtomicBool>) { fn run_ipc(base_path: &Path, client: Arc<Client>, snapshot_service: Arc<SnapshotService>, stop: Arc<AtomicBool>) {
let mut path = base_path.to_owned(); let mut path = base_path.to_owned();
path.push("parity-chain.ipc"); path.push("parity-chain.ipc");
let socket_addr = format!("ipc://{}", path.to_string_lossy()); let socket_addr = format!("ipc://{}", path.to_string_lossy());
let s = stop.clone();
::std::thread::spawn(move || { ::std::thread::spawn(move || {
let mut worker = nanoipc::Worker::new(&(client as Arc<BlockChainClient>)); let mut worker = nanoipc::Worker::new(&(client as Arc<BlockChainClient>));
worker.add_reqrep(&socket_addr).expect("Ipc expected to initialize with no issues"); worker.add_reqrep(&socket_addr).expect("Ipc expected to initialize with no issues");
while !s.load(::std::sync::atomic::Ordering::Relaxed) {
worker.poll();
}
});
let mut path = base_path.to_owned();
path.push("parity-snapshot.ipc");
let socket_addr = format!("ipc://{}", path.to_string_lossy());
::std::thread::spawn(move || {
let mut worker = nanoipc::Worker::new(&(snapshot_service as Arc<::snapshot::SnapshotService>));
worker.add_reqrep(&socket_addr).expect("Ipc expected to initialize with no issues");
while !stop.load(::std::sync::atomic::Ordering::Relaxed) { while !stop.load(::std::sync::atomic::Ordering::Relaxed) {
worker.poll(); worker.poll();
} }
@ -191,7 +204,7 @@ fn run_ipc(base_path: &Path, client: Arc<Client>, stop: Arc<AtomicBool>) {
} }
#[cfg(not(feature="ipc"))] #[cfg(not(feature="ipc"))]
fn run_ipc(_base_path: &Path, _client: Arc<Client>, _stop: Arc<AtomicBool>) { fn run_ipc(_base_path: &Path, _client: Arc<Client>, _snapshot_service: Arc<SnapshotService>, _stop: Arc<AtomicBool>) {
} }
#[cfg(test)] #[cfg(test)]

View File

@ -32,9 +32,9 @@ use util::Mutex;
use util::hash::{FixedHash, H256}; use util::hash::{FixedHash, H256};
use util::journaldb::{self, Algorithm, JournalDB}; use util::journaldb::{self, Algorithm, JournalDB};
use util::kvdb::Database; use util::kvdb::Database;
use util::sha3::SHA3_NULL_RLP;
use util::trie::{TrieDB, TrieDBMut, Trie, TrieMut}; use util::trie::{TrieDB, TrieDBMut, Trie, TrieMut};
use rlp::{DecoderError, RlpStream, Stream, UntrustedRlp, View, Compressible, RlpType}; use util::sha3::SHA3_NULL_RLP;
use rlp::{RlpStream, Stream, UntrustedRlp, View, Compressible, RlpType};
use self::account::Account; use self::account::Account;
use self::block::AbridgedBlock; use self::block::AbridgedBlock;
@ -44,7 +44,10 @@ use crossbeam::{scope, ScopedJoinHandle};
use rand::{Rng, OsRng}; use rand::{Rng, OsRng};
pub use self::error::Error; pub use self::error::Error;
pub use self::service::{RestorationStatus, Service, SnapshotService}; pub use self::service::{Service, DatabaseRestore};
pub use self::traits::{SnapshotService, RemoteSnapshotService};
pub use types::snapshot_manifest::ManifestData;
pub use types::restoration_status::RestorationStatus;
pub mod io; pub mod io;
pub mod service; pub mod service;
@ -56,6 +59,11 @@ mod error;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod traits {
#![allow(dead_code, unused_assignments, unused_variables, missing_docs)] // codegen issues
include!(concat!(env!("OUT_DIR"), "/snapshot_service_trait.rs"));
}
// Try to have chunks be around 4MB (before compression) // Try to have chunks be around 4MB (before compression)
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024; const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
@ -354,54 +362,6 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex<SnapshotWriter +
Ok(chunker.hashes) Ok(chunker.hashes)
} }
/// Manifest data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManifestData {
/// List of state chunk hashes.
pub state_hashes: Vec<H256>,
/// List of block chunk hashes.
pub block_hashes: Vec<H256>,
/// The final, expected state root.
pub state_root: H256,
/// Block number this snapshot was taken at.
pub block_number: u64,
/// Block hash this snapshot was taken at.
pub block_hash: H256,
}
impl ManifestData {
/// Encode the manifest data to rlp.
pub fn into_rlp(self) -> Bytes {
let mut stream = RlpStream::new_list(5);
stream.append(&self.state_hashes);
stream.append(&self.block_hashes);
stream.append(&self.state_root);
stream.append(&self.block_number);
stream.append(&self.block_hash);
stream.out()
}
/// Try to restore manifest data from raw bytes, interpreted as RLP.
pub fn from_rlp(raw: &[u8]) -> Result<Self, DecoderError> {
let decoder = UntrustedRlp::new(raw);
let state_hashes: Vec<H256> = try!(decoder.val_at(0));
let block_hashes: Vec<H256> = try!(decoder.val_at(1));
let state_root: H256 = try!(decoder.val_at(2));
let block_number: u64 = try!(decoder.val_at(3));
let block_hash: H256 = try!(decoder.val_at(4));
Ok(ManifestData {
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: state_root,
block_number: block_number,
block_hash: block_hash,
})
}
}
/// Used to rebuild the state trie piece by piece. /// Used to rebuild the state trie piece by piece.
pub struct StateRebuilder { pub struct StateRebuilder {
db: Box<JournalDB>, db: Box<JournalDB>,

View File

@ -23,7 +23,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use super::{ManifestData, StateRebuilder, BlockRebuilder}; use super::{ManifestData, StateRebuilder, BlockRebuilder, RestorationStatus, SnapshotService};
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter}; use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
use blockchain::BlockChain; use blockchain::BlockChain;
@ -39,51 +39,10 @@ use util::journaldb::Algorithm;
use util::kvdb::{Database, DatabaseConfig}; use util::kvdb::{Database, DatabaseConfig};
use util::snappy; use util::snappy;
/// Statuses for restorations. /// External database restoration handler
#[derive(PartialEq, Eq, Clone, Copy, Debug)] pub trait DatabaseRestore : Send + Sync {
pub enum RestorationStatus { /// Restart with a new backend. Takes ownership of passed database and moves it to a new location.
/// No restoration. fn restore_db(&self, new_db: &str) -> Result<(), Error>;
Inactive,
/// Ongoing restoration.
Ongoing,
/// Failed restoration.
Failed,
}
/// The interface for a snapshot network service.
/// This handles:
/// - restoration of snapshots to temporary databases.
/// - responding to queries for snapshot manifests and chunks
pub trait SnapshotService {
/// Query the most recent manifest data.
fn manifest(&self) -> Option<ManifestData>;
/// Get raw chunk for a given hash.
fn chunk(&self, hash: H256) -> Option<Bytes>;
/// Ask the snapshot service for the restoration status.
fn status(&self) -> RestorationStatus;
/// Ask the snapshot service for the number of chunks completed.
/// Return a tuple of (state_chunks, block_chunks).
/// Undefined when not restoring.
fn chunks_done(&self) -> (usize, usize);
/// Begin snapshot restoration.
/// If restoration in-progress, this will reset it.
/// From this point on, any previous snapshot may become unavailable.
fn begin_restore(&self, manifest: ManifestData);
/// Abort an in-progress restoration if there is one.
fn abort_restore(&self);
/// Feed a raw state chunk to the service to be processed asynchronously.
/// no-op if not currently restoring.
fn restore_state_chunk(&self, hash: H256, chunk: Bytes);
/// Feed a raw block chunk to the service to be processed asynchronously.
/// no-op if currently restoring.
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
} }
/// State restoration manager. /// State restoration manager.
@ -208,11 +167,12 @@ pub struct Service {
genesis_block: Bytes, genesis_block: Bytes,
state_chunks: AtomicUsize, state_chunks: AtomicUsize,
block_chunks: AtomicUsize, block_chunks: AtomicUsize,
db_restore: Arc<DatabaseRestore>,
} }
impl Service { impl Service {
/// Create a new snapshot service. /// Create a new snapshot service.
pub fn new(spec: &Spec, pruning: Algorithm, client_db: PathBuf, io_channel: Channel) -> Result<Self, Error> { pub fn new(spec: &Spec, pruning: Algorithm, client_db: PathBuf, io_channel: Channel, db_restore: Arc<DatabaseRestore>) -> Result<Self, Error> {
let db_path = try!(client_db.parent().and_then(Path::parent) let db_path = try!(client_db.parent().and_then(Path::parent)
.ok_or_else(|| UtilError::SimpleString("Failed to find database root.".into()))).to_owned(); .ok_or_else(|| UtilError::SimpleString("Failed to find database root.".into()))).to_owned();
@ -236,6 +196,7 @@ impl Service {
genesis_block: spec.genesis_block(), genesis_block: spec.genesis_block(),
state_chunks: AtomicUsize::new(0), state_chunks: AtomicUsize::new(0),
block_chunks: AtomicUsize::new(0), block_chunks: AtomicUsize::new(0),
db_restore: db_restore,
}; };
// create the root snapshot dir if it doesn't exist. // create the root snapshot dir if it doesn't exist.
@ -295,37 +256,8 @@ impl Service {
let our_db = self.restoration_db(); let our_db = self.restoration_db();
trace!(target: "snapshot", "replacing {:?} with {:?}", self.client_db, our_db); trace!(target: "snapshot", "replacing {:?} with {:?}", self.client_db, our_db);
try!(self.db_restore.restore_db(our_db.to_str().unwrap()));
let mut backup_db = self.restoration_dir(); Ok(())
backup_db.push("backup_db");
let _ = fs::remove_dir_all(&backup_db);
let existed = match fs::rename(&self.client_db, &backup_db) {
Ok(_) => true,
Err(e) => if let ErrorKind::NotFound = e.kind() {
false
} else {
return Err(e.into());
}
};
match fs::rename(&our_db, &self.client_db) {
Ok(_) => {
// clean up the backup.
if existed {
try!(fs::remove_dir_all(&backup_db));
}
Ok(())
}
Err(e) => {
// restore the backup.
if existed {
try!(fs::rename(&backup_db, &self.client_db));
}
Err(e.into())
}
}
} }
/// Initialize the restoration synchronously. /// Initialize the restoration synchronously.
@ -360,7 +292,10 @@ impl Service {
*res = Some(try!(Restoration::new(params))); *res = Some(try!(Restoration::new(params)));
*self.status.lock() = RestorationStatus::Ongoing; *self.status.lock() = RestorationStatus::Ongoing {
state_chunks_done: self.state_chunks.load(Ordering::Relaxed) as u32,
block_chunks_done: self.block_chunks.load(Ordering::Relaxed) as u32,
};
Ok(()) Ok(())
} }
@ -418,7 +353,7 @@ impl Service {
match self.status() { match self.status() {
RestorationStatus::Inactive | RestorationStatus::Failed => Ok(()), RestorationStatus::Inactive | RestorationStatus::Failed => Ok(()),
RestorationStatus::Ongoing => { RestorationStatus::Ongoing { .. } => {
let res = { let res = {
let rest = match *restoration { let rest = match *restoration {
Some(ref mut r) => r, Some(ref mut r) => r,
@ -489,10 +424,6 @@ impl SnapshotService for Service {
*self.status.lock() *self.status.lock()
} }
fn chunks_done(&self) -> (usize, usize) {
(self.state_chunks.load(Ordering::Relaxed), self.block_chunks.load(Ordering::Relaxed))
}
fn begin_restore(&self, manifest: ManifestData) { fn begin_restore(&self, manifest: ManifestData) {
self.io_channel.send(ClientIoMessage::BeginRestoration(manifest)) self.io_channel.send(ClientIoMessage::BeginRestoration(manifest))
.expect("snapshot service and io service are kept alive by client service; qed"); .expect("snapshot service and io service are kept alive by client service; qed");
@ -522,15 +453,23 @@ impl SnapshotService for Service {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc;
use service::ClientIoMessage; use service::ClientIoMessage;
use io::{IoService}; use io::{IoService};
use devtools::RandomTempPath; use devtools::RandomTempPath;
use tests::helpers::get_test_spec; use tests::helpers::get_test_spec;
use util::journaldb::Algorithm; use util::journaldb::Algorithm;
use error::Error;
use snapshot::ManifestData; use snapshot::{ManifestData, RestorationStatus, SnapshotService};
use super::*; use super::*;
struct NoopDBRestore;
impl DatabaseRestore for NoopDBRestore {
fn restore_db(&self, _new_db: &str) -> Result<(), Error> {
Ok(())
}
}
#[test] #[test]
fn sends_async_messages() { fn sends_async_messages() {
let service = IoService::<ClientIoMessage>::start().unwrap(); let service = IoService::<ClientIoMessage>::start().unwrap();
@ -544,13 +483,13 @@ mod tests {
&get_test_spec(), &get_test_spec(),
Algorithm::Archive, Algorithm::Archive,
dir, dir,
service.channel() service.channel(),
Arc::new(NoopDBRestore),
).unwrap(); ).unwrap();
assert!(service.manifest().is_none()); assert!(service.manifest().is_none());
assert!(service.chunk(Default::default()).is_none()); assert!(service.chunk(Default::default()).is_none());
assert_eq!(service.status(), RestorationStatus::Inactive); assert_eq!(service.status(), RestorationStatus::Inactive);
assert_eq!(service.chunks_done(), (0, 0));
let manifest = ManifestData { let manifest = ManifestData {
state_hashes: vec![], state_hashes: vec![],
@ -566,3 +505,9 @@ mod tests {
service.restore_block_chunk(Default::default(), vec![]); service.restore_block_chunk(Default::default(), vec![]);
} }
} }
impl Drop for Service {
fn drop(&mut self) {
self.abort_restore();
}
}

View File

@ -0,0 +1,54 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use super::{ManifestData, RestorationStatus};
use util::{Bytes, H256};
use ipc::IpcConfig;
/// The interface for a snapshot network service.
/// This handles:
/// - restoration of snapshots to temporary databases.
/// - responding to queries for snapshot manifests and chunks
#[derive(Ipc)]
#[ipc(client_ident="RemoteSnapshotService")]
pub trait SnapshotService : Sync + Send {
/// Query the most recent manifest data.
fn manifest(&self) -> Option<ManifestData>;
/// Get raw chunk for a given hash.
fn chunk(&self, hash: H256) -> Option<Bytes>;
/// Ask the snapshot service for the restoration status.
fn status(&self) -> RestorationStatus;
/// Begin snapshot restoration.
/// If restoration in-progress, this will reset it.
/// From this point on, any previous snapshot may become unavailable.
fn begin_restore(&self, manifest: ManifestData);
/// Abort an in-progress restoration if there is one.
fn abort_restore(&self);
/// Feed a raw state chunk to the service to be processed asynchronously.
/// no-op if not currently restoring.
fn restore_state_chunk(&self, hash: H256, chunk: Bytes);
/// Feed a raw block chunk to the service to be processed asynchronously.
/// no-op if currently restoring.
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
}
impl IpcConfig for SnapshotService { }

View File

@ -31,3 +31,5 @@ pub mod trace_filter;
pub mod call_analytics; pub mod call_analytics;
pub mod transaction_import; pub mod transaction_import;
pub mod block_import_error; pub mod block_import_error;
pub mod restoration_status;
pub mod snapshot_manifest;

View File

@ -0,0 +1,34 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Restoration status type definition
/// Statuses for restorations.
#[derive(PartialEq, Eq, Clone, Copy, Debug, Binary)]
pub enum RestorationStatus {
/// No restoration.
Inactive,
/// Ongoing restoration.
Ongoing {
/// Number of state chunks completed.
state_chunks_done: u32,
/// Number of block chunks completed.
block_chunks_done: u32,
},
/// Failed restoration.
Failed,
}

View File

@ -0,0 +1,70 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Snapshot manifest type definition
use util::hash::H256;
use rlp::*;
use util::Bytes;
/// Manifest data.
#[derive(Debug, Clone, PartialEq, Eq, Binary)]
pub struct ManifestData {
/// List of state chunk hashes.
pub state_hashes: Vec<H256>,
/// List of block chunk hashes.
pub block_hashes: Vec<H256>,
/// The final, expected state root.
pub state_root: H256,
/// Block number this snapshot was taken at.
pub block_number: u64,
/// Block hash this snapshot was taken at.
pub block_hash: H256,
}
impl ManifestData {
/// Encode the manifest data to rlp.
pub fn into_rlp(self) -> Bytes {
let mut stream = RlpStream::new_list(5);
stream.append(&self.state_hashes);
stream.append(&self.block_hashes);
stream.append(&self.state_root);
stream.append(&self.block_number);
stream.append(&self.block_hash);
stream.out()
}
/// Try to restore manifest data from raw bytes, interpreted as RLP.
pub fn from_rlp(raw: &[u8]) -> Result<Self, DecoderError> {
let decoder = UntrustedRlp::new(raw);
let state_hashes: Vec<H256> = try!(decoder.val_at(0));
let block_hashes: Vec<H256> = try!(decoder.val_at(1));
let state_root: H256 = try!(decoder.val_at(2));
let block_number: u64 = try!(decoder.val_at(3));
let block_hash: H256 = try!(decoder.val_at(4));
Ok(ManifestData {
state_hashes: state_hashes,
block_hashes: block_hashes,
state_root: state_root,
block_number: block_number,
block_hash: block_hash,
})
}
}

View File

@ -25,7 +25,7 @@ pub use self::canon_verifier::CanonVerifier;
pub use self::noop_verifier::NoopVerifier; pub use self::noop_verifier::NoopVerifier;
/// Verifier type. /// Verifier type.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq, Clone)]
pub enum VerifierType { pub enum VerifierType {
/// Verifies block normally. /// Verifies block normally.
Canon, Canon,

View File

@ -99,9 +99,11 @@ mod modules;
mod account; mod account;
mod blockchain; mod blockchain;
mod presale; mod presale;
mod run;
mod sync;
mod snapshot; mod snapshot;
mod run;
#[cfg(feature="ipc")]
mod sync;
#[cfg(feature="ipc")]
mod boot; mod boot;
#[cfg(feature="stratum")] #[cfg(feature="stratum")]
@ -158,10 +160,24 @@ mod stratum_optional {
} }
} }
fn main() { #[cfg(not(feature="ipc"))]
fn sync_main() -> bool {
false
}
#[cfg(feature="ipc")]
fn sync_main() -> bool {
// just redirect to the sync::main() // just redirect to the sync::main()
if std::env::args().nth(1).map_or(false, |arg| arg == "sync") { if std::env::args().nth(1).map_or(false, |arg| arg == "sync") {
sync::main(); sync::main();
true
} else {
false
}
}
fn main() {
if sync_main() {
return; return;
} }

View File

@ -18,6 +18,7 @@ use std::sync::Arc;
use ethcore::client::BlockChainClient; use ethcore::client::BlockChainClient;
use hypervisor::Hypervisor; use hypervisor::Hypervisor;
use ethsync::{SyncConfig, NetworkConfiguration, NetworkError}; use ethsync::{SyncConfig, NetworkConfiguration, NetworkError};
use ethcore::snapshot::SnapshotService;
#[cfg(not(feature="ipc"))] #[cfg(not(feature="ipc"))]
use self::no_ipc_deps::*; use self::no_ipc_deps::*;
#[cfg(feature="ipc")] #[cfg(feature="ipc")]
@ -25,10 +26,12 @@ use self::ipc_deps::*;
use ethcore_logger::Config as LogConfig; use ethcore_logger::Config as LogConfig;
use std::path::Path; use std::path::Path;
#[cfg(feature="ipc")]
pub mod service_urls { pub mod service_urls {
use std::path::PathBuf; use std::path::PathBuf;
pub const CLIENT: &'static str = "parity-chain.ipc"; pub const CLIENT: &'static str = "parity-chain.ipc";
pub const SNAPSHOT: &'static str = "parity-snapshot.ipc";
pub const SYNC: &'static str = "parity-sync.ipc"; pub const SYNC: &'static str = "parity-sync.ipc";
pub const SYNC_NOTIFY: &'static str = "parity-sync-notify.ipc"; pub const SYNC_NOTIFY: &'static str = "parity-sync-notify.ipc";
pub const NETWORK_MANAGER: &'static str = "parity-manage-net.ipc"; pub const NETWORK_MANAGER: &'static str = "parity-manage-net.ipc";
@ -119,6 +122,7 @@ pub fn sync
sync_cfg: SyncConfig, sync_cfg: SyncConfig,
net_cfg: NetworkConfiguration, net_cfg: NetworkConfiguration,
_client: Arc<BlockChainClient>, _client: Arc<BlockChainClient>,
_snapshot_service: Arc<SnapshotService>,
log_settings: &LogConfig, log_settings: &LogConfig,
) )
-> Result<SyncModules, NetworkError> -> Result<SyncModules, NetworkError>
@ -148,10 +152,11 @@ pub fn sync
sync_cfg: SyncConfig, sync_cfg: SyncConfig,
net_cfg: NetworkConfiguration, net_cfg: NetworkConfiguration,
client: Arc<BlockChainClient>, client: Arc<BlockChainClient>,
snapshot_service: Arc<SnapshotService>,
_log_settings: &LogConfig, _log_settings: &LogConfig,
) )
-> Result<SyncModules, NetworkError> -> Result<SyncModules, NetworkError>
{ {
let eth_sync = try!(EthSync::new(sync_cfg, client, net_cfg)); let eth_sync = try!(EthSync::new(sync_cfg, client, snapshot_service, net_cfg));
Ok((eth_sync.clone() as Arc<SyncProvider>, eth_sync.clone() as Arc<ManageNetwork>, eth_sync.clone() as Arc<ChainNotify>)) Ok((eth_sync.clone() as Arc<SyncProvider>, eth_sync.clone() as Arc<ManageNetwork>, eth_sync.clone() as Arc<ChainNotify>))
} }

View File

@ -179,13 +179,14 @@ pub fn execute(cmd: RunCmd) -> Result<(), String> {
// take handle to client // take handle to client
let client = service.client(); let client = service.client();
let snapshot_service = service.snapshot_service();
// create external miner // create external miner
let external_miner = Arc::new(ExternalMiner::default()); let external_miner = Arc::new(ExternalMiner::default());
// create sync object // create sync object
let (sync_provider, manage_network, chain_notify) = try!(modules::sync( let (sync_provider, manage_network, chain_notify) = try!(modules::sync(
&mut hypervisor, sync_config, net_conf.into(), client.clone(), &cmd.logger_config, &mut hypervisor, sync_config, net_conf.into(), client.clone(), snapshot_service, &cmd.logger_config,
).map_err(|e| format!("Sync error: {}", e))); ).map_err(|e| format!("Sync error: {}", e)));
service.add_notify(chain_notify.clone()); service.add_notify(chain_notify.clone());

View File

@ -129,10 +129,9 @@ impl SnapshotCommand {
let informant_handle = snapshot.clone(); let informant_handle = snapshot.clone();
::std::thread::spawn(move || { ::std::thread::spawn(move || {
while let RestorationStatus::Ongoing = informant_handle.status() { while let RestorationStatus::Ongoing { state_chunks_done, block_chunks_done } = informant_handle.status() {
let (state_chunks, block_chunks) = informant_handle.chunks_done();
info!("Processed {}/{} state chunks and {}/{} block chunks.", info!("Processed {}/{} state chunks and {}/{} block chunks.",
state_chunks, num_state, block_chunks, num_blocks); state_chunks_done, num_state, block_chunks_done, num_blocks);
::std::thread::sleep(Duration::from_secs(5)); ::std::thread::sleep(Duration::from_secs(5));
} }
@ -161,7 +160,7 @@ impl SnapshotCommand {
} }
match snapshot.status() { match snapshot.status() {
RestorationStatus::Ongoing => Err("Snapshot file is incomplete and missing chunks.".into()), RestorationStatus::Ongoing { .. } => Err("Snapshot file is incomplete and missing chunks.".into()),
RestorationStatus::Failed => Err("Snapshot restoration failed.".into()), RestorationStatus::Failed => Err("Snapshot restoration failed.".into()),
RestorationStatus::Inactive => { RestorationStatus::Inactive => {
info!("Restoration complete."); info!("Restoration complete.");

View File

@ -20,6 +20,7 @@ use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use hypervisor::{SYNC_MODULE_ID, HYPERVISOR_IPC_URL, ControlService}; use hypervisor::{SYNC_MODULE_ID, HYPERVISOR_IPC_URL, ControlService};
use ethcore::client::{RemoteClient, ChainNotify}; use ethcore::client::{RemoteClient, ChainNotify};
use ethcore::snapshot::{RemoteSnapshotService};
use ethsync::{SyncProvider, EthSync, ManageNetwork, ServiceConfiguration}; use ethsync::{SyncProvider, EthSync, ManageNetwork, ServiceConfiguration};
use modules::service_urls; use modules::service_urls;
use boot; use boot;
@ -45,8 +46,9 @@ pub fn main() {
.unwrap_or_else(|e| panic!("Fatal: error reading boot arguments ({:?})", e)); .unwrap_or_else(|e| panic!("Fatal: error reading boot arguments ({:?})", e));
let remote_client = dependency!(RemoteClient, &service_urls::with_base(&service_config.io_path, service_urls::CLIENT)); let remote_client = dependency!(RemoteClient, &service_urls::with_base(&service_config.io_path, service_urls::CLIENT));
let remote_snapshot = dependency!(RemoteSnapshotService, &service_urls::with_base(&service_config.io_path, service_urls::SNAPSHOT));
let sync = EthSync::new(service_config.sync, remote_client.service().clone(), service_config.net).unwrap(); let sync = EthSync::new(service_config.sync, remote_client.service().clone(), remote_snapshot.service().clone(), service_config.net).unwrap();
let _ = boot::main_thread(); let _ = boot::main_thread();
let service_stop = Arc::new(AtomicBool::new(false)); let service_stop = Arc::new(AtomicBool::new(false));

View File

@ -254,7 +254,8 @@ impl<C, S: ?Sized, M, EM> Eth for EthClient<C, S, M, EM> where
let status = take_weak!(self.sync).status(); let status = take_weak!(self.sync).status();
let res = match status.state { let res = match status.state {
SyncState::Idle => SyncStatus::None, SyncState::Idle => SyncStatus::None,
SyncState::Waiting | SyncState::Blocks | SyncState::NewBlocks | SyncState::ChainHead => { SyncState::Waiting | SyncState::Blocks | SyncState::NewBlocks | SyncState::ChainHead
| SyncState::SnapshotManifest | SyncState::SnapshotData | SyncState::SnapshotWaiting => {
let current_block = U256::from(take_weak!(self.client).chain_info().best_block_number); let current_block = U256::from(take_weak!(self.client).chain_info().best_block_number);
let highest_block = U256::from(status.highest_block_number.unwrap_or(status.start_block_number)); let highest_block = U256::from(status.highest_block_number.unwrap_or(status.start_block_number));

View File

@ -49,6 +49,8 @@ impl TestSyncProvider {
num_peers: config.num_peers, num_peers: config.num_peers,
num_active_peers: 0, num_active_peers: 0,
mem_used: 0, mem_used: 0,
num_snapshot_chunks: 0,
snapshot_chunks_done: 0,
}), }),
} }
} }

View File

@ -20,6 +20,7 @@ use network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId,
use util::{U256, H256}; use util::{U256, H256};
use io::{TimerToken}; use io::{TimerToken};
use ethcore::client::{BlockChainClient, ChainNotify}; use ethcore::client::{BlockChainClient, ChainNotify};
use ethcore::snapshot::SnapshotService;
use ethcore::header::BlockNumber; use ethcore::header::BlockNumber;
use sync_io::NetSyncIo; use sync_io::NetSyncIo;
use chain::{ChainSync, SyncStatus}; use chain::{ChainSync, SyncStatus};
@ -71,12 +72,12 @@ pub struct EthSync {
impl EthSync { impl EthSync {
/// Creates and register protocol with the network service /// Creates and register protocol with the network service
pub fn new(config: SyncConfig, chain: Arc<BlockChainClient>, network_config: NetworkConfiguration) -> Result<Arc<EthSync>, NetworkError> { pub fn new(config: SyncConfig, chain: Arc<BlockChainClient>, snapshot_service: Arc<SnapshotService>, network_config: NetworkConfiguration) -> Result<Arc<EthSync>, NetworkError> {
let chain_sync = ChainSync::new(config, &*chain); let chain_sync = ChainSync::new(config, &*chain);
let service = try!(NetworkService::new(try!(network_config.into_basic()))); let service = try!(NetworkService::new(try!(network_config.into_basic())));
let sync = Arc::new(EthSync{ let sync = Arc::new(EthSync{
network: service, network: service,
handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain }), handler: Arc::new(SyncProtocolHandler { sync: RwLock::new(chain_sync), chain: chain, snapshot_service: snapshot_service }),
}); });
Ok(sync) Ok(sync)
@ -93,8 +94,10 @@ impl SyncProvider for EthSync {
} }
struct SyncProtocolHandler { struct SyncProtocolHandler {
/// Shared blockchain client. TODO: this should evetually become an IPC endpoint /// Shared blockchain client.
chain: Arc<BlockChainClient>, chain: Arc<BlockChainClient>,
/// Shared snapshot service.
snapshot_service: Arc<SnapshotService>,
/// Sync strategy /// Sync strategy
sync: RwLock<ChainSync>, sync: RwLock<ChainSync>,
} }
@ -105,21 +108,21 @@ impl NetworkProtocolHandler for SyncProtocolHandler {
} }
fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) { fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
ChainSync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain), *peer, packet_id, data); ChainSync::dispatch_packet(&self.sync, &mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service), *peer, packet_id, data);
} }
fn connected(&self, io: &NetworkContext, peer: &PeerId) { fn connected(&self, io: &NetworkContext, peer: &PeerId) {
self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain), *peer); self.sync.write().on_peer_connected(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service), *peer);
} }
fn disconnected(&self, io: &NetworkContext, peer: &PeerId) { fn disconnected(&self, io: &NetworkContext, peer: &PeerId) {
self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain), *peer); self.sync.write().on_peer_aborting(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service), *peer);
} }
fn timeout(&self, io: &NetworkContext, _timer: TimerToken) { fn timeout(&self, io: &NetworkContext, _timer: TimerToken) {
self.sync.write().maintain_peers(&mut NetSyncIo::new(io, &*self.chain)); self.sync.write().maintain_peers(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service));
self.sync.write().maintain_sync(&mut NetSyncIo::new(io, &*self.chain)); self.sync.write().maintain_sync(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service));
self.sync.write().propagate_new_transactions(&mut NetSyncIo::new(io, &*self.chain)); self.sync.write().propagate_new_transactions(&mut NetSyncIo::new(io, &*self.chain, &*self.snapshot_service));
} }
} }
@ -133,7 +136,7 @@ impl ChainNotify for EthSync {
_duration: u64) _duration: u64)
{ {
self.network.with_context(ETH_PROTOCOL, |context| { self.network.with_context(ETH_PROTOCOL, |context| {
let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); let mut sync_io = NetSyncIo::new(context, &*self.handler.chain, &*self.handler.snapshot_service);
self.handler.sync.write().chain_new_blocks( self.handler.sync.write().chain_new_blocks(
&mut sync_io, &mut sync_io,
&imported, &imported,
@ -146,7 +149,7 @@ impl ChainNotify for EthSync {
fn start(&self) { fn start(&self) {
self.network.start().unwrap_or_else(|e| warn!("Error starting network: {:?}", e)); self.network.start().unwrap_or_else(|e| warn!("Error starting network: {:?}", e));
self.network.register_protocol(self.handler.clone(), ETH_PROTOCOL, &[62u8, 63u8]) self.network.register_protocol(self.handler.clone(), ETH_PROTOCOL, &[62u8, 63u8, 64u8])
.unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e)); .unwrap_or_else(|e| warn!("Error registering ethereum protocol: {:?}", e));
} }
@ -202,7 +205,7 @@ impl ManageNetwork for EthSync {
fn stop_network(&self) { fn stop_network(&self) {
self.network.with_context(ETH_PROTOCOL, |context| { self.network.with_context(ETH_PROTOCOL, |context| {
let mut sync_io = NetSyncIo::new(context, &*self.handler.chain); let mut sync_io = NetSyncIo::new(context, &*self.handler.chain, &*self.handler.snapshot_service);
self.handler.sync.write().abort(&mut sync_io); self.handler.sync.write().abort(&mut sync_io);
}); });
self.stop(); self.stop();

View File

@ -96,17 +96,19 @@ use ethcore::header::{BlockNumber, Header as BlockHeader};
use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError}; use ethcore::client::{BlockChainClient, BlockStatus, BlockID, BlockChainInfo, BlockImportError};
use ethcore::error::*; use ethcore::error::*;
use ethcore::block::Block; use ethcore::block::Block;
use ethcore::snapshot::{ManifestData, RestorationStatus};
use sync_io::SyncIo; use sync_io::SyncIo;
use time; use time;
use super::SyncConfig; use super::SyncConfig;
use blocks::BlockCollection; use blocks::BlockCollection;
use snapshot::{Snapshot, ChunkType};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
known_heap_size!(0, PeerInfo); known_heap_size!(0, PeerInfo);
type PacketDecodeError = DecoderError; type PacketDecodeError = DecoderError;
const PROTOCOL_VERSION: u8 = 63u8; const PROTOCOL_VERSION: u8 = 64u8;
const MAX_BODIES_TO_SEND: usize = 256; const MAX_BODIES_TO_SEND: usize = 256;
const MAX_HEADERS_TO_SEND: usize = 512; const MAX_HEADERS_TO_SEND: usize = 512;
const MAX_NODE_DATA_TO_SEND: usize = 1024; const MAX_NODE_DATA_TO_SEND: usize = 1024;
@ -136,14 +138,26 @@ const GET_NODE_DATA_PACKET: u8 = 0x0d;
const NODE_DATA_PACKET: u8 = 0x0e; const NODE_DATA_PACKET: u8 = 0x0e;
const GET_RECEIPTS_PACKET: u8 = 0x0f; const GET_RECEIPTS_PACKET: u8 = 0x0f;
const RECEIPTS_PACKET: u8 = 0x10; const RECEIPTS_PACKET: u8 = 0x10;
const GET_SNAPSHOT_MANIFEST_PACKET: u8 = 0x11;
const SNAPSHOT_MANIFEST_PACKET: u8 = 0x12;
const GET_SNAPSHOT_DATA_PACKET: u8 = 0x13;
const SNAPSHOT_DATA_PACKET: u8 = 0x14;
const HEADERS_TIMEOUT_SEC: f64 = 15f64; const HEADERS_TIMEOUT_SEC: f64 = 15f64;
const BODIES_TIMEOUT_SEC: f64 = 5f64; const BODIES_TIMEOUT_SEC: f64 = 5f64;
const FORK_HEADER_TIMEOUT_SEC: f64 = 3f64; const FORK_HEADER_TIMEOUT_SEC: f64 = 3f64;
const SNAPSHOT_MANIFEST_TIMEOUT_SEC: f64 = 3f64;
const SNAPSHOT_DATA_TIMEOUT_SEC: f64 = 10f64;
#[derive(Copy, Clone, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Debug)]
/// Sync state /// Sync state
pub enum SyncState { pub enum SyncState {
/// Waiting for pv64 peers to start snapshot syncing
SnapshotManifest,
/// Downloading snapshot data
SnapshotData,
/// Waiting for snapshot restoration to complete
SnapshotWaiting,
/// Downloading subchain heads /// Downloading subchain heads
ChainHead, ChainHead,
/// Initial chain sync complete. Waiting for new packets /// Initial chain sync complete. Waiting for new packets
@ -177,10 +191,14 @@ pub struct SyncStatus {
pub blocks_received: BlockNumber, pub blocks_received: BlockNumber,
/// Total number of connected peers /// Total number of connected peers
pub num_peers: usize, pub num_peers: usize,
/// Total number of active peers /// Total number of active peers.
pub num_active_peers: usize, pub num_active_peers: usize,
/// Heap memory used in bytes /// Heap memory used in bytes.
pub mem_used: usize, pub mem_used: usize,
/// Snapshot chunks
pub num_snapshot_chunks: usize,
/// Snapshot chunks downloaded
pub snapshot_chunks_done: usize,
} }
impl SyncStatus { impl SyncStatus {
@ -207,6 +225,8 @@ enum PeerAsking {
BlockHeaders, BlockHeaders,
BlockBodies, BlockBodies,
Heads, Heads,
SnapshotManifest,
SnapshotData,
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -240,6 +260,8 @@ struct PeerInfo {
asking_blocks: Vec<H256>, asking_blocks: Vec<H256>,
/// Holds requested header hash if currently requesting block header by hash /// Holds requested header hash if currently requesting block header by hash
asking_hash: Option<H256>, asking_hash: Option<H256>,
/// Holds requested snapshot chunk hash if any.
asking_snapshot_data: Option<H256>,
/// Request timestamp /// Request timestamp
ask_time: f64, ask_time: f64,
/// Holds a set of transactions recently sent to this peer to avoid spamming. /// Holds a set of transactions recently sent to this peer to avoid spamming.
@ -248,6 +270,10 @@ struct PeerInfo {
expired: bool, expired: bool,
/// Peer fork confirmation status /// Peer fork confirmation status
confirmation: ForkConfirmation, confirmation: ForkConfirmation,
/// Best snapshot hash
snapshot_hash: Option<H256>,
/// Best snapshot block number
snapshot_number: Option<BlockNumber>,
} }
impl PeerInfo { impl PeerInfo {
@ -293,6 +319,8 @@ pub struct ChainSync {
network_id: U256, network_id: U256,
/// Optional fork block to check /// Optional fork block to check
fork_block: Option<(BlockNumber, H256)>, fork_block: Option<(BlockNumber, H256)>,
/// Snapshot downloader.
snapshot: Snapshot,
} }
type RlpResponseResult = Result<Option<(PacketId, RlpStream)>, PacketDecodeError>; type RlpResponseResult = Result<Option<(PacketId, RlpStream)>, PacketDecodeError>;
@ -301,8 +329,8 @@ impl ChainSync {
/// Create a new instance of syncing strategy. /// Create a new instance of syncing strategy.
pub fn new(config: SyncConfig, chain: &BlockChainClient) -> ChainSync { pub fn new(config: SyncConfig, chain: &BlockChainClient) -> ChainSync {
let chain = chain.chain_info(); let chain = chain.chain_info();
let mut sync = ChainSync { ChainSync {
state: SyncState::ChainHead, state: SyncState::Idle,
starting_block: chain.best_block_number, starting_block: chain.best_block_number,
highest_block: None, highest_block: None,
last_imported_block: chain.best_block_number, last_imported_block: chain.best_block_number,
@ -317,16 +345,15 @@ impl ChainSync {
_max_download_ahead_blocks: max(MAX_HEADERS_TO_REQUEST, config.max_download_ahead_blocks), _max_download_ahead_blocks: max(MAX_HEADERS_TO_REQUEST, config.max_download_ahead_blocks),
network_id: config.network_id, network_id: config.network_id,
fork_block: config.fork_block, fork_block: config.fork_block,
}; snapshot: Snapshot::new(),
sync.reset(); }
sync
} }
/// @returns Synchonization status /// @returns Synchonization status
pub fn status(&self) -> SyncStatus { pub fn status(&self) -> SyncStatus {
SyncStatus { SyncStatus {
state: self.state.clone(), state: self.state.clone(),
protocol_version: 63, protocol_version: if self.state == SyncState::SnapshotData { 64 } else { 63 },
network_id: self.network_id, network_id: self.network_id,
start_block_number: self.starting_block, start_block_number: self.starting_block,
last_imported_block_number: Some(self.last_imported_block), last_imported_block_number: Some(self.last_imported_block),
@ -335,6 +362,8 @@ impl ChainSync {
blocks_total: match self.highest_block { Some(x) if x > self.starting_block => x - self.starting_block, _ => 0 }, blocks_total: match self.highest_block { Some(x) if x > self.starting_block => x - self.starting_block, _ => 0 },
num_peers: self.peers.values().filter(|p| p.is_allowed()).count(), num_peers: self.peers.values().filter(|p| p.is_allowed()).count(),
num_active_peers: self.peers.values().filter(|p| p.is_allowed() && p.asking != PeerAsking::Nothing).count(), num_active_peers: self.peers.values().filter(|p| p.is_allowed() && p.asking != PeerAsking::Nothing).count(),
num_snapshot_chunks: self.snapshot.total_chunks(),
snapshot_chunks_done: self.snapshot.done_chunks(),
mem_used: mem_used:
self.blocks.heap_size() self.blocks.heap_size()
+ self.peers.heap_size_of_children() + self.peers.heap_size_of_children()
@ -350,8 +379,13 @@ impl ChainSync {
#[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()` #[cfg_attr(feature="dev", allow(for_kv_map))] // Because it's not possible to get `values_mut()`
/// Reset sync. Clear all downloaded data but keep the queue /// Reset sync. Clear all downloaded data but keep the queue
fn reset(&mut self) { fn reset(&mut self, io: &mut SyncIo) {
self.blocks.clear(); self.blocks.clear();
self.snapshot.clear();
if self.state == SyncState::SnapshotData {
debug!(target:"sync", "Aborting snapshot restore");
io.snapshot_service().abort_restore();
}
for (_, ref mut p) in &mut self.peers { for (_, ref mut p) in &mut self.peers {
p.asking_blocks.clear(); p.asking_blocks.clear();
p.asking_hash = None; p.asking_hash = None;
@ -368,7 +402,7 @@ impl ChainSync {
/// Restart sync /// Restart sync
pub fn restart(&mut self, io: &mut SyncIo) { pub fn restart(&mut self, io: &mut SyncIo) {
trace!(target: "sync", "Restarting"); trace!(target: "sync", "Restarting");
self.reset(); self.reset(io);
self.start_sync_round(io); self.start_sync_round(io);
self.continue_sync(io); self.continue_sync(io);
} }
@ -380,13 +414,19 @@ impl ChainSync {
if self.active_peers.is_empty() { if self.active_peers.is_empty() {
trace!(target: "sync", "No more active peers"); trace!(target: "sync", "No more active peers");
if self.state == SyncState::ChainHead { if self.state == SyncState::ChainHead {
self.complete_sync(); self.complete_sync(io);
} else { } else {
self.restart(io); self.restart(io);
} }
} }
} }
fn start_snapshot_sync(&mut self, io: &mut SyncIo, peer_id: PeerId) {
self.snapshot.clear();
self.request_snapshot_manifest(io, peer_id);
self.state = SyncState::SnapshotManifest;
}
/// Restart sync after bad block has been detected. May end up re-downloading up to QUEUE_SIZE blocks /// Restart sync after bad block has been detected. May end up re-downloading up to QUEUE_SIZE blocks
fn restart_on_bad_block(&mut self, io: &mut SyncIo) { fn restart_on_bad_block(&mut self, io: &mut SyncIo) {
// Do not assume that the block queue/chain still has our last_imported_block // Do not assume that the block queue/chain still has our last_imported_block
@ -398,8 +438,9 @@ impl ChainSync {
/// Called by peer to report status /// Called by peer to report status
fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> { fn on_peer_status(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
let protocol_version: u32 = try!(r.val_at(0));
let peer = PeerInfo { let peer = PeerInfo {
protocol_version: try!(r.val_at(0)), protocol_version: protocol_version,
network_id: try!(r.val_at(1)), network_id: try!(r.val_at(1)),
difficulty: Some(try!(r.val_at(2))), difficulty: Some(try!(r.val_at(2))),
latest_hash: try!(r.val_at(3)), latest_hash: try!(r.val_at(3)),
@ -412,6 +453,9 @@ impl ChainSync {
last_sent_transactions: HashSet::new(), last_sent_transactions: HashSet::new(),
expired: false, expired: false,
confirmation: if self.fork_block.is_none() { ForkConfirmation::Confirmed } else { ForkConfirmation::Unconfirmed }, confirmation: if self.fork_block.is_none() { ForkConfirmation::Confirmed } else { ForkConfirmation::Unconfirmed },
asking_snapshot_data: None,
snapshot_hash: if protocol_version == 64 { Some(try!(r.val_at(5))) } else { None },
snapshot_number: if protocol_version == 64 { Some(try!(r.val_at(6))) } else { None },
}; };
trace!(target: "sync", "New peer {} (protocol: {}, network: {:?}, difficulty: {:?}, latest:{}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.difficulty, peer.latest_hash, peer.genesis); trace!(target: "sync", "New peer {} (protocol: {}, network: {:?}, difficulty: {:?}, latest:{}, genesis:{})", peer_id, peer.protocol_version, peer.network_id, peer.difficulty, peer.latest_hash, peer.genesis);
@ -749,6 +793,96 @@ impl ChainSync {
Ok(()) Ok(())
} }
/// Called when snapshot manifest is downloaded from a peer.
fn on_snapshot_manifest(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
trace!(target: "sync", "Ignoring snapshot manifest from unconfirmed peer {}", peer_id);
return Ok(());
}
self.clear_peer_download(peer_id);
if !self.reset_peer_asking(peer_id, PeerAsking::SnapshotManifest) || self.state != SyncState::SnapshotManifest {
trace!(target: "sync", "{}: Ignored unexpected manifest", peer_id);
self.continue_sync(io);
return Ok(());
}
let manifest_rlp = try!(r.at(0));
let manifest = match ManifestData::from_rlp(&manifest_rlp.as_raw()) {
Err(e) => {
trace!(target: "sync", "{}: Ignored bad manifest: {:?}", peer_id, e);
io.disconnect_peer(peer_id);
self.continue_sync(io);
return Ok(());
}
Ok(manifest) => manifest,
};
self.snapshot.reset_to(&manifest, &manifest_rlp.as_raw().sha3());
io.snapshot_service().begin_restore(manifest);
self.state = SyncState::SnapshotData;
// give a task to the same peer first.
self.sync_peer(io, peer_id, false);
// give tasks to other peers
self.continue_sync(io);
Ok(())
}
/// Called when snapshot data is downloaded from a peer.
fn on_snapshot_data(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
if !self.peers.get(&peer_id).map_or(false, |p| p.can_sync()) {
trace!(target: "sync", "Ignoring snapshot data from unconfirmed peer {}", peer_id);
return Ok(());
}
self.clear_peer_download(peer_id);
if !self.reset_peer_asking(peer_id, PeerAsking::SnapshotData) || self.state != SyncState::SnapshotData {
trace!(target: "sync", "{}: Ignored unexpected snapshot data", peer_id);
self.continue_sync(io);
return Ok(());
}
// check service status
match io.snapshot_service().status() {
RestorationStatus::Inactive | RestorationStatus::Failed => {
trace!(target: "sync", "{}: Snapshot restoration aborted", peer_id);
self.state = SyncState::Idle;
self.snapshot.clear();
self.continue_sync(io);
return Ok(());
},
RestorationStatus::Ongoing { .. } => {
trace!(target: "sync", "{}: Snapshot restoration is ongoing", peer_id);
},
}
let snapshot_data: Bytes = try!(r.val_at(0));
match self.snapshot.validate_chunk(&snapshot_data) {
Ok(ChunkType::Block(hash)) => {
trace!(target: "sync", "{}: Processing block chunk", peer_id);
io.snapshot_service().restore_block_chunk(hash, snapshot_data);
}
Ok(ChunkType::State(hash)) => {
trace!(target: "sync", "{}: Processing state chunk", peer_id);
io.snapshot_service().restore_state_chunk(hash, snapshot_data);
}
Err(()) => {
trace!(target: "sync", "{}: Got bad snapshot chunk", peer_id);
io.disconnect_peer(peer_id);
self.continue_sync(io);
return Ok(());
}
}
if self.snapshot.is_complete() {
// wait for snapshot restoration process to complete
self.state = SyncState::SnapshotWaiting;
}
// give a task to the same peer first.
self.sync_peer(io, peer_id, false);
// give tasks to other peers
self.continue_sync(io);
Ok(())
}
/// Called by peer when it is disconnecting /// Called by peer when it is disconnecting
pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) { pub fn on_peer_aborting(&mut self, io: &mut SyncIo, peer: PeerId) {
trace!(target: "sync", "== Disconnecting {}: {}", peer, io.peer_info(peer)); trace!(target: "sync", "== Disconnecting {}: {}", peer, io.peer_info(peer));
@ -764,7 +898,7 @@ impl ChainSync {
/// Called when a new peer is connected /// Called when a new peer is connected
pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) { pub fn on_peer_connected(&mut self, io: &mut SyncIo, peer: PeerId) {
trace!(target: "sync", "== Connected {}: {}", peer, io.peer_info(peer)); trace!(target: "sync", "== Connected {}: {}", peer, io.peer_info(peer));
if let Err(e) = self.send_status(io) { if let Err(e) = self.send_status(io, peer) {
debug!(target:"sync", "Error sending status request: {:?}", e); debug!(target:"sync", "Error sending status request: {:?}", e);
io.disable_peer(peer); io.disable_peer(peer);
} }
@ -772,24 +906,27 @@ impl ChainSync {
/// Resume downloading /// Resume downloading
fn continue_sync(&mut self, io: &mut SyncIo) { fn continue_sync(&mut self, io: &mut SyncIo) {
let mut peers: Vec<(PeerId, U256)> = self.peers.iter().filter_map(|(k, p)| let mut peers: Vec<(PeerId, U256, u32)> = self.peers.iter().filter_map(|(k, p)|
if p.can_sync() { Some((*k, p.difficulty.unwrap_or_else(U256::zero))) } else { None }).collect(); if p.can_sync() { Some((*k, p.difficulty.unwrap_or_else(U256::zero), p.protocol_version)) } else { None }).collect();
thread_rng().shuffle(&mut peers); //TODO: sort by rating thread_rng().shuffle(&mut peers); //TODO: sort by rating
// prefer peers with higher protocol version
peers.sort_by(|&(_, _, ref v1), &(_, _, ref v2)| v1.cmp(v2));
trace!(target: "sync", "Syncing with {}/{} peers", self.active_peers.len(), peers.len()); trace!(target: "sync", "Syncing with {}/{} peers", self.active_peers.len(), peers.len());
for (p, _) in peers { for (p, _, _) in peers {
if self.active_peers.contains(&p) { if self.active_peers.contains(&p) {
self.sync_peer(io, p, false); self.sync_peer(io, p, false);
} }
} }
if self.state != SyncState::Waiting && !self.peers.values().any(|p| p.asking != PeerAsking::Nothing && p.can_sync()) { if self.state != SyncState::Waiting && self.state != SyncState::SnapshotWaiting
self.complete_sync(); && !self.peers.values().any(|p| p.asking != PeerAsking::Nothing && p.can_sync()) {
self.complete_sync(io);
} }
} }
/// Called after all blocks have been downloaded /// Called after all blocks have been downloaded
fn complete_sync(&mut self) { fn complete_sync(&mut self, io: &mut SyncIo) {
trace!(target: "sync", "Sync complete"); trace!(target: "sync", "Sync complete");
self.reset(); self.reset(io);
self.state = SyncState::Idle; self.state = SyncState::Idle;
} }
@ -805,7 +942,7 @@ impl ChainSync {
trace!(target: "sync", "Skipping deactivated peer"); trace!(target: "sync", "Skipping deactivated peer");
return; return;
} }
let (peer_latest, peer_difficulty) = { let (peer_latest, peer_difficulty, peer_snapshot_number, peer_snapshot_hash) = {
let peer = self.peers.get_mut(&peer_id).unwrap(); let peer = self.peers.get_mut(&peer_id).unwrap();
if peer.asking != PeerAsking::Nothing || !peer.can_sync() { if peer.asking != PeerAsking::Nothing || !peer.can_sync() {
return; return;
@ -814,7 +951,11 @@ impl ChainSync {
trace!(target: "sync", "Waiting for the block queue"); trace!(target: "sync", "Waiting for the block queue");
return; return;
} }
(peer.latest_hash.clone(), peer.difficulty.clone()) if self.state == SyncState::SnapshotWaiting {
trace!(target: "sync", "Waiting for the snapshot restoration");
return;
}
(peer.latest_hash.clone(), peer.difficulty.clone(), peer.snapshot_number.as_ref().cloned(), peer.snapshot_hash.as_ref().cloned())
}; };
let chain_info = io.chain().chain_info(); let chain_info = io.chain().chain_info();
let td = chain_info.pending_total_difficulty; let td = chain_info.pending_total_difficulty;
@ -823,13 +964,18 @@ impl ChainSync {
if force || self.state == SyncState::NewBlocks || peer_difficulty.map_or(true, |pd| pd > syncing_difficulty) { if force || self.state == SyncState::NewBlocks || peer_difficulty.map_or(true, |pd| pd > syncing_difficulty) {
match self.state { match self.state {
SyncState::Idle => { SyncState::Idle => {
if self.last_imported_block < chain_info.best_block_number { // check if we can start snapshot sync with this peer
self.last_imported_block = chain_info.best_block_number; if peer_snapshot_number.unwrap_or(0) > 0 && chain_info.best_block_number == 0 {
self.last_imported_hash = chain_info.best_block_hash; self.start_snapshot_sync(io, peer_id);
} else {
if self.last_imported_block < chain_info.best_block_number {
self.last_imported_block = chain_info.best_block_number;
self.last_imported_hash = chain_info.best_block_hash;
}
trace!(target: "sync", "Starting sync with {}", peer_id);
self.start_sync_round(io);
self.sync_peer(io, peer_id, force);
} }
trace!(target: "sync", "Starting sync with {}", peer_id);
self.start_sync_round(io);
self.sync_peer(io, peer_id, force);
}, },
SyncState::ChainHead => { SyncState::ChainHead => {
// Request subchain headers // Request subchain headers
@ -843,8 +989,14 @@ impl ChainSync {
if io.chain().block_status(BlockID::Hash(peer_latest)) == BlockStatus::Unknown { if io.chain().block_status(BlockID::Hash(peer_latest)) == BlockStatus::Unknown {
self.request_blocks(io, peer_id, false); self.request_blocks(io, peer_id, false);
} }
} },
SyncState::Waiting => () SyncState::SnapshotData => {
if peer_snapshot_hash.is_some() && peer_snapshot_hash == self.snapshot.snapshot_hash() {
self.request_snapshot_data(io, peer_id);
}
},
SyncState::SnapshotManifest => (), //already downloading from other peer
SyncState::Waiting | SyncState::SnapshotWaiting => ()
} }
} }
} }
@ -903,6 +1055,16 @@ impl ChainSync {
} }
} }
/// Find some headers or blocks to download for a peer.
fn request_snapshot_data(&mut self, io: &mut SyncIo, peer_id: PeerId) {
self.clear_peer_download(peer_id);
// find chunk data to download
if let Some(hash) = self.snapshot.needed_chunk() {
self.peers.get_mut(&peer_id).unwrap().asking_snapshot_data = Some(hash.clone());
self.request_snapshot_chunk(io, peer_id, &hash);
}
}
/// Clear all blocks/headers marked as being downloaded by a peer. /// Clear all blocks/headers marked as being downloaded by a peer.
fn clear_peer_download(&mut self, peer_id: PeerId) { fn clear_peer_download(&mut self, peer_id: PeerId) {
let peer = self.peers.get_mut(&peer_id).unwrap(); let peer = self.peers.get_mut(&peer_id).unwrap();
@ -917,9 +1079,15 @@ impl ChainSync {
self.blocks.clear_body_download(b); self.blocks.clear_body_download(b);
} }
}, },
PeerAsking::SnapshotData => {
if let Some(hash) = peer.asking_snapshot_data {
self.snapshot.clear_chunk_download(&hash);
}
},
_ => (), _ => (),
} }
peer.asking_blocks.clear(); peer.asking_blocks.clear();
peer.asking_snapshot_data = None;
} }
fn block_imported(&mut self, hash: &H256, number: BlockNumber, parent: &H256) { fn block_imported(&mut self, hash: &H256, number: BlockNumber, parent: &H256) {
@ -1016,6 +1184,22 @@ impl ChainSync {
rlp.append(&if reverse {1u32} else {0u32}); rlp.append(&if reverse {1u32} else {0u32});
self.send_request(sync, peer_id, asking, GET_BLOCK_HEADERS_PACKET, rlp.out()); self.send_request(sync, peer_id, asking, GET_BLOCK_HEADERS_PACKET, rlp.out());
} }
/// Request snapshot manifest from a peer.
fn request_snapshot_manifest(&mut self, sync: &mut SyncIo, peer_id: PeerId) {
trace!(target: "sync", "{} <- GetSnapshotManifest", peer_id);
let rlp = RlpStream::new_list(0);
self.send_request(sync, peer_id, PeerAsking::SnapshotManifest, GET_SNAPSHOT_MANIFEST_PACKET, rlp.out());
}
/// Request snapshot chunk from a peer.
fn request_snapshot_chunk(&mut self, sync: &mut SyncIo, peer_id: PeerId, chunk: &H256) {
trace!(target: "sync", "{} <- GetSnapshotData {:?}", peer_id, chunk);
let mut rlp = RlpStream::new_list(1);
rlp.append(chunk);
self.send_request(sync, peer_id, PeerAsking::SnapshotData, GET_SNAPSHOT_DATA_PACKET, rlp.out());
}
/// Request block bodies from a peer /// Request block bodies from a peer
fn request_bodies(&mut self, sync: &mut SyncIo, peer_id: PeerId, hashes: Vec<H256>) { fn request_bodies(&mut self, sync: &mut SyncIo, peer_id: PeerId, hashes: Vec<H256>) {
let mut rlp = RlpStream::new_list(hashes.len()); let mut rlp = RlpStream::new_list(hashes.len());
@ -1086,14 +1270,22 @@ impl ChainSync {
} }
/// Send Status message /// Send Status message
fn send_status(&mut self, io: &mut SyncIo) -> Result<(), NetworkError> { fn send_status(&mut self, io: &mut SyncIo, peer: PeerId) -> Result<(), NetworkError> {
let mut packet = RlpStream::new_list(5); let pv64 = io.eth_protocol_version(peer) >= 64;
let mut packet = RlpStream::new_list(if pv64 { 7 } else { 5 });
let chain = io.chain().chain_info(); let chain = io.chain().chain_info();
packet.append(&(PROTOCOL_VERSION as u32)); packet.append(&(PROTOCOL_VERSION as u32));
packet.append(&self.network_id); packet.append(&self.network_id);
packet.append(&chain.total_difficulty); packet.append(&chain.total_difficulty);
packet.append(&chain.best_block_hash); packet.append(&chain.best_block_hash);
packet.append(&chain.genesis_hash); packet.append(&chain.genesis_hash);
if pv64 {
let manifest = io.snapshot_service().manifest();
let block_number = manifest.as_ref().map_or(0, |m| m.block_number);
let manifest_hash = manifest.map_or(H256::new(), |m| m.into_rlp().sha3());
packet.append(&manifest_hash);
packet.append(&block_number);
}
io.respond(STATUS_PACKET, packet.out()) io.respond(STATUS_PACKET, packet.out())
} }
@ -1230,6 +1422,48 @@ impl ChainSync {
Ok(Some((RECEIPTS_PACKET, rlp_result))) Ok(Some((RECEIPTS_PACKET, rlp_result)))
} }
/// Respond to GetSnapshotManifest request
fn return_snapshot_manifest(io: &SyncIo, r: &UntrustedRlp, peer_id: PeerId) -> RlpResponseResult {
let count = r.item_count();
trace!(target: "sync", "{} -> GetSnapshotManifest", peer_id);
if count != 0 {
debug!(target: "sync", "Invalid GetSnapshotManifest request, ignoring.");
return Ok(None);
}
let rlp = match io.snapshot_service().manifest() {
Some(manifest) => {
trace!(target: "sync", "{} <- SnapshotManifest", peer_id);
let mut rlp = RlpStream::new_list(1);
rlp.append_raw(&manifest.into_rlp(), 1);
rlp
},
None => {
trace!(target: "sync", "{}: No manifest to return", peer_id);
let rlp = RlpStream::new_list(0);
rlp
}
};
Ok(Some((SNAPSHOT_MANIFEST_PACKET, rlp)))
}
/// Respond to GetSnapshotManifest request
fn return_snapshot_data(io: &SyncIo, r: &UntrustedRlp, peer_id: PeerId) -> RlpResponseResult {
let hash: H256 = try!(r.val_at(0));
trace!(target: "sync", "{} -> GetSnapshotData {:?}", peer_id, hash);
let rlp = match io.snapshot_service().chunk(hash) {
Some(data) => {
let mut rlp = RlpStream::new_list(1);
rlp.append(&data);
rlp
},
None => {
let rlp = RlpStream::new_list(0);
rlp
}
};
Ok(Some((SNAPSHOT_DATA_PACKET, rlp)))
}
fn return_rlp<FRlp, FError>(io: &mut SyncIo, rlp: &UntrustedRlp, peer: PeerId, rlp_func: FRlp, error_func: FError) -> Result<(), PacketDecodeError> fn return_rlp<FRlp, FError>(io: &mut SyncIo, rlp: &UntrustedRlp, peer: PeerId, rlp_func: FRlp, error_func: FError) -> Result<(), PacketDecodeError>
where FRlp : Fn(&SyncIo, &UntrustedRlp, PeerId) -> RlpResponseResult, where FRlp : Fn(&SyncIo, &UntrustedRlp, PeerId) -> RlpResponseResult,
FError : FnOnce(NetworkError) -> String FError : FnOnce(NetworkError) -> String
@ -1266,6 +1500,14 @@ impl ChainSync {
ChainSync::return_node_data, ChainSync::return_node_data,
|e| format!("Error sending nodes: {:?}", e)), |e| format!("Error sending nodes: {:?}", e)),
GET_SNAPSHOT_MANIFEST_PACKET => ChainSync::return_rlp(io, &rlp, peer,
ChainSync::return_snapshot_manifest,
|e| format!("Error sending snapshot manifest: {:?}", e)),
GET_SNAPSHOT_DATA_PACKET => ChainSync::return_rlp(io, &rlp, peer,
ChainSync::return_snapshot_data,
|e| format!("Error sending snapshot data: {:?}", e)),
_ => { _ => {
sync.write().on_packet(io, peer, packet_id, data); sync.write().on_packet(io, peer, packet_id, data);
Ok(()) Ok(())
@ -1289,6 +1531,8 @@ impl ChainSync {
BLOCK_BODIES_PACKET => self.on_peer_block_bodies(io, peer, &rlp), BLOCK_BODIES_PACKET => self.on_peer_block_bodies(io, peer, &rlp),
NEW_BLOCK_PACKET => self.on_peer_new_block(io, peer, &rlp), NEW_BLOCK_PACKET => self.on_peer_new_block(io, peer, &rlp),
NEW_BLOCK_HASHES_PACKET => self.on_peer_new_hashes(io, peer, &rlp), NEW_BLOCK_HASHES_PACKET => self.on_peer_new_hashes(io, peer, &rlp),
SNAPSHOT_MANIFEST_PACKET => self.on_snapshot_manifest(io, peer, &rlp),
SNAPSHOT_DATA_PACKET => self.on_snapshot_data(io, peer, &rlp),
_ => { _ => {
debug!(target: "sync", "Unknown packet {}", packet_id); debug!(target: "sync", "Unknown packet {}", packet_id);
Ok(()) Ok(())
@ -1308,6 +1552,8 @@ impl ChainSync {
PeerAsking::BlockBodies => (tick - peer.ask_time) > BODIES_TIMEOUT_SEC, PeerAsking::BlockBodies => (tick - peer.ask_time) > BODIES_TIMEOUT_SEC,
PeerAsking::Nothing => false, PeerAsking::Nothing => false,
PeerAsking::ForkHeader => (tick - peer.ask_time) > FORK_HEADER_TIMEOUT_SEC, PeerAsking::ForkHeader => (tick - peer.ask_time) > FORK_HEADER_TIMEOUT_SEC,
PeerAsking::SnapshotManifest => (tick - peer.ask_time) > SNAPSHOT_MANIFEST_TIMEOUT_SEC,
PeerAsking::SnapshotData => (tick - peer.ask_time) > SNAPSHOT_DATA_TIMEOUT_SEC,
}; };
if timeout { if timeout {
trace!(target:"sync", "Timeout {}", peer_id); trace!(target:"sync", "Timeout {}", peer_id);
@ -1321,9 +1567,12 @@ impl ChainSync {
} }
fn check_resume(&mut self, io: &mut SyncIo) { fn check_resume(&mut self, io: &mut SyncIo) {
if !io.chain().queue_info().is_full() && self.state == SyncState::Waiting { if self.state == SyncState::Waiting && !io.chain().queue_info().is_full() && self.state == SyncState::Waiting {
self.state = SyncState::Blocks; self.state = SyncState::Blocks;
self.continue_sync(io); self.continue_sync(io);
} else if self.state == SyncState::SnapshotWaiting && io.snapshot_service().status() == RestorationStatus::Inactive {
self.state = SyncState::Idle;
self.continue_sync(io);
} }
} }
@ -1559,6 +1808,7 @@ impl ChainSync {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use tests::helpers::*; use tests::helpers::*;
use tests::snapshot::TestSnapshotService;
use super::*; use super::*;
use ::SyncConfig; use ::SyncConfig;
use util::*; use util::*;
@ -1612,7 +1862,8 @@ mod tests {
fn return_receipts_empty() { fn return_receipts_empty() {
let mut client = TestBlockChainClient::new(); let mut client = TestBlockChainClient::new();
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let result = ChainSync::return_receipts(&io, &UntrustedRlp::new(&[0xc0]), 0); let result = ChainSync::return_receipts(&io, &UntrustedRlp::new(&[0xc0]), 0);
@ -1624,7 +1875,8 @@ mod tests {
let mut client = TestBlockChainClient::new(); let mut client = TestBlockChainClient::new();
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let sync = dummy_sync_with_peer(H256::new(), &client); let sync = dummy_sync_with_peer(H256::new(), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let mut receipt_list = RlpStream::new_list(4); let mut receipt_list = RlpStream::new_list(4);
receipt_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555")); receipt_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555"));
@ -1679,7 +1931,8 @@ mod tests {
let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect(); let hashes: Vec<_> = headers.iter().map(|h| HeaderView::new(h).sha3()).collect();
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let unknown: H256 = H256::new(); let unknown: H256 = H256::new();
let result = ChainSync::return_block_headers(&io, &UntrustedRlp::new(&make_hash_req(&unknown, 1, 0, false)), 0); let result = ChainSync::return_block_headers(&io, &UntrustedRlp::new(&make_hash_req(&unknown, 1, 0, false)), 0);
@ -1717,7 +1970,8 @@ mod tests {
let mut client = TestBlockChainClient::new(); let mut client = TestBlockChainClient::new();
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let sync = dummy_sync_with_peer(H256::new(), &client); let sync = dummy_sync_with_peer(H256::new(), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let mut node_list = RlpStream::new_list(3); let mut node_list = RlpStream::new_list(3);
node_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555")); node_list.append(&H256::from("0000000000000000000000000000000000000000000000005555555555555555"));
@ -1758,6 +2012,9 @@ mod tests {
last_sent_transactions: HashSet::new(), last_sent_transactions: HashSet::new(),
expired: false, expired: false,
confirmation: super::ForkConfirmation::Confirmed, confirmation: super::ForkConfirmation::Confirmed,
snapshot_number: None,
snapshot_hash: None,
asking_snapshot_data: None,
}); });
sync sync
} }
@ -1769,7 +2026,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(10), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(10), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let lagging_peers = sync.get_lagging_peers(&chain_info, &io); let lagging_peers = sync.get_lagging_peers(&chain_info, &io);
@ -1800,7 +2058,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peers = sync.get_lagging_peers(&chain_info, &io); let peers = sync.get_lagging_peers(&chain_info, &io);
let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers); let peer_count = sync.propagate_new_hashes(&chain_info, &mut io, &peers);
@ -1820,7 +2079,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peers = sync.get_lagging_peers(&chain_info, &io); let peers = sync.get_lagging_peers(&chain_info, &io);
let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[], &peers);
@ -1840,7 +2100,8 @@ mod tests {
let hash = client.block_hash(BlockID::Number(99)).unwrap(); let hash = client.block_hash(BlockID::Number(99)).unwrap();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peers = sync.get_lagging_peers(&chain_info, &io); let peers = sync.get_lagging_peers(&chain_info, &io);
let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers); let peer_count = sync.propagate_blocks(&chain_info, &mut io, &[hash.clone()], &peers);
@ -1859,7 +2120,8 @@ mod tests {
client.insert_transaction_to_queue(); client.insert_transaction_to_queue();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peer_count = sync.propagate_new_transactions(&mut io); let peer_count = sync.propagate_new_transactions(&mut io);
// Try to propagate same transactions for the second time // Try to propagate same transactions for the second time
let peer_count2 = sync.propagate_new_transactions(&mut io); let peer_count2 = sync.propagate_new_transactions(&mut io);
@ -1880,7 +2142,8 @@ mod tests {
client.insert_transaction_to_queue(); client.insert_transaction_to_queue();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peer_count = sync.propagate_new_transactions(&mut io); let peer_count = sync.propagate_new_transactions(&mut io);
sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]); sync.chain_new_blocks(&mut io, &[], &[], &[], &[], &[]);
// Try to propagate same transactions for the second time // Try to propagate same transactions for the second time
@ -1903,17 +2166,17 @@ mod tests {
client.insert_transaction_to_queue(); client.insert_transaction_to_queue();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(1), &client);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut ss = TestSnapshotService::new();
// should sent some // should sent some
{ {
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let mut io = TestIo::new(&mut client, &mut queue, None);
let peer_count = sync.propagate_new_transactions(&mut io); let peer_count = sync.propagate_new_transactions(&mut io);
assert_eq!(1, io.queue.len()); assert_eq!(1, io.queue.len());
assert_eq!(1, peer_count); assert_eq!(1, peer_count);
} }
// Insert some more // Insert some more
client.insert_transaction_to_queue(); client.insert_transaction_to_queue();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
// Propagate new transactions // Propagate new transactions
let peer_count2 = sync.propagate_new_transactions(&mut io); let peer_count2 = sync.propagate_new_transactions(&mut io);
// And now the peer should have all transactions // And now the peer should have all transactions
@ -1939,7 +2202,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
//sync.have_common_block = true; //sync.have_common_block = true;
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let block = UntrustedRlp::new(&block_data); let block = UntrustedRlp::new(&block_data);
@ -1957,7 +2221,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let block = UntrustedRlp::new(&block_data); let block = UntrustedRlp::new(&block_data);
@ -1972,7 +2237,8 @@ mod tests {
client.add_blocks(10, EachBlockWith::Uncle); client.add_blocks(10, EachBlockWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let empty_data = vec![]; let empty_data = vec![];
let block = UntrustedRlp::new(&empty_data); let block = UntrustedRlp::new(&empty_data);
@ -1988,7 +2254,8 @@ mod tests {
client.add_blocks(10, EachBlockWith::Uncle); client.add_blocks(10, EachBlockWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let hashes_data = get_dummy_hashes(); let hashes_data = get_dummy_hashes();
let hashes_rlp = UntrustedRlp::new(&hashes_data); let hashes_rlp = UntrustedRlp::new(&hashes_data);
@ -2004,7 +2271,8 @@ mod tests {
client.add_blocks(10, EachBlockWith::Uncle); client.add_blocks(10, EachBlockWith::Uncle);
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let empty_hashes_data = vec![]; let empty_hashes_data = vec![];
let hashes_rlp = UntrustedRlp::new(&empty_hashes_data); let hashes_rlp = UntrustedRlp::new(&empty_hashes_data);
@ -2023,7 +2291,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peers = sync.get_lagging_peers(&chain_info, &io); let peers = sync.get_lagging_peers(&chain_info, &io);
sync.propagate_new_hashes(&chain_info, &mut io, &peers); sync.propagate_new_hashes(&chain_info, &mut io, &peers);
@ -2042,7 +2311,8 @@ mod tests {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client); let mut sync = dummy_sync_with_peer(client.block_hash_delta_minus(5), &client);
let chain_info = client.chain_info(); let chain_info = client.chain_info();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
let peers = sync.get_lagging_peers(&chain_info, &io); let peers = sync.get_lagging_peers(&chain_info, &io);
sync.propagate_blocks(&chain_info, &mut io, &[], &peers); sync.propagate_blocks(&chain_info, &mut io, &[], &peers);
@ -2076,7 +2346,8 @@ mod tests {
// when // when
{ {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &[], &good_blocks);
sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]);
assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0); assert_eq!(io.chain.miner.status().transactions_in_future_queue, 0);
@ -2090,7 +2361,8 @@ mod tests {
} }
{ {
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks); io.chain.miner.chain_new_blocks(io.chain, &[], &[], &good_blocks, &retracted_blocks);
sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]); sync.chain_new_blocks(&mut io, &[], &[], &good_blocks, &retracted_blocks, &[]);
} }
@ -2114,7 +2386,8 @@ mod tests {
let retracted_blocks = vec![client.block_hash_delta_minus(1)]; let retracted_blocks = vec![client.block_hash_delta_minus(1)];
let mut queue = VecDeque::new(); let mut queue = VecDeque::new();
let mut io = TestIo::new(&mut client, &mut queue, None); let mut ss = TestSnapshotService::new();
let mut io = TestIo::new(&mut client, &mut ss, &mut queue, None);
// when // when
sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]); sync.chain_new_blocks(&mut io, &[], &[], &[], &good_blocks, &[]);

View File

@ -26,40 +26,6 @@
//! Implements ethereum protocol version 63 as specified here: //! Implements ethereum protocol version 63 as specified here:
//! https://github.com/ethereum/wiki/wiki/Ethereum-Wire-Protocol //! https://github.com/ethereum/wiki/wiki/Ethereum-Wire-Protocol
//! //!
//! Usage example:
//!
//! ```rust
//! extern crate ethcore_util as util;
//! extern crate ethcore_io as io;
//! extern crate ethcore;
//! extern crate ethsync;
//! use std::env;
//! use io::IoChannel;
//! use ethcore::client::{Client, ClientConfig};
//! use ethsync::{EthSync, SyncConfig, ManageNetwork, NetworkConfiguration};
//! use ethcore::ethereum;
//! use ethcore::miner::{GasPricer, Miner};
//!
//! fn main() {
//! let dir = env::temp_dir();
//! let spec = ethereum::new_frontier();
//! let miner = Miner::new(
//! Default::default(),
//! GasPricer::new_fixed(20_000_000_000u64.into()),
//! &spec,
//! None
//! );
//! let client = Client::new(
//! ClientConfig::default(),
//! &spec,
//! &dir,
//! miner,
//! IoChannel::disconnected()
//! ).unwrap();
//! let sync = EthSync::new(SyncConfig::default(), client, NetworkConfiguration::from(NetworkConfiguration::new())).unwrap();
//! sync.start_network();
//! }
//! ```
extern crate ethcore_network as network; extern crate ethcore_network as network;
extern crate ethcore_io as io; extern crate ethcore_io as io;
@ -83,6 +49,7 @@ extern crate ethcore_ipc as ipc;
mod chain; mod chain;
mod blocks; mod blocks;
mod sync_io; mod sync_io;
mod snapshot;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -96,4 +63,3 @@ pub use api::{EthSync, SyncProvider, SyncClient, NetworkManagerClient, ManageNet
ServiceConfiguration, NetworkConfiguration}; ServiceConfiguration, NetworkConfiguration};
pub use chain::{SyncStatus, SyncState}; pub use chain::{SyncStatus, SyncState};
pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError}; pub use network::{is_valid_node_url, NonReservedPeerMode, NetworkError};

200
sync/src/snapshot.rs Normal file
View File

@ -0,0 +1,200 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::{H256, Hashable};
use std::collections::HashSet;
use ethcore::snapshot::ManifestData;
#[derive(PartialEq, Eq, Debug)]
pub enum ChunkType {
State(H256),
Block(H256),
}
pub struct Snapshot {
pending_state_chunks: Vec<H256>,
pending_block_chunks: Vec<H256>,
downloading_chunks: HashSet<H256>,
completed_chunks: HashSet<H256>,
snapshot_hash: Option<H256>,
}
impl Snapshot {
/// Create a new instance.
pub fn new() -> Snapshot {
Snapshot {
pending_state_chunks: Vec::new(),
pending_block_chunks: Vec::new(),
downloading_chunks: HashSet::new(),
completed_chunks: HashSet::new(),
snapshot_hash: None,
}
}
/// Clear everything.
pub fn clear(&mut self) {
self.pending_state_chunks.clear();
self.pending_block_chunks.clear();
self.downloading_chunks.clear();
self.completed_chunks.clear();
self.snapshot_hash = None;
}
/// Reset collection for a manifest RLP
pub fn reset_to(&mut self, manifest: &ManifestData, hash: &H256) {
self.clear();
self.pending_state_chunks = manifest.state_hashes.clone();
self.pending_block_chunks = manifest.block_hashes.clone();
self.snapshot_hash = Some(hash.clone());
}
/// Validate chunk and mark it as downloaded
pub fn validate_chunk(&mut self, chunk: &[u8]) -> Result<ChunkType, ()> {
let hash = chunk.sha3();
if self.completed_chunks.contains(&hash) {
trace!(target: "sync", "Ignored proccessed chunk: {}", hash.hex());
return Err(());
}
self.downloading_chunks.remove(&hash);
if self.pending_block_chunks.iter().any(|h| h == &hash) {
self.completed_chunks.insert(hash.clone());
return Ok(ChunkType::Block(hash));
}
if self.pending_state_chunks.iter().any(|h| h == &hash) {
self.completed_chunks.insert(hash.clone());
return Ok(ChunkType::State(hash));
}
trace!(target: "sync", "Ignored unknown chunk: {}", hash.hex());
Err(())
}
/// Find a chunk to download
pub fn needed_chunk(&mut self) -> Option<H256> {
// check state chunks first
let mut chunk = self.pending_state_chunks.iter()
.find(|&h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h))
.cloned();
if chunk.is_none() {
chunk = self.pending_block_chunks.iter()
.find(|&h| !self.downloading_chunks.contains(h) && !self.completed_chunks.contains(h))
.cloned();
}
if let Some(hash) = chunk {
self.downloading_chunks.insert(hash.clone());
}
chunk
}
pub fn clear_chunk_download(&mut self, hash: &H256) {
self.downloading_chunks.remove(hash);
}
pub fn snapshot_hash(&self) -> Option<H256> {
self.snapshot_hash
}
pub fn total_chunks(&self) -> usize {
self.pending_block_chunks.len() + self.pending_state_chunks.len()
}
pub fn done_chunks(&self) -> usize {
self.total_chunks() - self.completed_chunks.len()
}
pub fn is_complete(&self) -> bool {
self.total_chunks() == self.completed_chunks.len()
}
}
#[cfg(test)]
mod test {
use util::*;
use super::*;
use ethcore::snapshot::ManifestData;
fn is_empty(snapshot: &Snapshot) -> bool {
snapshot.pending_block_chunks.is_empty() &&
snapshot.pending_state_chunks.is_empty() &&
snapshot.completed_chunks.is_empty() &&
snapshot.downloading_chunks.is_empty() &&
snapshot.snapshot_hash.is_none()
}
fn test_manifest() -> (ManifestData, H256, Vec<Bytes>, Vec<Bytes>) {
let state_chunks: Vec<Bytes> = (0..20).map(|_| H256::random().to_vec()).collect();
let block_chunks: Vec<Bytes> = (0..20).map(|_| H256::random().to_vec()).collect();
let manifest = ManifestData {
state_hashes: state_chunks.iter().map(|data| data.sha3()).collect(),
block_hashes: block_chunks.iter().map(|data| data.sha3()).collect(),
state_root: H256::new(),
block_number: 42,
block_hash: H256::new(),
};
let mhash = manifest.clone().into_rlp().sha3();
(manifest, mhash, state_chunks, block_chunks)
}
#[test]
fn create_clear() {
let mut snapshot = Snapshot::new();
assert!(is_empty(&snapshot));
let (manifest, mhash, _, _,) = test_manifest();
snapshot.reset_to(&manifest, &mhash);
assert!(!is_empty(&snapshot));
snapshot.clear();
assert!(is_empty(&snapshot));
}
#[test]
fn validate_chunks() {
let mut snapshot = Snapshot::new();
let (manifest, mhash, state_chunks, block_chunks) = test_manifest();
snapshot.reset_to(&manifest, &mhash);
assert!(snapshot.validate_chunk(&H256::random().to_vec()).is_err());
let requested: Vec<H256> = (0..40).map(|_| snapshot.needed_chunk().unwrap()).collect();
assert!(snapshot.needed_chunk().is_none());
assert_eq!(&requested[0..20], &manifest.state_hashes[..]);
assert_eq!(&requested[20..40], &manifest.block_hashes[..]);
assert_eq!(snapshot.downloading_chunks.len(), 40);
assert_eq!(snapshot.validate_chunk(&state_chunks[4]), Ok(ChunkType::State(manifest.state_hashes[4].clone())));
assert_eq!(snapshot.completed_chunks.len(), 1);
assert_eq!(snapshot.downloading_chunks.len(), 39);
assert_eq!(snapshot.validate_chunk(&block_chunks[10]), Ok(ChunkType::Block(manifest.block_hashes[10].clone())));
assert_eq!(snapshot.completed_chunks.len(), 2);
assert_eq!(snapshot.downloading_chunks.len(), 38);
for (i, data) in state_chunks.iter().enumerate() {
if i != 4 {
assert!(snapshot.validate_chunk(data).is_ok());
}
}
for (i, data) in block_chunks.iter().enumerate() {
if i != 10 {
assert!(snapshot.validate_chunk(data).is_ok());
}
}
assert!(snapshot.is_complete());
assert_eq!(snapshot.snapshot_hash(), Some(manifest.into_rlp().sha3()));
}
}

View File

@ -16,6 +16,8 @@
use network::{NetworkContext, PeerId, PacketId, NetworkError}; use network::{NetworkContext, PeerId, PacketId, NetworkError};
use ethcore::client::BlockChainClient; use ethcore::client::BlockChainClient;
use ethcore::snapshot::SnapshotService;
use api::ETH_PROTOCOL;
/// IO interface for the syning handler. /// IO interface for the syning handler.
/// Provides peer connection management and an interface to the blockchain client. /// Provides peer connection management and an interface to the blockchain client.
@ -31,10 +33,14 @@ pub trait SyncIo {
fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec<u8>) -> Result<(), NetworkError>; fn send(&mut self, peer_id: PeerId, packet_id: PacketId, data: Vec<u8>) -> Result<(), NetworkError>;
/// Get the blockchain /// Get the blockchain
fn chain(&self) -> &BlockChainClient; fn chain(&self) -> &BlockChainClient;
/// Get the snapshot service.
fn snapshot_service(&self) -> &SnapshotService;
/// Returns peer client identifier string /// Returns peer client identifier string
fn peer_info(&self, peer_id: PeerId) -> String { fn peer_info(&self, peer_id: PeerId) -> String {
peer_id.to_string() peer_id.to_string()
} }
/// Maximum mutuallt supported ETH protocol version
fn eth_protocol_version(&self, peer_id: PeerId) -> u8;
/// Returns if the chain block queue empty /// Returns if the chain block queue empty
fn is_chain_queue_empty(&self) -> bool { fn is_chain_queue_empty(&self) -> bool {
self.chain().queue_info().is_empty() self.chain().queue_info().is_empty()
@ -46,15 +52,17 @@ pub trait SyncIo {
/// Wraps `NetworkContext` and the blockchain client /// Wraps `NetworkContext` and the blockchain client
pub struct NetSyncIo<'s, 'h> where 'h: 's { pub struct NetSyncIo<'s, 'h> where 'h: 's {
network: &'s NetworkContext<'h>, network: &'s NetworkContext<'h>,
chain: &'s BlockChainClient chain: &'s BlockChainClient,
snapshot_service: &'s SnapshotService,
} }
impl<'s, 'h> NetSyncIo<'s, 'h> { impl<'s, 'h> NetSyncIo<'s, 'h> {
/// Creates a new instance from the `NetworkContext` and the blockchain client reference. /// Creates a new instance from the `NetworkContext` and the blockchain client reference.
pub fn new(network: &'s NetworkContext<'h>, chain: &'s BlockChainClient) -> NetSyncIo<'s, 'h> { pub fn new(network: &'s NetworkContext<'h>, chain: &'s BlockChainClient, snapshot_service: &'s SnapshotService) -> NetSyncIo<'s, 'h> {
NetSyncIo { NetSyncIo {
network: network, network: network,
chain: chain, chain: chain,
snapshot_service: snapshot_service,
} }
} }
} }
@ -80,6 +88,10 @@ impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> {
self.chain self.chain
} }
fn snapshot_service(&self) -> &SnapshotService {
self.snapshot_service
}
fn peer_info(&self, peer_id: PeerId) -> String { fn peer_info(&self, peer_id: PeerId) -> String {
self.network.peer_info(peer_id) self.network.peer_info(peer_id)
} }
@ -87,6 +99,10 @@ impl<'s, 'h> SyncIo for NetSyncIo<'s, 'h> {
fn is_expired(&self) -> bool { fn is_expired(&self) -> bool {
self.network.is_expired() self.network.is_expired()
} }
fn eth_protocol_version(&self, peer_id: PeerId) -> u8 {
self.network.protocol_version(peer_id, ETH_PROTOCOL).unwrap_or(0)
}
} }

View File

@ -16,22 +16,26 @@
use util::*; use util::*;
use network::*; use network::*;
use tests::snapshot::*;
use ethcore::client::{TestBlockChainClient, BlockChainClient}; use ethcore::client::{TestBlockChainClient, BlockChainClient};
use ethcore::header::BlockNumber; use ethcore::header::BlockNumber;
use ethcore::snapshot::SnapshotService;
use sync_io::SyncIo; use sync_io::SyncIo;
use chain::ChainSync; use chain::ChainSync;
use ::SyncConfig; use ::SyncConfig;
pub struct TestIo<'p> { pub struct TestIo<'p> {
pub chain: &'p mut TestBlockChainClient, pub chain: &'p mut TestBlockChainClient,
pub snapshot_service: &'p TestSnapshotService,
pub queue: &'p mut VecDeque<TestPacket>, pub queue: &'p mut VecDeque<TestPacket>,
pub sender: Option<PeerId>, pub sender: Option<PeerId>,
} }
impl<'p> TestIo<'p> { impl<'p> TestIo<'p> {
pub fn new(chain: &'p mut TestBlockChainClient, queue: &'p mut VecDeque<TestPacket>, sender: Option<PeerId>) -> TestIo<'p> { pub fn new(chain: &'p mut TestBlockChainClient, ss: &'p TestSnapshotService, queue: &'p mut VecDeque<TestPacket>, sender: Option<PeerId>) -> TestIo<'p> {
TestIo { TestIo {
chain: chain, chain: chain,
snapshot_service: ss,
queue: queue, queue: queue,
sender: sender sender: sender
} }
@ -70,6 +74,14 @@ impl<'p> SyncIo for TestIo<'p> {
fn chain(&self) -> &BlockChainClient { fn chain(&self) -> &BlockChainClient {
self.chain self.chain
} }
fn snapshot_service(&self) -> &SnapshotService {
self.snapshot_service
}
fn eth_protocol_version(&self, _peer: PeerId) -> u8 {
64
}
} }
pub struct TestPacket { pub struct TestPacket {
@ -80,6 +92,7 @@ pub struct TestPacket {
pub struct TestPeer { pub struct TestPeer {
pub chain: TestBlockChainClient, pub chain: TestBlockChainClient,
pub snapshot_service: Arc<TestSnapshotService>,
pub sync: RwLock<ChainSync>, pub sync: RwLock<ChainSync>,
pub queue: VecDeque<TestPacket>, pub queue: VecDeque<TestPacket>,
} }
@ -103,9 +116,11 @@ impl TestNet {
let chain = TestBlockChainClient::new(); let chain = TestBlockChainClient::new();
let mut config = SyncConfig::default(); let mut config = SyncConfig::default();
config.fork_block = fork; config.fork_block = fork;
let ss = Arc::new(TestSnapshotService::new());
let sync = ChainSync::new(config, &chain); let sync = ChainSync::new(config, &chain);
net.peers.push(TestPeer { net.peers.push(TestPeer {
sync: RwLock::new(sync), sync: RwLock::new(sync),
snapshot_service: ss,
chain: chain, chain: chain,
queue: VecDeque::new(), queue: VecDeque::new(),
}); });
@ -126,7 +141,7 @@ impl TestNet {
for client in 0..self.peers.len() { for client in 0..self.peers.len() {
if peer != client { if peer != client {
let mut p = self.peers.get_mut(peer).unwrap(); let mut p = self.peers.get_mut(peer).unwrap();
p.sync.write().on_peer_connected(&mut TestIo::new(&mut p.chain, &mut p.queue, Some(client as PeerId)), client as PeerId); p.sync.write().on_peer_connected(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(client as PeerId)), client as PeerId);
} }
} }
} }
@ -137,22 +152,22 @@ impl TestNet {
if let Some(packet) = self.peers[peer].queue.pop_front() { if let Some(packet) = self.peers[peer].queue.pop_front() {
let mut p = self.peers.get_mut(packet.recipient).unwrap(); let mut p = self.peers.get_mut(packet.recipient).unwrap();
trace!("--- {} -> {} ---", peer, packet.recipient); trace!("--- {} -> {} ---", peer, packet.recipient);
ChainSync::dispatch_packet(&p.sync, &mut TestIo::new(&mut p.chain, &mut p.queue, Some(peer as PeerId)), peer as PeerId, packet.packet_id, &packet.data); ChainSync::dispatch_packet(&p.sync, &mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, Some(peer as PeerId)), peer as PeerId, packet.packet_id, &packet.data);
trace!("----------------"); trace!("----------------");
} }
let mut p = self.peers.get_mut(peer).unwrap(); let mut p = self.peers.get_mut(peer).unwrap();
p.sync.write().maintain_sync(&mut TestIo::new(&mut p.chain, &mut p.queue, None)); p.sync.write().maintain_sync(&mut TestIo::new(&mut p.chain, &p.snapshot_service, &mut p.queue, None));
} }
} }
pub fn sync_step_peer(&mut self, peer_num: usize) { pub fn sync_step_peer(&mut self, peer_num: usize) {
let mut peer = self.peer_mut(peer_num); let mut peer = self.peer_mut(peer_num);
peer.sync.write().maintain_sync(&mut TestIo::new(&mut peer.chain, &mut peer.queue, None)); peer.sync.write().maintain_sync(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None));
} }
pub fn restart_peer(&mut self, i: usize) { pub fn restart_peer(&mut self, i: usize) {
let peer = self.peer_mut(i); let peer = self.peer_mut(i);
peer.sync.write().restart(&mut TestIo::new(&mut peer.chain, &mut peer.queue, None)); peer.sync.write().restart(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None));
} }
pub fn sync(&mut self) -> u32 { pub fn sync(&mut self) -> u32 {
@ -181,6 +196,6 @@ impl TestNet {
pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) { pub fn trigger_chain_new_blocks(&mut self, peer_id: usize) {
let mut peer = self.peer_mut(peer_id); let mut peer = self.peer_mut(peer_id);
peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &mut peer.queue, None), &[], &[], &[], &[], &[]); peer.sync.write().chain_new_blocks(&mut TestIo::new(&mut peer.chain, &peer.snapshot_service, &mut peer.queue, None), &[], &[], &[], &[], &[]);
} }
} }

View File

@ -15,5 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
pub mod helpers; pub mod helpers;
pub mod snapshot;
mod chain; mod chain;
mod rpc; mod rpc;

123
sync/src/tests/snapshot.rs Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use util::*;
use ethcore::snapshot::{SnapshotService, ManifestData, RestorationStatus};
use ethcore::header::BlockNumber;
use ethcore::client::{EachBlockWith};
use super::helpers::*;
pub struct TestSnapshotService {
manifest: Option<ManifestData>,
chunks: HashMap<H256, Bytes>,
restoration_manifest: Mutex<Option<ManifestData>>,
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
block_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
}
impl TestSnapshotService {
pub fn new() -> TestSnapshotService {
TestSnapshotService {
manifest: None,
chunks: HashMap::new(),
restoration_manifest: Mutex::new(None),
state_restoration_chunks: Mutex::new(HashMap::new()),
block_restoration_chunks: Mutex::new(HashMap::new()),
}
}
pub fn new_with_snapshot(num_chunks: usize, block_hash: H256, block_number: BlockNumber) -> TestSnapshotService {
let num_state_chunks = num_chunks / 2;
let num_block_chunks = num_chunks - num_state_chunks;
let state_chunks: Vec<Bytes> = (0..num_state_chunks).map(|_| H256::random().to_vec()).collect();
let block_chunks: Vec<Bytes> = (0..num_block_chunks).map(|_| H256::random().to_vec()).collect();
let manifest = ManifestData {
state_hashes: state_chunks.iter().map(|data| data.sha3()).collect(),
block_hashes: block_chunks.iter().map(|data| data.sha3()).collect(),
state_root: H256::new(),
block_number: block_number,
block_hash: block_hash,
};
let mut chunks: HashMap<H256, Bytes> = state_chunks.into_iter().map(|data| (data.sha3(), data)).collect();
chunks.extend(block_chunks.into_iter().map(|data| (data.sha3(), data)));
TestSnapshotService {
manifest: Some(manifest),
chunks: chunks,
restoration_manifest: Mutex::new(None),
state_restoration_chunks: Mutex::new(HashMap::new()),
block_restoration_chunks: Mutex::new(HashMap::new()),
}
}
}
impl SnapshotService for TestSnapshotService {
fn manifest(&self) -> Option<ManifestData> {
self.manifest.as_ref().cloned()
}
fn chunk(&self, hash: H256) -> Option<Bytes> {
self.chunks.get(&hash).cloned()
}
fn status(&self) -> RestorationStatus {
match &*self.restoration_manifest.lock() {
&Some(ref manifest) if self.state_restoration_chunks.lock().len() == manifest.state_hashes.len() &&
self.block_restoration_chunks.lock().len() == manifest.block_hashes.len() => RestorationStatus::Inactive,
&Some(_) => RestorationStatus::Ongoing {
state_chunks_done: self.state_restoration_chunks.lock().len() as u32,
block_chunks_done: self.block_restoration_chunks.lock().len() as u32,
},
&None => RestorationStatus::Inactive,
}
}
fn begin_restore(&self, manifest: ManifestData) {
*self.restoration_manifest.lock() = Some(manifest);
self.state_restoration_chunks.lock().clear();
self.block_restoration_chunks.lock().clear();
}
fn abort_restore(&self) {
*self.restoration_manifest.lock() = None;
self.state_restoration_chunks.lock().clear();
self.block_restoration_chunks.lock().clear();
}
fn restore_state_chunk(&self, hash: H256, chunk: Bytes) {
if self.restoration_manifest.lock().as_ref().map_or(false, |ref m| m.state_hashes.iter().any(|h| h == &hash)) {
self.state_restoration_chunks.lock().insert(hash, chunk);
}
}
fn restore_block_chunk(&self, hash: H256, chunk: Bytes) {
if self.restoration_manifest.lock().as_ref().map_or(false, |ref m| m.block_hashes.iter().any(|h| h == &hash)) {
self.block_restoration_chunks.lock().insert(hash, chunk);
}
}
}
#[test]
fn snapshot_sync() {
::env_logger::init().ok();
let mut net = TestNet::new(2);
net.peer_mut(0).snapshot_service = Arc::new(TestSnapshotService::new_with_snapshot(16, H256::new(), 1));
net.peer_mut(0).chain.add_blocks(1, EachBlockWith::Nothing);
net.sync_steps(19); // status + manifest + chunks
assert_eq!(net.peer(1).snapshot_service.state_restoration_chunks.lock().len(), net.peer(0).snapshot_service.manifest.as_ref().unwrap().state_hashes.len());
assert_eq!(net.peer(1).snapshot_service.block_restoration_chunks.lock().len(), net.peer(0).snapshot_service.manifest.as_ref().unwrap().block_hashes.len());
}

View File

@ -282,6 +282,12 @@ impl<'s> NetworkContext<'s> {
} }
"unknown".to_owned() "unknown".to_owned()
} }
/// Returns max version for a given protocol.
pub fn protocol_version(&self, peer: PeerId, protocol: &str) -> Option<u8> {
let session = self.resolve_session(peer);
session.and_then(|s| s.lock().capability_version(protocol))
}
} }
/// Shared host information /// Shared host information

View File

@ -243,6 +243,11 @@ impl Session {
self.info.capabilities.iter().any(|c| c.protocol == protocol) self.info.capabilities.iter().any(|c| c.protocol == protocol)
} }
/// Checks if peer supports given capability
pub fn capability_version(&self, protocol: &str) -> Option<u8> {
self.info.capabilities.iter().filter_map(|c| if c.protocol == protocol { Some(c.version) } else { None }).max()
}
/// Register the session socket with the event loop /// Register the session socket with the event loop
pub fn register_socket<Host:Handler<Timeout = Token>>(&self, reg: Token, event_loop: &mut EventLoop<Host>) -> Result<(), NetworkError> { pub fn register_socket<Host:Handler<Timeout = Token>>(&self, reg: Token, event_loop: &mut EventLoop<Host>) -> Result<(), NetworkError> {
if self.expired() { if self.expired() {

View File

@ -16,9 +16,11 @@
//! Key-Value store abstraction with `RocksDB` backend. //! Key-Value store abstraction with `RocksDB` backend.
use std::io::ErrorKind;
use common::*; use common::*;
use elastic_array::*; use elastic_array::*;
use std::default::Default; use std::default::Default;
use std::path::PathBuf;
use rlp::{UntrustedRlp, RlpType, View, Compressible}; use rlp::{UntrustedRlp, RlpType, View, Compressible};
use rocksdb::{DB, Writable, WriteBatch, WriteOptions, IteratorMode, DBIterator, use rocksdb::{DB, Writable, WriteBatch, WriteOptions, IteratorMode, DBIterator,
Options, DBCompactionStyle, BlockBasedOptions, Direction, Cache, Column}; Options, DBCompactionStyle, BlockBasedOptions, Direction, Cache, Column};
@ -189,12 +191,18 @@ impl<'a> Iterator for DatabaseIterator {
} }
} }
struct DBAndColumns {
db: DB,
cfs: Vec<Column>,
}
/// Key-Value database. /// Key-Value database.
pub struct Database { pub struct Database {
db: DB, db: RwLock<Option<DBAndColumns>>,
config: DatabaseConfig,
write_opts: WriteOptions, write_opts: WriteOptions,
cfs: Vec<Column>,
overlay: RwLock<Vec<HashMap<ElasticArray32<u8>, KeyState>>>, overlay: RwLock<Vec<HashMap<ElasticArray32<u8>, KeyState>>>,
path: String,
} }
impl Database { impl Database {
@ -278,11 +286,13 @@ impl Database {
}, },
Err(s) => { return Err(s); } Err(s) => { return Err(s); }
}; };
let num_cols = cfs.len();
Ok(Database { Ok(Database {
db: db, db: RwLock::new(Some(DBAndColumns{ db: db, cfs: cfs })),
config: config.clone(),
write_opts: write_opts, write_opts: write_opts,
overlay: RwLock::new((0..(cfs.len() + 1)).map(|_| HashMap::new()).collect()), overlay: RwLock::new((0..(num_cols + 1)).map(|_| HashMap::new()).collect()),
cfs: cfs, path: path.to_owned(),
}) })
} }
@ -320,94 +330,167 @@ impl Database {
/// Commit buffered changes to database. /// Commit buffered changes to database.
pub fn flush(&self) -> Result<(), String> { pub fn flush(&self) -> Result<(), String> {
let batch = WriteBatch::new(); match &*self.db.read() {
let mut overlay = self.overlay.write(); &Some(DBAndColumns { ref db, ref cfs }) => {
let batch = WriteBatch::new();
let mut overlay = self.overlay.write();
for (c, column) in overlay.iter_mut().enumerate() { for (c, column) in overlay.iter_mut().enumerate() {
let column_data = mem::replace(column, HashMap::new()); let column_data = mem::replace(column, HashMap::new());
for (key, state) in column_data.into_iter() { for (key, state) in column_data.into_iter() {
match state { match state {
KeyState::Delete => { KeyState::Delete => {
if c > 0 { if c > 0 {
try!(batch.delete_cf(self.cfs[c - 1], &key)); try!(batch.delete_cf(cfs[c - 1], &key));
} else { } else {
try!(batch.delete(&key)); try!(batch.delete(&key));
} }
}, },
KeyState::Insert(value) => { KeyState::Insert(value) => {
if c > 0 { if c > 0 {
try!(batch.put_cf(self.cfs[c - 1], &key, &value)); try!(batch.put_cf(cfs[c - 1], &key, &value));
} else { } else {
try!(batch.put(&key, &value)); try!(batch.put(&key, &value));
} }
}, },
KeyState::InsertCompressed(value) => { KeyState::InsertCompressed(value) => {
let compressed = UntrustedRlp::new(&value).compress(RlpType::Blocks); let compressed = UntrustedRlp::new(&value).compress(RlpType::Blocks);
if c > 0 { if c > 0 {
try!(batch.put_cf(self.cfs[c - 1], &key, &compressed)); try!(batch.put_cf(cfs[c - 1], &key, &compressed));
} else { } else {
try!(batch.put(&key, &value)); try!(batch.put(&key, &value));
}
}
} }
} }
} }
} db.write_opt(batch, &self.write_opts)
},
&None => Err("Database is closed".to_owned())
} }
self.db.write_opt(batch, &self.write_opts)
} }
/// Commit transaction to database. /// Commit transaction to database.
pub fn write(&self, tr: DBTransaction) -> Result<(), String> { pub fn write(&self, tr: DBTransaction) -> Result<(), String> {
let batch = WriteBatch::new(); match &*self.db.read() {
let ops = tr.ops; &Some(DBAndColumns { ref db, ref cfs }) => {
for op in ops { let batch = WriteBatch::new();
match op { let ops = tr.ops;
DBOp::Insert { col, key, value } => { for op in ops {
try!(col.map_or_else(|| batch.put(&key, &value), |c| batch.put_cf(self.cfs[c as usize], &key, &value))) match op {
}, DBOp::Insert { col, key, value } => {
DBOp::InsertCompressed { col, key, value } => { try!(col.map_or_else(|| batch.put(&key, &value), |c| batch.put_cf(cfs[c as usize], &key, &value)))
let compressed = UntrustedRlp::new(&value).compress(RlpType::Blocks); },
try!(col.map_or_else(|| batch.put(&key, &compressed), |c| batch.put_cf(self.cfs[c as usize], &key, &compressed))) DBOp::InsertCompressed { col, key, value } => {
}, let compressed = UntrustedRlp::new(&value).compress(RlpType::Blocks);
DBOp::Delete { col, key } => { try!(col.map_or_else(|| batch.put(&key, &compressed), |c| batch.put_cf(cfs[c as usize], &key, &compressed)))
try!(col.map_or_else(|| batch.delete(&key), |c| batch.delete_cf(self.cfs[c as usize], &key))) },
}, DBOp::Delete { col, key } => {
} try!(col.map_or_else(|| batch.delete(&key), |c| batch.delete_cf(cfs[c as usize], &key)))
},
}
}
db.write_opt(batch, &self.write_opts)
},
&None => Err("Database is closed".to_owned())
} }
self.db.write_opt(batch, &self.write_opts)
} }
/// Get value by key. /// Get value by key.
pub fn get(&self, col: Option<u32>, key: &[u8]) -> Result<Option<Bytes>, String> { pub fn get(&self, col: Option<u32>, key: &[u8]) -> Result<Option<Bytes>, String> {
let overlay = &self.overlay.read()[Self::to_overlay_column(col)]; match &*self.db.read() {
match overlay.get(key) { &Some(DBAndColumns { ref db, ref cfs }) => {
Some(&KeyState::Insert(ref value)) | Some(&KeyState::InsertCompressed(ref value)) => Ok(Some(value.clone())), let overlay = &self.overlay.read()[Self::to_overlay_column(col)];
Some(&KeyState::Delete) => Ok(None), match overlay.get(key) {
None => { Some(&KeyState::Insert(ref value)) | Some(&KeyState::InsertCompressed(ref value)) => Ok(Some(value.clone())),
col.map_or_else( Some(&KeyState::Delete) => Ok(None),
|| self.db.get(key).map(|r| r.map(|v| v.to_vec())), None => {
|c| self.db.get_cf(self.cfs[c as usize], key).map(|r| r.map(|v| v.to_vec()))) col.map_or_else(
|| db.get(key).map(|r| r.map(|v| v.to_vec())),
|c| db.get_cf(cfs[c as usize], key).map(|r| r.map(|v| v.to_vec())))
},
}
}, },
&None => Ok(None),
} }
} }
/// Get value by partial key. Prefix size should match configured prefix size. Only searches flushed values. /// Get value by partial key. Prefix size should match configured prefix size. Only searches flushed values.
// TODO: support prefix seek for unflushed ata // TODO: support prefix seek for unflushed data
pub fn get_by_prefix(&self, col: Option<u32>, prefix: &[u8]) -> Option<Box<[u8]>> { pub fn get_by_prefix(&self, col: Option<u32>, prefix: &[u8]) -> Option<Box<[u8]>> {
let mut iter = col.map_or_else(|| self.db.iterator(IteratorMode::From(prefix, Direction::Forward)), match &*self.db.read() {
|c| self.db.iterator_cf(self.cfs[c as usize], IteratorMode::From(prefix, Direction::Forward)).unwrap()); &Some(DBAndColumns { ref db, ref cfs }) => {
match iter.next() { let mut iter = col.map_or_else(|| db.iterator(IteratorMode::From(prefix, Direction::Forward)),
// TODO: use prefix_same_as_start read option (not availabele in C API currently) |c| db.iterator_cf(cfs[c as usize], IteratorMode::From(prefix, Direction::Forward)).unwrap());
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Some(v) } else { None }, match iter.next() {
_ => None // TODO: use prefix_same_as_start read option (not availabele in C API currently)
Some((k, v)) => if k[0 .. prefix.len()] == prefix[..] { Some(v) } else { None },
_ => None
}
},
&None => None,
} }
} }
/// Get database iterator for flushed data. /// Get database iterator for flushed data.
pub fn iter(&self, col: Option<u32>) -> DatabaseIterator { pub fn iter(&self, col: Option<u32>) -> DatabaseIterator {
//TODO: iterate over overlay //TODO: iterate over overlay
col.map_or_else(|| DatabaseIterator { iter: self.db.iterator(IteratorMode::Start) }, match &*self.db.read() {
|c| DatabaseIterator { iter: self.db.iterator_cf(self.cfs[c as usize], IteratorMode::Start).unwrap() }) &Some(DBAndColumns { ref db, ref cfs }) => {
col.map_or_else(|| DatabaseIterator { iter: db.iterator(IteratorMode::Start) },
|c| DatabaseIterator { iter: db.iterator_cf(cfs[c as usize], IteratorMode::Start).unwrap() })
},
&None => panic!("Not supported yet") //TODO: return an empty iterator or change return type
}
}
/// Close the database
fn close(&self) {
*self.db.write() = None;
self.overlay.write().clear();
}
/// Restore the database from a copy at given path.
pub fn restore(&self, new_db: &str) -> Result<(), UtilError> {
self.close();
let mut backup_db = PathBuf::from(&self.path);
backup_db.pop();
backup_db.push("backup_db");
println!("Path at {:?}", self.path);
println!("Backup at {:?}", backup_db);
let existed = match fs::rename(&self.path, &backup_db) {
Ok(_) => true,
Err(e) => if let ErrorKind::NotFound = e.kind() {
false
} else {
return Err(e.into());
}
};
match fs::rename(&new_db, &self.path) {
Ok(_) => {
// clean up the backup.
if existed {
try!(fs::remove_dir_all(&backup_db));
}
}
Err(e) => {
// restore the backup.
if existed {
try!(fs::rename(&backup_db, &self.path));
}
return Err(e.into())
}
}
// reopen the database and steal handles into self
let db = try!(Self::open(&self.config, &self.path));
*self.db.write() = mem::replace(&mut *db.db.write(), None);
*self.overlay.write() = mem::replace(&mut *db.overlay.write(), Vec::new());
Ok(())
} }
} }