@@ -8,14 +8,17 @@ use std::{
8
8
sync:: Arc ,
9
9
} ;
10
10
11
+ use indexmap:: IndexMap ;
11
12
use zebra_chain:: {
12
- block:: { self , Block , Hash } ,
13
+ block:: { self , Block , Hash , Height } ,
13
14
parameters:: Network ,
14
- sprout, transparent,
15
+ sprout:: { self } ,
16
+ transparent,
15
17
} ;
16
18
17
19
use crate :: {
18
- constants:: MAX_NON_FINALIZED_CHAIN_FORKS ,
20
+ constants:: { MAX_INVALIDATED_BLOCKS , MAX_NON_FINALIZED_CHAIN_FORKS } ,
21
+ error:: ReconsiderError ,
19
22
request:: { ContextuallyVerifiedBlock , FinalizableBlock } ,
20
23
service:: { check, finalized_state:: ZebraDb } ,
21
24
SemanticallyVerifiedBlock , ValidateContextError ,
@@ -47,7 +50,7 @@ pub struct NonFinalizedState {
47
50
48
51
/// Blocks that have been invalidated in, and removed from, the non finalized
49
52
/// state.
50
- invalidated_blocks : HashMap < Hash , Arc < Vec < ContextuallyVerifiedBlock > > > ,
53
+ invalidated_blocks : IndexMap < Height , Arc < Vec < ContextuallyVerifiedBlock > > > ,
51
54
52
55
// Configuration
53
56
//
@@ -233,6 +236,10 @@ impl NonFinalizedState {
233
236
self . insert ( side_chain) ;
234
237
}
235
238
239
+ // Remove all invalidated_blocks at or below the finalized height
240
+ self . invalidated_blocks
241
+ . retain ( |height, _blocks| * height >= best_chain_root. height ) ;
242
+
236
243
self . update_metrics_for_chains ( ) ;
237
244
238
245
// Add the treestate to the finalized block.
@@ -294,13 +301,100 @@ impl NonFinalizedState {
294
301
invalidated_blocks
295
302
} ;
296
303
297
- self . invalidated_blocks
298
- . insert ( block_hash, Arc :: new ( invalidated_blocks) ) ;
304
+ self . invalidated_blocks . insert (
305
+ invalidated_blocks. first ( ) . unwrap ( ) . clone ( ) . height ,
306
+ Arc :: new ( invalidated_blocks) ,
307
+ ) ;
308
+
309
+ while self . invalidated_blocks . len ( ) > MAX_INVALIDATED_BLOCKS {
310
+ self . invalidated_blocks . shift_remove_index ( 0 ) ;
311
+ }
299
312
300
313
self . update_metrics_for_chains ( ) ;
301
314
self . update_metrics_bars ( ) ;
302
315
}
303
316
317
+ /// Reconsiders a previously invalidated block and its descendants into the non-finalized state
318
+ /// based on a block_hash. Reconsidered blocks are inserted into the previous chain and re-inserted
319
+ /// into the chain_set.
320
+ pub fn reconsider_block (
321
+ & mut self ,
322
+ block_hash : block:: Hash ,
323
+ finalized_state : & ZebraDb ,
324
+ ) -> Result < ( ) , ReconsiderError > {
325
+ // Get the invalidated blocks that were invalidated by the given block_hash
326
+ let height = self
327
+ . invalidated_blocks
328
+ . iter ( )
329
+ . find_map ( |( height, blocks) | {
330
+ if blocks. first ( ) ?. hash == block_hash {
331
+ Some ( height)
332
+ } else {
333
+ None
334
+ }
335
+ } )
336
+ . ok_or ( ReconsiderError :: MissingInvalidatedBlock ( block_hash) ) ?;
337
+
338
+ let mut invalidated_blocks = self
339
+ . invalidated_blocks
340
+ . clone ( )
341
+ . shift_remove ( height)
342
+ . ok_or ( ReconsiderError :: MissingInvalidatedBlock ( block_hash) ) ?;
343
+ let mut_blocks = Arc :: make_mut ( & mut invalidated_blocks) ;
344
+
345
+ // Find and fork the parent chain of the invalidated_root. Update the parent chain
346
+ // with the invalidated_descendants
347
+ let invalidated_root = mut_blocks
348
+ . first ( )
349
+ . ok_or ( ReconsiderError :: InvalidatedBlocksEmpty ) ?;
350
+
351
+ let root_parent_hash = invalidated_root. block . header . previous_block_hash ;
352
+
353
+ // If the parent is the tip of the finalized_state we create a new chain and insert it
354
+ // into the non finalized state
355
+ let chain_result = if root_parent_hash == finalized_state. finalized_tip_hash ( ) {
356
+ let chain = Chain :: new (
357
+ & self . network ,
358
+ finalized_state
359
+ . finalized_tip_height ( )
360
+ . ok_or ( ReconsiderError :: ParentChainNotFound ( block_hash) ) ?,
361
+ finalized_state. sprout_tree_for_tip ( ) ,
362
+ finalized_state. sapling_tree_for_tip ( ) ,
363
+ finalized_state. orchard_tree_for_tip ( ) ,
364
+ finalized_state. history_tree ( ) ,
365
+ finalized_state. finalized_value_pool ( ) ,
366
+ ) ;
367
+ Arc :: new ( chain)
368
+ } else {
369
+ // The parent is not the finalized_tip and still exist in the NonFinalizedState
370
+ // or else we return an error due to the parent not existing in the NonFinalizedState
371
+ self . parent_chain ( root_parent_hash)
372
+ . map_err ( |_| ReconsiderError :: ParentChainNotFound ( block_hash) ) ?
373
+ } ;
374
+
375
+ let mut modified_chain = Arc :: unwrap_or_clone ( chain_result) ;
376
+ for block in Arc :: unwrap_or_clone ( invalidated_blocks) {
377
+ modified_chain = modified_chain. push ( block) ?;
378
+ }
379
+
380
+ let ( height, hash) = modified_chain. non_finalized_tip ( ) ;
381
+
382
+ // Only track invalidated_blocks that are not yet finalized. Once blocks are finalized (below the best_chain_root_height)
383
+ // we can discard the block.
384
+ if let Some ( best_chain_root_height) = finalized_state. finalized_tip_height ( ) {
385
+ self . invalidated_blocks
386
+ . retain ( |height, _blocks| * height >= best_chain_root_height) ;
387
+ }
388
+
389
+ self . insert_with ( Arc :: new ( modified_chain) , |chain_set| {
390
+ chain_set. retain ( |chain| chain. non_finalized_tip_hash ( ) != root_parent_hash)
391
+ } ) ;
392
+
393
+ self . update_metrics_for_committed_block ( height, hash) ;
394
+
395
+ Ok ( ( ) )
396
+ }
397
+
304
398
/// Commit block to the non-finalized state as a new chain where its parent
305
399
/// is the finalized tip.
306
400
#[ tracing:: instrument( level = "debug" , skip( self , finalized_state, prepared) ) ]
@@ -352,6 +446,12 @@ impl NonFinalizedState {
352
446
prepared : SemanticallyVerifiedBlock ,
353
447
finalized_state : & ZebraDb ,
354
448
) -> Result < Arc < Chain > , ValidateContextError > {
449
+ if self . invalidated_blocks . contains_key ( & prepared. height ) {
450
+ return Err ( ValidateContextError :: BlockPreviouslyInvalidated {
451
+ block_hash : prepared. hash ,
452
+ } ) ;
453
+ }
454
+
355
455
// Reads from disk
356
456
//
357
457
// TODO: if these disk reads show up in profiles, run them in parallel, using std::thread::spawn()
@@ -624,7 +724,7 @@ impl NonFinalizedState {
624
724
}
625
725
626
726
/// Return the invalidated blocks.
627
- pub fn invalidated_blocks ( & self ) -> HashMap < block :: Hash , Arc < Vec < ContextuallyVerifiedBlock > > > {
727
+ pub fn invalidated_blocks ( & self ) -> IndexMap < Height , Arc < Vec < ContextuallyVerifiedBlock > > > {
628
728
self . invalidated_blocks . clone ( )
629
729
}
630
730
0 commit comments