@@ -23,25 +23,29 @@ use kvdb::{KeyValueDB, DBTransaction};
2323
2424use client:: blockchain:: { BlockStatus , Cache as BlockchainCache ,
2525 HeaderBackend as BlockchainHeaderBackend , Info as BlockchainInfo } ;
26+ use client:: cht;
2627use client:: error:: { ErrorKind as ClientErrorKind , Result as ClientResult } ;
2728use client:: light:: blockchain:: Storage as LightBlockchainStorage ;
2829use codec:: { Decode , Encode } ;
29- use primitives:: AuthorityId ;
30+ use primitives:: { AuthorityId , H256 , KeccakHasher } ;
3031use runtime_primitives:: generic:: BlockId ;
31- use runtime_primitives:: traits:: { Block as BlockT , Header as HeaderT , Hash , HashFor , Zero , As } ;
32+ use runtime_primitives:: traits:: { Block as BlockT , Header as HeaderT , Hash , HashFor ,
33+ Zero , One , As , NumberFor } ;
3234use cache:: DbCache ;
33- use utils:: { meta_keys, Meta , db_err, number_to_db_key, open_database, read_db, read_id, read_meta} ;
35+ use utils:: { meta_keys, Meta , db_err, number_to_db_key, db_key_to_number, open_database,
36+ read_db, read_id, read_meta} ;
3437use DatabaseSettings ;
3538
3639pub ( crate ) mod columns {
3740 pub const META : Option < u32 > = :: utils:: COLUMN_META ;
3841 pub const BLOCK_INDEX : Option < u32 > = Some ( 1 ) ;
3942 pub const HEADER : Option < u32 > = Some ( 2 ) ;
4043 pub const AUTHORITIES : Option < u32 > = Some ( 3 ) ;
44+ pub const CHT : Option < u32 > = Some ( 4 ) ;
4145}
4246
4347/// Keep authorities for last 'AUTHORITIES_ENTRIES_TO_KEEP' blocks.
44- pub ( crate ) const AUTHORITIES_ENTRIES_TO_KEEP : u64 = 2048 ;
48+ pub ( crate ) const AUTHORITIES_ENTRIES_TO_KEEP : u64 = cht :: SIZE ;
4549
4650/// Light blockchain storage. Stores most recent headers + CHTs for older headers.
4751pub struct LightStorage < Block : BlockT > {
@@ -146,6 +150,14 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
146150 }
147151 }
148152
153+ fn number ( & self , hash : Block :: Hash ) -> ClientResult < Option < <<Block as BlockT >:: Header as HeaderT >:: Number > > {
154+ read_id :: < Block > ( & * self . db , columns:: BLOCK_INDEX , BlockId :: Hash ( hash) )
155+ . and_then ( |key| match key {
156+ Some ( key) => Ok ( Some ( db_key_to_number ( & key) ?) ) ,
157+ None => Ok ( None ) ,
158+ } )
159+ }
160+
149161 fn hash ( & self , number : <<Block as BlockT >:: Header as HeaderT >:: Number ) -> ClientResult < Option < Block :: Hash > > {
150162 read_db :: < Block > ( & * self . db , columns:: BLOCK_INDEX , columns:: HEADER , BlockId :: Number ( number) ) . map ( |x|
151163 x. map ( |raw| HashFor :: < Block > :: hash ( & raw [ ..] ) ) . map ( Into :: into)
@@ -156,6 +168,7 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
156168impl < Block > LightBlockchainStorage < Block > for LightStorage < Block >
157169 where
158170 Block : BlockT ,
171+ Block :: Hash : From < H256 > ,
159172{
160173 fn import_header ( & self , is_new_best : bool , header : Block :: Header , authorities : Option < Vec < AuthorityId > > ) -> ClientResult < ( ) > {
161174 let mut transaction = DBTransaction :: new ( ) ;
@@ -187,6 +200,27 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
187200 None
188201 } ;
189202
203+ // build new CHT if required
204+ if let Some ( new_cht_number) = cht:: is_build_required ( cht:: SIZE , * header. number ( ) ) {
205+ let new_cht_start: NumberFor < Block > = cht:: start_number ( cht:: SIZE , new_cht_number) ;
206+ let new_cht_root: Option < Block :: Hash > = cht:: compute_root :: < Block :: Header , KeccakHasher , _ > (
207+ cht:: SIZE , new_cht_number, ( new_cht_start. as_ ( ) ..)
208+ . map ( |num| self . hash ( As :: sa ( num) ) . unwrap_or_default ( ) ) ) ;
209+
210+ if let Some ( new_cht_root) = new_cht_root {
211+ transaction. put ( columns:: CHT , & number_to_db_key ( new_cht_start) , new_cht_root. as_ref ( ) ) ;
212+
213+ let mut prune_block = new_cht_start;
214+ let new_cht_end = cht:: end_number ( cht:: SIZE , new_cht_number) ;
215+ trace ! ( target: "db" , "Replacing blocks [{}..{}] with CHT#{}" , new_cht_start, new_cht_end, new_cht_number) ;
216+
217+ while prune_block <= new_cht_end {
218+ transaction. delete ( columns:: HEADER , & number_to_db_key ( prune_block) ) ;
219+ prune_block += <<Block as BlockT >:: Header as HeaderT >:: Number :: one ( ) ;
220+ }
221+ }
222+ }
223+
190224 debug ! ( "Light DB Commit {:?} ({})" , hash, number) ;
191225 self . db . write ( transaction) . map_err ( db_err) ?;
192226 self . update_meta ( hash, number, is_new_best) ;
@@ -197,13 +231,24 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
197231 Ok ( ( ) )
198232 }
199233
234+ fn cht_root ( & self , cht_size : u64 , block : <<Block as BlockT >:: Header as HeaderT >:: Number ) -> ClientResult < Block :: Hash > {
235+ let no_cht_for_block = || ClientErrorKind :: Backend ( format ! ( "CHT for block {} not exists" , block) ) . into ( ) ;
236+
237+ let cht_number = cht:: block_to_cht_number ( cht_size, block) . ok_or_else ( no_cht_for_block) ?;
238+ let cht_start = cht:: start_number ( cht_size, cht_number) ;
239+ self . db . get ( columns:: CHT , & number_to_db_key ( cht_start) ) . map_err ( db_err) ?
240+ . ok_or_else ( no_cht_for_block)
241+ . and_then ( |hash| Block :: Hash :: decode ( & mut & * hash) . ok_or_else ( no_cht_for_block) )
242+ }
243+
200244 fn cache ( & self ) -> Option < & BlockchainCache < Block > > {
201245 Some ( & self . cache )
202246 }
203247}
204248
205249#[ cfg( test) ]
206250pub ( crate ) mod tests {
251+ use client:: cht;
207252 use runtime_primitives:: testing:: { H256 as Hash , Header , Block as RawBlock } ;
208253 use super :: * ;
209254
@@ -289,4 +334,59 @@ pub(crate) mod tests {
289334 assert_eq ! ( db. db. iter( columns:: HEADER ) . count( ) , 2 ) ;
290335 assert_eq ! ( db. db. iter( columns:: BLOCK_INDEX ) . count( ) , 2 ) ;
291336 }
337+
338+ #[ test]
339+ fn ancient_headers_are_replaced_with_cht ( ) {
340+ let db = LightStorage :: new_test ( ) ;
341+
342+ // insert genesis block header (never pruned)
343+ let mut prev_hash = insert_block ( & db, & Default :: default ( ) , 0 , None ) ;
344+
345+ // insert SIZE blocks && ensure that nothing is pruned
346+ for number in 0 ..cht:: SIZE {
347+ prev_hash = insert_block ( & db, & prev_hash, 1 + number, None ) ;
348+ }
349+ assert_eq ! ( db. db. iter( columns:: HEADER ) . count( ) , ( 1 + cht:: SIZE ) as usize ) ;
350+ assert_eq ! ( db. db. iter( columns:: CHT ) . count( ) , 0 ) ;
351+
352+ // insert next SIZE blocks && ensure that nothing is pruned
353+ for number in 0 ..cht:: SIZE {
354+ prev_hash = insert_block ( & db, & prev_hash, 1 + cht:: SIZE + number, None ) ;
355+ }
356+ assert_eq ! ( db. db. iter( columns:: HEADER ) . count( ) , ( 1 + cht:: SIZE + cht:: SIZE ) as usize ) ;
357+ assert_eq ! ( db. db. iter( columns:: CHT ) . count( ) , 0 ) ;
358+
359+ // insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned
360+ insert_block ( & db, & prev_hash, 1 + cht:: SIZE + cht:: SIZE , None ) ;
361+ assert_eq ! ( db. db. iter( columns:: HEADER ) . count( ) , ( 1 + cht:: SIZE + 1 ) as usize ) ;
362+ assert_eq ! ( db. db. iter( columns:: CHT ) . count( ) , 1 ) ;
363+ assert ! ( ( 0 ..cht:: SIZE ) . all( |i| db. db. get( columns:: HEADER , & number_to_db_key( 1 + i) ) . unwrap( ) . is_none( ) ) ) ;
364+ }
365+
366+ #[ test]
367+ fn get_cht_fails_for_genesis_block ( ) {
368+ assert ! ( LightStorage :: <Block >:: new_test( ) . cht_root( cht:: SIZE , 0 ) . is_err( ) ) ;
369+ }
370+
371+ #[ test]
372+ fn get_cht_fails_for_non_existant_cht ( ) {
373+ assert ! ( LightStorage :: <Block >:: new_test( ) . cht_root( cht:: SIZE , ( cht:: SIZE / 2 ) as u64 ) . is_err( ) ) ;
374+ }
375+
376+ #[ test]
377+ fn get_cht_works ( ) {
378+ let db = LightStorage :: new_test ( ) ;
379+
380+ // insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created
381+ let mut prev_hash = Default :: default ( ) ;
382+ for i in 0 ..1 + cht:: SIZE + cht:: SIZE + 1 {
383+ prev_hash = insert_block ( & db, & prev_hash, i as u64 , None ) ;
384+ }
385+
386+ let cht_root_1 = db. cht_root ( cht:: SIZE , cht:: start_number ( cht:: SIZE , 0 ) ) . unwrap ( ) ;
387+ let cht_root_2 = db. cht_root ( cht:: SIZE , ( cht:: start_number ( cht:: SIZE , 0 ) + cht:: SIZE / 2 ) as u64 ) . unwrap ( ) ;
388+ let cht_root_3 = db. cht_root ( cht:: SIZE , cht:: end_number ( cht:: SIZE , 0 ) ) . unwrap ( ) ;
389+ assert_eq ! ( cht_root_1, cht_root_2) ;
390+ assert_eq ! ( cht_root_2, cht_root_3) ;
391+ }
292392}
0 commit comments