@@ -6,7 +6,7 @@ use std::{
6
6
thread:: { self , JoinHandle } ,
7
7
} ;
8
8
9
- use crossbeam_channel:: { bounded, Receiver , RecvTimeoutError , Sender , TryRecvError } ;
9
+ use crossbeam_channel:: { bounded, Receiver , RecvTimeoutError , Sender } ;
10
10
use semver:: Version ;
11
11
use tracing:: Span ;
12
12
@@ -20,21 +20,59 @@ use zebra_chain::{
20
20
21
21
use DbFormatChange :: * ;
22
22
23
- use crate :: {
24
- constants:: latest_version_for_adding_subtrees,
25
- service:: finalized_state:: { DiskWriteBatch , ZebraDb } ,
26
- } ;
23
+ use crate :: { constants:: latest_version_for_adding_subtrees, service:: finalized_state:: ZebraDb } ;
27
24
28
25
pub ( crate ) mod add_subtrees;
29
26
pub ( crate ) mod cache_genesis_roots;
30
27
pub ( crate ) mod fix_tree_key_type;
28
+ pub ( crate ) mod prune_trees;
31
29
32
30
#[ cfg( not( feature = "indexer" ) ) ]
33
31
pub ( crate ) mod drop_tx_locs_by_spends;
34
32
35
33
#[ cfg( feature = "indexer" ) ]
36
34
pub ( crate ) mod track_tx_locs_by_spends;
37
35
36
+ /// Defines method signature for running disk format upgrades.
37
+ pub trait DiskFormatUpgrade {
38
+ /// Returns the version at which this upgrade is applied.
39
+ fn version ( & self ) -> Version ;
40
+
41
+ /// Returns the description of this upgrade.
42
+ fn description ( & self ) -> & ' static str ;
43
+
44
+ /// Runs disk format upgrade.
45
+ fn run (
46
+ & self ,
47
+ initial_tip_height : Height ,
48
+ db : & ZebraDb ,
49
+ cancel_receiver : & Receiver < CancelFormatChange > ,
50
+ ) ;
51
+
52
+ /// Check that state has been upgraded to this format correctly.
53
+ ///
54
+ /// # Panics
55
+ ///
56
+ /// If the state has not been upgraded to this format correctly.
57
+ fn validate (
58
+ & self ,
59
+ _db : & ZebraDb ,
60
+ _cancel_receiver : & Receiver < CancelFormatChange > ,
61
+ ) -> Result < Result < ( ) , String > , CancelFormatChange > {
62
+ Ok ( Ok ( ( ) ) )
63
+ }
64
+ }
65
+
66
+ fn format_upgrades ( ) -> Vec < Box < dyn DiskFormatUpgrade > > {
67
+ vec ! [
68
+ Box :: new( prune_trees:: PruneTrees ) ,
69
+ // TODO:
70
+ // Box::new(add_subtrees::AddSubtrees),
71
+ // Box::new(cache_genesis_roots::CacheGenesisRoots),
72
+ // Box::new(fix_tree_key_type::FixTreeKeyType),
73
+ ]
74
+ }
75
+
38
76
/// The kind of database format change or validity check we're performing.
39
77
#[ derive( Clone , Debug , Eq , PartialEq ) ]
40
78
pub enum DbFormatChange {
@@ -474,78 +512,33 @@ impl DbFormatChange {
474
512
return Ok ( ( ) ) ;
475
513
} ;
476
514
477
- // Note commitment tree de-duplication database upgrade task.
478
-
479
- let version_for_pruning_trees =
480
- Version :: parse ( "25.1.1" ) . expect ( "Hardcoded version string should be valid." ) ;
481
-
482
- // Check if we need to prune the note commitment trees in the database.
483
- if older_disk_version < & version_for_pruning_trees {
484
- let timer = CodeTimer :: start ( ) ;
485
-
486
- // Prune duplicate Sapling note commitment trees.
487
-
488
- // The last tree we checked.
489
- let mut last_tree = db
490
- . sapling_tree_by_height ( & Height ( 0 ) )
491
- . expect ( "Checked above that the genesis block is in the database." ) ;
492
-
493
- // Run through all the possible duplicate trees in the finalized chain.
494
- // The block after genesis is the first possible duplicate.
495
- for ( height, tree) in db. sapling_tree_by_height_range ( Height ( 1 ) ..=initial_tip_height) {
496
- // Return early if there is a cancel signal.
497
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
498
- return Err ( CancelFormatChange ) ;
499
- }
500
-
501
- // Delete any duplicate trees.
502
- if tree == last_tree {
503
- let mut batch = DiskWriteBatch :: new ( ) ;
504
- batch. delete_sapling_tree ( db, & height) ;
505
- db. write_batch ( batch)
506
- . expect ( "Deleting Sapling note commitment trees should always succeed." ) ;
507
- }
508
-
509
- // Compare against the last tree to find unique trees.
510
- last_tree = tree;
515
+ // Apply or validate format upgrades
516
+ for upgrade in format_upgrades ( ) {
517
+ if older_disk_version >= & upgrade. version ( ) {
518
+ upgrade
519
+ . validate ( db, cancel_receiver) ?
520
+ . expect ( "failed to validate db format" ) ;
521
+ continue ;
511
522
}
512
523
513
- // Prune duplicate Orchard note commitment trees.
514
-
515
- // The last tree we checked.
516
- let mut last_tree = db
517
- . orchard_tree_by_height ( & Height ( 0 ) )
518
- . expect ( "Checked above that the genesis block is in the database." ) ;
519
-
520
- // Run through all the possible duplicate trees in the finalized chain.
521
- // The block after genesis is the first possible duplicate.
522
- for ( height, tree) in db. orchard_tree_by_height_range ( Height ( 1 ) ..=initial_tip_height) {
523
- // Return early if there is a cancel signal.
524
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
525
- return Err ( CancelFormatChange ) ;
526
- }
527
-
528
- // Delete any duplicate trees.
529
- if tree == last_tree {
530
- let mut batch = DiskWriteBatch :: new ( ) ;
531
- batch. delete_orchard_tree ( db, & height) ;
532
- db. write_batch ( batch)
533
- . expect ( "Deleting Orchard note commitment trees should always succeed." ) ;
534
- }
524
+ let timer = CodeTimer :: start ( ) ;
535
525
536
- // Compare against the last tree to find unique trees.
537
- last_tree = tree;
538
- }
526
+ upgrade. run ( initial_tip_height, db, cancel_receiver) ;
539
527
540
528
// Before marking the state as upgraded, check that the upgrade completed successfully.
541
- Self :: check_for_duplicate_trees ( db, cancel_receiver) ?
542
- . expect ( "database format is valid after upgrade" ) ;
529
+ upgrade
530
+ . validate ( db, cancel_receiver) ?
531
+ . expect ( "db should be valid after upgrade" ) ;
543
532
544
533
// Mark the database as upgraded. Zebra won't repeat the upgrade anymore once the
545
534
// database is marked, so the upgrade MUST be complete at this point.
546
- Self :: mark_as_upgraded_to ( db, & version_for_pruning_trees) ;
535
+ info ! (
536
+ newer_running_version = ?upgrade. version( ) ,
537
+ "Zebra automatically upgraded the database format"
538
+ ) ;
539
+ Self :: mark_as_upgraded_to ( db, & upgrade. version ( ) ) ;
547
540
548
- timer. finish ( module_path ! ( ) , line ! ( ) , "deduplicate trees upgrade" ) ;
541
+ timer. finish ( module_path ! ( ) , line ! ( ) , upgrade. description ( ) ) ;
549
542
}
550
543
551
544
// Note commitment subtree creation database upgrade task.
@@ -669,7 +662,10 @@ impl DbFormatChange {
669
662
// Do the quick checks first, so we don't have to do this in every detailed check.
670
663
results. push ( Self :: format_validity_checks_quick ( db) ) ;
671
664
672
- results. push ( Self :: check_for_duplicate_trees ( db, cancel_receiver) ?) ;
665
+ for upgrade in format_upgrades ( ) {
666
+ results. push ( upgrade. validate ( db, cancel_receiver) ?) ;
667
+ }
668
+
673
669
results. push ( add_subtrees:: subtree_format_validity_checks_detailed (
674
670
db,
675
671
cancel_receiver,
@@ -689,66 +685,6 @@ impl DbFormatChange {
689
685
Ok ( Ok ( ( ) ) )
690
686
}
691
687
692
- /// Check that note commitment trees were correctly de-duplicated.
693
- //
694
- // TODO: move this method into an deduplication upgrade module file,
695
- // along with the upgrade code above.
696
- #[ allow( clippy:: unwrap_in_result) ]
697
- fn check_for_duplicate_trees (
698
- db : & ZebraDb ,
699
- cancel_receiver : & Receiver < CancelFormatChange > ,
700
- ) -> Result < Result < ( ) , String > , CancelFormatChange > {
701
- // Runtime test: make sure we removed all duplicates.
702
- // We always run this test, even if the state has supposedly been upgraded.
703
- let mut result = Ok ( ( ) ) ;
704
-
705
- let mut prev_height = None ;
706
- let mut prev_tree = None ;
707
- for ( height, tree) in db. sapling_tree_by_height_range ( ..) {
708
- // Return early if the format check is cancelled.
709
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
710
- return Err ( CancelFormatChange ) ;
711
- }
712
-
713
- if prev_tree == Some ( tree. clone ( ) ) {
714
- result = Err ( format ! (
715
- "found duplicate sapling trees after running de-duplicate tree upgrade:\
716
- height: {height:?}, previous height: {:?}, tree root: {:?}",
717
- prev_height. unwrap( ) ,
718
- tree. root( )
719
- ) ) ;
720
- error ! ( ?result) ;
721
- }
722
-
723
- prev_height = Some ( height) ;
724
- prev_tree = Some ( tree) ;
725
- }
726
-
727
- let mut prev_height = None ;
728
- let mut prev_tree = None ;
729
- for ( height, tree) in db. orchard_tree_by_height_range ( ..) {
730
- // Return early if the format check is cancelled.
731
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
732
- return Err ( CancelFormatChange ) ;
733
- }
734
-
735
- if prev_tree == Some ( tree. clone ( ) ) {
736
- result = Err ( format ! (
737
- "found duplicate orchard trees after running de-duplicate tree upgrade:\
738
- height: {height:?}, previous height: {:?}, tree root: {:?}",
739
- prev_height. unwrap( ) ,
740
- tree. root( )
741
- ) ) ;
742
- error ! ( ?result) ;
743
- }
744
-
745
- prev_height = Some ( height) ;
746
- prev_tree = Some ( tree) ;
747
- }
748
-
749
- Ok ( result)
750
- }
751
-
752
688
/// Mark a newly created database with the current format version.
753
689
///
754
690
/// This should be called when a newly created database is opened.
0 commit comments