Canonical state cache (master) (#2311)
* State cache * Reduced copying data between caches Whitespace and optional symbols * Reduced copying data between caches Whitespace and optional symbols * Set a limit on storage cache * Style and docs
This commit is contained in:
parent
9d4bee4922
commit
ad63780b4d
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -288,6 +288,7 @@ dependencies = [
|
|||||||
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
|
"hyper 0.9.4 (git+https://github.com/ethcore/hyper)",
|
||||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lru-cache 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -863,6 +864,11 @@ name = "libc"
|
|||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -873,6 +879,14 @@ name = "log"
|
|||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-cache"
|
||||||
|
version = "0.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matches"
|
name = "matches"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
|
@ -37,6 +37,7 @@ ethkey = { path = "../ethkey" }
|
|||||||
ethcore-ipc-nano = { path = "../ipc/nano" }
|
ethcore-ipc-nano = { path = "../ipc/nano" }
|
||||||
rlp = { path = "../util/rlp" }
|
rlp = { path = "../util/rlp" }
|
||||||
rand = "0.3"
|
rand = "0.3"
|
||||||
|
lru-cache = "0.0.7"
|
||||||
|
|
||||||
[dependencies.hyper]
|
[dependencies.hyper]
|
||||||
git = "https://github.com/ethcore/hyper"
|
git = "https://github.com/ethcore/hyper"
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
use common::*;
|
use common::*;
|
||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
use state::*;
|
use state::*;
|
||||||
|
use state_db::StateDB;
|
||||||
use verification::PreverifiedBlock;
|
use verification::PreverifiedBlock;
|
||||||
use trace::FlatTrace;
|
use trace::FlatTrace;
|
||||||
use factory::Factories;
|
use factory::Factories;
|
||||||
@ -179,7 +180,7 @@ pub trait IsBlock {
|
|||||||
/// Trait for a object that has a state database.
|
/// Trait for a object that has a state database.
|
||||||
pub trait Drain {
|
pub trait Drain {
|
||||||
/// Drop this object and return the underlieing database.
|
/// Drop this object and return the underlieing database.
|
||||||
fn drain(self) -> Box<JournalDB>;
|
fn drain(self) -> StateDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IsBlock for ExecutedBlock {
|
impl IsBlock for ExecutedBlock {
|
||||||
@ -231,7 +232,7 @@ impl<'x> OpenBlock<'x> {
|
|||||||
engine: &'x Engine,
|
engine: &'x Engine,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
tracing: bool,
|
tracing: bool,
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
parent: &Header,
|
parent: &Header,
|
||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
author: Address,
|
author: Address,
|
||||||
@ -474,7 +475,9 @@ impl LockedBlock {
|
|||||||
|
|
||||||
impl Drain for LockedBlock {
|
impl Drain for LockedBlock {
|
||||||
/// Drop this object and return the underlieing database.
|
/// Drop this object and return the underlieing database.
|
||||||
fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
fn drain(self) -> StateDB {
|
||||||
|
self.block.state.drop().1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SealedBlock {
|
impl SealedBlock {
|
||||||
@ -490,7 +493,9 @@ impl SealedBlock {
|
|||||||
|
|
||||||
impl Drain for SealedBlock {
|
impl Drain for SealedBlock {
|
||||||
/// Drop this object and return the underlieing database.
|
/// Drop this object and return the underlieing database.
|
||||||
fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
|
fn drain(self) -> StateDB {
|
||||||
|
self.block.state.drop().1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IsBlock for SealedBlock {
|
impl IsBlock for SealedBlock {
|
||||||
@ -505,7 +510,7 @@ pub fn enact(
|
|||||||
uncles: &[Header],
|
uncles: &[Header],
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
tracing: bool,
|
tracing: bool,
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
parent: &Header,
|
parent: &Header,
|
||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
@ -537,7 +542,7 @@ pub fn enact_bytes(
|
|||||||
block_bytes: &[u8],
|
block_bytes: &[u8],
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
tracing: bool,
|
tracing: bool,
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
parent: &Header,
|
parent: &Header,
|
||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
@ -553,7 +558,7 @@ pub fn enact_verified(
|
|||||||
block: &PreverifiedBlock,
|
block: &PreverifiedBlock,
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
tracing: bool,
|
tracing: bool,
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
parent: &Header,
|
parent: &Header,
|
||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
@ -568,7 +573,7 @@ pub fn enact_and_seal(
|
|||||||
block_bytes: &[u8],
|
block_bytes: &[u8],
|
||||||
engine: &Engine,
|
engine: &Engine,
|
||||||
tracing: bool,
|
tracing: bool,
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
parent: &Header,
|
parent: &Header,
|
||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
@ -588,9 +593,9 @@ mod tests {
|
|||||||
use spec::*;
|
use spec::*;
|
||||||
let spec = Spec::new_test();
|
let spec = Spec::new_test();
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
@ -604,25 +609,25 @@ mod tests {
|
|||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
|
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap()
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap()
|
||||||
.close_and_lock().seal(engine, vec![]).unwrap();
|
.close_and_lock().seal(engine, vec![]).unwrap();
|
||||||
let orig_bytes = b.rlp_bytes();
|
let orig_bytes = b.rlp_bytes();
|
||||||
let orig_db = b.drain();
|
let orig_db = b.drain();
|
||||||
|
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap();
|
let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap();
|
||||||
|
|
||||||
assert_eq!(e.rlp_bytes(), orig_bytes);
|
assert_eq!(e.rlp_bytes(), orig_bytes);
|
||||||
|
|
||||||
let db = e.drain();
|
let db = e.drain();
|
||||||
assert_eq!(orig_db.keys(), db.keys());
|
assert_eq!(orig_db.journal_db().keys(), db.journal_db().keys());
|
||||||
assert!(orig_db.keys().iter().filter(|k| orig_db.get(k.0) != db.get(k.0)).next() == None);
|
assert!(orig_db.journal_db().keys().iter().filter(|k| orig_db.journal_db().get(k.0) != db.journal_db().get(k.0)).next() == None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -632,9 +637,9 @@ mod tests {
|
|||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
|
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let mut uncle1_header = Header::new();
|
let mut uncle1_header = Header::new();
|
||||||
@ -648,9 +653,9 @@ mod tests {
|
|||||||
let orig_bytes = b.rlp_bytes();
|
let orig_bytes = b.rlp_bytes();
|
||||||
let orig_db = b.drain();
|
let orig_db = b.drain();
|
||||||
|
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap();
|
let e = enact_and_seal(&orig_bytes, engine, false, db, &genesis_header, last_hashes, Default::default()).unwrap();
|
||||||
|
|
||||||
let bytes = e.rlp_bytes();
|
let bytes = e.rlp_bytes();
|
||||||
@ -659,7 +664,7 @@ mod tests {
|
|||||||
assert_eq!(uncles[1].extra_data(), b"uncle2");
|
assert_eq!(uncles[1].extra_data(), b"uncle2");
|
||||||
|
|
||||||
let db = e.drain();
|
let db = e.drain();
|
||||||
assert_eq!(orig_db.keys(), db.keys());
|
assert_eq!(orig_db.journal_db().keys(), db.journal_db().keys());
|
||||||
assert!(orig_db.keys().iter().filter(|k| orig_db.get(k.0) != db.get(k.0)).next() == None);
|
assert!(orig_db.journal_db().keys().iter().filter(|k| orig_db.journal_db().get(k.0) != db.journal_db().get(k.0)).next() == None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ use time::precise_time_ns;
|
|||||||
|
|
||||||
// util
|
// util
|
||||||
use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock};
|
use util::{Bytes, PerfTimer, Itertools, Mutex, RwLock};
|
||||||
use util::journaldb::{self, JournalDB};
|
use util::journaldb;
|
||||||
use util::{U256, H256, Address, H2048, Uint};
|
use util::{U256, H256, Address, H2048, Uint};
|
||||||
use util::TrieFactory;
|
use util::TrieFactory;
|
||||||
use util::kvdb::*;
|
use util::kvdb::*;
|
||||||
@ -65,7 +65,7 @@ use miner::{Miner, MinerService};
|
|||||||
use snapshot::{self, io as snapshot_io};
|
use snapshot::{self, io as snapshot_io};
|
||||||
use factory::Factories;
|
use factory::Factories;
|
||||||
use rlp::{View, UntrustedRlp};
|
use rlp::{View, UntrustedRlp};
|
||||||
|
use state_db::StateDB;
|
||||||
|
|
||||||
// re-export
|
// re-export
|
||||||
pub use types::blockchain_info::BlockChainInfo;
|
pub use types::blockchain_info::BlockChainInfo;
|
||||||
@ -125,9 +125,9 @@ pub struct Client {
|
|||||||
tracedb: RwLock<TraceDB<BlockChain>>,
|
tracedb: RwLock<TraceDB<BlockChain>>,
|
||||||
engine: Arc<Engine>,
|
engine: Arc<Engine>,
|
||||||
config: ClientConfig,
|
config: ClientConfig,
|
||||||
db: RwLock<Arc<Database>>,
|
|
||||||
pruning: journaldb::Algorithm,
|
pruning: journaldb::Algorithm,
|
||||||
state_db: RwLock<Box<JournalDB>>,
|
db: RwLock<Arc<Database>>,
|
||||||
|
state_db: Mutex<StateDB>,
|
||||||
block_queue: BlockQueue,
|
block_queue: BlockQueue,
|
||||||
report: RwLock<ClientReport>,
|
report: RwLock<ClientReport>,
|
||||||
import_lock: Mutex<()>,
|
import_lock: Mutex<()>,
|
||||||
@ -171,14 +171,15 @@ impl Client {
|
|||||||
let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone()));
|
let chain = Arc::new(BlockChain::new(config.blockchain.clone(), &gb, db.clone()));
|
||||||
let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone()));
|
let tracedb = RwLock::new(TraceDB::new(config.tracing.clone(), db.clone(), chain.clone()));
|
||||||
|
|
||||||
let mut state_db = journaldb::new(db.clone(), config.pruning, ::db::COL_STATE);
|
let journal_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())) {
|
let mut state_db = StateDB::new(journal_db);
|
||||||
|
if state_db.journal_db().is_empty() && try!(spec.ensure_db_good(&mut state_db)) {
|
||||||
let mut batch = DBTransaction::new(&db);
|
let mut batch = DBTransaction::new(&db);
|
||||||
try!(state_db.commit(&mut batch, 0, &spec.genesis_header().hash(), None));
|
try!(state_db.commit(&mut batch, 0, &spec.genesis_header().hash(), None));
|
||||||
try!(db.write(batch).map_err(ClientError::Database));
|
try!(db.write(batch).map_err(ClientError::Database));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !chain.block_header(&chain.best_block_hash()).map_or(true, |h| state_db.contains(h.state_root())) {
|
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());
|
warn!("State root not found for block #{} ({})", chain.best_block_number(), chain.best_block_hash().hex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ impl Client {
|
|||||||
verifier: verification::new(config.verifier_type.clone()),
|
verifier: verification::new(config.verifier_type.clone()),
|
||||||
config: config,
|
config: config,
|
||||||
db: RwLock::new(db),
|
db: RwLock::new(db),
|
||||||
state_db: RwLock::new(state_db),
|
state_db: Mutex::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(()),
|
||||||
@ -298,7 +299,8 @@ 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.read().boxed_clone();
|
let is_canon = header.parent_hash() == &chain.best_block_hash();
|
||||||
|
let db = if is_canon { self.state_db.lock().boxed_clone_canon() } else { self.state_db.lock().boxed_clone() };
|
||||||
|
|
||||||
let enact_result = enact_verified(block, engine, self.tracedb.read().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 {
|
||||||
@ -441,7 +443,8 @@ impl Client {
|
|||||||
// 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.");
|
let mut state = block.drain();
|
||||||
|
state.commit(&mut batch, number, hash, ancient).expect("DB commit failed.");
|
||||||
|
|
||||||
let route = chain.insert_block(&mut batch, block_data, receipts);
|
let route = chain.insert_block(&mut batch, block_data, receipts);
|
||||||
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
self.tracedb.read().import(&mut batch, TraceImportRequest {
|
||||||
@ -454,7 +457,6 @@ impl Client {
|
|||||||
// Final commit to the DB
|
// Final commit to the DB
|
||||||
self.db.read().write_buffered(batch);
|
self.db.read().write_buffered(batch);
|
||||||
chain.commit();
|
chain.commit();
|
||||||
|
|
||||||
self.update_last_hashes(&parent, hash);
|
self.update_last_hashes(&parent, hash);
|
||||||
route
|
route
|
||||||
}
|
}
|
||||||
@ -496,7 +498,7 @@ impl Client {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.block_header(id).and_then(|header| {
|
self.block_header(id).and_then(|header| {
|
||||||
let db = self.state_db.read().boxed_clone();
|
let db = self.state_db.lock().boxed_clone();
|
||||||
|
|
||||||
// early exit for pruned blocks
|
// 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 + HISTORY {
|
||||||
@ -527,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.read().boxed_clone(),
|
self.state_db.lock().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())
|
||||||
@ -542,7 +544,7 @@ impl Client {
|
|||||||
/// 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.read().mem_used();
|
report.state_db_mem = self.state_db.lock().mem_used();
|
||||||
report
|
report
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,7 +600,7 @@ impl Client {
|
|||||||
/// 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<(), EthcoreError> {
|
pub fn take_snapshot<W: snapshot_io::SnapshotWriter + Send>(&self, writer: W, at: BlockID, p: &snapshot::Progress) -> Result<(), EthcoreError> {
|
||||||
let db = self.state_db.read().boxed_clone();
|
let db = self.state_db.lock().journal_db().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)));
|
||||||
|
|
||||||
@ -677,14 +679,14 @@ impl snapshot::DatabaseRestore for Client {
|
|||||||
trace!(target: "snapshot", "Replacing client database with {:?}", new_db);
|
trace!(target: "snapshot", "Replacing client database with {:?}", new_db);
|
||||||
|
|
||||||
let _import_lock = self.import_lock.lock();
|
let _import_lock = self.import_lock.lock();
|
||||||
let mut state_db = self.state_db.write();
|
let mut state_db = self.state_db.lock();
|
||||||
let mut chain = self.chain.write();
|
let mut chain = self.chain.write();
|
||||||
let mut tracedb = self.tracedb.write();
|
let mut tracedb = self.tracedb.write();
|
||||||
self.miner.clear();
|
self.miner.clear();
|
||||||
let db = self.db.write();
|
let db = self.db.write();
|
||||||
try!(db.restore(new_db));
|
try!(db.restore(new_db));
|
||||||
|
|
||||||
*state_db = journaldb::new(db.clone(), self.pruning, ::db::COL_STATE);
|
*state_db = StateDB::new(journaldb::new(db.clone(), self.pruning, ::db::COL_STATE));
|
||||||
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
|
*chain = Arc::new(BlockChain::new(self.config.blockchain.clone(), &[], db.clone()));
|
||||||
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
|
*tracedb = TraceDB::new(self.config.tracing.clone(), db.clone(), chain.clone());
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -908,7 +910,7 @@ impl BlockChainClient for Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn state_data(&self, hash: &H256) -> Option<Bytes> {
|
fn state_data(&self, hash: &H256) -> Option<Bytes> {
|
||||||
self.state_db.read().state(hash)
|
self.state_db.lock().journal_db().state(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
fn block_receipts(&self, hash: &H256) -> Option<Bytes> {
|
||||||
@ -1050,7 +1052,7 @@ impl MiningBlockChainClient for Client {
|
|||||||
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.read().boxed_clone(),
|
self.state_db.lock().boxed_clone(),
|
||||||
&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,
|
||||||
|
@ -42,6 +42,7 @@ use block::{OpenBlock, SealedBlock};
|
|||||||
use executive::Executed;
|
use executive::Executed;
|
||||||
use error::CallError;
|
use error::CallError;
|
||||||
use trace::LocalizedTrace;
|
use trace::LocalizedTrace;
|
||||||
|
use state_db::StateDB;
|
||||||
|
|
||||||
/// Test client.
|
/// Test client.
|
||||||
pub struct TestBlockChainClient {
|
pub struct TestBlockChainClient {
|
||||||
@ -283,13 +284,14 @@ impl TestBlockChainClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temp_journal_db() -> GuardedTempResult<Box<JournalDB>> {
|
pub fn get_temp_state_db() -> GuardedTempResult<StateDB> {
|
||||||
let temp = RandomTempPath::new();
|
let temp = RandomTempPath::new();
|
||||||
let db = Database::open_default(temp.as_str()).unwrap();
|
let db = Database::open_default(temp.as_str()).unwrap();
|
||||||
let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::EarlyMerge, None);
|
let journal_db = journaldb::new(Arc::new(db), journaldb::Algorithm::EarlyMerge, None);
|
||||||
|
let state_db = StateDB::new(journal_db);
|
||||||
GuardedTempResult {
|
GuardedTempResult {
|
||||||
_temp: temp,
|
_temp: temp,
|
||||||
result: Some(journal_db)
|
result: Some(state_db)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,9 +299,9 @@ impl MiningBlockChainClient for TestBlockChainClient {
|
|||||||
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.spec.engine;
|
let engine = &*self.spec.engine;
|
||||||
let genesis_header = self.spec.genesis_header();
|
let genesis_header = self.spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
self.spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
self.spec.ensure_db_good(&mut db).unwrap();
|
||||||
|
|
||||||
let last_hashes = vec![genesis_header.hash()];
|
let last_hashes = vec![genesis_header.hash()];
|
||||||
let mut open_block = OpenBlock::new(
|
let mut open_block = OpenBlock::new(
|
||||||
|
@ -252,9 +252,9 @@ mod tests {
|
|||||||
let spec = new_test_authority();
|
let spec = new_test_authority();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
|
@ -81,9 +81,9 @@ mod tests {
|
|||||||
let spec = Spec::new_test_instant();
|
let spec = Spec::new_test_instant();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
|
@ -355,9 +355,9 @@ mod tests {
|
|||||||
let spec = new_morden();
|
let spec = new_morden();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close();
|
let b = b.close();
|
||||||
@ -369,9 +369,9 @@ mod tests {
|
|||||||
let spec = new_morden();
|
let spec = new_morden();
|
||||||
let engine = &*spec.engine;
|
let engine = &*spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let mut uncle = Header::new();
|
let mut uncle = Header::new();
|
||||||
|
@ -69,9 +69,9 @@ mod tests {
|
|||||||
let spec = new_morden();
|
let spec = new_morden();
|
||||||
let engine = &spec.engine;
|
let engine = &spec.engine;
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap();
|
let s = State::from_existing(db, genesis_header.state_root().clone(), engine.account_start_nonce(), Default::default()).unwrap();
|
||||||
assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()), 1u64.into());
|
assert_eq!(s.balance(&"0000000000000000000000000000000000000001".into()), 1u64.into());
|
||||||
assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()), 1u64.into());
|
assert_eq!(s.balance(&"0000000000000000000000000000000000000002".into()), 1u64.into());
|
||||||
|
@ -141,7 +141,7 @@ impl Ext for FakeExt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extcodesize(&self, address: &Address) -> usize {
|
fn extcodesize(&self, address: &Address) -> usize {
|
||||||
self.codes.get(address).map(|v| v.len()).unwrap_or(0)
|
self.codes.get(address).map_or(0, |c| c.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log(&mut self, topics: Vec<H256>, data: &[u8]) {
|
fn log(&mut self, topics: Vec<H256>, data: &[u8]) {
|
||||||
|
@ -209,7 +209,6 @@ impl<'a, T, V> Ext for Externalities<'a, T, V> where T: 'a + Tracer, V: 'a + VMT
|
|||||||
self.state.code_size(address).unwrap_or(0)
|
self.state.code_size(address).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg_attr(feature="dev", allow(match_ref_pats))]
|
#[cfg_attr(feature="dev", allow(match_ref_pats))]
|
||||||
fn ret(mut self, gas: &U256, data: &[u8]) -> evm::Result<U256>
|
fn ret(mut self, gas: &U256, data: &[u8]) -> evm::Result<U256>
|
||||||
where Self: Sized {
|
where Self: Sized {
|
||||||
|
@ -110,6 +110,7 @@ extern crate lazy_static;
|
|||||||
extern crate heapsize;
|
extern crate heapsize;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate ethcore_ipc as ipc;
|
extern crate ethcore_ipc as ipc;
|
||||||
|
extern crate lru_cache;
|
||||||
|
|
||||||
#[cfg(feature = "jit" )]
|
#[cfg(feature = "jit" )]
|
||||||
extern crate evmjit;
|
extern crate evmjit;
|
||||||
@ -142,6 +143,7 @@ mod basic_types;
|
|||||||
mod env_info;
|
mod env_info;
|
||||||
mod pod_account;
|
mod pod_account;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod state_db;
|
||||||
mod account_db;
|
mod account_db;
|
||||||
mod builtin;
|
mod builtin;
|
||||||
mod executive;
|
mod executive;
|
||||||
|
@ -48,7 +48,7 @@ impl PodAccount {
|
|||||||
PodAccount {
|
PodAccount {
|
||||||
balance: *acc.balance(),
|
balance: *acc.balance(),
|
||||||
nonce: *acc.nonce(),
|
nonce: *acc.nonce(),
|
||||||
storage: acc.storage_overlay().iter().fold(BTreeMap::new(), |mut m, (k, &(_, ref v))| {m.insert(k.clone(), v.clone()); m}),
|
storage: acc.storage_changes().iter().fold(BTreeMap::new(), |mut m, (k, v)| {m.insert(k.clone(), v.clone()); m}),
|
||||||
code: acc.code().map(|x| x.to_vec()),
|
code: acc.code().map(|x| x.to_vec()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ impl Account {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use account_db::{AccountDB, AccountDBMut};
|
use account_db::{AccountDB, AccountDBMut};
|
||||||
use tests::helpers::get_temp_journal_db;
|
use tests::helpers::get_temp_state_db;
|
||||||
use snapshot::tests::helpers::fill_storage;
|
use snapshot::tests::helpers::fill_storage;
|
||||||
|
|
||||||
use util::sha3::{SHA3_EMPTY, SHA3_NULL_RLP};
|
use util::sha3::{SHA3_EMPTY, SHA3_NULL_RLP};
|
||||||
@ -218,8 +218,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encoding_basic() {
|
fn encoding_basic() {
|
||||||
let mut db = get_temp_journal_db();
|
let mut db = get_temp_state_db();
|
||||||
let mut db = &mut **db;
|
|
||||||
let addr = Address::random();
|
let addr = Address::random();
|
||||||
|
|
||||||
let account = Account {
|
let account = Account {
|
||||||
@ -239,8 +238,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encoding_storage() {
|
fn encoding_storage() {
|
||||||
let mut db = get_temp_journal_db();
|
let mut db = get_temp_state_db();
|
||||||
let mut db = &mut **db;
|
|
||||||
let addr = Address::random();
|
let addr = Address::random();
|
||||||
|
|
||||||
let account = {
|
let account = {
|
||||||
@ -265,8 +263,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn encoding_code() {
|
fn encoding_code() {
|
||||||
let mut db = get_temp_journal_db();
|
let mut db = get_temp_state_db();
|
||||||
let mut db = &mut **db;
|
|
||||||
|
|
||||||
let addr1 = Address::random();
|
let addr1 = Address::random();
|
||||||
let addr2 = Address::random();
|
let addr2 = Address::random();
|
||||||
|
@ -20,6 +20,7 @@ use common::*;
|
|||||||
use engines::{Engine, NullEngine, InstantSeal, BasicAuthority};
|
use engines::{Engine, NullEngine, InstantSeal, BasicAuthority};
|
||||||
use pod_state::*;
|
use pod_state::*;
|
||||||
use account_db::*;
|
use account_db::*;
|
||||||
|
use state_db::StateDB;
|
||||||
use super::genesis::Genesis;
|
use super::genesis::Genesis;
|
||||||
use super::seal::Generic as GenericSeal;
|
use super::seal::Generic as GenericSeal;
|
||||||
use ethereum;
|
use ethereum;
|
||||||
@ -226,19 +227,19 @@ impl Spec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure that the given state DB has the trie nodes in for the genesis state.
|
/// Ensure that the given state DB has the trie nodes in for the genesis state.
|
||||||
pub fn ensure_db_good(&self, db: &mut HashDB) -> Result<bool, Box<TrieError>> {
|
pub fn ensure_db_good(&self, db: &mut StateDB) -> Result<bool, Box<TrieError>> {
|
||||||
if !db.contains(&self.state_root()) {
|
if !db.as_hashdb().contains(&self.state_root()) {
|
||||||
let mut root = H256::new();
|
let mut root = H256::new();
|
||||||
{
|
{
|
||||||
let mut t = SecTrieDBMut::new(db, &mut root);
|
let mut t = SecTrieDBMut::new(db.as_hashdb_mut(), &mut root);
|
||||||
for (address, account) in self.genesis_state.get().iter() {
|
for (address, account) in self.genesis_state.get().iter() {
|
||||||
try!(t.insert(&**address, &account.rlp()));
|
try!(t.insert(&**address, &account.rlp()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (address, account) in self.genesis_state.get().iter() {
|
for (address, account) in self.genesis_state.get().iter() {
|
||||||
account.insert_additional(&mut AccountDBMut::new(db, address));
|
account.insert_additional(&mut AccountDBMut::new(db.as_hashdb_mut(), address));
|
||||||
}
|
}
|
||||||
assert!(db.contains(&self.state_root()));
|
assert!(db.as_hashdb().contains(&self.state_root()));
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else { Ok(false) }
|
} else { Ok(false) }
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,13 @@ use std::collections::hash_map::Entry;
|
|||||||
use util::*;
|
use util::*;
|
||||||
use pod_account::*;
|
use pod_account::*;
|
||||||
use rlp::*;
|
use rlp::*;
|
||||||
|
use lru_cache::LruCache;
|
||||||
|
|
||||||
use std::cell::{Ref, RefCell, Cell};
|
use std::cell::{RefCell, Cell};
|
||||||
|
|
||||||
|
const STORAGE_CACHE_ITEMS: usize = 4096;
|
||||||
|
|
||||||
/// Single account in the system.
|
/// Single account in the system.
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
// Balance of the account.
|
// Balance of the account.
|
||||||
balance: U256,
|
balance: U256,
|
||||||
@ -32,10 +34,16 @@ pub struct Account {
|
|||||||
nonce: U256,
|
nonce: U256,
|
||||||
// Trie-backed storage.
|
// Trie-backed storage.
|
||||||
storage_root: H256,
|
storage_root: H256,
|
||||||
// Overlay on trie-backed storage - tuple is (<clean>, <value>).
|
// LRU Cache of the trie-backed storage.
|
||||||
storage_overlay: RefCell<HashMap<H256, (Filth, H256)>>,
|
// This is limited to `STORAGE_CACHE_ITEMS` recent queries
|
||||||
|
storage_cache: RefCell<LruCache<H256, H256>>,
|
||||||
|
// Modified storage. Accumulates changes to storage made in `set_storage`
|
||||||
|
// Takes precedence over `storage_cache`.
|
||||||
|
storage_changes: HashMap<H256, H256>,
|
||||||
// Code hash of the account. If None, means that it's a contract whose code has not yet been set.
|
// Code hash of the account. If None, means that it's a contract whose code has not yet been set.
|
||||||
code_hash: Option<H256>,
|
code_hash: Option<H256>,
|
||||||
|
// Size of the accoun code.
|
||||||
|
code_size: Option<usize>,
|
||||||
// Code cache of the account.
|
// Code cache of the account.
|
||||||
code_cache: Bytes,
|
code_cache: Bytes,
|
||||||
// Account is new or has been modified
|
// Account is new or has been modified
|
||||||
@ -52,23 +60,31 @@ impl Account {
|
|||||||
balance: balance,
|
balance: balance,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: storage,
|
||||||
code_hash: Some(code.sha3()),
|
code_hash: Some(code.sha3()),
|
||||||
|
code_size: Some(code.len()),
|
||||||
code_cache: code,
|
code_cache: code,
|
||||||
filth: Filth::Dirty,
|
filth: Filth::Dirty,
|
||||||
address_hash: Cell::new(None),
|
address_hash: Cell::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn empty_storage_cache() -> RefCell<LruCache<H256, H256>> {
|
||||||
|
RefCell::new(LruCache::new(STORAGE_CACHE_ITEMS))
|
||||||
|
}
|
||||||
|
|
||||||
/// General constructor.
|
/// General constructor.
|
||||||
pub fn from_pod(pod: PodAccount) -> Account {
|
pub fn from_pod(pod: PodAccount) -> Account {
|
||||||
Account {
|
Account {
|
||||||
balance: pod.balance,
|
balance: pod.balance,
|
||||||
nonce: pod.nonce,
|
nonce: pod.nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(pod.storage.into_iter().map(|(k, v)| (k, (Filth::Dirty, v))).collect()),
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: pod.storage.into_iter().collect(),
|
||||||
code_hash: pod.code.as_ref().map(|c| c.sha3()),
|
code_hash: pod.code.as_ref().map(|c| c.sha3()),
|
||||||
code_cache: pod.code.as_ref().map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c.clone()),
|
code_size: Some(pod.code.as_ref().map_or(0, |c| c.len())),
|
||||||
|
code_cache: pod.code.map_or_else(|| { warn!("POD account with unknown code is being created! Assuming no code."); vec![] }, |c| c),
|
||||||
filth: Filth::Dirty,
|
filth: Filth::Dirty,
|
||||||
address_hash: Cell::new(None),
|
address_hash: Cell::new(None),
|
||||||
}
|
}
|
||||||
@ -80,9 +96,11 @@ impl Account {
|
|||||||
balance: balance,
|
balance: balance,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(HashMap::new()),
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: HashMap::new(),
|
||||||
code_hash: Some(SHA3_EMPTY),
|
code_hash: Some(SHA3_EMPTY),
|
||||||
code_cache: vec![],
|
code_cache: vec![],
|
||||||
|
code_size: Some(0),
|
||||||
filth: Filth::Dirty,
|
filth: Filth::Dirty,
|
||||||
address_hash: Cell::new(None),
|
address_hash: Cell::new(None),
|
||||||
}
|
}
|
||||||
@ -95,9 +113,11 @@ impl Account {
|
|||||||
nonce: r.val_at(0),
|
nonce: r.val_at(0),
|
||||||
balance: r.val_at(1),
|
balance: r.val_at(1),
|
||||||
storage_root: r.val_at(2),
|
storage_root: r.val_at(2),
|
||||||
storage_overlay: RefCell::new(HashMap::new()),
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: HashMap::new(),
|
||||||
code_hash: Some(r.val_at(3)),
|
code_hash: Some(r.val_at(3)),
|
||||||
code_cache: vec![],
|
code_cache: vec![],
|
||||||
|
code_size: None,
|
||||||
filth: Filth::Clean,
|
filth: Filth::Clean,
|
||||||
address_hash: Cell::new(None),
|
address_hash: Cell::new(None),
|
||||||
}
|
}
|
||||||
@ -110,9 +130,11 @@ impl Account {
|
|||||||
balance: balance,
|
balance: balance,
|
||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
storage_root: SHA3_NULL_RLP,
|
storage_root: SHA3_NULL_RLP,
|
||||||
storage_overlay: RefCell::new(HashMap::new()),
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: HashMap::new(),
|
||||||
code_hash: None,
|
code_hash: None,
|
||||||
code_cache: vec![],
|
code_cache: vec![],
|
||||||
|
code_size: None,
|
||||||
filth: Filth::Dirty,
|
filth: Filth::Dirty,
|
||||||
address_hash: Cell::new(None),
|
address_hash: Cell::new(None),
|
||||||
}
|
}
|
||||||
@ -123,33 +145,38 @@ impl Account {
|
|||||||
pub fn init_code(&mut self, code: Bytes) {
|
pub fn init_code(&mut self, code: Bytes) {
|
||||||
assert!(self.code_hash.is_none());
|
assert!(self.code_hash.is_none());
|
||||||
self.code_cache = code;
|
self.code_cache = code;
|
||||||
|
self.code_size = Some(self.code_cache.len());
|
||||||
self.filth = Filth::Dirty;
|
self.filth = Filth::Dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset this account's code to the given code.
|
/// Reset this account's code to the given code.
|
||||||
pub fn reset_code(&mut self, code: Bytes) {
|
pub fn reset_code(&mut self, code: Bytes) {
|
||||||
self.code_hash = None;
|
self.code_hash = None;
|
||||||
|
self.code_size = Some(0);
|
||||||
self.init_code(code);
|
self.init_code(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
|
/// Set (and cache) the contents of the trie's storage at `key` to `value`.
|
||||||
pub fn set_storage(&mut self, key: H256, value: H256) {
|
pub fn set_storage(&mut self, key: H256, value: H256) {
|
||||||
match self.storage_overlay.borrow_mut().entry(key) {
|
match self.storage_changes.entry(key) {
|
||||||
Entry::Occupied(ref mut entry) if entry.get().1 != value => {
|
Entry::Occupied(ref mut entry) if entry.get() != &value => {
|
||||||
entry.insert((Filth::Dirty, value));
|
entry.insert(value);
|
||||||
self.filth = Filth::Dirty;
|
self.filth = Filth::Dirty;
|
||||||
},
|
},
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert((Filth::Dirty, value));
|
entry.insert(value);
|
||||||
self.filth = Filth::Dirty;
|
self.filth = Filth::Dirty;
|
||||||
},
|
},
|
||||||
_ => (),
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get (and cache) the contents of the trie's storage at `key`.
|
/// Get (and cache) the contents of the trie's storage at `key`.
|
||||||
|
/// Takes modifed storage into account.
|
||||||
pub fn storage_at(&self, db: &HashDB, key: &H256) -> H256 {
|
pub fn storage_at(&self, db: &HashDB, key: &H256) -> H256 {
|
||||||
self.storage_overlay.borrow_mut().entry(key.clone()).or_insert_with(||{
|
if let Some(value) = self.cached_storage_at(key) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
let db = SecTrieDB::new(db, &self.storage_root)
|
let db = SecTrieDB::new(db, &self.storage_root)
|
||||||
.expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \
|
.expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \
|
||||||
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
|
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
|
||||||
@ -159,8 +186,21 @@ impl Account {
|
|||||||
Ok(x) => x.map_or_else(U256::zero, decode),
|
Ok(x) => x.map_or_else(U256::zero, decode),
|
||||||
Err(e) => panic!("Encountered potential DB corruption: {}", e),
|
Err(e) => panic!("Encountered potential DB corruption: {}", e),
|
||||||
};
|
};
|
||||||
(Filth::Clean, item.into())
|
let value: H256 = item.into();
|
||||||
}).1.clone()
|
self.storage_cache.borrow_mut().insert(key.clone(), value.clone());
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get cached storage value if any. Returns `None` if the
|
||||||
|
/// key is not in the cache.
|
||||||
|
pub fn cached_storage_at(&self, key: &H256) -> Option<H256> {
|
||||||
|
if let Some(value) = self.storage_changes.get(key) {
|
||||||
|
return Some(value.clone())
|
||||||
|
}
|
||||||
|
if let Some(value) = self.storage_cache.borrow_mut().get_mut(key) {
|
||||||
|
return Some(value.clone())
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return the balance associated with this account.
|
/// return the balance associated with this account.
|
||||||
@ -196,6 +236,12 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns the account's code size. If `None` then the code cache or code size cache isn't available -
|
||||||
|
/// get someone who knows to call `note_code`.
|
||||||
|
pub fn code_size(&self) -> Option<usize> {
|
||||||
|
self.code_size.clone()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
/// Provide a byte array which hashes to the `code_hash`. returns the hash as a result.
|
/// Provide a byte array which hashes to the `code_hash`. returns the hash as a result.
|
||||||
pub fn note_code(&mut self, code: Bytes) -> Result<(), H256> {
|
pub fn note_code(&mut self, code: Bytes) -> Result<(), H256> {
|
||||||
@ -203,6 +249,7 @@ impl Account {
|
|||||||
match self.code_hash {
|
match self.code_hash {
|
||||||
Some(ref i) if h == *i => {
|
Some(ref i) if h == *i => {
|
||||||
self.code_cache = code;
|
self.code_cache = code;
|
||||||
|
self.code_size = Some(self.code_cache.len());
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
_ => Err(h)
|
_ => Err(h)
|
||||||
@ -216,11 +263,12 @@ impl Account {
|
|||||||
|
|
||||||
/// Is this a new or modified account?
|
/// Is this a new or modified account?
|
||||||
pub fn is_dirty(&self) -> bool {
|
pub fn is_dirty(&self) -> bool {
|
||||||
self.filth == Filth::Dirty
|
self.filth == Filth::Dirty || !self.storage_is_clean()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mark account as clean.
|
/// Mark account as clean.
|
||||||
pub fn set_clean(&mut self) {
|
pub fn set_clean(&mut self) {
|
||||||
|
assert!(self.storage_is_clean());
|
||||||
self.filth = Filth::Clean
|
self.filth = Filth::Clean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +279,31 @@ impl Account {
|
|||||||
self.is_cached() ||
|
self.is_cached() ||
|
||||||
match self.code_hash {
|
match self.code_hash {
|
||||||
Some(ref h) => match db.get(h) {
|
Some(ref h) => match db.get(h) {
|
||||||
Some(x) => { self.code_cache = x.to_vec(); true },
|
Some(x) => {
|
||||||
|
self.code_cache = x.to_vec();
|
||||||
|
self.code_size = Some(x.len());
|
||||||
|
true
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
warn!("Failed reverse get of {}", h);
|
||||||
|
false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a database to get `code_size`. Should not be called if it is a contract without code.
|
||||||
|
pub fn cache_code_size(&mut self, db: &HashDB) -> bool {
|
||||||
|
// TODO: fill out self.code_cache;
|
||||||
|
trace!("Account::cache_code_size: ic={}; self.code_hash={:?}, self.code_cache={}", self.is_cached(), self.code_hash, self.code_cache.pretty());
|
||||||
|
self.code_size.is_some() ||
|
||||||
|
match self.code_hash {
|
||||||
|
Some(ref h) if h != &SHA3_EMPTY => match db.get(h) {
|
||||||
|
Some(x) => {
|
||||||
|
self.code_size = Some(x.len());
|
||||||
|
true
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Failed reverse get of {}", h);
|
warn!("Failed reverse get of {}", h);
|
||||||
false
|
false
|
||||||
@ -241,16 +313,15 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
/// Determine whether there are any un-`commit()`-ed storage-setting operations.
|
/// Determine whether there are any un-`commit()`-ed storage-setting operations.
|
||||||
pub fn storage_is_clean(&self) -> bool { self.storage_overlay.borrow().iter().find(|&(_, &(f, _))| f == Filth::Dirty).is_none() }
|
pub fn storage_is_clean(&self) -> bool { self.storage_changes.is_empty() }
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
/// return the storage root associated with this account or None if it has been altered via the overlay.
|
||||||
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }
|
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }
|
||||||
|
|
||||||
/// return the storage overlay.
|
/// return the storage overlay.
|
||||||
pub fn storage_overlay(&self) -> Ref<HashMap<H256, (Filth, H256)>> { self.storage_overlay.borrow() }
|
pub fn storage_changes(&self) -> &HashMap<H256, H256> { &self.storage_changes }
|
||||||
|
|
||||||
/// Increment the nonce of the account by one.
|
/// Increment the nonce of the account by one.
|
||||||
pub fn inc_nonce(&mut self) {
|
pub fn inc_nonce(&mut self) {
|
||||||
@ -276,26 +347,24 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit the `storage_overlay` to the backing DB and update `storage_root`.
|
/// Commit the `storage_changes` to the backing DB and update `storage_root`.
|
||||||
pub fn commit_storage(&mut self, trie_factory: &TrieFactory, db: &mut HashDB) {
|
pub fn commit_storage(&mut self, trie_factory: &TrieFactory, db: &mut HashDB) {
|
||||||
let mut t = trie_factory.from_existing(db, &mut self.storage_root)
|
let mut t = trie_factory.from_existing(db, &mut self.storage_root)
|
||||||
.expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \
|
.expect("Account storage_root initially set to zero (valid) and only altered by SecTrieDBMut. \
|
||||||
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
|
SecTrieDBMut would not set it to an invalid state root. Therefore the root is valid and DB creation \
|
||||||
using it will not fail.");
|
using it will not fail.");
|
||||||
for (k, &mut (ref mut f, ref mut v)) in self.storage_overlay.borrow_mut().iter_mut() {
|
for (k, v) in self.storage_changes.drain() {
|
||||||
if f == &Filth::Dirty {
|
|
||||||
// cast key and value to trait type,
|
// cast key and value to trait type,
|
||||||
// so we can call overloaded `to_bytes` method
|
// so we can call overloaded `to_bytes` method
|
||||||
let res = match v.is_zero() {
|
let res = match v.is_zero() {
|
||||||
true => t.remove(k),
|
true => t.remove(&k),
|
||||||
false => t.insert(k, &encode(&U256::from(&*v))),
|
false => t.insert(&k, &encode(&U256::from(&*v))),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
warn!("Encountered potential DB corruption: {}", e);
|
warn!("Encountered potential DB corruption: {}", e);
|
||||||
}
|
}
|
||||||
*f = Filth::Clean;
|
self.storage_cache.borrow_mut().insert(k, v);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,9 +372,13 @@ impl Account {
|
|||||||
pub fn commit_code(&mut self, db: &mut HashDB) {
|
pub fn commit_code(&mut self, db: &mut HashDB) {
|
||||||
trace!("Commiting code of {:?} - {:?}, {:?}", self, self.code_hash.is_none(), self.code_cache.is_empty());
|
trace!("Commiting code of {:?} - {:?}, {:?}", self, self.code_hash.is_none(), self.code_cache.is_empty());
|
||||||
match (self.code_hash.is_none(), self.code_cache.is_empty()) {
|
match (self.code_hash.is_none(), self.code_cache.is_empty()) {
|
||||||
(true, true) => self.code_hash = Some(SHA3_EMPTY),
|
(true, true) => {
|
||||||
|
self.code_hash = Some(SHA3_EMPTY);
|
||||||
|
self.code_size = Some(0);
|
||||||
|
},
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
self.code_hash = Some(db.insert(&self.code_cache));
|
self.code_hash = Some(db.insert(&self.code_cache));
|
||||||
|
self.code_size = Some(self.code_cache.len());
|
||||||
},
|
},
|
||||||
(false, _) => {},
|
(false, _) => {},
|
||||||
}
|
}
|
||||||
@ -317,9 +390,57 @@ impl Account {
|
|||||||
stream.append(&self.nonce);
|
stream.append(&self.nonce);
|
||||||
stream.append(&self.balance);
|
stream.append(&self.balance);
|
||||||
stream.append(&self.storage_root);
|
stream.append(&self.storage_root);
|
||||||
stream.append(self.code_hash.as_ref().expect("Cannot form RLP of contract account without code."));
|
stream.append(self.code_hash.as_ref().unwrap_or(&SHA3_EMPTY));
|
||||||
stream.out()
|
stream.out()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clone basic account data
|
||||||
|
pub fn clone_basic(&self) -> Account {
|
||||||
|
Account {
|
||||||
|
balance: self.balance.clone(),
|
||||||
|
nonce: self.nonce.clone(),
|
||||||
|
storage_root: self.storage_root.clone(),
|
||||||
|
storage_cache: Self::empty_storage_cache(),
|
||||||
|
storage_changes: HashMap::new(),
|
||||||
|
code_hash: self.code_hash.clone(),
|
||||||
|
code_size: self.code_size.clone(),
|
||||||
|
code_cache: Bytes::new(),
|
||||||
|
filth: self.filth,
|
||||||
|
address_hash: self.address_hash.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone account data and dirty storage keys
|
||||||
|
pub fn clone_dirty(&self) -> Account {
|
||||||
|
let mut account = self.clone_basic();
|
||||||
|
account.storage_changes = self.storage_changes.clone();
|
||||||
|
account.code_cache = self.code_cache.clone();
|
||||||
|
account
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone account data, dirty storage keys and cached storage keys.
|
||||||
|
pub fn clone_all(&self) -> Account {
|
||||||
|
let mut account = self.clone_dirty();
|
||||||
|
account.storage_cache = self.storage_cache.clone();
|
||||||
|
account
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replace self with the data from other account merging storage cache
|
||||||
|
pub fn merge_with(&mut self, other: Account) {
|
||||||
|
assert!(self.storage_is_clean());
|
||||||
|
assert!(other.storage_is_clean());
|
||||||
|
self.balance = other.balance;
|
||||||
|
self.nonce = other.nonce;
|
||||||
|
self.storage_root = other.storage_root;
|
||||||
|
self.code_hash = other.code_hash;
|
||||||
|
self.code_cache = other.code_cache;
|
||||||
|
self.code_size = other.code_size;
|
||||||
|
self.address_hash = other.address_hash;
|
||||||
|
let mut cache = self.storage_cache.borrow_mut();
|
||||||
|
for (k, v) in other.storage_cache.into_inner().into_iter() {
|
||||||
|
cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Account {
|
impl fmt::Debug for Account {
|
||||||
@ -416,6 +537,7 @@ mod tests {
|
|||||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||||
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
||||||
assert_eq!(a.code_hash(), SHA3_EMPTY);
|
assert_eq!(a.code_hash(), SHA3_EMPTY);
|
||||||
|
assert_eq!(a.code_size(), Some(3));
|
||||||
a.commit_code(&mut db);
|
a.commit_code(&mut db);
|
||||||
assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb");
|
assert_eq!(a.code_hash().hex(), "af231e631776a517ca23125370d542873eca1fb4d613ed9b5d5335a46ae5b7eb");
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ use trace::FlatTrace;
|
|||||||
use pod_account::*;
|
use pod_account::*;
|
||||||
use pod_state::{self, PodState};
|
use pod_state::{self, PodState};
|
||||||
use types::state_diff::StateDiff;
|
use types::state_diff::StateDiff;
|
||||||
|
use state_db::StateDB;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod substate;
|
mod substate;
|
||||||
@ -41,23 +42,92 @@ pub struct ApplyOutcome {
|
|||||||
/// Result type for the execution ("application") of a transaction.
|
/// Result type for the execution ("application") of a transaction.
|
||||||
pub type ApplyResult = Result<ApplyOutcome, Error>;
|
pub type ApplyResult = Result<ApplyOutcome, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum AccountEntry {
|
||||||
|
/// Contains account data.
|
||||||
|
Cached(Account),
|
||||||
|
/// Account has been deleted.
|
||||||
|
Killed,
|
||||||
|
/// Account does not exist.
|
||||||
|
Missing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountEntry {
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
AccountEntry::Cached(ref a) => a.is_dirty(),
|
||||||
|
AccountEntry::Killed => true,
|
||||||
|
AccountEntry::Missing => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone dirty data into new `AccountEntry`.
|
||||||
|
/// Returns None if clean.
|
||||||
|
fn clone_dirty(&self) -> Option<AccountEntry> {
|
||||||
|
match *self {
|
||||||
|
AccountEntry::Cached(ref acc) if acc.is_dirty() => Some(AccountEntry::Cached(acc.clone_dirty())),
|
||||||
|
AccountEntry::Killed => Some(AccountEntry::Killed),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone account entry data that needs to be saved in the snapshot.
|
||||||
|
/// This includes basic account information and all locally cached storage keys
|
||||||
|
fn clone_for_snapshot(&self) -> AccountEntry {
|
||||||
|
match *self {
|
||||||
|
AccountEntry::Cached(ref acc) => AccountEntry::Cached(acc.clone_all()),
|
||||||
|
AccountEntry::Killed => AccountEntry::Killed,
|
||||||
|
AccountEntry::Missing => AccountEntry::Missing,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Representation of the entire state of all accounts in the system.
|
/// Representation of the entire state of all accounts in the system.
|
||||||
|
///
|
||||||
|
/// `State` can work together with `StateDB` to share account cache.
|
||||||
|
///
|
||||||
|
/// Local cache contains changes made locally and changes accumulated
|
||||||
|
/// locally from previous commits. Global cache reflects the database
|
||||||
|
/// state and never contains any changes.
|
||||||
|
///
|
||||||
|
/// Account data can be in the following cache states:
|
||||||
|
/// * In global but not local - something that was queried from the database,
|
||||||
|
/// but never modified
|
||||||
|
/// * In local but not global - something that was just added (e.g. new account)
|
||||||
|
/// * In both with the same value - something that was changed to a new value,
|
||||||
|
/// but changed back to a previous block in the same block (same State instance)
|
||||||
|
/// * In both with different values - something that was overwritten with a
|
||||||
|
/// new value.
|
||||||
|
///
|
||||||
|
/// All read-only state queries check local cache/modifications first,
|
||||||
|
/// then global state cache. If data is not found in any of the caches
|
||||||
|
/// it is loaded from the DB to the local cache.
|
||||||
|
///
|
||||||
|
/// Upon destruction all the local cache data merged into the global cache.
|
||||||
|
/// The merge might be rejected if current state is non-canonical.
|
||||||
pub struct State {
|
pub struct State {
|
||||||
db: Box<JournalDB>,
|
db: StateDB,
|
||||||
root: H256,
|
root: H256,
|
||||||
cache: RefCell<HashMap<Address, Option<Account>>>,
|
cache: RefCell<HashMap<Address, AccountEntry>>,
|
||||||
snapshots: RefCell<Vec<HashMap<Address, Option<Option<Account>>>>>,
|
snapshots: RefCell<Vec<HashMap<Address, Option<AccountEntry>>>>,
|
||||||
account_start_nonce: U256,
|
account_start_nonce: U256,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
enum RequireCache {
|
||||||
|
None,
|
||||||
|
CodeSize,
|
||||||
|
Code,
|
||||||
|
}
|
||||||
|
|
||||||
const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \
|
const SEC_TRIE_DB_UNWRAP_STR: &'static str = "A state can only be created with valid root. Creating a SecTrieDB with a valid root will not fail. \
|
||||||
Therefore creating a SecTrieDB with this state's root will not fail.";
|
Therefore creating a SecTrieDB with this state's root will not fail.";
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
/// Creates new state with empty state root
|
/// Creates new state with empty state root
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn new(mut db: Box<JournalDB>, account_start_nonce: U256, factories: Factories) -> State {
|
pub fn new(mut db: StateDB, account_start_nonce: U256, factories: Factories) -> State {
|
||||||
let mut root = H256::new();
|
let mut root = H256::new();
|
||||||
{
|
{
|
||||||
// init trie and reset root too null
|
// init trie and reset root too null
|
||||||
@ -75,7 +145,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates new state with existing state root
|
/// Creates new state with existing state root
|
||||||
pub fn from_existing(db: Box<JournalDB>, root: H256, account_start_nonce: U256, factories: Factories) -> Result<State, TrieError> {
|
pub fn from_existing(db: StateDB, root: H256, account_start_nonce: U256, factories: Factories) -> Result<State, TrieError> {
|
||||||
if !db.as_hashdb().contains(&root) {
|
if !db.as_hashdb().contains(&root) {
|
||||||
return Err(TrieError::InvalidStateRoot(root));
|
return Err(TrieError::InvalidStateRoot(root));
|
||||||
}
|
}
|
||||||
@ -119,14 +189,21 @@ impl State {
|
|||||||
self.cache.borrow_mut().insert(k, v);
|
self.cache.borrow_mut().insert(k, v);
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
self.cache.borrow_mut().remove(&k);
|
match self.cache.borrow_mut().entry(k) {
|
||||||
|
::std::collections::hash_map::Entry::Occupied(e) => {
|
||||||
|
if e.get().is_dirty() {
|
||||||
|
e.remove();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_cache(&self, address: &Address, account: Option<Account>) {
|
fn insert_cache(&self, address: &Address, account: AccountEntry) {
|
||||||
if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() {
|
if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() {
|
||||||
if !snapshot.contains_key(address) {
|
if !snapshot.contains_key(address) {
|
||||||
snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account));
|
snapshot.insert(address.clone(), self.cache.borrow_mut().insert(address.clone(), account));
|
||||||
@ -139,13 +216,14 @@ impl State {
|
|||||||
fn note_cache(&self, address: &Address) {
|
fn note_cache(&self, address: &Address) {
|
||||||
if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() {
|
if let Some(ref mut snapshot) = self.snapshots.borrow_mut().last_mut() {
|
||||||
if !snapshot.contains_key(address) {
|
if !snapshot.contains_key(address) {
|
||||||
snapshot.insert(address.clone(), self.cache.borrow().get(address).cloned());
|
snapshot.insert(address.clone(), self.cache.borrow().get(address).map(AccountEntry::clone_for_snapshot));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy the current object and return root and database.
|
/// Destroy the current object and return root and database.
|
||||||
pub fn drop(self) -> (H256, Box<JournalDB>) {
|
pub fn drop(mut self) -> (H256, StateDB) {
|
||||||
|
self.commit_cache();
|
||||||
(self.root, self.db)
|
(self.root, self.db)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,50 +235,99 @@ impl State {
|
|||||||
/// Create a new contract at address `contract`. If there is already an account at the address
|
/// Create a new contract at address `contract`. If there is already an account at the address
|
||||||
/// it will have its code reset, ready for `init_code()`.
|
/// it will have its code reset, ready for `init_code()`.
|
||||||
pub fn new_contract(&mut self, contract: &Address, balance: U256) {
|
pub fn new_contract(&mut self, contract: &Address, balance: U256) {
|
||||||
self.insert_cache(contract, Some(Account::new_contract(balance, self.account_start_nonce)));
|
self.insert_cache(contract, AccountEntry::Cached(Account::new_contract(balance, self.account_start_nonce)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an existing account.
|
/// Remove an existing account.
|
||||||
pub fn kill_account(&mut self, account: &Address) {
|
pub fn kill_account(&mut self, account: &Address) {
|
||||||
self.insert_cache(account, None);
|
self.insert_cache(account, AccountEntry::Killed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether an account exists.
|
/// Determine whether an account exists.
|
||||||
pub fn exists(&self, a: &Address) -> bool {
|
pub fn exists(&self, a: &Address) -> bool {
|
||||||
self.ensure_cached(a, false, |a| a.is_some())
|
self.ensure_cached(a, RequireCache::None, |a| a.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the balance of account `a`.
|
/// Get the balance of account `a`.
|
||||||
pub fn balance(&self, a: &Address) -> U256 {
|
pub fn balance(&self, a: &Address) -> U256 {
|
||||||
self.ensure_cached(a, false,
|
self.ensure_cached(a, RequireCache::None,
|
||||||
|a| a.as_ref().map_or(U256::zero(), |account| *account.balance()))
|
|a| a.as_ref().map_or(U256::zero(), |account| *account.balance()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the nonce of account `a`.
|
/// Get the nonce of account `a`.
|
||||||
pub fn nonce(&self, a: &Address) -> U256 {
|
pub fn nonce(&self, a: &Address) -> U256 {
|
||||||
self.ensure_cached(a, false,
|
self.ensure_cached(a, RequireCache::None,
|
||||||
|a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce()))
|
|a| a.as_ref().map_or(self.account_start_nonce, |account| *account.nonce()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mutate storage of account `address` so that it is `value` for `key`.
|
/// Mutate storage of account `address` so that it is `value` for `key`.
|
||||||
pub fn storage_at(&self, address: &Address, key: &H256) -> H256 {
|
pub fn storage_at(&self, address: &Address, key: &H256) -> H256 {
|
||||||
self.ensure_cached(address, false, |a| a.as_ref().map_or(H256::new(), |a| {
|
// Storage key search and update works like this:
|
||||||
let addr_hash = a.address_hash(address);
|
// 1. If there's an entry for the account in the local cache check for the key and return it if found.
|
||||||
let db = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
|
// 2. If there's an entry for the account in the global cache check for the key or load it into that account.
|
||||||
a.storage_at(db.as_hashdb(), key)
|
// 3. If account is missing in the global cache load it into the local cache and cache the key there.
|
||||||
}))
|
|
||||||
|
// check local cache first without updating
|
||||||
|
{
|
||||||
|
let local_cache = self.cache.borrow_mut();
|
||||||
|
let mut local_account = None;
|
||||||
|
if let Some(maybe_acc) = local_cache.get(address) {
|
||||||
|
match *maybe_acc {
|
||||||
|
AccountEntry::Cached(ref account) => {
|
||||||
|
if let Some(value) = account.cached_storage_at(key) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
local_account = Some(maybe_acc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => return H256::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check the global cache and and cache storage key there if found,
|
||||||
|
// otherwise cache the account localy and cache storage key there.
|
||||||
|
if let Some(result) = self.db.get_cached(address, |acc| acc.map_or(H256::new(), |a| {
|
||||||
|
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address));
|
||||||
|
a.storage_at(account_db.as_hashdb(), key)
|
||||||
|
})) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if let Some(ref mut acc) = local_account {
|
||||||
|
if let AccountEntry::Cached(ref account) = **acc {
|
||||||
|
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(address));
|
||||||
|
return account.storage_at(account_db.as_hashdb(), key)
|
||||||
|
} else {
|
||||||
|
return H256::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the code of account `a`.
|
// account is not found in the global cache, get from the DB and insert into local
|
||||||
|
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
||||||
|
let maybe_acc = match db.get(address) {
|
||||||
|
Ok(acc) => acc.map(Account::from_rlp),
|
||||||
|
Err(e) => panic!("Potential DB corruption encountered: {}", e),
|
||||||
|
};
|
||||||
|
let r = maybe_acc.as_ref().map_or(H256::new(), |a| {
|
||||||
|
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address));
|
||||||
|
a.storage_at(account_db.as_hashdb(), key)
|
||||||
|
});
|
||||||
|
match maybe_acc {
|
||||||
|
Some(account) => self.insert_cache(address, AccountEntry::Cached(account)),
|
||||||
|
None => self.insert_cache(address, AccountEntry::Missing),
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get accounts' code.
|
||||||
pub fn code(&self, a: &Address) -> Option<Bytes> {
|
pub fn code(&self, a: &Address) -> Option<Bytes> {
|
||||||
self.ensure_cached(a, true,
|
self.ensure_cached(a, RequireCache::Code,
|
||||||
|a| a.as_ref().map_or(None, |a| a.code().map(|x|x.to_vec())))
|
|a| a.as_ref().map_or(None, |a| a.code().map(|x|x.to_vec())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the code size of account `a`.
|
/// Get accounts' code size.
|
||||||
pub fn code_size(&self, a: &Address) -> Option<usize> {
|
pub fn code_size(&self, a: &Address) -> Option<usize> {
|
||||||
self.ensure_cached(a, true,
|
self.ensure_cached(a, RequireCache::CodeSize,
|
||||||
|a| a.as_ref().map_or(None, |a| a.code().map(|x| x.len())))
|
|a| a.as_ref().and_then(|a| a.code_size()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add `incr` to the balance of account `a`.
|
/// Add `incr` to the balance of account `a`.
|
||||||
@ -262,19 +389,19 @@ impl State {
|
|||||||
/// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit.
|
/// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit.
|
||||||
/// `accounts` is mutable because we may need to commit the code or storage and record that.
|
/// `accounts` is mutable because we may need to commit the code or storage and record that.
|
||||||
#[cfg_attr(feature="dev", allow(match_ref_pats))]
|
#[cfg_attr(feature="dev", allow(match_ref_pats))]
|
||||||
pub fn commit_into(
|
fn commit_into(
|
||||||
factories: &Factories,
|
factories: &Factories,
|
||||||
db: &mut HashDB,
|
db: &mut StateDB,
|
||||||
root: &mut H256,
|
root: &mut H256,
|
||||||
accounts: &mut HashMap<Address, Option<Account>>
|
accounts: &mut HashMap<Address, AccountEntry>
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// first, commit the sub trees.
|
// first, commit the sub trees.
|
||||||
// TODO: is this necessary or can we dispense with the `ref mut a` for just `a`?
|
// 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() {
|
for (address, ref mut a) in accounts.iter_mut() {
|
||||||
match a {
|
match a {
|
||||||
&mut&mut Some(ref mut account) if account.is_dirty() => {
|
&mut&mut AccountEntry::Cached(ref mut account) if account.is_dirty() => {
|
||||||
let addr_hash = account.address_hash(address);
|
let addr_hash = account.address_hash(address);
|
||||||
let mut account_db = factories.accountdb.create(db, addr_hash);
|
let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash);
|
||||||
account.commit_storage(&factories.trie, account_db.as_hashdb_mut());
|
account.commit_storage(&factories.trie, account_db.as_hashdb_mut());
|
||||||
account.commit_code(account_db.as_hashdb_mut());
|
account.commit_code(account_db.as_hashdb_mut());
|
||||||
}
|
}
|
||||||
@ -283,15 +410,18 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut trie = factories.trie.from_existing(db, root).unwrap();
|
let mut trie = factories.trie.from_existing(db.as_hashdb_mut(), root).unwrap();
|
||||||
for (address, ref mut a) in accounts.iter_mut() {
|
for (address, ref mut a) in accounts.iter_mut() {
|
||||||
match **a {
|
match **a {
|
||||||
Some(ref mut account) if account.is_dirty() => {
|
AccountEntry::Cached(ref mut account) if account.is_dirty() => {
|
||||||
account.set_clean();
|
account.set_clean();
|
||||||
try!(trie.insert(address, &account.rlp()))
|
try!(trie.insert(address, &account.rlp()));
|
||||||
},
|
},
|
||||||
None => try!(trie.remove(address)),
|
AccountEntry::Killed => {
|
||||||
_ => (),
|
try!(trie.remove(address));
|
||||||
|
**a = AccountEntry::Missing;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,10 +429,27 @@ impl State {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn commit_cache(&mut self) {
|
||||||
|
let mut addresses = self.cache.borrow_mut();
|
||||||
|
for (address, a) in addresses.drain() {
|
||||||
|
match a {
|
||||||
|
AccountEntry::Cached(account) => {
|
||||||
|
if !account.is_dirty() {
|
||||||
|
self.db.cache_account(address, Some(account));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AccountEntry::Missing => {
|
||||||
|
self.db.cache_account(address, None);
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Commits our cached account changes into the trie.
|
/// Commits our cached account changes into the trie.
|
||||||
pub fn commit(&mut self) -> Result<(), Error> {
|
pub fn commit(&mut self) -> Result<(), Error> {
|
||||||
assert!(self.snapshots.borrow().is_empty());
|
assert!(self.snapshots.borrow().is_empty());
|
||||||
Self::commit_into(&self.factories, self.db.as_hashdb_mut(), &mut self.root, &mut *self.cache.borrow_mut())
|
Self::commit_into(&self.factories, &mut self.db, &mut self.root, &mut *self.cache.borrow_mut())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear state cache
|
/// Clear state cache
|
||||||
@ -316,7 +463,7 @@ impl State {
|
|||||||
pub fn populate_from(&mut self, accounts: PodState) {
|
pub fn populate_from(&mut self, accounts: PodState) {
|
||||||
assert!(self.snapshots.borrow().is_empty());
|
assert!(self.snapshots.borrow().is_empty());
|
||||||
for (add, acc) in accounts.drain().into_iter() {
|
for (add, acc) in accounts.drain().into_iter() {
|
||||||
self.cache.borrow_mut().insert(add, Some(Account::from_pod(acc)));
|
self.cache.borrow_mut().insert(add, AccountEntry::Cached(Account::from_pod(acc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,7 +473,7 @@ impl State {
|
|||||||
// TODO: handle database rather than just the cache.
|
// TODO: handle database rather than just the cache.
|
||||||
// will need fat db.
|
// will need fat db.
|
||||||
PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
|
PodState::from(self.cache.borrow().iter().fold(BTreeMap::new(), |mut m, (add, opt)| {
|
||||||
if let Some(ref acc) = *opt {
|
if let AccountEntry::Cached(ref acc) = *opt {
|
||||||
m.insert(add.clone(), PodAccount::from_account(acc));
|
m.insert(add.clone(), PodAccount::from_account(acc));
|
||||||
}
|
}
|
||||||
m
|
m
|
||||||
@ -335,7 +482,7 @@ impl State {
|
|||||||
|
|
||||||
fn query_pod(&mut self, query: &PodState) {
|
fn query_pod(&mut self, query: &PodState) {
|
||||||
for (address, pod_account) in query.get() {
|
for (address, pod_account) in query.get() {
|
||||||
self.ensure_cached(address, true, |a| {
|
self.ensure_cached(address, RequireCache::Code, |a| {
|
||||||
if a.is_some() {
|
if a.is_some() {
|
||||||
for key in pod_account.storage.keys() {
|
for key in pod_account.storage.keys() {
|
||||||
self.storage_at(address, key);
|
self.storage_at(address, key);
|
||||||
@ -354,28 +501,61 @@ impl State {
|
|||||||
pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post)
|
pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure account `a` is in our cache of the trie DB and return a handle for getting it.
|
fn update_account_cache(require: RequireCache, account: &mut Account, db: &HashDB) {
|
||||||
/// `require_code` requires that the code be cached, too.
|
match require {
|
||||||
fn ensure_cached<'a, F, U>(&'a self, a: &'a Address, require_code: bool, f: F) -> U
|
RequireCache::None => {},
|
||||||
where F: FnOnce(&Option<Account>) -> U {
|
RequireCache::Code => {
|
||||||
let have_key = self.cache.borrow().contains_key(a);
|
account.cache_code(db);
|
||||||
if !have_key {
|
}
|
||||||
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
RequireCache::CodeSize => {
|
||||||
let maybe_acc = match db.get(a) {
|
account.cache_code_size(db);
|
||||||
Ok(acc) => acc.map(Account::from_rlp),
|
|
||||||
Err(e) => panic!("Potential DB corruption encountered: {}", e),
|
|
||||||
};
|
|
||||||
self.insert_cache(a, maybe_acc);
|
|
||||||
}
|
}
|
||||||
if require_code {
|
|
||||||
if let Some(ref mut account) = self.cache.borrow_mut().get_mut(a).unwrap().as_mut() {
|
|
||||||
let addr_hash = account.address_hash(a);
|
|
||||||
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
|
|
||||||
account.cache_code(accountdb.as_hashdb());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f(self.cache.borrow().get(a).unwrap())
|
/// Check caches for required data
|
||||||
|
/// First searches for account in the local, then the shared cache.
|
||||||
|
/// Populates local cache if nothing found.
|
||||||
|
fn ensure_cached<F, U>(&self, a: &Address, require: RequireCache, f: F) -> U
|
||||||
|
where F: Fn(Option<&Account>) -> U {
|
||||||
|
// check local cache first
|
||||||
|
if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) {
|
||||||
|
if let AccountEntry::Cached(ref mut account) = **maybe_acc {
|
||||||
|
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a));
|
||||||
|
Self::update_account_cache(require, account, accountdb.as_hashdb());
|
||||||
|
return f(Some(account));
|
||||||
|
}
|
||||||
|
return f(None);
|
||||||
|
}
|
||||||
|
// check global cache
|
||||||
|
let result = self.db.get_cached(a, |mut acc| {
|
||||||
|
if let Some(ref mut account) = acc {
|
||||||
|
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a));
|
||||||
|
Self::update_account_cache(require, account, accountdb.as_hashdb());
|
||||||
|
}
|
||||||
|
f(acc.map(|a| &*a))
|
||||||
|
});
|
||||||
|
match result {
|
||||||
|
Some(r) => r,
|
||||||
|
None => {
|
||||||
|
// not found in the global cache, get from the DB and insert into local
|
||||||
|
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
||||||
|
let mut maybe_acc = match db.get(a) {
|
||||||
|
Ok(acc) => acc.map(Account::from_rlp),
|
||||||
|
Err(e) => panic!("Potential DB corruption encountered: {}", e),
|
||||||
|
};
|
||||||
|
if let Some(ref mut account) = maybe_acc.as_mut() {
|
||||||
|
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a));
|
||||||
|
Self::update_account_cache(require, account, accountdb.as_hashdb());
|
||||||
|
}
|
||||||
|
let r = f(maybe_acc.as_ref());
|
||||||
|
match maybe_acc {
|
||||||
|
Some(account) => self.insert_cache(a, AccountEntry::Cached(account)),
|
||||||
|
None => self.insert_cache(a, AccountEntry::Missing),
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too.
|
/// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too.
|
||||||
@ -390,30 +570,40 @@ impl State {
|
|||||||
{
|
{
|
||||||
let contains_key = self.cache.borrow().contains_key(a);
|
let contains_key = self.cache.borrow().contains_key(a);
|
||||||
if !contains_key {
|
if !contains_key {
|
||||||
|
match self.db.get_cached_account(a) {
|
||||||
|
Some(Some(acc)) => self.insert_cache(a, AccountEntry::Cached(acc)),
|
||||||
|
Some(None) => self.insert_cache(a, AccountEntry::Missing),
|
||||||
|
None => {
|
||||||
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR);
|
||||||
let maybe_acc = match db.get(a) {
|
let maybe_acc = match db.get(a) {
|
||||||
Ok(acc) => acc.map(Account::from_rlp),
|
Ok(Some(acc)) => AccountEntry::Cached(Account::from_rlp(acc)),
|
||||||
|
Ok(None) => AccountEntry::Missing,
|
||||||
Err(e) => panic!("Potential DB corruption encountered: {}", e),
|
Err(e) => panic!("Potential DB corruption encountered: {}", e),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.insert_cache(a, maybe_acc);
|
self.insert_cache(a, maybe_acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.note_cache(a);
|
self.note_cache(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.cache.borrow_mut().get_mut(a).unwrap() {
|
match self.cache.borrow_mut().get_mut(a).unwrap() {
|
||||||
&mut Some(ref mut acc) => not_default(acc),
|
&mut AccountEntry::Cached(ref mut acc) => not_default(acc),
|
||||||
slot @ &mut None => *slot = Some(default()),
|
slot => *slot = AccountEntry::Cached(default()),
|
||||||
}
|
}
|
||||||
|
|
||||||
RefMut::map(self.cache.borrow_mut(), |c| {
|
RefMut::map(self.cache.borrow_mut(), |c| {
|
||||||
let account = c.get_mut(a).unwrap().as_mut().unwrap();
|
match c.get_mut(a).unwrap() {
|
||||||
|
&mut AccountEntry::Cached(ref mut account) => {
|
||||||
if require_code {
|
if require_code {
|
||||||
let addr_hash = account.address_hash(a);
|
let addr_hash = account.address_hash(a);
|
||||||
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
|
let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash);
|
||||||
account.cache_code(accountdb.as_hashdb());
|
account.cache_code(accountdb.as_hashdb());
|
||||||
}
|
}
|
||||||
account
|
account
|
||||||
|
},
|
||||||
|
_ => panic!("Required account must always exist; qed"),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,17 +617,10 @@ impl fmt::Debug for State {
|
|||||||
impl Clone for State {
|
impl Clone for State {
|
||||||
fn clone(&self) -> State {
|
fn clone(&self) -> State {
|
||||||
let cache = {
|
let cache = {
|
||||||
let mut cache = HashMap::new();
|
let mut cache: HashMap<Address, AccountEntry> = HashMap::new();
|
||||||
for (key, val) in self.cache.borrow().iter() {
|
for (key, val) in self.cache.borrow().iter() {
|
||||||
let key = key.clone();
|
if let Some(entry) = val.clone_dirty() {
|
||||||
match *val {
|
cache.insert(key.clone(), entry);
|
||||||
Some(ref acc) if acc.is_dirty() => {
|
|
||||||
cache.insert(key, Some(acc.clone()));
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
cache.insert(key, None);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache
|
cache
|
||||||
@ -447,7 +630,7 @@ impl Clone for State {
|
|||||||
db: self.db.boxed_clone(),
|
db: self.db.boxed_clone(),
|
||||||
root: self.root.clone(),
|
root: self.root.clone(),
|
||||||
cache: RefCell::new(cache),
|
cache: RefCell::new(cache),
|
||||||
snapshots: RefCell::new(self.snapshots.borrow().clone()),
|
snapshots: RefCell::new(Vec::new()),
|
||||||
account_start_nonce: self.account_start_nonce.clone(),
|
account_start_nonce: self.account_start_nonce.clone(),
|
||||||
factories: self.factories.clone(),
|
factories: self.factories.clone(),
|
||||||
}
|
}
|
||||||
|
161
ethcore/src/state_db.rs
Normal file
161
ethcore/src/state_db.rs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// 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 lru_cache::LruCache;
|
||||||
|
use util::journaldb::JournalDB;
|
||||||
|
use util::hash::{H256};
|
||||||
|
use util::hashdb::HashDB;
|
||||||
|
use util::{Arc, Address, DBTransaction, UtilError, Mutex};
|
||||||
|
use state::Account;
|
||||||
|
|
||||||
|
const STATE_CACHE_ITEMS: usize = 65536;
|
||||||
|
|
||||||
|
struct AccountCache {
|
||||||
|
/// DB Account cache. `None` indicates that account is known to be missing.
|
||||||
|
accounts: LruCache<Address, Option<Account>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// State database abstraction.
|
||||||
|
/// Manages shared global state cache.
|
||||||
|
/// A clone of `StateDB` may be created as canonical or not.
|
||||||
|
/// For canonical clones cache changes are accumulated and applied
|
||||||
|
/// on commit.
|
||||||
|
/// For non-canonical clones cache is cleared on commit.
|
||||||
|
pub struct StateDB {
|
||||||
|
db: Box<JournalDB>,
|
||||||
|
account_cache: Arc<Mutex<AccountCache>>,
|
||||||
|
cache_overlay: Vec<(Address, Option<Account>)>,
|
||||||
|
is_canon: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StateDB {
|
||||||
|
/// Create a new instance wrapping `JournalDB`
|
||||||
|
pub fn new(db: Box<JournalDB>) -> StateDB {
|
||||||
|
StateDB {
|
||||||
|
db: db,
|
||||||
|
account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(STATE_CACHE_ITEMS) })),
|
||||||
|
cache_overlay: Vec::new(),
|
||||||
|
is_canon: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit all recent insert operations and canonical historical commits' removals from the
|
||||||
|
/// old era to the backing database, reverting any non-canonical historical commit's inserts.
|
||||||
|
pub fn commit(&mut self, batch: &mut DBTransaction, now: u64, id: &H256, end: Option<(u64, H256)>) -> Result<u32, UtilError> {
|
||||||
|
let records = try!(self.db.commit(batch, now, id, end));
|
||||||
|
if self.is_canon {
|
||||||
|
self.commit_cache();
|
||||||
|
} else {
|
||||||
|
self.clear_cache();
|
||||||
|
}
|
||||||
|
Ok(records)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an interface to HashDB.
|
||||||
|
pub fn as_hashdb(&self) -> &HashDB {
|
||||||
|
self.db.as_hashdb()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an interface to mutable HashDB.
|
||||||
|
pub fn as_hashdb_mut(&mut self) -> &mut HashDB {
|
||||||
|
self.db.as_hashdb_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone the database.
|
||||||
|
pub fn boxed_clone(&self) -> StateDB {
|
||||||
|
StateDB {
|
||||||
|
db: self.db.boxed_clone(),
|
||||||
|
account_cache: self.account_cache.clone(),
|
||||||
|
cache_overlay: Vec::new(),
|
||||||
|
is_canon: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clone the database for a canonical state.
|
||||||
|
pub fn boxed_clone_canon(&self) -> StateDB {
|
||||||
|
StateDB {
|
||||||
|
db: self.db.boxed_clone(),
|
||||||
|
account_cache: self.account_cache.clone(),
|
||||||
|
cache_overlay: Vec::new(),
|
||||||
|
is_canon: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if pruning is enabled on the database.
|
||||||
|
pub fn is_pruned(&self) -> bool {
|
||||||
|
self.db.is_pruned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Heap size used.
|
||||||
|
pub fn mem_used(&self) -> usize {
|
||||||
|
self.db.mem_used() //TODO: + self.account_cache.lock().heap_size_of_children()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns underlying `JournalDB`.
|
||||||
|
pub fn journal_db(&self) -> &JournalDB {
|
||||||
|
&*self.db
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enqueue cache change.
|
||||||
|
pub fn cache_account(&mut self, addr: Address, data: Option<Account>) {
|
||||||
|
self.cache_overlay.push((addr, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply pending cache changes.
|
||||||
|
fn commit_cache(&mut self) {
|
||||||
|
let mut cache = self.account_cache.lock();
|
||||||
|
for (address, account) in self.cache_overlay.drain(..) {
|
||||||
|
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) {
|
||||||
|
if let Some(new) = account {
|
||||||
|
existing.merge_with(new);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cache.accounts.insert(address, account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the cache.
|
||||||
|
pub fn clear_cache(&mut self) {
|
||||||
|
self.cache_overlay.clear();
|
||||||
|
let mut cache = self.account_cache.lock();
|
||||||
|
cache.accounts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get basic copy of the cached account. Does not include storage.
|
||||||
|
/// Returns 'None' if the state is non-canonical and cache is disabled
|
||||||
|
/// or if the account is not cached.
|
||||||
|
pub fn get_cached_account(&self, addr: &Address) -> Option<Option<Account>> {
|
||||||
|
if !self.is_canon {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut cache = self.account_cache.lock();
|
||||||
|
cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get value from a cached account.
|
||||||
|
/// Returns 'None' if the state is non-canonical and cache is disabled
|
||||||
|
/// or if the account is not cached.
|
||||||
|
pub fn get_cached<F, U>(&self, a: &Address, f: F) -> Option<U>
|
||||||
|
where F: FnOnce(Option<&mut Account>) -> U {
|
||||||
|
if !self.is_canon {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut cache = self.account_cache.lock();
|
||||||
|
cache.accounts.get_mut(a).map(|c| f(c.as_mut()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ use io::*;
|
|||||||
use client::{BlockChainClient, Client, ClientConfig};
|
use client::{BlockChainClient, Client, ClientConfig};
|
||||||
use common::*;
|
use common::*;
|
||||||
use spec::*;
|
use spec::*;
|
||||||
|
use state_db::StateDB;
|
||||||
use block::{OpenBlock, Drain};
|
use block::{OpenBlock, Drain};
|
||||||
use blockchain::{BlockChain, Config as BlockChainConfig};
|
use blockchain::{BlockChain, Config as BlockChainConfig};
|
||||||
use state::*;
|
use state::*;
|
||||||
@ -146,9 +147,9 @@ pub fn generate_dummy_client_with_spec_and_data<F>(get_test_spec: F, block_numbe
|
|||||||
).unwrap();
|
).unwrap();
|
||||||
let test_engine = &*test_spec.engine;
|
let test_engine = &*test_spec.engine;
|
||||||
|
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
test_spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
test_spec.ensure_db_good(&mut db).unwrap();
|
||||||
let genesis_header = test_spec.genesis_header();
|
let genesis_header = test_spec.genesis_header();
|
||||||
|
|
||||||
let mut rolling_timestamp = 40;
|
let mut rolling_timestamp = 40;
|
||||||
@ -321,9 +322,9 @@ pub fn generate_dummy_empty_blockchain() -> GuardedTempResult<BlockChain> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temp_journal_db() -> GuardedTempResult<Box<JournalDB>> {
|
pub fn get_temp_state_db() -> GuardedTempResult<StateDB> {
|
||||||
let temp = RandomTempPath::new();
|
let temp = RandomTempPath::new();
|
||||||
let journal_db = get_temp_journal_db_in(temp.as_path());
|
let journal_db = get_temp_state_db_in(temp.as_path());
|
||||||
|
|
||||||
GuardedTempResult {
|
GuardedTempResult {
|
||||||
_temp: temp,
|
_temp: temp,
|
||||||
@ -333,7 +334,7 @@ pub fn get_temp_journal_db() -> GuardedTempResult<Box<JournalDB>> {
|
|||||||
|
|
||||||
pub fn get_temp_state() -> GuardedTempResult<State> {
|
pub fn get_temp_state() -> GuardedTempResult<State> {
|
||||||
let temp = RandomTempPath::new();
|
let temp = RandomTempPath::new();
|
||||||
let journal_db = get_temp_journal_db_in(temp.as_path());
|
let journal_db = get_temp_state_db_in(temp.as_path());
|
||||||
|
|
||||||
GuardedTempResult {
|
GuardedTempResult {
|
||||||
_temp: temp,
|
_temp: temp,
|
||||||
@ -341,13 +342,14 @@ pub fn get_temp_state() -> GuardedTempResult<State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temp_journal_db_in(path: &Path) -> Box<JournalDB> {
|
pub fn get_temp_state_db_in(path: &Path) -> StateDB {
|
||||||
let db = new_db(path.to_str().expect("Only valid utf8 paths for tests."));
|
let db = new_db(path.to_str().expect("Only valid utf8 paths for tests."));
|
||||||
journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, None)
|
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, None);
|
||||||
|
StateDB::new(journal_db)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_temp_state_in(path: &Path) -> State {
|
pub fn get_temp_state_in(path: &Path) -> State {
|
||||||
let journal_db = get_temp_journal_db_in(path);
|
let journal_db = get_temp_state_db_in(path);
|
||||||
State::new(journal_db, U256::from(0), Default::default())
|
State::new(journal_db, U256::from(0), Default::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user