|
|
|
|
@@ -30,7 +30,7 @@ use util::kvdb::*;
|
|
|
|
|
|
|
|
|
|
// other
|
|
|
|
|
use io::*;
|
|
|
|
|
use views::{HeaderView, BodyView};
|
|
|
|
|
use views::{HeaderView, BodyView, BlockView};
|
|
|
|
|
use error::{ImportError, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
|
|
|
|
|
use header::BlockNumber;
|
|
|
|
|
use state::State;
|
|
|
|
|
@@ -46,7 +46,7 @@ use transaction::{LocalizedTransaction, SignedTransaction, Action};
|
|
|
|
|
use blockchain::extras::TransactionAddress;
|
|
|
|
|
use types::filter::Filter;
|
|
|
|
|
use log_entry::LocalizedLogEntry;
|
|
|
|
|
use verification::queue::{BlockQueue, QueueInfo as BlockQueueInfo};
|
|
|
|
|
use verification::queue::BlockQueue;
|
|
|
|
|
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
|
|
|
|
|
use client::{
|
|
|
|
|
BlockID, TransactionID, UncleID, TraceId, ClientConfig, BlockChainClient,
|
|
|
|
|
@@ -71,9 +71,11 @@ use state_db::StateDB;
|
|
|
|
|
pub use types::blockchain_info::BlockChainInfo;
|
|
|
|
|
pub use types::block_status::BlockStatus;
|
|
|
|
|
pub use blockchain::CacheSize as BlockChainCacheSize;
|
|
|
|
|
pub use verification::queue::QueueInfo as BlockQueueInfo;
|
|
|
|
|
|
|
|
|
|
const MAX_TX_QUEUE_SIZE: usize = 4096;
|
|
|
|
|
const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2;
|
|
|
|
|
const MIN_HISTORY_SIZE: u64 = 8;
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for BlockChainInfo {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
@@ -141,12 +143,9 @@ pub struct Client {
|
|
|
|
|
queue_transactions: AtomicUsize,
|
|
|
|
|
last_hashes: RwLock<VecDeque<H256>>,
|
|
|
|
|
factories: Factories,
|
|
|
|
|
history: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The pruning constant -- how old blocks must be before we
|
|
|
|
|
/// assume finality of a given candidate.
|
|
|
|
|
pub const HISTORY: u64 = 1200;
|
|
|
|
|
|
|
|
|
|
impl Client {
|
|
|
|
|
/// Create a new client with given spec and DB path and custom verifier.
|
|
|
|
|
pub fn new(
|
|
|
|
|
@@ -170,13 +169,35 @@ impl Client {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let journal_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE);
|
|
|
|
|
let mut state_db = StateDB::new(journal_db);
|
|
|
|
|
let mut state_db = StateDB::new(journal_db, config.state_cache_size);
|
|
|
|
|
if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db)) {
|
|
|
|
|
let mut batch = DBTransaction::new(&db);
|
|
|
|
|
try!(state_db.commit(&mut batch, 0, &spec.genesis_header().hash(), None));
|
|
|
|
|
try!(state_db.journal_under(&mut batch, 0, &spec.genesis_header().hash()));
|
|
|
|
|
try!(db.write(batch).map_err(ClientError::Database));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
trace!("Cleanup journal: DB Earliest = {:?}, Latest = {:?}", state_db.journal_db().earliest_era(), state_db.journal_db().latest_era());
|
|
|
|
|
|
|
|
|
|
let history = if config.history < MIN_HISTORY_SIZE {
|
|
|
|
|
info!(target: "client", "Ignoring pruning history parameter of {}\
|
|
|
|
|
, falling back to minimum of {}",
|
|
|
|
|
config.history, MIN_HISTORY_SIZE);
|
|
|
|
|
MIN_HISTORY_SIZE
|
|
|
|
|
} else {
|
|
|
|
|
config.history
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let (Some(earliest), Some(latest)) = (state_db.journal_db().earliest_era(), state_db.journal_db().latest_era()) {
|
|
|
|
|
if latest > earliest && latest - earliest > history {
|
|
|
|
|
for era in earliest..(latest - history + 1) {
|
|
|
|
|
trace!("Removing era {}", era);
|
|
|
|
|
let mut batch = DBTransaction::new(&db);
|
|
|
|
|
try!(state_db.mark_canonical(&mut batch, era, &chain.block_hash(era).expect("Old block not found in the database")));
|
|
|
|
|
try!(db.write(batch).map_err(ClientError::Database));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.journal_db().contains(h.state_root())) {
|
|
|
|
|
warn!("State root not found for block #{} ({})", chain.best_block_number(), chain.best_block_hash().hex());
|
|
|
|
|
}
|
|
|
|
|
@@ -190,7 +211,7 @@ impl Client {
|
|
|
|
|
let awake = match config.mode { Mode::Dark(..) => false, _ => true };
|
|
|
|
|
|
|
|
|
|
let factories = Factories {
|
|
|
|
|
vm: EvmFactory::new(config.vm_type.clone()),
|
|
|
|
|
vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size),
|
|
|
|
|
trie: TrieFactory::new(trie_spec),
|
|
|
|
|
accountdb: Default::default(),
|
|
|
|
|
};
|
|
|
|
|
@@ -217,6 +238,7 @@ impl Client {
|
|
|
|
|
queue_transactions: AtomicUsize::new(0),
|
|
|
|
|
last_hashes: RwLock::new(VecDeque::new()),
|
|
|
|
|
factories: factories,
|
|
|
|
|
history: history,
|
|
|
|
|
};
|
|
|
|
|
Ok(Arc::new(client))
|
|
|
|
|
}
|
|
|
|
|
@@ -275,7 +297,7 @@ impl Client {
|
|
|
|
|
let chain = self.chain.read();
|
|
|
|
|
// Check the block isn't so old we won't be able to enact it.
|
|
|
|
|
let best_block_number = chain.best_block_number();
|
|
|
|
|
if best_block_number >= HISTORY && header.number() <= best_block_number - HISTORY {
|
|
|
|
|
if best_block_number >= self.history && header.number() <= best_block_number - self.history {
|
|
|
|
|
warn!(target: "client", "Block import failed for #{} ({})\nBlock is ancient (current best block: #{}).", header.number(), header.hash(), best_block_number);
|
|
|
|
|
return Err(());
|
|
|
|
|
}
|
|
|
|
|
@@ -340,16 +362,19 @@ impl Client {
|
|
|
|
|
|
|
|
|
|
/// This is triggered by a message coming from a block queue when the block is ready for insertion
|
|
|
|
|
pub fn import_verified_blocks(&self) -> usize {
|
|
|
|
|
let max_blocks_to_import = 64;
|
|
|
|
|
let (imported_blocks, import_results, invalid_blocks, imported, duration) = {
|
|
|
|
|
let max_blocks_to_import = 4;
|
|
|
|
|
let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = {
|
|
|
|
|
let mut imported_blocks = Vec::with_capacity(max_blocks_to_import);
|
|
|
|
|
let mut invalid_blocks = HashSet::new();
|
|
|
|
|
let mut import_results = Vec::with_capacity(max_blocks_to_import);
|
|
|
|
|
|
|
|
|
|
let _import_lock = self.import_lock.lock();
|
|
|
|
|
let blocks = self.block_queue.drain(max_blocks_to_import);
|
|
|
|
|
if blocks.is_empty() {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
let _timer = PerfTimer::new("import_verified_blocks");
|
|
|
|
|
let start = precise_time_ns();
|
|
|
|
|
let blocks = self.block_queue.drain(max_blocks_to_import);
|
|
|
|
|
|
|
|
|
|
for block in blocks {
|
|
|
|
|
let header = &block.header;
|
|
|
|
|
@@ -373,23 +398,19 @@ impl Client {
|
|
|
|
|
let imported = imported_blocks.len();
|
|
|
|
|
let invalid_blocks = invalid_blocks.into_iter().collect::<Vec<H256>>();
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
if !invalid_blocks.is_empty() {
|
|
|
|
|
self.block_queue.mark_as_bad(&invalid_blocks);
|
|
|
|
|
}
|
|
|
|
|
if !imported_blocks.is_empty() {
|
|
|
|
|
self.block_queue.mark_as_good(&imported_blocks);
|
|
|
|
|
}
|
|
|
|
|
if !invalid_blocks.is_empty() {
|
|
|
|
|
self.block_queue.mark_as_bad(&invalid_blocks);
|
|
|
|
|
}
|
|
|
|
|
let is_empty = self.block_queue.mark_as_good(&imported_blocks);
|
|
|
|
|
let duration_ns = precise_time_ns() - start;
|
|
|
|
|
(imported_blocks, import_results, invalid_blocks, imported, duration_ns)
|
|
|
|
|
(imported_blocks, import_results, invalid_blocks, imported, duration_ns, is_empty)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
if !imported_blocks.is_empty() && self.block_queue.queue_info().is_empty() {
|
|
|
|
|
if !imported_blocks.is_empty() && is_empty {
|
|
|
|
|
let (enacted, retracted) = self.calculate_enacted_retracted(&import_results);
|
|
|
|
|
|
|
|
|
|
if self.queue_info().is_empty() {
|
|
|
|
|
if is_empty {
|
|
|
|
|
self.miner.chain_new_blocks(self, &imported_blocks, &invalid_blocks, &enacted, &retracted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -410,17 +431,33 @@ impl Client {
|
|
|
|
|
imported
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Import a block with transaction receipts.
|
|
|
|
|
/// The block is guaranteed to be the next best blocks in the first block sequence.
|
|
|
|
|
/// Does no sealing or transaction validation.
|
|
|
|
|
fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> H256 {
|
|
|
|
|
let block = BlockView::new(&block_bytes);
|
|
|
|
|
let hash = block.header().hash();
|
|
|
|
|
let _import_lock = self.import_lock.lock();
|
|
|
|
|
{
|
|
|
|
|
let _timer = PerfTimer::new("import_old_block");
|
|
|
|
|
let chain = self.chain.read();
|
|
|
|
|
|
|
|
|
|
// Commit results
|
|
|
|
|
let receipts = ::rlp::decode(&receipts_bytes);
|
|
|
|
|
let mut batch = DBTransaction::new(&self.db.read());
|
|
|
|
|
chain.insert_unordered_block(&mut batch, &block_bytes, receipts, None, false, true);
|
|
|
|
|
// Final commit to the DB
|
|
|
|
|
self.db.read().write_buffered(batch);
|
|
|
|
|
chain.commit();
|
|
|
|
|
}
|
|
|
|
|
self.db.read().flush().expect("DB flush failed.");
|
|
|
|
|
hash
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn commit_block<B>(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain {
|
|
|
|
|
let number = block.header().number();
|
|
|
|
|
let parent = block.header().parent_hash().clone();
|
|
|
|
|
let chain = self.chain.read();
|
|
|
|
|
// Are we committing an era?
|
|
|
|
|
let ancient = if number >= HISTORY {
|
|
|
|
|
let n = number - HISTORY;
|
|
|
|
|
Some((n, chain.block_hash(n).expect("only verified blocks can be commited; verified block has hash; qed")))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Commit results
|
|
|
|
|
let receipts = block.receipts().to_owned();
|
|
|
|
|
@@ -436,7 +473,17 @@ impl Client {
|
|
|
|
|
// already-imported block of the same number.
|
|
|
|
|
// TODO: Prove it with a test.
|
|
|
|
|
let mut state = block.drain();
|
|
|
|
|
state.commit(&mut batch, number, hash, ancient).expect("DB commit failed.");
|
|
|
|
|
|
|
|
|
|
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
|
|
|
|
|
|
|
|
|
|
if number >= self.history {
|
|
|
|
|
let n = number - self.history;
|
|
|
|
|
if let Some(ancient_hash) = chain.block_hash(n) {
|
|
|
|
|
state.mark_canonical(&mut batch, n, &ancient_hash).expect("DB commit failed");
|
|
|
|
|
} else {
|
|
|
|
|
debug!(target: "client", "Missing expected hash for block {}", n);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let route = chain.insert_block(&mut batch, block_data, receipts);
|
|
|
|
|
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
|
|
|
|
@@ -446,6 +493,7 @@ impl Client {
|
|
|
|
|
enacted: route.enacted.clone(),
|
|
|
|
|
retracted: route.retracted.len()
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let is_canon = route.enacted.last().map_or(false, |h| h == hash);
|
|
|
|
|
state.sync_cache(&route.enacted, &route.retracted, is_canon);
|
|
|
|
|
// Final commit to the DB
|
|
|
|
|
@@ -501,7 +549,7 @@ impl Client {
|
|
|
|
|
let db = self.state_db.lock().boxed_clone();
|
|
|
|
|
|
|
|
|
|
// early exit for pruned blocks
|
|
|
|
|
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + HISTORY {
|
|
|
|
|
if db.is_pruned() && self.chain.read().best_block_number() >= block_number + self.history {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -606,20 +654,23 @@ impl Client {
|
|
|
|
|
let best_block_number = self.chain_info().best_block_number;
|
|
|
|
|
let block_number = try!(self.block_number(at).ok_or(snapshot::Error::InvalidStartingBlock(at)));
|
|
|
|
|
|
|
|
|
|
if best_block_number > HISTORY + block_number && db.is_pruned() {
|
|
|
|
|
if best_block_number > self.history + block_number && db.is_pruned() {
|
|
|
|
|
return Err(snapshot::Error::OldBlockPrunedDB.into());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let history = ::std::cmp::min(self.history, 1000);
|
|
|
|
|
|
|
|
|
|
let start_hash = match at {
|
|
|
|
|
BlockID::Latest => {
|
|
|
|
|
let start_num = if best_block_number > 1000 {
|
|
|
|
|
best_block_number - 1000
|
|
|
|
|
} else {
|
|
|
|
|
0
|
|
|
|
|
let start_num = match db.earliest_era() {
|
|
|
|
|
Some(era) => ::std::cmp::max(era, best_block_number - history),
|
|
|
|
|
None => best_block_number - history,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
self.block_hash(BlockID::Number(start_num))
|
|
|
|
|
.expect("blocks within HISTORY are always stored.")
|
|
|
|
|
match self.block_hash(BlockID::Number(start_num)) {
|
|
|
|
|
Some(h) => h,
|
|
|
|
|
None => return Err(snapshot::Error::InvalidStartingBlock(at).into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => match self.block_hash(at) {
|
|
|
|
|
Some(hash) => hash,
|
|
|
|
|
@@ -632,6 +683,11 @@ impl Client {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ask the client what the history parameter is.
|
|
|
|
|
pub fn pruning_history(&self) -> u64 {
|
|
|
|
|
self.history
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn block_hash(chain: &BlockChain, id: BlockID) -> Option<H256> {
|
|
|
|
|
match id {
|
|
|
|
|
BlockID::Hash(hash) => Some(hash),
|
|
|
|
|
@@ -688,7 +744,8 @@ impl snapshot::DatabaseRestore for Client {
|
|
|
|
|
let db = self.db.write();
|
|
|
|
|
try!(db.restore(new_db));
|
|
|
|
|
|
|
|
|
|
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE));
|
|
|
|
|
let cache_size = state_db.cache_size();
|
|
|
|
|
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE), cache_size);
|
|
|
|
|
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
|
|
|
|
|
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
|
|
|
|
|
Ok(())
|
|
|
|
|
@@ -974,6 +1031,20 @@ impl BlockChainClient for Client {
|
|
|
|
|
Ok(try!(self.block_queue.import(unverified)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn import_block_with_receipts(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result<H256, BlockImportError> {
|
|
|
|
|
{
|
|
|
|
|
// check block order
|
|
|
|
|
let header = BlockView::new(&block_bytes).header_view();
|
|
|
|
|
if self.chain.read().is_known(&header.hash()) {
|
|
|
|
|
return Err(BlockImportError::Import(ImportError::AlreadyInChain));
|
|
|
|
|
}
|
|
|
|
|
if self.block_status(BlockID::Hash(header.parent_hash())) == BlockStatus::Unknown {
|
|
|
|
|
return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash())));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(self.import_old_block(block_bytes, receipts_bytes))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn queue_info(&self) -> BlockQueueInfo {
|
|
|
|
|
self.block_queue.queue_info()
|
|
|
|
|
}
|
|
|
|
|
@@ -983,14 +1054,7 @@ impl BlockChainClient for Client {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn chain_info(&self) -> BlockChainInfo {
|
|
|
|
|
let chain = self.chain.read();
|
|
|
|
|
BlockChainInfo {
|
|
|
|
|
total_difficulty: chain.best_block_total_difficulty(),
|
|
|
|
|
pending_total_difficulty: chain.best_block_total_difficulty(),
|
|
|
|
|
genesis_hash: chain.genesis_hash(),
|
|
|
|
|
best_block_hash: chain.best_block_hash(),
|
|
|
|
|
best_block_number: From::from(chain.best_block_number())
|
|
|
|
|
}
|
|
|
|
|
self.chain.read().chain_info()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn additional_params(&self) -> BTreeMap<String, String> {
|
|
|
|
|
@@ -1120,21 +1184,22 @@ impl MiningBlockChainClient for Client {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn import_sealed_block(&self, block: SealedBlock) -> ImportResult {
|
|
|
|
|
let _import_lock = self.import_lock.lock();
|
|
|
|
|
let _timer = PerfTimer::new("import_sealed_block");
|
|
|
|
|
let start = precise_time_ns();
|
|
|
|
|
|
|
|
|
|
let h = block.header().hash();
|
|
|
|
|
let number = block.header().number();
|
|
|
|
|
|
|
|
|
|
let block_data = block.rlp_bytes();
|
|
|
|
|
let route = self.commit_block(block, &h, &block_data);
|
|
|
|
|
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
|
|
|
|
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
|
|
|
|
let start = precise_time_ns();
|
|
|
|
|
let route = {
|
|
|
|
|
// scope for self.import_lock
|
|
|
|
|
let _import_lock = self.import_lock.lock();
|
|
|
|
|
let _timer = PerfTimer::new("import_sealed_block");
|
|
|
|
|
|
|
|
|
|
let number = block.header().number();
|
|
|
|
|
let block_data = block.rlp_bytes();
|
|
|
|
|
let route = self.commit_block(block, &h, &block_data);
|
|
|
|
|
trace!(target: "client", "Imported sealed block #{} ({})", number, h);
|
|
|
|
|
self.state_db.lock().sync_cache(&route.enacted, &route.retracted, false);
|
|
|
|
|
route
|
|
|
|
|
};
|
|
|
|
|
let (enacted, retracted) = self.calculate_enacted_retracted(&[route]);
|
|
|
|
|
self.miner.chain_new_blocks(self, &[h.clone()], &[], &enacted, &retracted);
|
|
|
|
|
|
|
|
|
|
self.notify(|notify| {
|
|
|
|
|
notify.new_blocks(
|
|
|
|
|
vec![h.clone()],
|
|
|
|
|
|