14
14
// You should have received a copy of the GNU General Public License
15
15
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
16
16
17
+ use std:: collections:: { VecDeque , HashSet } ;
17
18
use lru_cache:: LruCache ;
18
19
use util:: journaldb:: JournalDB ;
19
20
use util:: hash:: { H256 } ;
20
21
use util:: hashdb:: HashDB ;
21
22
use state:: Account ;
23
+ use header:: BlockNumber ;
22
24
use util:: { Arc , Address , Database , DBTransaction , UtilError , Mutex , Hashable } ;
23
25
use bloom_journal:: { Bloom , BloomJournal } ;
24
26
use db:: COL_ACCOUNT_BLOOM ;
25
27
use byteorder:: { LittleEndian , ByteOrder } ;
26
28
27
29
const STATE_CACHE_ITEMS : usize = 65536 ;
30
+ const STATE_CACHE_BLOCKS : usize = 8 ;
31
+
28
32
29
33
pub const ACCOUNT_BLOOM_SPACE : usize = 1048576 ;
30
34
pub const DEFAULT_ACCOUNT_PRESET : usize = 1000000 ;
@@ -34,6 +38,24 @@ pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count";
34
38
struct AccountCache {
35
39
/// DB Account cache. `None` indicates that account is known to be missing.
36
40
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 ,
37
59
}
38
60
39
61
/// State database abstraction.
@@ -43,27 +65,23 @@ struct AccountCache {
43
65
/// on commit.
44
66
/// For non-canonical clones cache is cleared on commit.
45
67
pub struct StateDB {
68
+ // Backing database.
46
69
db : Box < JournalDB > ,
70
+ // Shared canonical state cache.
47
71
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.
50
75
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 > ,
51
82
}
52
83
53
84
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
-
67
85
/// Loads accounts bloom from the database
68
86
/// This bloom is used to handle request for the non-existant account fast
69
87
pub fn load_bloom ( db : & Database ) -> Bloom {
@@ -91,6 +109,23 @@ impl StateDB {
91
109
bloom
92
110
}
93
111
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
+
94
129
pub fn check_account_bloom ( & self , address : & Address ) -> bool {
95
130
trace ! ( target: "account_bloom" , "Check account bloom: {:?}" , address) ;
96
131
let bloom = self . account_bloom . lock ( ) ;
@@ -125,14 +160,103 @@ impl StateDB {
125
160
try!( Self :: commit_bloom ( batch, bloom_lock. drain_journal ( ) ) ) ;
126
161
}
127
162
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) ;
133
165
Ok ( records)
134
166
}
135
167
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
+
136
260
/// Returns an interface to HashDB.
137
261
pub fn as_hashdb ( & self ) -> & HashDB {
138
262
self . db . as_hashdb ( )
@@ -149,19 +273,23 @@ impl StateDB {
149
273
db : self . db . boxed_clone ( ) ,
150
274
account_cache : self . account_cache . clone ( ) ,
151
275
cache_overlay : Vec :: new ( ) ,
152
- is_canon : false ,
153
276
account_bloom : self . account_bloom . clone ( ) ,
277
+ parent_hash : None ,
278
+ commit_hash : None ,
279
+ commit_number : None ,
154
280
}
155
281
}
156
282
157
283
/// 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 {
159
285
StateDB {
160
286
db : self . db . boxed_clone ( ) ,
161
287
account_cache : self . account_cache . clone ( ) ,
162
288
cache_overlay : Vec :: new ( ) ,
163
- is_canon : true ,
164
289
account_bloom : self . account_bloom . clone ( ) ,
290
+ parent_hash : Some ( parent. clone ( ) ) ,
291
+ commit_hash : None ,
292
+ commit_number : None ,
165
293
}
166
294
}
167
295
@@ -181,26 +309,17 @@ impl StateDB {
181
309
}
182
310
183
311
/// 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
+ } )
200
318
}
201
319
202
320
/// Clear the cache.
203
321
pub fn clear_cache ( & mut self ) {
322
+ trace ! ( "Clearing cache" ) ;
204
323
self . cache_overlay . clear ( ) ;
205
324
let mut cache = self . account_cache . lock ( ) ;
206
325
cache. accounts . clear ( ) ;
@@ -210,10 +329,10 @@ impl StateDB {
210
329
/// Returns 'None' if the state is non-canonical and cache is disabled
211
330
/// or if the account is not cached.
212
331
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 ) {
214
334
return None ;
215
335
}
216
- let mut cache = self . account_cache . lock ( ) ;
217
336
cache. accounts . get_mut ( & addr) . map ( |a| a. as_ref ( ) . map ( |a| a. clone_basic ( ) ) )
218
337
}
219
338
@@ -222,11 +341,46 @@ impl StateDB {
222
341
/// or if the account is not cached.
223
342
pub fn get_cached < F , U > ( & self , a : & Address , f : F ) -> Option < U >
224
343
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 ) {
226
346
return None ;
227
347
}
228
- let mut cache = self . account_cache . lock ( ) ;
229
348
cache. accounts . get_mut ( a) . map ( |c| f ( c. as_mut ( ) ) )
230
349
}
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
+ }
231
385
}
232
386
0 commit comments