Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit abbf0ef

Browse files
committed
Handle reorganizations in the state cache
1 parent ecf098e commit abbf0ef

File tree

3 files changed

+206
-49
lines changed

3 files changed

+206
-49
lines changed

ethcore/src/client/client.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,7 @@ impl Client {
304304
// Enact Verified Block
305305
let parent = chain_has_parent.unwrap();
306306
let last_hashes = self.build_last_hashes(header.parent_hash().clone());
307-
let is_canon = header.parent_hash() == &chain.best_block_hash();
308-
let db = if is_canon { self.state_db.lock().boxed_clone_canon() } else { self.state_db.lock().boxed_clone() };
307+
let db = self.state_db.lock().boxed_clone_canon(&header.parent_hash());
309308

310309
let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone());
311310
if let Err(e) = enact_result {
@@ -443,8 +442,8 @@ impl Client {
443442
.collect();
444443

445444
//let traces = From::from(block.traces().clone().unwrap_or_else(Vec::new));
446-
447445
let mut batch = DBTransaction::new(&self.db.read());
446+
448447
// CHECK! I *think* this is fine, even if the state_root is equal to another
449448
// already-imported block of the same number.
450449
// TODO: Prove it with a test.
@@ -459,6 +458,8 @@ impl Client {
459458
enacted: route.enacted.clone(),
460459
retracted: route.retracted.len()
461460
});
461+
let is_canon = route.omitted.len() == 0;
462+
state.update_cache(&route.enacted, &route.retracted, is_canon);
462463
// Final commit to the DB
463464
self.db.read().write_buffered(batch);
464465
chain.commit();
@@ -533,9 +534,11 @@ impl Client {
533534

534535
/// Get a copy of the best block's state.
535536
pub fn state(&self) -> State {
537+
let header = self.best_block_header();
538+
let header = HeaderView::new(&header);
536539
State::from_existing(
537-
self.state_db.lock().boxed_clone(),
538-
HeaderView::new(&self.best_block_header()).state_root(),
540+
self.state_db.lock().boxed_clone_canon(&header.hash()),
541+
header.state_root(),
539542
self.engine.account_start_nonce(),
540543
self.factories.clone())
541544
.expect("State root of best block header always valid.")

ethcore/src/state/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ impl State {
486486
let mut addresses = self.cache.borrow_mut();
487487
trace!("Committing cache {:?} entries", addresses.len());
488488
for (address, a) in addresses.drain().filter(|&(_, ref a)| !a.is_dirty()) {
489-
self.db.cache_account(address, a.account);
489+
self.db.cache_account(address, a.account, a.state == AccountState::Commited);
490490
}
491491
}
492492

ethcore/src/state_db.rs

+197-43
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
1616

17+
use std::collections::{VecDeque, HashSet};
1718
use lru_cache::LruCache;
1819
use util::journaldb::JournalDB;
1920
use util::hash::{H256};
2021
use util::hashdb::HashDB;
2122
use state::Account;
23+
use header::BlockNumber;
2224
use util::{Arc, Address, Database, DBTransaction, UtilError, Mutex, Hashable};
2325
use bloom_journal::{Bloom, BloomJournal};
2426
use db::COL_ACCOUNT_BLOOM;
2527
use byteorder::{LittleEndian, ByteOrder};
2628

2729
const STATE_CACHE_ITEMS: usize = 65536;
30+
const STATE_CACHE_BLOCKS: usize = 8;
31+
2832

2933
pub const ACCOUNT_BLOOM_SPACE: usize = 1048576;
3034
pub const DEFAULT_ACCOUNT_PRESET: usize = 1000000;
@@ -34,6 +38,24 @@ pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count";
3438
struct AccountCache {
3539
/// DB Account cache. `None` indicates that account is known to be missing.
3640
accounts: LruCache<Address, Option<Account>>,
41+
/// Accounts changed in recent blocks. Ordered by block number.
42+
modifications: VecDeque<BlockChanges>,
43+
}
44+
45+
struct CacheQueueItem {
46+
address: Address,
47+
account: Option<Account>,
48+
modified: bool,
49+
}
50+
51+
#[derive(Debug)]
52+
// Accumulates a list of accounts changed in a block.
53+
struct BlockChanges {
54+
number: BlockNumber,
55+
hash: H256,
56+
parent: H256,
57+
accounts: HashSet<Address>,
58+
is_canon: bool,
3759
}
3860

3961
/// State database abstraction.
@@ -43,27 +65,23 @@ struct AccountCache {
4365
/// on commit.
4466
/// For non-canonical clones cache is cleared on commit.
4567
pub struct StateDB {
68+
// Backing database.
4669
db: Box<JournalDB>,
70+
// Shared canonical state cache.
4771
account_cache: Arc<Mutex<AccountCache>>,
48-
cache_overlay: Vec<(Address, Option<Account>)>,
49-
is_canon: bool,
72+
// Local pending cache changes.
73+
cache_overlay: Vec<CacheQueueItem>,
74+
// Shared account bloom. Does not handle chain reorganizations.
5075
account_bloom: Arc<Mutex<Bloom>>,
76+
// Hash of the block on top of which this instance was created
77+
parent_hash: Option<H256>,
78+
// Hash of the committing block
79+
commit_hash: Option<H256>,
80+
// Number of the committing block
81+
commit_number: Option<BlockNumber>,
5182
}
5283

5384
impl StateDB {
54-
55-
/// Create a new instance wrapping `JournalDB`
56-
pub fn new(db: Box<JournalDB>) -> StateDB {
57-
let bloom = Self::load_bloom(db.backing());
58-
StateDB {
59-
db: db,
60-
account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(STATE_CACHE_ITEMS) })),
61-
cache_overlay: Vec::new(),
62-
is_canon: false,
63-
account_bloom: Arc::new(Mutex::new(bloom)),
64-
}
65-
}
66-
6785
/// Loads accounts bloom from the database
6886
/// This bloom is used to handle request for the non-existant account fast
6987
pub fn load_bloom(db: &Database) -> Bloom {
@@ -91,6 +109,23 @@ impl StateDB {
91109
bloom
92110
}
93111

112+
/// Create a new instance wrapping `JournalDB`
113+
pub fn new(db: Box<JournalDB>) -> StateDB {
114+
let bloom = Self::load_bloom(db.backing());
115+
StateDB {
116+
db: db,
117+
account_cache: Arc::new(Mutex::new(AccountCache {
118+
accounts: LruCache::new(STATE_CACHE_ITEMS),
119+
modifications: VecDeque::new(),
120+
})),
121+
cache_overlay: Vec::new(),
122+
account_bloom: Arc::new(Mutex::new(bloom)),
123+
parent_hash: None,
124+
commit_hash: None,
125+
commit_number: None,
126+
}
127+
}
128+
94129
pub fn check_account_bloom(&self, address: &Address) -> bool {
95130
trace!(target: "account_bloom", "Check account bloom: {:?}", address);
96131
let bloom = self.account_bloom.lock();
@@ -125,14 +160,103 @@ impl StateDB {
125160
try!(Self::commit_bloom(batch, bloom_lock.drain_journal()));
126161
}
127162
let records = try!(self.db.commit(batch, now, id, end));
128-
if self.is_canon {
129-
self.commit_cache();
130-
} else {
131-
self.clear_cache();
132-
}
163+
self.commit_hash = Some(id.clone());
164+
self.commit_number = Some(now);
133165
Ok(records)
134166
}
135167

168+
/// Update canonical cache. This should be called after the block has been commited and the
169+
/// blockchain route has ben calculated.
170+
/// `retracted` is the list of the retracted block hashes.
171+
pub fn update_cache(&mut self, enacted: &[H256], retracted: &[H256], is_best: bool) {
172+
trace!("commit id = (#{:?}, {:?}), parent={:?}, best={}", self.commit_number, self.commit_hash, self.parent_hash, is_best);
173+
let mut cache = self.account_cache.lock();
174+
let mut cache = &mut *cache;
175+
176+
// Clean changes from re-enacted and retracted blocks
177+
let mut clear = false;
178+
for block in enacted.iter().filter(|h| self.commit_hash.as_ref().map_or(false, |p| *h != p)) {
179+
clear = clear || {
180+
if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) {
181+
trace!("reverting enacted block {:?}", block);
182+
m.is_canon = true;
183+
for a in &m.accounts {
184+
trace!("reverting enacted address {:?}", a);
185+
cache.accounts.remove(a);
186+
}
187+
false
188+
} else {
189+
true
190+
}
191+
};
192+
}
193+
194+
for block in retracted {
195+
clear = clear || {
196+
if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) {
197+
trace!("retracting block {:?}", block);
198+
m.is_canon = false;
199+
for a in &m.accounts {
200+
trace!("retracted address {:?}", a);
201+
cache.accounts.remove(a);
202+
}
203+
false
204+
} else {
205+
true
206+
}
207+
};
208+
}
209+
if clear {
210+
// We don't know anything about the block; clear everything
211+
trace!("wiping cache");
212+
cache.accounts.clear();
213+
cache.modifications.clear();
214+
}
215+
216+
// Apply cache changes only if committing on top of the latest canonical state
217+
// blocks are ordered by number and only one block with a given number is marked as canonical
218+
// (contributed to canonical state cache)
219+
if let (Some(ref number), Some(ref hash), Some(ref parent)) = (self.commit_number, self.commit_hash, self.parent_hash) {
220+
if cache.modifications.len() == STATE_CACHE_BLOCKS {
221+
cache.modifications.pop_back();
222+
}
223+
let mut modifications = HashSet::new();
224+
trace!("committing {} cache entries", self.cache_overlay.len());
225+
for account in self.cache_overlay.drain(..) {
226+
if account.modified {
227+
modifications.insert(account.address.clone());
228+
}
229+
if is_best {
230+
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&account.address) {
231+
if let Some(new) = account.account {
232+
if account.modified {
233+
existing.merge_with(new);
234+
}
235+
continue;
236+
}
237+
}
238+
cache.accounts.insert(account.address, account.account);
239+
}
240+
}
241+
242+
// Save modified accounts. These are ordered by the block number.
243+
let block_changes = BlockChanges {
244+
accounts: modifications,
245+
number: *number,
246+
hash: hash.clone(),
247+
is_canon: is_best,
248+
parent: parent.clone(),
249+
};
250+
let insert_at = cache.modifications.iter().enumerate().find(|&(_, ref m)| m.number < *number).map(|(i, _)| i);
251+
trace!("inserting modifications at {:?}", insert_at);
252+
if let Some(insert_at) = insert_at {
253+
cache.modifications.insert(insert_at, block_changes);
254+
} else {
255+
cache.modifications.push_back(block_changes);
256+
}
257+
}
258+
}
259+
136260
/// Returns an interface to HashDB.
137261
pub fn as_hashdb(&self) -> &HashDB {
138262
self.db.as_hashdb()
@@ -149,19 +273,23 @@ impl StateDB {
149273
db: self.db.boxed_clone(),
150274
account_cache: self.account_cache.clone(),
151275
cache_overlay: Vec::new(),
152-
is_canon: false,
153276
account_bloom: self.account_bloom.clone(),
277+
parent_hash: None,
278+
commit_hash: None,
279+
commit_number: None,
154280
}
155281
}
156282

157283
/// Clone the database for a canonical state.
158-
pub fn boxed_clone_canon(&self) -> StateDB {
284+
pub fn boxed_clone_canon(&self, parent: &H256) -> StateDB {
159285
StateDB {
160286
db: self.db.boxed_clone(),
161287
account_cache: self.account_cache.clone(),
162288
cache_overlay: Vec::new(),
163-
is_canon: true,
164289
account_bloom: self.account_bloom.clone(),
290+
parent_hash: Some(parent.clone()),
291+
commit_hash: None,
292+
commit_number: None,
165293
}
166294
}
167295

@@ -181,26 +309,17 @@ impl StateDB {
181309
}
182310

183311
/// Enqueue cache change.
184-
pub fn cache_account(&mut self, addr: Address, data: Option<Account>) {
185-
self.cache_overlay.push((addr, data));
186-
}
187-
188-
/// Apply pending cache changes.
189-
fn commit_cache(&mut self) {
190-
let mut cache = self.account_cache.lock();
191-
for (address, account) in self.cache_overlay.drain(..) {
192-
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) {
193-
if let Some(new) = account {
194-
existing.merge_with(new);
195-
continue;
196-
}
197-
}
198-
cache.accounts.insert(address, account);
199-
}
312+
pub fn cache_account(&mut self, addr: Address, data: Option<Account>, modified: bool) {
313+
self.cache_overlay.push(CacheQueueItem {
314+
address: addr,
315+
account: data,
316+
modified: modified,
317+
})
200318
}
201319

202320
/// Clear the cache.
203321
pub fn clear_cache(&mut self) {
322+
trace!("Clearing cache");
204323
self.cache_overlay.clear();
205324
let mut cache = self.account_cache.lock();
206325
cache.accounts.clear();
@@ -210,10 +329,10 @@ impl StateDB {
210329
/// Returns 'None' if the state is non-canonical and cache is disabled
211330
/// or if the account is not cached.
212331
pub fn get_cached_account(&self, addr: &Address) -> Option<Option<Account>> {
213-
if !self.is_canon {
332+
let mut cache = self.account_cache.lock();
333+
if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) {
214334
return None;
215335
}
216-
let mut cache = self.account_cache.lock();
217336
cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic()))
218337
}
219338

@@ -222,11 +341,46 @@ impl StateDB {
222341
/// or if the account is not cached.
223342
pub fn get_cached<F, U>(&self, a: &Address, f: F) -> Option<U>
224343
where F: FnOnce(Option<&mut Account>) -> U {
225-
if !self.is_canon {
344+
let mut cache = self.account_cache.lock();
345+
if !Self::is_allowed(a, &self.parent_hash, &cache.modifications) {
226346
return None;
227347
}
228-
let mut cache = self.account_cache.lock();
229348
cache.accounts.get_mut(a).map(|c| f(c.as_mut()))
230349
}
350+
351+
// Check if the account can be returned from cache by matching current block parent hash against canonical
352+
// state and filtering out account modified in later blocks.
353+
fn is_allowed(addr: &Address, parent_hash: &Option<H256>, modifications: &VecDeque<BlockChanges>) -> bool {
354+
let mut parent = match *parent_hash {
355+
None => {
356+
trace!("cache lookup skipped for {:?}: no parent hash", addr);
357+
return false;
358+
}
359+
Some(ref parent) => parent,
360+
};
361+
if modifications.is_empty() {
362+
return true;
363+
}
364+
// Ignore all accounts modified in later blocks
365+
// Modifications contains block ordered by the number
366+
// We search for our parent in that list first and then for
367+
// all its parent until we hit the canonical block,
368+
// checking against all the intermediate modifications.
369+
let mut iter = modifications.iter();
370+
while let Some(ref m) = iter.next() {
371+
if &m.hash == parent {
372+
if m.is_canon {
373+
return true;
374+
}
375+
parent = &m.parent;
376+
}
377+
if m.accounts.contains(addr) {
378+
trace!("cache lookup skipped for {:?}: modified in a later block", addr);
379+
return false;
380+
}
381+
}
382+
trace!("cache lookup skipped for {:?}: parent hash is unknown", addr);
383+
return false;
384+
}
231385
}
232386

0 commit comments

Comments
 (0)