snapshot chunk and restore traits
This commit is contained in:
parent
4d3f137e1e
commit
2ec3397b7d
@ -894,7 +894,7 @@ impl Client {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
snapshot::take_snapshot(&self.chain.read(), start_hash, db.as_hashdb(), writer, p)?;
|
snapshot::take_snapshot(&*self.engine, &self.chain.read(), start_hash, db.as_hashdb(), writer, p)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ use error::{Error, TransactionError};
|
|||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
|
use snapshot::SnapshotComponents;
|
||||||
use transaction::{UnverifiedTransaction, SignedTransaction};
|
use transaction::{UnverifiedTransaction, SignedTransaction};
|
||||||
use receipt::Receipt;
|
use receipt::Receipt;
|
||||||
|
|
||||||
@ -294,4 +295,10 @@ pub trait Engine : Sync + Send {
|
|||||||
|
|
||||||
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
||||||
fn stop(&self) {}
|
fn stop(&self) {}
|
||||||
|
|
||||||
|
/// Create a factory for building snapshot chunks and restoring from them.
|
||||||
|
/// Returning `None` indicates that this engine doesn't support snapshot creation.
|
||||||
|
fn snapshot_components(&self) -> Option<Box<SnapshotComponents>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,4 +60,8 @@ impl Engine for NullEngine {
|
|||||||
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
|
||||||
Schedule::new_homestead()
|
Schedule::new_homestead()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||||
|
Some(Box::new(::snapshot::PowSnapshot))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,6 +405,10 @@ impl Engine for Arc<Ethash> {
|
|||||||
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<::engines::EpochVerifier>, Error> {
|
fn epoch_verifier(&self, _header: &Header, _proof: &[u8]) -> Result<Box<::engines::EpochVerifier>, Error> {
|
||||||
Ok(Box::new(self.clone()))
|
Ok(Box::new(self.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
|
||||||
|
Some(Box::new(::snapshot::PowSnapshot))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to round gas_limit a bit so that:
|
// Try to round gas_limit a bit so that:
|
||||||
|
349
ethcore/src/snapshot/consensus/mod.rs
Normal file
349
ethcore/src/snapshot/consensus/mod.rs
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (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/>.
|
||||||
|
|
||||||
|
//! Secondary chunk creation and restoration, implementations for different consensus
|
||||||
|
//! engines.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::io;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use blockchain::{BlockChain, BlockProvider};
|
||||||
|
use engines::Engine;
|
||||||
|
use snapshot::{Error, ManifestData};
|
||||||
|
use snapshot::block::AbridgedBlock;
|
||||||
|
|
||||||
|
use util::{Bytes, H256};
|
||||||
|
use util::kvdb::KeyValueDB;
|
||||||
|
use rand::OsRng;
|
||||||
|
use rlp::{RlpStream, UntrustedRlp};
|
||||||
|
|
||||||
|
|
||||||
|
/// A sink for produced chunks.
|
||||||
|
pub type ChunkSink<'a> = FnMut(&[u8]) -> io::Result<()> + 'a;
|
||||||
|
|
||||||
|
// How many blocks to include in a snapshot, starting from the head of the chain.
|
||||||
|
const SNAPSHOT_BLOCKS: u64 = 30000;
|
||||||
|
|
||||||
|
/// Components necessary for snapshot creation and restoration.
|
||||||
|
pub trait SnapshotComponents: Send {
|
||||||
|
/// Create secondary snapshot chunks; these corroborate the state data
|
||||||
|
/// in the state chunks.
|
||||||
|
///
|
||||||
|
/// Chunks shouldn't exceed the given preferred size, and should be fed
|
||||||
|
/// uncompressed into the sink.
|
||||||
|
///
|
||||||
|
/// This will vary by consensus engine, so it's exposed as a trait.
|
||||||
|
fn chunk_all(
|
||||||
|
&mut self,
|
||||||
|
chain: &BlockChain,
|
||||||
|
block_at: H256,
|
||||||
|
chunk_sink: &mut ChunkSink,
|
||||||
|
preferred_size: usize,
|
||||||
|
) -> Result<(), Error>;
|
||||||
|
|
||||||
|
/// Create a rebuilder, which will have chunks fed into it in aribtrary
|
||||||
|
/// order and then be finalized.
|
||||||
|
///
|
||||||
|
/// The manifest, a database, and fresh `BlockChain` are supplied.
|
||||||
|
// TODO: supply anything for state?
|
||||||
|
fn rebuilder(
|
||||||
|
&self,
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
manifest: &ManifestData,
|
||||||
|
) -> Result<Box<Rebuilder>, ::error::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Restore from secondary snapshot chunks.
|
||||||
|
pub trait Rebuilder: Send {
|
||||||
|
/// Feed a chunk, potentially out of order.
|
||||||
|
///
|
||||||
|
/// Check `abort_flag` periodically while doing heavy work. If set to `false`, should bail with
|
||||||
|
/// `Error::RestorationAborted`.
|
||||||
|
fn feed(
|
||||||
|
&mut self,
|
||||||
|
chunk: &[u8],
|
||||||
|
engine: &Engine,
|
||||||
|
abort_flag: &AtomicBool,
|
||||||
|
) -> Result<(), ::error::Error>;
|
||||||
|
|
||||||
|
/// Finalize the restoration. Will be done after all chunks have been
|
||||||
|
/// fed successfully.
|
||||||
|
/// This will apply the necessary "glue" between chunks.
|
||||||
|
fn finalize(&mut self) -> Result<(), Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snapshot creation and restoration for PoW chains.
|
||||||
|
/// This includes blocks from the head of the chain as a
|
||||||
|
/// loose assurance that the chain is valid.
|
||||||
|
pub struct PowSnapshot;
|
||||||
|
|
||||||
|
impl SnapshotComponents for PowSnapshot {
|
||||||
|
fn chunk_all(
|
||||||
|
&mut self,
|
||||||
|
chain: &BlockChain,
|
||||||
|
block_at: H256,
|
||||||
|
chunk_sink: &mut ChunkSink,
|
||||||
|
preferred_size: usize,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
PowWorker {
|
||||||
|
chain: chain,
|
||||||
|
rlps: VecDeque::new(),
|
||||||
|
current_hash: block_at,
|
||||||
|
writer: chunk_sink,
|
||||||
|
preferred_size: preferred_size,
|
||||||
|
}.chunk_all()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuilder(
|
||||||
|
&self,
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
manifest: &ManifestData,
|
||||||
|
) -> Result<Box<Rebuilder>, ::error::Error> {
|
||||||
|
PowRebuilder::new(chain, db, manifest).map(|r| Box::new(r) as Box<_>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to build block chunks.
|
||||||
|
struct PowWorker<'a> {
|
||||||
|
chain: &'a BlockChain,
|
||||||
|
// block, receipt rlp pairs.
|
||||||
|
rlps: VecDeque<Bytes>,
|
||||||
|
current_hash: H256,
|
||||||
|
writer: &'a mut ChunkSink<'a>,
|
||||||
|
preferred_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PowWorker<'a> {
|
||||||
|
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
|
||||||
|
// Loops until we reach the first desired block, and writes out the remainder.
|
||||||
|
fn chunk_all(&mut self) -> Result<(), Error> {
|
||||||
|
let mut loaded_size = 0;
|
||||||
|
let mut last = self.current_hash;
|
||||||
|
|
||||||
|
let genesis_hash = self.chain.genesis_hash();
|
||||||
|
|
||||||
|
for _ in 0..SNAPSHOT_BLOCKS {
|
||||||
|
if self.current_hash == genesis_hash { break }
|
||||||
|
|
||||||
|
let (block, receipts) = self.chain.block(&self.current_hash)
|
||||||
|
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
|
||||||
|
.ok_or(Error::BlockNotFound(self.current_hash))?;
|
||||||
|
|
||||||
|
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
|
||||||
|
|
||||||
|
let pair = {
|
||||||
|
let mut pair_stream = RlpStream::new_list(2);
|
||||||
|
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
|
||||||
|
pair_stream.out()
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_loaded_size = loaded_size + pair.len();
|
||||||
|
|
||||||
|
// cut off the chunk if too large.
|
||||||
|
|
||||||
|
if new_loaded_size > self.preferred_size && !self.rlps.is_empty() {
|
||||||
|
self.write_chunk(last)?;
|
||||||
|
loaded_size = pair.len();
|
||||||
|
} else {
|
||||||
|
loaded_size = new_loaded_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rlps.push_front(pair);
|
||||||
|
|
||||||
|
last = self.current_hash;
|
||||||
|
self.current_hash = block.header_view().parent_hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
if loaded_size != 0 {
|
||||||
|
self.write_chunk(last)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// write out the data in the buffers to a chunk on disk
|
||||||
|
//
|
||||||
|
// we preface each chunk with the parent of the first block's details,
|
||||||
|
// obtained from the details of the last block written.
|
||||||
|
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
|
||||||
|
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
|
||||||
|
|
||||||
|
let (last_header, last_details) = self.chain.block_header(&last)
|
||||||
|
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
|
||||||
|
.ok_or(Error::BlockNotFound(last))?;
|
||||||
|
|
||||||
|
let parent_number = last_header.number() - 1;
|
||||||
|
let parent_hash = last_header.parent_hash();
|
||||||
|
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
|
||||||
|
|
||||||
|
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
|
||||||
|
|
||||||
|
let num_entries = self.rlps.len();
|
||||||
|
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
|
||||||
|
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
|
||||||
|
|
||||||
|
for pair in self.rlps.drain(..) {
|
||||||
|
rlp_stream.append_raw(&pair, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_data = rlp_stream.out();
|
||||||
|
|
||||||
|
(self.writer)(&raw_data)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rebuilder for proof-of-work chains.
|
||||||
|
/// Does basic verification for all blocks, but `PoW` verification for some.
|
||||||
|
/// Blocks must be fed in-order.
|
||||||
|
///
|
||||||
|
/// The first block in every chunk is disconnected from the last block in the
|
||||||
|
/// chunk before it, as chunks may be submitted out-of-order.
|
||||||
|
///
|
||||||
|
/// After all chunks have been submitted, we "glue" the chunks together.
|
||||||
|
pub struct PowRebuilder {
|
||||||
|
chain: BlockChain,
|
||||||
|
db: Arc<KeyValueDB>,
|
||||||
|
rng: OsRng,
|
||||||
|
disconnected: Vec<(u64, H256)>,
|
||||||
|
best_number: u64,
|
||||||
|
best_hash: H256,
|
||||||
|
best_root: H256,
|
||||||
|
fed_blocks: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PowRebuilder {
|
||||||
|
/// Create a new PowRebuilder.
|
||||||
|
fn new(chain: BlockChain, db: Arc<KeyValueDB>, manifest: &ManifestData) -> Result<Self, ::error::Error> {
|
||||||
|
Ok(PowRebuilder {
|
||||||
|
chain: chain,
|
||||||
|
db: db,
|
||||||
|
rng: OsRng::new()?,
|
||||||
|
disconnected: Vec::new(),
|
||||||
|
best_number: manifest.block_number,
|
||||||
|
best_hash: manifest.block_hash,
|
||||||
|
best_root: manifest.state_root,
|
||||||
|
fed_blocks: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rebuilder for PowRebuilder {
|
||||||
|
/// Feed the rebuilder an uncompressed block chunk.
|
||||||
|
/// Returns the number of blocks fed or any errors.
|
||||||
|
fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<(), ::error::Error> {
|
||||||
|
use basic_types::Seal::With;
|
||||||
|
use views::BlockView;
|
||||||
|
use snapshot::verify_old_block;
|
||||||
|
use util::U256;
|
||||||
|
use util::triehash::ordered_trie_root;
|
||||||
|
|
||||||
|
let rlp = UntrustedRlp::new(chunk);
|
||||||
|
let item_count = rlp.item_count()?;
|
||||||
|
let num_blocks = (item_count - 3) as u64;
|
||||||
|
|
||||||
|
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
|
||||||
|
|
||||||
|
if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS {
|
||||||
|
return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: assert here that these values are consistent with chunks being in order.
|
||||||
|
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
|
||||||
|
let mut parent_hash = rlp.val_at::<H256>(1)?;
|
||||||
|
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
|
||||||
|
|
||||||
|
for idx in 3..item_count {
|
||||||
|
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
|
||||||
|
|
||||||
|
let pair = rlp.at(idx)?;
|
||||||
|
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
|
||||||
|
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
|
||||||
|
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
|
||||||
|
let receipts_root = ordered_trie_root(
|
||||||
|
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
|
||||||
|
);
|
||||||
|
|
||||||
|
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
|
||||||
|
let block_bytes = block.rlp_bytes(With);
|
||||||
|
let is_best = cur_number == self.best_number;
|
||||||
|
|
||||||
|
if is_best {
|
||||||
|
if block.header.hash() != self.best_hash {
|
||||||
|
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.header.state_root() != &self.best_root {
|
||||||
|
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_old_block(
|
||||||
|
&mut self.rng,
|
||||||
|
&block.header,
|
||||||
|
engine,
|
||||||
|
&self.chain,
|
||||||
|
Some(&block_bytes),
|
||||||
|
is_best
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut batch = self.db.transaction();
|
||||||
|
|
||||||
|
// special-case the first block in each chunk.
|
||||||
|
if idx == 3 {
|
||||||
|
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
|
||||||
|
self.disconnected.push((cur_number, block.header.hash()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
|
||||||
|
}
|
||||||
|
self.db.write_buffered(batch);
|
||||||
|
self.chain.commit();
|
||||||
|
|
||||||
|
parent_hash = BlockView::new(&block_bytes).hash();
|
||||||
|
cur_number += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fed_blocks += num_blocks;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Glue together any disconnected chunks and check that the chain is complete.
|
||||||
|
fn finalize(&mut self) -> Result<(), Error> {
|
||||||
|
let mut batch = self.db.transaction();
|
||||||
|
|
||||||
|
for (first_num, first_hash) in self.disconnected.drain(..) {
|
||||||
|
let parent_num = first_num - 1;
|
||||||
|
|
||||||
|
// check if the parent is even in the chain.
|
||||||
|
// since we don't restore every single block in the chain,
|
||||||
|
// the first block of the first chunks has nothing to connect to.
|
||||||
|
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
|
||||||
|
// if so, add the child to it.
|
||||||
|
self.chain.add_child(&mut batch, parent_hash, first_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.db.write_buffered(batch);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,8 @@ pub enum Error {
|
|||||||
VersionNotSupported(u64),
|
VersionNotSupported(u64),
|
||||||
/// Max chunk size is to small to fit basic account data.
|
/// Max chunk size is to small to fit basic account data.
|
||||||
ChunkTooSmall,
|
ChunkTooSmall,
|
||||||
|
/// Snapshots not supported by the consensus engine.
|
||||||
|
SnapshotsUnsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Error {
|
impl fmt::Display for Error {
|
||||||
@ -79,6 +81,7 @@ impl fmt::Display for Error {
|
|||||||
Error::Trie(ref err) => err.fmt(f),
|
Error::Trie(ref err) => err.fmt(f),
|
||||||
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
|
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
|
||||||
Error::ChunkTooSmall => write!(f, "Chunk size is too small."),
|
Error::ChunkTooSmall => write!(f, "Chunk size is too small."),
|
||||||
|
Error::SnapshotsUnsupported => write!(f, "Snapshots unsupported by consensus engine."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
//! Snapshot creation, restoration, and network service.
|
//! Snapshot creation, restoration, and network service.
|
||||||
//!
|
//!
|
||||||
//! Documentation of the format can be found at
|
//! Documentation of the format can be found at
|
||||||
//! https://github.com/paritytech/parity/wiki/%22PV64%22-Snapshot-Format
|
//! https://github.com/paritytech/parity/wiki/Warp-Sync-Snapshot-Format
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
@ -28,7 +28,6 @@ use blockchain::{BlockChain, BlockProvider};
|
|||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
use header::Header;
|
use header::Header;
|
||||||
use ids::BlockId;
|
use ids::BlockId;
|
||||||
use views::BlockView;
|
|
||||||
|
|
||||||
use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint};
|
use util::{Bytes, Hashable, HashDB, DBValue, snappy, U256, Uint};
|
||||||
use util::Mutex;
|
use util::Mutex;
|
||||||
@ -40,7 +39,6 @@ use util::sha3::SHA3_NULL_RLP;
|
|||||||
use rlp::{RlpStream, UntrustedRlp};
|
use rlp::{RlpStream, UntrustedRlp};
|
||||||
use bloom_journal::Bloom;
|
use bloom_journal::Bloom;
|
||||||
|
|
||||||
use self::block::AbridgedBlock;
|
|
||||||
use self::io::SnapshotWriter;
|
use self::io::SnapshotWriter;
|
||||||
|
|
||||||
use super::state_db::StateDB;
|
use super::state_db::StateDB;
|
||||||
@ -51,6 +49,7 @@ use rand::{Rng, OsRng};
|
|||||||
|
|
||||||
pub use self::error::Error;
|
pub use self::error::Error;
|
||||||
|
|
||||||
|
pub use self::consensus::*;
|
||||||
pub use self::service::{Service, DatabaseRestore};
|
pub use self::service::{Service, DatabaseRestore};
|
||||||
pub use self::traits::SnapshotService;
|
pub use self::traits::SnapshotService;
|
||||||
pub use self::watcher::Watcher;
|
pub use self::watcher::Watcher;
|
||||||
@ -63,6 +62,7 @@ pub mod service;
|
|||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod block;
|
mod block;
|
||||||
|
mod consensus;
|
||||||
mod error;
|
mod error;
|
||||||
mod watcher;
|
mod watcher;
|
||||||
|
|
||||||
@ -83,9 +83,6 @@ mod traits {
|
|||||||
// 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;
|
||||||
|
|
||||||
// How many blocks to include in a snapshot, starting from the head of the chain.
|
|
||||||
const SNAPSHOT_BLOCKS: u64 = 30000;
|
|
||||||
|
|
||||||
/// A progress indicator for snapshots.
|
/// A progress indicator for snapshots.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
@ -122,6 +119,7 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
/// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer.
|
/// Take a snapshot using the given blockchain, starting block hash, and database, writing into the given writer.
|
||||||
pub fn take_snapshot<W: SnapshotWriter + Send>(
|
pub fn take_snapshot<W: SnapshotWriter + Send>(
|
||||||
|
engine: &Engine,
|
||||||
chain: &BlockChain,
|
chain: &BlockChain,
|
||||||
block_at: H256,
|
block_at: H256,
|
||||||
state_db: &HashDB,
|
state_db: &HashDB,
|
||||||
@ -136,9 +134,11 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
|
|||||||
info!("Taking snapshot starting at block {}", number);
|
info!("Taking snapshot starting at block {}", number);
|
||||||
|
|
||||||
let writer = Mutex::new(writer);
|
let writer = Mutex::new(writer);
|
||||||
|
let chunker = engine.snapshot_components().ok_or(Error::SnapshotsUnsupported)?;
|
||||||
let (state_hashes, block_hashes) = scope(|scope| {
|
let (state_hashes, block_hashes) = scope(|scope| {
|
||||||
let block_guard = scope.spawn(|| chunk_blocks(chain, block_at, &writer, p));
|
let writer = &writer;
|
||||||
let state_res = chunk_state(state_db, state_root, &writer, p);
|
let block_guard = scope.spawn(move || chunk_secondary(chunker, chain, block_at, writer, p));
|
||||||
|
let state_res = chunk_state(state_db, state_root, writer, p);
|
||||||
|
|
||||||
state_res.and_then(|state_hashes| {
|
state_res.and_then(|state_hashes| {
|
||||||
block_guard.join().map(|block_hashes| (state_hashes, block_hashes))
|
block_guard.join().map(|block_hashes| (state_hashes, block_hashes))
|
||||||
@ -163,128 +163,41 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used to build block chunks.
|
/// Create and write out all secondary chunks to disk, returning a vector of all
|
||||||
struct BlockChunker<'a> {
|
/// the hashes of secondary chunks created.
|
||||||
chain: &'a BlockChain,
|
|
||||||
// block, receipt rlp pairs.
|
|
||||||
rlps: VecDeque<Bytes>,
|
|
||||||
current_hash: H256,
|
|
||||||
hashes: Vec<H256>,
|
|
||||||
snappy_buffer: Vec<u8>,
|
|
||||||
writer: &'a Mutex<SnapshotWriter + 'a>,
|
|
||||||
progress: &'a Progress,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BlockChunker<'a> {
|
|
||||||
// Repeatedly fill the buffers and writes out chunks, moving backwards from starting block hash.
|
|
||||||
// Loops until we reach the first desired block, and writes out the remainder.
|
|
||||||
fn chunk_all(&mut self) -> Result<(), Error> {
|
|
||||||
let mut loaded_size = 0;
|
|
||||||
let mut last = self.current_hash;
|
|
||||||
|
|
||||||
let genesis_hash = self.chain.genesis_hash();
|
|
||||||
|
|
||||||
for _ in 0..SNAPSHOT_BLOCKS {
|
|
||||||
if self.current_hash == genesis_hash { break }
|
|
||||||
|
|
||||||
let (block, receipts) = self.chain.block(&self.current_hash)
|
|
||||||
.and_then(|b| self.chain.block_receipts(&self.current_hash).map(|r| (b, r)))
|
|
||||||
.ok_or(Error::BlockNotFound(self.current_hash))?;
|
|
||||||
|
|
||||||
let abridged_rlp = AbridgedBlock::from_block_view(&block.view()).into_inner();
|
|
||||||
|
|
||||||
let pair = {
|
|
||||||
let mut pair_stream = RlpStream::new_list(2);
|
|
||||||
pair_stream.append_raw(&abridged_rlp, 1).append(&receipts);
|
|
||||||
pair_stream.out()
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_loaded_size = loaded_size + pair.len();
|
|
||||||
|
|
||||||
// cut off the chunk if too large.
|
|
||||||
|
|
||||||
if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() {
|
|
||||||
self.write_chunk(last)?;
|
|
||||||
loaded_size = pair.len();
|
|
||||||
} else {
|
|
||||||
loaded_size = new_loaded_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.rlps.push_front(pair);
|
|
||||||
|
|
||||||
last = self.current_hash;
|
|
||||||
self.current_hash = block.header_view().parent_hash();
|
|
||||||
}
|
|
||||||
|
|
||||||
if loaded_size != 0 {
|
|
||||||
self.write_chunk(last)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// write out the data in the buffers to a chunk on disk
|
|
||||||
//
|
|
||||||
// we preface each chunk with the parent of the first block's details,
|
|
||||||
// obtained from the details of the last block written.
|
|
||||||
fn write_chunk(&mut self, last: H256) -> Result<(), Error> {
|
|
||||||
trace!(target: "snapshot", "prepared block chunk with {} blocks", self.rlps.len());
|
|
||||||
|
|
||||||
let (last_header, last_details) = self.chain.block_header(&last)
|
|
||||||
.and_then(|n| self.chain.block_details(&last).map(|d| (n, d)))
|
|
||||||
.ok_or(Error::BlockNotFound(last))?;
|
|
||||||
|
|
||||||
let parent_number = last_header.number() - 1;
|
|
||||||
let parent_hash = last_header.parent_hash();
|
|
||||||
let parent_total_difficulty = last_details.total_difficulty - *last_header.difficulty();
|
|
||||||
|
|
||||||
trace!(target: "snapshot", "parent last written block: {}", parent_hash);
|
|
||||||
|
|
||||||
let num_entries = self.rlps.len();
|
|
||||||
let mut rlp_stream = RlpStream::new_list(3 + num_entries);
|
|
||||||
rlp_stream.append(&parent_number).append(parent_hash).append(&parent_total_difficulty);
|
|
||||||
|
|
||||||
for pair in self.rlps.drain(..) {
|
|
||||||
rlp_stream.append_raw(&pair, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw_data = rlp_stream.out();
|
|
||||||
|
|
||||||
let size = snappy::compress_into(&raw_data, &mut self.snappy_buffer);
|
|
||||||
let compressed = &self.snappy_buffer[..size];
|
|
||||||
let hash = compressed.sha3();
|
|
||||||
|
|
||||||
self.writer.lock().write_block_chunk(hash, compressed)?;
|
|
||||||
trace!(target: "snapshot", "wrote block chunk. hash: {}, size: {}, uncompressed size: {}", hash.hex(), size, raw_data.len());
|
|
||||||
|
|
||||||
self.progress.size.fetch_add(size, Ordering::SeqCst);
|
|
||||||
self.progress.blocks.fetch_add(num_entries, Ordering::SeqCst);
|
|
||||||
|
|
||||||
self.hashes.push(hash);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and write out all block chunks to disk, returning a vector of all
|
|
||||||
/// the hashes of block chunks created.
|
|
||||||
///
|
///
|
||||||
/// The path parameter is the directory to store the block chunks in.
|
/// Secondary chunks are engine-specific, but they intend to corroborate the state data
|
||||||
/// This function assumes the directory exists already.
|
/// in the state chunks.
|
||||||
/// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis.
|
/// Returns a list of chunk hashes, with the first having the blocks furthest from the genesis.
|
||||||
pub fn chunk_blocks<'a>(chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> {
|
pub fn chunk_secondary<'a>(mut chunker: Box<SnapshotComponents>, chain: &'a BlockChain, start_hash: H256, writer: &Mutex<SnapshotWriter + 'a>, progress: &'a Progress) -> Result<Vec<H256>, Error> {
|
||||||
let mut chunker = BlockChunker {
|
let mut chunk_hashes = Vec::new();
|
||||||
chain: chain,
|
let mut snappy_buffer = vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)];
|
||||||
rlps: VecDeque::new(),
|
|
||||||
current_hash: start_hash,
|
|
||||||
hashes: Vec::new(),
|
|
||||||
snappy_buffer: vec![0; snappy::max_compressed_len(PREFERRED_CHUNK_SIZE)],
|
|
||||||
writer: writer,
|
|
||||||
progress: progress,
|
|
||||||
};
|
|
||||||
|
|
||||||
chunker.chunk_all()?;
|
{
|
||||||
|
let mut chunk_sink = |raw_data: &[u8]| {
|
||||||
|
let compressed_size = snappy::compress_into(raw_data, &mut snappy_buffer);
|
||||||
|
let compressed = &snappy_buffer[..compressed_size];
|
||||||
|
let hash = compressed.sha3();
|
||||||
|
let size = compressed.len();
|
||||||
|
|
||||||
Ok(chunker.hashes)
|
writer.lock().write_block_chunk(hash, compressed)?;
|
||||||
|
trace!(target: "snapshot", "wrote secondary chunk. hash: {}, size: {}, uncompressed size: {}",
|
||||||
|
hash.hex(), size, raw_data.len());
|
||||||
|
|
||||||
|
progress.size.fetch_add(size, Ordering::SeqCst);
|
||||||
|
chunk_hashes.push(hash);
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
chunker.chunk_all(
|
||||||
|
chain,
|
||||||
|
start_hash,
|
||||||
|
&mut chunk_sink,
|
||||||
|
PREFERRED_CHUNK_SIZE,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(chunk_hashes)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State trie chunker.
|
/// State trie chunker.
|
||||||
@ -564,158 +477,15 @@ const POW_VERIFY_RATE: f32 = 0.02;
|
|||||||
/// the fullest verification possible. If not, it will take a random sample to determine whether it will
|
/// the fullest verification possible. If not, it will take a random sample to determine whether it will
|
||||||
/// do heavy or light verification.
|
/// do heavy or light verification.
|
||||||
pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> {
|
pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> {
|
||||||
|
engine.verify_block_basic(header, body)?;
|
||||||
|
|
||||||
if always || rng.gen::<f32>() <= POW_VERIFY_RATE {
|
if always || rng.gen::<f32>() <= POW_VERIFY_RATE {
|
||||||
|
engine.verify_block_unordered(header, body)?;
|
||||||
match chain.block_header(header.parent_hash()) {
|
match chain.block_header(header.parent_hash()) {
|
||||||
Some(parent) => engine.verify_block_family(header, &parent, body),
|
Some(parent) => engine.verify_block_family(header, &parent, body),
|
||||||
None => engine.verify_block_seal(header), // TODO: fetch validation proof as necessary.
|
None => Ok(()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
engine.verify_block_basic(header, body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rebuilds the blockchain from chunks.
|
|
||||||
///
|
|
||||||
/// Does basic verification for all blocks, but `PoW` verification for some.
|
|
||||||
/// Blocks must be fed in-order.
|
|
||||||
///
|
|
||||||
/// The first block in every chunk is disconnected from the last block in the
|
|
||||||
/// chunk before it, as chunks may be submitted out-of-order.
|
|
||||||
///
|
|
||||||
/// After all chunks have been submitted, we "glue" the chunks together.
|
|
||||||
pub struct BlockRebuilder {
|
|
||||||
chain: BlockChain,
|
|
||||||
db: Arc<Database>,
|
|
||||||
rng: OsRng,
|
|
||||||
disconnected: Vec<(u64, H256)>,
|
|
||||||
best_number: u64,
|
|
||||||
best_hash: H256,
|
|
||||||
best_root: H256,
|
|
||||||
fed_blocks: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BlockRebuilder {
|
|
||||||
/// Create a new BlockRebuilder.
|
|
||||||
pub fn new(chain: BlockChain, db: Arc<Database>, manifest: &ManifestData) -> Result<Self, ::error::Error> {
|
|
||||||
Ok(BlockRebuilder {
|
|
||||||
chain: chain,
|
|
||||||
db: db,
|
|
||||||
rng: OsRng::new()?,
|
|
||||||
disconnected: Vec::new(),
|
|
||||||
best_number: manifest.block_number,
|
|
||||||
best_hash: manifest.block_hash,
|
|
||||||
best_root: manifest.state_root,
|
|
||||||
fed_blocks: 0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Feed the rebuilder an uncompressed block chunk.
|
|
||||||
/// Returns the number of blocks fed or any errors.
|
|
||||||
pub fn feed(&mut self, chunk: &[u8], engine: &Engine, abort_flag: &AtomicBool) -> Result<u64, ::error::Error> {
|
|
||||||
use basic_types::Seal::With;
|
|
||||||
use util::U256;
|
|
||||||
use util::triehash::ordered_trie_root;
|
|
||||||
|
|
||||||
let rlp = UntrustedRlp::new(chunk);
|
|
||||||
let item_count = rlp.item_count()?;
|
|
||||||
let num_blocks = (item_count - 3) as u64;
|
|
||||||
|
|
||||||
trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3);
|
|
||||||
|
|
||||||
if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS {
|
|
||||||
return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: assert here that these values are consistent with chunks being in order.
|
|
||||||
let mut cur_number = rlp.val_at::<u64>(0)? + 1;
|
|
||||||
let mut parent_hash = rlp.val_at::<H256>(1)?;
|
|
||||||
let parent_total_difficulty = rlp.val_at::<U256>(2)?;
|
|
||||||
|
|
||||||
for idx in 3..item_count {
|
|
||||||
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
|
|
||||||
|
|
||||||
let pair = rlp.at(idx)?;
|
|
||||||
let abridged_rlp = pair.at(0)?.as_raw().to_owned();
|
|
||||||
let abridged_block = AbridgedBlock::from_raw(abridged_rlp);
|
|
||||||
let receipts: Vec<::receipt::Receipt> = pair.list_at(1)?;
|
|
||||||
let receipts_root = ordered_trie_root(
|
|
||||||
pair.at(1)?.iter().map(|r| r.as_raw().to_owned())
|
|
||||||
);
|
|
||||||
|
|
||||||
let block = abridged_block.to_block(parent_hash, cur_number, receipts_root)?;
|
|
||||||
let block_bytes = block.rlp_bytes(With);
|
|
||||||
let is_best = cur_number == self.best_number;
|
|
||||||
|
|
||||||
if is_best {
|
|
||||||
if block.header.hash() != self.best_hash {
|
|
||||||
return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into())
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.header.state_root() != &self.best_root {
|
|
||||||
return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
verify_old_block(
|
|
||||||
&mut self.rng,
|
|
||||||
&block.header,
|
|
||||||
engine,
|
|
||||||
&self.chain,
|
|
||||||
Some(&block_bytes),
|
|
||||||
is_best
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut batch = self.db.transaction();
|
|
||||||
|
|
||||||
// special-case the first block in each chunk.
|
|
||||||
if idx == 3 {
|
|
||||||
if self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, Some(parent_total_difficulty), is_best, false) {
|
|
||||||
self.disconnected.push((cur_number, block.header.hash()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, is_best, false);
|
|
||||||
}
|
|
||||||
self.db.write_buffered(batch);
|
|
||||||
self.chain.commit();
|
|
||||||
|
|
||||||
parent_hash = BlockView::new(&block_bytes).hash();
|
|
||||||
cur_number += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.fed_blocks += num_blocks;
|
|
||||||
|
|
||||||
Ok(num_blocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Glue together any disconnected chunks and check that the chain is complete.
|
|
||||||
pub fn finalize(self, canonical: HashMap<u64, H256>) -> Result<(), Error> {
|
|
||||||
let mut batch = self.db.transaction();
|
|
||||||
|
|
||||||
for (first_num, first_hash) in self.disconnected {
|
|
||||||
let parent_num = first_num - 1;
|
|
||||||
|
|
||||||
// check if the parent is even in the chain.
|
|
||||||
// since we don't restore every single block in the chain,
|
|
||||||
// the first block of the first chunks has nothing to connect to.
|
|
||||||
if let Some(parent_hash) = self.chain.block_hash(parent_num) {
|
|
||||||
// if so, add the child to it.
|
|
||||||
self.chain.add_child(&mut batch, parent_hash, first_hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.db.write_buffered(batch);
|
|
||||||
|
|
||||||
let best_number = self.best_number;
|
|
||||||
for num in (0..self.fed_blocks).map(|x| best_number - x) {
|
|
||||||
|
|
||||||
let hash = self.chain.block_hash(num).ok_or(Error::IncompleteChain)?;
|
|
||||||
|
|
||||||
if let Some(canon_hash) = canonical.get(&num).cloned() {
|
|
||||||
if canon_hash != hash {
|
|
||||||
return Err(Error::WrongBlockHash(num, canon_hash, hash));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,14 @@
|
|||||||
|
|
||||||
//! Snapshot network service implementation.
|
//! Snapshot network service implementation.
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashSet;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
use super::{ManifestData, StateRebuilder, BlockRebuilder, RestorationStatus, SnapshotService};
|
use super::{ManifestData, StateRebuilder, Rebuilder, RestorationStatus, SnapshotService};
|
||||||
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
|
use super::io::{SnapshotReader, LooseReader, SnapshotWriter, LooseWriter};
|
||||||
|
|
||||||
use blockchain::BlockChain;
|
use blockchain::BlockChain;
|
||||||
@ -69,12 +69,11 @@ struct Restoration {
|
|||||||
state_chunks_left: HashSet<H256>,
|
state_chunks_left: HashSet<H256>,
|
||||||
block_chunks_left: HashSet<H256>,
|
block_chunks_left: HashSet<H256>,
|
||||||
state: StateRebuilder,
|
state: StateRebuilder,
|
||||||
blocks: BlockRebuilder,
|
secondary: Box<Rebuilder>,
|
||||||
writer: Option<LooseWriter>,
|
writer: Option<LooseWriter>,
|
||||||
snappy_buffer: Bytes,
|
snappy_buffer: Bytes,
|
||||||
final_state_root: H256,
|
final_state_root: H256,
|
||||||
guard: Guard,
|
guard: Guard,
|
||||||
canonical_hashes: HashMap<u64, H256>,
|
|
||||||
db: Arc<Database>,
|
db: Arc<Database>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +85,7 @@ struct RestorationParams<'a> {
|
|||||||
writer: Option<LooseWriter>, // writer for recovered snapshot.
|
writer: Option<LooseWriter>, // writer for recovered snapshot.
|
||||||
genesis: &'a [u8], // genesis block of the chain.
|
genesis: &'a [u8], // genesis block of the chain.
|
||||||
guard: Guard, // guard for the restoration directory.
|
guard: Guard, // guard for the restoration directory.
|
||||||
|
engine: &'a Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Restoration {
|
impl Restoration {
|
||||||
@ -100,7 +100,10 @@ impl Restoration {
|
|||||||
.map_err(UtilError::SimpleString)?);
|
.map_err(UtilError::SimpleString)?);
|
||||||
|
|
||||||
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
|
let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone());
|
||||||
let blocks = BlockRebuilder::new(chain, raw_db.clone(), &manifest)?;
|
let components = params.engine.snapshot_components()
|
||||||
|
.ok_or_else(|| ::snapshot::Error::SnapshotsUnsupported)?;
|
||||||
|
|
||||||
|
let secondary = components.rebuilder(chain, raw_db.clone(), &manifest)?;
|
||||||
|
|
||||||
let root = manifest.state_root.clone();
|
let root = manifest.state_root.clone();
|
||||||
Ok(Restoration {
|
Ok(Restoration {
|
||||||
@ -108,12 +111,11 @@ impl Restoration {
|
|||||||
state_chunks_left: state_chunks,
|
state_chunks_left: state_chunks,
|
||||||
block_chunks_left: block_chunks,
|
block_chunks_left: block_chunks,
|
||||||
state: StateRebuilder::new(raw_db.clone(), params.pruning),
|
state: StateRebuilder::new(raw_db.clone(), params.pruning),
|
||||||
blocks: blocks,
|
secondary: secondary,
|
||||||
writer: params.writer,
|
writer: params.writer,
|
||||||
snappy_buffer: Vec::new(),
|
snappy_buffer: Vec::new(),
|
||||||
final_state_root: root,
|
final_state_root: root,
|
||||||
guard: params.guard,
|
guard: params.guard,
|
||||||
canonical_hashes: HashMap::new(),
|
|
||||||
db: raw_db,
|
db: raw_db,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -138,7 +140,7 @@ impl Restoration {
|
|||||||
if self.block_chunks_left.remove(&hash) {
|
if self.block_chunks_left.remove(&hash) {
|
||||||
let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?;
|
let len = snappy::decompress_into(chunk, &mut self.snappy_buffer)?;
|
||||||
|
|
||||||
self.blocks.feed(&self.snappy_buffer[..len], engine, flag)?;
|
self.secondary.feed(&self.snappy_buffer[..len], engine, flag)?;
|
||||||
if let Some(ref mut writer) = self.writer.as_mut() {
|
if let Some(ref mut writer) = self.writer.as_mut() {
|
||||||
writer.write_block_chunk(hash, chunk)?;
|
writer.write_block_chunk(hash, chunk)?;
|
||||||
}
|
}
|
||||||
@ -147,13 +149,8 @@ impl Restoration {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// note canonical hashes.
|
|
||||||
fn note_canonical(&mut self, hashes: &[(u64, H256)]) {
|
|
||||||
self.canonical_hashes.extend(hashes.iter().cloned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// finish up restoration.
|
// finish up restoration.
|
||||||
fn finalize(self) -> Result<(), Error> {
|
fn finalize(mut self) -> Result<(), Error> {
|
||||||
use util::trie::TrieError;
|
use util::trie::TrieError;
|
||||||
|
|
||||||
if !self.is_done() { return Ok(()) }
|
if !self.is_done() { return Ok(()) }
|
||||||
@ -169,7 +166,7 @@ impl Restoration {
|
|||||||
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
|
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
|
||||||
|
|
||||||
// connect out-of-order chunks and verify chain integrity.
|
// connect out-of-order chunks and verify chain integrity.
|
||||||
self.blocks.finalize(self.canonical_hashes)?;
|
self.secondary.finalize()?;
|
||||||
|
|
||||||
if let Some(writer) = self.writer {
|
if let Some(writer) = self.writer {
|
||||||
writer.finish(self.manifest)?;
|
writer.finish(self.manifest)?;
|
||||||
@ -425,6 +422,7 @@ impl Service {
|
|||||||
writer: writer,
|
writer: writer,
|
||||||
genesis: &self.genesis_block,
|
genesis: &self.genesis_block,
|
||||||
guard: Guard::new(rest_dir),
|
guard: Guard::new(rest_dir),
|
||||||
|
engine: &*self.engine,
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_chunks = params.manifest.state_hashes.len();
|
let state_chunks = params.manifest.state_hashes.len();
|
||||||
@ -593,14 +591,6 @@ impl SnapshotService for Service {
|
|||||||
trace!("Error sending snapshot service message: {:?}", e);
|
trace!("Error sending snapshot service message: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]) {
|
|
||||||
let mut rest = self.restoration.lock();
|
|
||||||
|
|
||||||
if let Some(ref mut rest) = rest.as_mut() {
|
|
||||||
rest.note_canonical(canonical);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Service {
|
impl Drop for Service {
|
||||||
|
@ -48,10 +48,6 @@ pub trait SnapshotService : Sync + Send {
|
|||||||
/// Feed a raw block chunk to the service to be processed asynchronously.
|
/// Feed a raw block chunk to the service to be processed asynchronously.
|
||||||
/// no-op if currently restoring.
|
/// no-op if currently restoring.
|
||||||
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
|
fn restore_block_chunk(&self, hash: H256, chunk: Bytes);
|
||||||
|
|
||||||
/// Give the restoration in-progress some canonical block hashes for
|
|
||||||
/// extra verification (performed at the end)
|
|
||||||
fn provide_canon_hashes(&self, canonical: &[(u64, H256)]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IpcConfig for SnapshotService { }
|
impl IpcConfig for SnapshotService { }
|
||||||
|
@ -21,13 +21,12 @@ use error::Error;
|
|||||||
|
|
||||||
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
use blockchain::generator::{ChainGenerator, ChainIterator, BlockFinalizer};
|
||||||
use blockchain::BlockChain;
|
use blockchain::BlockChain;
|
||||||
use snapshot::{chunk_blocks, BlockRebuilder, Error as SnapshotError, Progress};
|
use snapshot::{chunk_secondary, Error as SnapshotError, Progress, SnapshotComponents};
|
||||||
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
|
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
|
||||||
|
|
||||||
use util::{Mutex, snappy};
|
use util::{Mutex, snappy};
|
||||||
use util::kvdb::{Database, DatabaseConfig};
|
use util::kvdb::{self, KeyValueDB, DBTransaction};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
@ -35,19 +34,18 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
let mut canon_chain = ChainGenerator::default();
|
let mut canon_chain = ChainGenerator::default();
|
||||||
let mut finalizer = BlockFinalizer::default();
|
let mut finalizer = BlockFinalizer::default();
|
||||||
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
let genesis = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let components = ::snapshot::PowSnapshot;
|
||||||
|
|
||||||
let engine = Arc::new(::engines::NullEngine::default());
|
let engine = Arc::new(::engines::NullEngine::default());
|
||||||
let orig_path = RandomTempPath::create_dir();
|
|
||||||
let new_path = RandomTempPath::create_dir();
|
let new_path = RandomTempPath::create_dir();
|
||||||
let mut snapshot_path = new_path.as_path().to_owned();
|
let mut snapshot_path = new_path.as_path().to_owned();
|
||||||
snapshot_path.push("SNAP");
|
snapshot_path.push("SNAP");
|
||||||
|
|
||||||
let old_db = Arc::new(Database::open(&db_cfg, orig_path.as_str()).unwrap());
|
let old_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let bc = BlockChain::new(Default::default(), &genesis, old_db.clone());
|
let bc = BlockChain::new(Default::default(), &genesis, old_db.clone());
|
||||||
|
|
||||||
// build the blockchain.
|
// build the blockchain.
|
||||||
let mut batch = old_db.transaction();
|
let mut batch = DBTransaction::new();
|
||||||
for _ in 0..amount {
|
for _ in 0..amount {
|
||||||
let block = canon_chain.generate(&mut finalizer).unwrap();
|
let block = canon_chain.generate(&mut finalizer).unwrap();
|
||||||
bc.insert_block(&mut batch, &block, vec![]);
|
bc.insert_block(&mut batch, &block, vec![]);
|
||||||
@ -56,12 +54,18 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
|
|
||||||
old_db.write(batch).unwrap();
|
old_db.write(batch).unwrap();
|
||||||
|
|
||||||
|
|
||||||
let best_hash = bc.best_block_hash();
|
let best_hash = bc.best_block_hash();
|
||||||
|
|
||||||
// snapshot it.
|
// snapshot it.
|
||||||
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
|
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
|
||||||
let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap();
|
let block_hashes = chunk_secondary(
|
||||||
|
Box::new(::snapshot::PowSnapshot),
|
||||||
|
&bc,
|
||||||
|
best_hash,
|
||||||
|
&writer,
|
||||||
|
&Progress::default()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
let manifest = ::snapshot::ManifestData {
|
let manifest = ::snapshot::ManifestData {
|
||||||
version: 2,
|
version: 2,
|
||||||
state_hashes: Vec::new(),
|
state_hashes: Vec::new(),
|
||||||
@ -74,9 +78,10 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
writer.into_inner().finish(manifest.clone()).unwrap();
|
writer.into_inner().finish(manifest.clone()).unwrap();
|
||||||
|
|
||||||
// restore it.
|
// restore it.
|
||||||
let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap());
|
let new_db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
|
let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone());
|
||||||
let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap();
|
let mut rebuilder = components.rebuilder(new_chain, new_db.clone(), &manifest).unwrap();
|
||||||
|
|
||||||
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
|
let reader = PackedReader::new(&snapshot_path).unwrap().unwrap();
|
||||||
let flag = AtomicBool::new(true);
|
let flag = AtomicBool::new(true);
|
||||||
for chunk_hash in &reader.manifest().block_hashes {
|
for chunk_hash in &reader.manifest().block_hashes {
|
||||||
@ -85,7 +90,8 @@ fn chunk_and_restore(amount: u64) {
|
|||||||
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
|
rebuilder.feed(&chunk, engine.as_ref(), &flag).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuilder.finalize(HashMap::new()).unwrap();
|
rebuilder.finalize().unwrap();
|
||||||
|
drop(rebuilder);
|
||||||
|
|
||||||
// and test it.
|
// and test it.
|
||||||
let new_chain = BlockChain::new(Default::default(), &genesis, new_db);
|
let new_chain = BlockChain::new(Default::default(), &genesis, new_db);
|
||||||
@ -118,10 +124,8 @@ fn checks_flag() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let chunk = stream.out();
|
let chunk = stream.out();
|
||||||
let path = RandomTempPath::create_dir();
|
|
||||||
|
|
||||||
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
let db = Arc::new(kvdb::in_memory(::db::NUM_COLUMNS.unwrap_or(0)));
|
||||||
let db = Arc::new(Database::open(&db_cfg, path.as_str()).unwrap());
|
|
||||||
let engine = Arc::new(::engines::NullEngine::default());
|
let engine = Arc::new(::engines::NullEngine::default());
|
||||||
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
|
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
|
||||||
|
|
||||||
@ -134,7 +138,7 @@ fn checks_flag() {
|
|||||||
block_hash: H256::default(),
|
block_hash: H256::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut rebuilder = BlockRebuilder::new(chain, db.clone(), &manifest).unwrap();
|
let mut rebuilder = ::snapshot::PowSnapshot.rebuilder(chain, db.clone(), &manifest).unwrap();
|
||||||
|
|
||||||
match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) {
|
match rebuilder.feed(&chunk, engine.as_ref(), &AtomicBool::new(false)) {
|
||||||
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}
|
Err(Error::Snapshot(SnapshotError::RestorationAborted)) => {}
|
||||||
|
@ -47,5 +47,4 @@ impl SnapshotService for TestSnapshotService {
|
|||||||
fn abort_restore(&self) { }
|
fn abort_restore(&self) { }
|
||||||
fn restore_state_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
fn restore_state_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||||
fn restore_block_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
fn restore_block_chunk(&self, _hash: H256, _chunk: Bytes) { }
|
||||||
fn provide_canon_hashes(&self, _hashes: &[(u64, H256)]) { }
|
|
||||||
}
|
}
|
@ -24,7 +24,6 @@ use SyncConfig;
|
|||||||
pub struct TestSnapshotService {
|
pub struct TestSnapshotService {
|
||||||
manifest: Option<ManifestData>,
|
manifest: Option<ManifestData>,
|
||||||
chunks: HashMap<H256, Bytes>,
|
chunks: HashMap<H256, Bytes>,
|
||||||
canon_hashes: Mutex<HashMap<u64, H256>>,
|
|
||||||
|
|
||||||
restoration_manifest: Mutex<Option<ManifestData>>,
|
restoration_manifest: Mutex<Option<ManifestData>>,
|
||||||
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
state_restoration_chunks: Mutex<HashMap<H256, Bytes>>,
|
||||||
@ -36,7 +35,6 @@ impl TestSnapshotService {
|
|||||||
TestSnapshotService {
|
TestSnapshotService {
|
||||||
manifest: None,
|
manifest: None,
|
||||||
chunks: HashMap::new(),
|
chunks: HashMap::new(),
|
||||||
canon_hashes: Mutex::new(HashMap::new()),
|
|
||||||
restoration_manifest: Mutex::new(None),
|
restoration_manifest: Mutex::new(None),
|
||||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
@ -61,7 +59,6 @@ impl TestSnapshotService {
|
|||||||
TestSnapshotService {
|
TestSnapshotService {
|
||||||
manifest: Some(manifest),
|
manifest: Some(manifest),
|
||||||
chunks: chunks,
|
chunks: chunks,
|
||||||
canon_hashes: Mutex::new(HashMap::new()),
|
|
||||||
restoration_manifest: Mutex::new(None),
|
restoration_manifest: Mutex::new(None),
|
||||||
state_restoration_chunks: Mutex::new(HashMap::new()),
|
state_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
block_restoration_chunks: Mutex::new(HashMap::new()),
|
block_restoration_chunks: Mutex::new(HashMap::new()),
|
||||||
@ -115,10 +112,6 @@ impl SnapshotService for TestSnapshotService {
|
|||||||
self.block_restoration_chunks.lock().insert(hash, chunk);
|
self.block_restoration_chunks.lock().insert(hash, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn provide_canon_hashes(&self, hashes: &[(u64, H256)]) {
|
|
||||||
self.canon_hashes.lock().extend(hashes.iter().cloned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
Loading…
Reference in New Issue
Block a user