Merge branch 'master' into sync-svc

This commit is contained in:
NikVolf
2016-07-18 15:20:57 +02:00
26 changed files with 380 additions and 258 deletions

View File

@@ -35,6 +35,8 @@ pub struct Account {
code_hash: Option<H256>,
// Code cache of the account.
code_cache: Bytes,
// Account is new or has been modified
dirty: bool,
}
impl Account {
@@ -47,7 +49,8 @@ impl Account {
storage_root: SHA3_NULL_RLP,
storage_overlay: RefCell::new(storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
code_hash: Some(code.sha3()),
code_cache: code
code_cache: code,
dirty: true,
}
}
@@ -59,7 +62,8 @@ impl Account {
storage_root: SHA3_NULL_RLP,
storage_overlay: RefCell::new(pod.storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
code_hash: Some(pod.code.sha3()),
code_cache: pod.code
code_cache: pod.code,
dirty: true,
}
}
@@ -72,6 +76,7 @@ impl Account {
storage_overlay: RefCell::new(HashMap::new()),
code_hash: Some(SHA3_EMPTY),
code_cache: vec![],
dirty: true,
}
}
@@ -85,6 +90,7 @@ impl Account {
storage_overlay: RefCell::new(HashMap::new()),
code_hash: Some(r.val_at(3)),
code_cache: vec![],
dirty: false,
}
}
@@ -98,6 +104,7 @@ impl Account {
storage_overlay: RefCell::new(HashMap::new()),
code_hash: None,
code_cache: vec![],
dirty: true,
}
}
@@ -106,6 +113,7 @@ impl Account {
pub fn init_code(&mut self, code: Bytes) {
assert!(self.code_hash.is_none());
self.code_cache = code;
self.dirty = true;
}
/// Reset this account's code to the given code.
@@ -117,6 +125,7 @@ impl Account {
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
pub fn set_storage(&mut self, key: H256, value: H256) {
self.storage_overlay.borrow_mut().insert(key, (Filth::Dirty, value));
self.dirty = true;
}
/// Get (and cache) the contents of the trie's storage at `key`.
@@ -172,6 +181,10 @@ impl Account {
!self.code_cache.is_empty() || (self.code_cache.is_empty() && self.code_hash == Some(SHA3_EMPTY))
}
/// Is this a new or modified account?
pub fn is_dirty(&self) -> bool {
self.dirty
}
/// Provide a database to get `code_hash`. Should not be called if it is a contract without code.
pub fn cache_code(&mut self, db: &AccountDB) -> bool {
// TODO: fill out self.code_cache;
@@ -201,16 +214,23 @@ impl Account {
pub fn storage_overlay(&self) -> Ref<HashMap<H256, (Filth, H256)>> { self.storage_overlay.borrow() }
/// Increment the nonce of the account by one.
pub fn inc_nonce(&mut self) { self.nonce = self.nonce + U256::from(1u8); }
pub fn inc_nonce(&mut self) {
self.nonce = self.nonce + U256::from(1u8);
self.dirty = true;
}
/// Increment the nonce of the account by one.
pub fn add_balance(&mut self, x: &U256) { self.balance = self.balance + *x; }
pub fn add_balance(&mut self, x: &U256) {
self.balance = self.balance + *x;
self.dirty = true;
}
/// Increment the nonce of the account by one.
/// Panics if balance is less than `x`
pub fn sub_balance(&mut self, x: &U256) {
assert!(self.balance >= *x);
self.balance = self.balance - *x;
self.dirty = true;
}
/// Commit the `storage_overlay` to the backing DB and update `storage_root`.

View File

@@ -296,32 +296,20 @@ impl BlockChain {
// load best block
let best_block_hash = match bc.extras_db.get(b"best").unwrap() {
Some(best) => {
let best = H256::from_slice(&best);
let mut b = best.clone();
let mut removed = 0;
let mut best_num = 0;
while !bc.blocks_db.get(&b).unwrap().is_some() {
// track back to the best block we have in the blocks database
let extras: BlockDetails = bc.extras_db.read(&b).unwrap();
type DetailsKey = Key<BlockDetails, Target=H264>;
bc.extras_db.delete(&(DetailsKey::key(&b))).unwrap();
b = extras.parent;
best_num = extras.number;
removed += 1;
}
if b != best {
let batch = DBTransaction::new();
let range = (best_num + 1) as bc::Number .. (best_num + removed) as bc::Number;
let chain = bc::group::BloomGroupChain::new(bc.blooms_config, &bc);
let changes = chain.replace(&range, vec![]);
for (k, v) in changes.into_iter() {
batch.write(&LogGroupPosition::from(k), &BloomGroup::from(v));
let mut new_best = H256::from_slice(&best);
while !bc.blocks_db.get(&new_best).unwrap().is_some() {
match bc.rewind() {
Some(h) => {
new_best = h;
}
None => {
warn!("Can't rewind blockchain");
break;
}
}
batch.put(b"best", &b).unwrap();
bc.extras_db.write(batch).unwrap();
info!("Restored mismatched best block. Was: {}, new: {}", best.hex(), b.hex());
info!("Restored mismatched best block. Was: {}, new: {}", H256::from_slice(&best).hex(), new_best.hex());
}
b
new_best
}
None => {
// best block does not exist
@@ -359,6 +347,52 @@ impl BlockChain {
bc
}
/// Returns true if the given parent block has given child
/// (though not necessarily a part of the canon chain).
fn is_known_child(&self, parent: &H256, hash: &H256) -> bool {
self.extras_db.read_with_cache(&self.block_details, parent).map_or(false, |d| d.children.contains(hash))
}
/// Rewind to a previous block
pub fn rewind(&self) -> Option<H256> {
let batch = DBTransaction::new();
// track back to the best block we have in the blocks database
if let Some(best_block_hash) = self.extras_db.get(b"best").unwrap() {
let best_block_hash = H256::from_slice(&best_block_hash);
if best_block_hash == self.genesis_hash() {
return None;
}
if let Some(extras) = self.extras_db.read(&best_block_hash) as Option<BlockDetails> {
type DetailsKey = Key<BlockDetails, Target=H264>;
batch.delete(&(DetailsKey::key(&best_block_hash))).unwrap();
let hash = extras.parent;
let range = extras.number as bc::Number .. extras.number as bc::Number;
let chain = bc::group::BloomGroupChain::new(self.blooms_config, self);
let changes = chain.replace(&range, vec![]);
for (k, v) in changes.into_iter() {
batch.write(&LogGroupPosition::from(k), &BloomGroup::from(v));
}
batch.put(b"best", &hash).unwrap();
let mut best_block = self.best_block.write();
best_block.number = extras.number - 1;
best_block.total_difficulty = self.block_details(&hash).unwrap().total_difficulty;
best_block.hash = hash;
// update parent extras
if let Some(mut details) = self.extras_db.read(&hash) as Option<BlockDetails> {
details.children.clear();
batch.write(&hash, &details);
}
self.extras_db.write(batch).unwrap();
self.block_details.write().clear();
self.block_hashes.write().clear();
self.blocks.write().clear();
self.block_receipts.write().clear();
return Some(hash);
}
}
return None;
}
/// Set the cache configuration.
pub fn configure_cache(&self, pref_cache_size: usize, max_cache_size: usize) {
self.pref_cache_size.store(pref_cache_size, AtomicOrder::Relaxed);
@@ -463,7 +497,7 @@ impl BlockChain {
let header = block.header_view();
let hash = header.sha3();
if self.is_known(&hash) {
if self.is_known_child(&header.parent_hash(), &hash) {
return ImportRoute::none();
}
@@ -508,14 +542,15 @@ impl BlockChain {
batch.extend_with_cache(&mut *write_blocks_blooms, update.blocks_blooms, CacheUpdatePolicy::Remove);
}
// These cached values must be updated last and togeterh
// These cached values must be updated last with all three locks taken to avoid
// cache decoherence
{
let mut best_block = self.best_block.write();
// update best block
match update.info.location {
BlockLocation::Branch => (),
_ => {
batch.put(b"best", &update.info.hash).unwrap();
let mut best_block = self.best_block.write();
*best_block = BestBlock {
hash: update.info.hash,
number: update.info.number,
@@ -1216,4 +1251,30 @@ mod tests {
let bc = BlockChain::new(Config::default(), &genesis, temp.as_path());
assert_eq!(bc.best_block_number(), 5);
}
#[test]
fn test_rewind() {
let mut canon_chain = ChainGenerator::default();
let mut finalizer = BlockFinalizer::default();
let genesis = canon_chain.generate(&mut finalizer).unwrap();
let first = canon_chain.generate(&mut finalizer).unwrap();
let second = canon_chain.generate(&mut finalizer).unwrap();
let genesis_hash = BlockView::new(&genesis).header_view().sha3();
let first_hash = BlockView::new(&first).header_view().sha3();
let second_hash = BlockView::new(&second).header_view().sha3();
let temp = RandomTempPath::new();
let bc = BlockChain::new(Config::default(), &genesis, temp.as_path());
bc.insert_block(&first, vec![]);
bc.insert_block(&second, vec![]);
assert_eq!(bc.rewind(), Some(first_hash.clone()));
assert!(!bc.is_known(&second_hash));
assert_eq!(bc.best_block_number(), 1);
assert_eq!(bc.best_block_hash(), first_hash.clone());
assert_eq!(bc.rewind(), Some(genesis_hash.clone()));
assert_eq!(bc.rewind(), None);
}
}

View File

@@ -22,21 +22,18 @@ use std::sync::{Arc, Weak};
use std::path::{Path, PathBuf};
use std::fmt;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering as AtomicOrdering};
use std::time::Instant;
use std::time::{Instant, Duration};
use time::precise_time_ns;
// util
use util::{journaldb, rlp, Bytes, Stream, View, PerfTimer, Itertools, Mutex, RwLock, Colour};
use util::journaldb::JournalDB;
use util::rlp::{RlpStream, Rlp, UntrustedRlp};
use util::numbers::*;
use util::panics::*;
use util::io::*;
use util::rlp;
use util::sha3::*;
use util::Bytes;
use util::rlp::{RlpStream, Rlp, UntrustedRlp};
use util::journaldb;
use util::journaldb::JournalDB;
use util::kvdb::*;
use util::{Stream, View, PerfTimer, Itertools};
use util::{Mutex, RwLock};
// other
use views::BlockView;
@@ -145,6 +142,9 @@ pub struct Client {
notify: RwLock<Option<Weak<ChainNotify>>>,
queue_transactions: AtomicUsize,
previous_enode: Mutex<Option<String>>,
skipped: AtomicUsize,
last_import: Mutex<Instant>,
last_hashes: RwLock<VecDeque<H256>>,
}
const HISTORY: u64 = 1200;
@@ -205,6 +205,11 @@ impl Client {
state_db.commit(0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB");
}
while !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.contains(h.state_root())) {
warn!("State root not found for block #{} ({}), recovering...", chain.best_block_number(), chain.best_block_hash().hex());
chain.rewind();
}
let engine = Arc::new(spec.engine);
let block_queue = BlockQueue::new(config.queue, engine.clone(), message_channel.clone());
@@ -232,6 +237,9 @@ impl Client {
notify: RwLock::new(None),
queue_transactions: AtomicUsize::new(0),
previous_enode: Mutex::new(None),
skipped: AtomicUsize::new(0),
last_import: Mutex::new(Instant::now()),
last_hashes: RwLock::new(VecDeque::new()),
};
Ok(Arc::new(client))
}
@@ -253,6 +261,14 @@ impl Client {
}
fn build_last_hashes(&self, parent_hash: H256) -> LastHashes {
{
let hashes = self.last_hashes.read();
if hashes.front().map_or(false, |h| h == &parent_hash) {
let mut res = Vec::from(hashes.clone());
res.resize(256, H256::default());
return res;
}
}
let mut last_hashes = LastHashes::new();
last_hashes.resize(256, H256::new());
last_hashes[0] = parent_hash;
@@ -264,6 +280,8 @@ impl Client {
None => break,
}
}
let mut cached_hashes = self.last_hashes.write();
*cached_hashes = VecDeque::from(last_hashes.clone());
last_hashes
}
@@ -355,16 +373,21 @@ impl Client {
for block in blocks {
let header = &block.header;
let start = precise_time_ns();
if invalid_blocks.contains(&header.parent_hash) {
invalid_blocks.insert(header.hash());
continue;
}
let tx_count = block.transactions.len();
let size = block.bytes.len();
let closed_block = self.check_and_close_block(&block);
if let Err(_) = closed_block {
invalid_blocks.insert(header.hash());
continue;
}
let closed_block = closed_block.unwrap();
imported_blocks.push(header.hash());
@@ -372,7 +395,30 @@ impl Client {
import_results.push(route);
self.report.write().accrue_block(&block);
trace!(target: "client", "Imported #{} ({})", header.number(), header.hash());
let duration_ns = precise_time_ns() - start;
let mut last_import = self.last_import.lock();
if Instant::now() > *last_import + Duration::from_secs(1) {
let queue_info = self.queue_info();
let importing = queue_info.unverified_queue_size + queue_info.verified_queue_size > 3;
if !importing {
let skipped = self.skipped.load(AtomicOrdering::Relaxed);
info!(target: "import", "Imported {} {} ({} txs, {} Mgas, {} ms, {} KiB){}",
Colour::White.bold().paint(format!("#{}", header.number())),
Colour::White.bold().paint(format!("{}", header.hash())),
Colour::Yellow.bold().paint(format!("{}", tx_count)),
Colour::Yellow.bold().paint(format!("{:.2}", header.gas_used.low_u64() as f32 / 1000000f32)),
Colour::Purple.bold().paint(format!("{:.2}", duration_ns as f32 / 1000000f32)),
Colour::Blue.bold().paint(format!("{:.2}", size as f32 / 1024f32)),
if skipped > 0 { format!(" + another {} block(s)", Colour::Red.bold().paint(format!("{}", skipped))) } else { String::new() }
);
*last_import = Instant::now();
}
self.skipped.store(0, AtomicOrdering::Relaxed);
} else {
self.skipped.fetch_add(1, AtomicOrdering::Relaxed);
}
}
let imported = imported_blocks.len();
@@ -418,6 +464,7 @@ impl Client {
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();
// Are we committing an era?
let ancient = if number >= HISTORY {
let n = number - HISTORY;
@@ -445,9 +492,20 @@ impl Client {
enacted: route.enacted.clone(),
retracted: route.retracted.len()
});
self.update_last_hashes(&parent, hash);
route
}
fn update_last_hashes(&self, parent: &H256, hash: &H256) {
let mut hashes = self.last_hashes.write();
if hashes.front().map_or(false, |h| h == parent) {
if hashes.len() > 255 {
hashes.pop_back();
}
hashes.push_front(hash.clone());
}
}
/// Import transactions from the IO queue
pub fn import_queued_transactions(&self, transactions: &[Bytes]) -> usize {
let _timer = PerfTimer::new("import_queued_transactions");

View File

@@ -59,8 +59,6 @@ pub enum TransactionError {
},
/// Transaction's gas limit (aka gas) is invalid.
InvalidGasLimit(OutOfBounds<U256>),
/// Transaction is invalid for some other reason.
DAORescue,
}
impl fmt::Display for TransactionError {
@@ -79,7 +77,6 @@ impl fmt::Display for TransactionError {
GasLimitExceeded { limit, got } =>
format!("Gas limit exceeded. Limit={}, Given={}", limit, got),
InvalidGasLimit(ref err) => format!("Invalid gas limit. {}", err),
DAORescue => "Transaction is invalid due to the DAO rescue.".into(),
};
f.write_fmt(format_args!("Transaction error ({})", msg))

View File

@@ -80,8 +80,6 @@ pub struct Schedule {
pub tx_data_non_zero_gas: usize,
/// Gas price for copying memory
pub copy_gas: usize,
/// DAO Rescue softfork block
pub reject_dao_transactions: bool,
}
impl Schedule {
@@ -128,7 +126,6 @@ impl Schedule {
tx_data_zero_gas: 4,
tx_data_non_zero_gas: 68,
copy_gas: 3,
reject_dao_transactions: false,
}
}
}

View File

@@ -232,36 +232,10 @@ impl State {
let options = TransactOptions { tracing: tracing, vm_tracing: false, check_nonce: true };
let e = try!(Executive::new(self, env_info, engine, vm_factory).transact(t, options));
let broken_dao = H256::from("6a5d24750f78441e56fec050dc52fe8e911976485b7472faac7464a176a67caa");
// dao attack soft fork
if engine.schedule(&env_info).reject_dao_transactions {
let whitelisted = if let Action::Call(to) = t.action {
to == Address::from("Da4a4626d3E16e094De3225A751aAb7128e96526") ||
to == Address::from("2ba9D006C1D72E67A70b5526Fc6b4b0C0fd6D334")
} else { false };
if !whitelisted {
// collect all the addresses which have changed.
let addresses = self.cache.borrow().iter().map(|(addr, _)| addr.clone()).collect::<Vec<_>>();
for a in &addresses {
if self.code(a).map_or(false, |c| c.sha3() == broken_dao) {
// Figure out if the balance has been reduced.
let maybe_original = self.trie_factory
.readonly(self.db.as_hashdb(), &self.root)
.expect(SEC_TRIE_DB_UNWRAP_STR)
.get(&a).map(Account::from_rlp);
if maybe_original.map_or(false, |original| *original.balance() > self.balance(a)) {
return Err(Error::Transaction(TransactionError::DAORescue));
}
}
}
}
}
// TODO uncomment once to_pod() works correctly.
// trace!("Applied transaction. Diff:\n{}\n", state_diff::diff_pod(&old, &self.to_pod()));
self.commit();
self.clear();
let receipt = Receipt::new(self.root().clone(), e.cumulative_gas_used, e.logs);
// trace!("Transaction receipt: {:?}", receipt);
Ok(ApplyOutcome{receipt: receipt, trace: e.trace})
@@ -275,12 +249,12 @@ impl State {
// TODO: is this necessary or can we dispense with the `ref mut a` for just `a`?
for (address, ref mut a) in accounts.iter_mut() {
match a {
&mut&mut Some(ref mut account) => {
&mut&mut Some(ref mut account) if account.is_dirty() => {
let mut account_db = AccountDBMut::new(db, address);
account.commit_storage(trie_factory, &mut account_db);
account.commit_code(&mut account_db);
}
&mut&mut None => {}
_ => {}
}
}
@@ -288,8 +262,9 @@ impl State {
let mut trie = trie_factory.from_existing(db, root).unwrap();
for (address, ref a) in accounts.iter() {
match **a {
Some(ref account) => trie.insert(address, &account.rlp()),
Some(ref account) if account.is_dirty() => trie.insert(address, &account.rlp()),
None => trie.remove(address),
_ => (),
}
}
}
@@ -301,6 +276,11 @@ impl State {
Self::commit_into(&self.trie_factory, self.db.as_hashdb_mut(), &mut self.root, self.cache.borrow_mut().deref_mut());
}
/// Clear state cache
pub fn clear(&mut self) {
self.cache.borrow_mut().clear();
}
#[cfg(test)]
#[cfg(feature = "json-tests")]
/// Populate the state from `accounts`.