@@ -5,6 +5,7 @@ use std::sync::Arc;
5
5
use crossbeam_channel:: { Receiver , TryRecvError } ;
6
6
use hex_literal:: hex;
7
7
use itertools:: Itertools ;
8
+ use semver:: Version ;
8
9
use tracing:: instrument;
9
10
10
11
use zebra_chain:: {
@@ -17,90 +18,144 @@ use zebra_chain::{
17
18
} ;
18
19
19
20
use crate :: service:: finalized_state:: {
20
- disk_format:: upgrade:: CancelFormatChange , DiskWriteBatch , ZebraDb ,
21
+ disk_format:: upgrade:: { CancelFormatChange , DiskFormatUpgrade } ,
22
+ DiskWriteBatch , ZebraDb ,
21
23
} ;
22
24
23
- /// Runs disk format upgrade for adding Sapling and Orchard note commitment subtrees to database.
24
- ///
25
- /// Trees are added to the database in reverse height order, so that wallets can sync correctly
26
- /// while the upgrade is running.
27
- ///
28
- /// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled.
29
- #[ allow( clippy:: unwrap_in_result) ]
30
- #[ instrument( skip( upgrade_db, cancel_receiver) ) ]
31
- pub fn run (
32
- initial_tip_height : Height ,
33
- upgrade_db : & ZebraDb ,
34
- cancel_receiver : & Receiver < CancelFormatChange > ,
35
- ) -> Result < ( ) , CancelFormatChange > {
36
- // # Consensus
37
- //
38
- // Zebra stores exactly one note commitment tree for every block with sapling notes.
39
- // (It also stores the empty note commitment tree for the genesis block, but we skip that.)
40
- //
41
- // The consensus rules limit blocks to less than 2^16 sapling and 2^16 orchard outputs. So a
42
- // block can't complete multiple level 16 subtrees (or complete an entire subtree by itself).
43
- // Currently, with 2MB blocks and v4/v5 sapling and orchard output sizes, the subtree index can
44
- // increase by at most 1 every ~20 blocks.
45
- //
46
- // # Compatibility
47
- //
48
- // Because wallets search backwards from the chain tip, subtrees need to be added to the
49
- // database in reverse height order. (Tip first, genesis last.)
50
- //
51
- // Otherwise, wallets that sync during the upgrade will be missing some notes.
25
+ /// Implements [`DiskFormatUpgrade`] for populating Sapling and Orchard note commitment subtrees.
26
+ pub struct AddSubtrees ;
52
27
53
- // Generate a list of sapling subtree inputs: previous and current trees, and their end heights.
54
- let subtrees = upgrade_db
55
- . sapling_tree_by_reversed_height_range ( ..=initial_tip_height)
56
- // We need both the tree and its previous tree for each shielded block.
57
- . tuple_windows ( )
58
- // Because the iterator is reversed, the larger tree is first.
59
- . map ( |( ( end_height, tree) , ( prev_end_height, prev_tree) ) | {
60
- ( prev_end_height, prev_tree, end_height, tree)
61
- } )
62
- // Find new subtrees.
63
- . filter ( |( _prev_end_height, prev_tree, _end_height, tree) | {
64
- tree. contains_new_subtree ( prev_tree)
65
- } ) ;
28
+ impl DiskFormatUpgrade for AddSubtrees {
29
+ fn version ( & self ) -> Version {
30
+ Version :: new ( 25 , 2 , 2 )
31
+ }
66
32
67
- for ( prev_end_height, prev_tree, end_height, tree) in subtrees {
68
- // Return early if the upgrade is cancelled.
69
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
70
- return Err ( CancelFormatChange ) ;
33
+ fn description ( & self ) -> & ' static str {
34
+ "add subtrees upgrade"
35
+ }
36
+
37
+ fn prepare (
38
+ & self ,
39
+ initial_tip_height : Height ,
40
+ upgrade_db : & ZebraDb ,
41
+ cancel_receiver : & Receiver < CancelFormatChange > ,
42
+ older_disk_version : & Version ,
43
+ ) -> Result < ( ) , CancelFormatChange > {
44
+ let first_version_for_adding_subtrees = Version :: new ( 25 , 2 , 0 ) ;
45
+ if older_disk_version >= & first_version_for_adding_subtrees {
46
+ // Clear previous upgrade data, because it was incorrect.
47
+ reset ( initial_tip_height, upgrade_db, cancel_receiver) ?;
71
48
}
72
49
73
- let subtree =
74
- calculate_sapling_subtree ( upgrade_db, prev_end_height, prev_tree, end_height, tree) ;
75
- write_sapling_subtree ( upgrade_db, subtree) ;
50
+ Ok ( ( ) )
76
51
}
77
52
78
- // Generate a list of orchard subtree inputs: previous and current trees, and their end heights.
79
- let subtrees = upgrade_db
80
- . orchard_tree_by_reversed_height_range ( ..=initial_tip_height)
81
- // We need both the tree and its previous tree for each shielded block.
82
- . tuple_windows ( )
83
- // Because the iterator is reversed, the larger tree is first.
84
- . map ( |( ( end_height, tree) , ( prev_end_height, prev_tree) ) | {
85
- ( prev_end_height, prev_tree, end_height, tree)
86
- } )
87
- // Find new subtrees.
88
- . filter ( |( _prev_end_height, prev_tree, _end_height, tree) | {
89
- tree. contains_new_subtree ( prev_tree)
90
- } ) ;
53
+ /// Runs disk format upgrade for adding Sapling and Orchard note commitment subtrees to database.
54
+ ///
55
+ /// Trees are added to the database in reverse height order, so that wallets can sync correctly
56
+ /// while the upgrade is running.
57
+ ///
58
+ /// Returns `Ok` if the upgrade completed, and `Err` if it was cancelled.
59
+ fn run (
60
+ & self ,
61
+ initial_tip_height : Height ,
62
+ upgrade_db : & ZebraDb ,
63
+ cancel_receiver : & Receiver < CancelFormatChange > ,
64
+ ) -> Result < ( ) , CancelFormatChange > {
65
+ // # Consensus
66
+ //
67
+ // Zebra stores exactly one note commitment tree for every block with sapling notes.
68
+ // (It also stores the empty note commitment tree for the genesis block, but we skip that.)
69
+ //
70
+ // The consensus rules limit blocks to less than 2^16 sapling and 2^16 orchard outputs. So a
71
+ // block can't complete multiple level 16 subtrees (or complete an entire subtree by itself).
72
+ // Currently, with 2MB blocks and v4/v5 sapling and orchard output sizes, the subtree index can
73
+ // increase by at most 1 every ~20 blocks.
74
+ //
75
+ // # Compatibility
76
+ //
77
+ // Because wallets search backwards from the chain tip, subtrees need to be added to the
78
+ // database in reverse height order. (Tip first, genesis last.)
79
+ //
80
+ // Otherwise, wallets that sync during the upgrade will be missing some notes.
81
+
82
+ // Generate a list of sapling subtree inputs: previous and current trees, and their end heights.
83
+ let subtrees = upgrade_db
84
+ . sapling_tree_by_reversed_height_range ( ..=initial_tip_height)
85
+ // We need both the tree and its previous tree for each shielded block.
86
+ . tuple_windows ( )
87
+ // Because the iterator is reversed, the larger tree is first.
88
+ . map ( |( ( end_height, tree) , ( prev_end_height, prev_tree) ) | {
89
+ ( prev_end_height, prev_tree, end_height, tree)
90
+ } )
91
+ // Find new subtrees.
92
+ . filter ( |( _prev_end_height, prev_tree, _end_height, tree) | {
93
+ tree. contains_new_subtree ( prev_tree)
94
+ } ) ;
95
+
96
+ for ( prev_end_height, prev_tree, end_height, tree) in subtrees {
97
+ // Return early if the upgrade is cancelled.
98
+ if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
99
+ return Err ( CancelFormatChange ) ;
100
+ }
91
101
92
- for ( prev_end_height, prev_tree, end_height, tree) in subtrees {
93
- // Return early if the upgrade is cancelled.
94
- if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
95
- return Err ( CancelFormatChange ) ;
102
+ let subtree =
103
+ calculate_sapling_subtree ( upgrade_db, prev_end_height, prev_tree, end_height, tree) ;
104
+ write_sapling_subtree ( upgrade_db, subtree) ;
96
105
}
97
106
98
- let subtree =
99
- calculate_orchard_subtree ( upgrade_db, prev_end_height, prev_tree, end_height, tree) ;
100
- write_orchard_subtree ( upgrade_db, subtree) ;
107
+ // Generate a list of orchard subtree inputs: previous and current trees, and their end heights.
108
+ let subtrees = upgrade_db
109
+ . orchard_tree_by_reversed_height_range ( ..=initial_tip_height)
110
+ // We need both the tree and its previous tree for each shielded block.
111
+ . tuple_windows ( )
112
+ // Because the iterator is reversed, the larger tree is first.
113
+ . map ( |( ( end_height, tree) , ( prev_end_height, prev_tree) ) | {
114
+ ( prev_end_height, prev_tree, end_height, tree)
115
+ } )
116
+ // Find new subtrees.
117
+ . filter ( |( _prev_end_height, prev_tree, _end_height, tree) | {
118
+ tree. contains_new_subtree ( prev_tree)
119
+ } ) ;
120
+
121
+ for ( prev_end_height, prev_tree, end_height, tree) in subtrees {
122
+ // Return early if the upgrade is cancelled.
123
+ if !matches ! ( cancel_receiver. try_recv( ) , Err ( TryRecvError :: Empty ) ) {
124
+ return Err ( CancelFormatChange ) ;
125
+ }
126
+
127
+ let subtree =
128
+ calculate_orchard_subtree ( upgrade_db, prev_end_height, prev_tree, end_height, tree) ;
129
+ write_orchard_subtree ( upgrade_db, subtree) ;
130
+ }
131
+
132
+ Ok ( ( ) )
101
133
}
102
134
103
- Ok ( ( ) )
135
+ #[ allow( clippy:: unwrap_in_result) ]
136
+ fn validate (
137
+ & self ,
138
+ db : & ZebraDb ,
139
+ cancel_receiver : & Receiver < CancelFormatChange > ,
140
+ ) -> Result < Result < ( ) , String > , CancelFormatChange > {
141
+ // This is redundant in some code paths, but not in others. But it's quick anyway.
142
+ let quick_result = subtree_format_calculation_pre_checks ( db) ;
143
+
144
+ // Check the entire format before returning any errors.
145
+ let sapling_result = check_sapling_subtrees ( db, cancel_receiver) ?;
146
+ let orchard_result = check_orchard_subtrees ( db, cancel_receiver) ?;
147
+
148
+ if quick_result. is_err ( ) || sapling_result. is_err ( ) || orchard_result. is_err ( ) {
149
+ let err = Err ( format ! (
150
+ "missing or invalid subtree(s): \
151
+ quick: {quick_result:?}, sapling: {sapling_result:?}, orchard: {orchard_result:?}"
152
+ ) ) ;
153
+ warn ! ( ?err) ;
154
+ return Ok ( err) ;
155
+ }
156
+
157
+ Ok ( Ok ( ( ) ) )
158
+ }
104
159
}
105
160
106
161
/// Reset data from previous upgrades. This data can be complete or incomplete.
@@ -304,30 +359,6 @@ fn quick_check_orchard_subtrees(db: &ZebraDb) -> Result<(), &'static str> {
304
359
Ok ( ( ) )
305
360
}
306
361
307
- /// Check that note commitment subtrees were correctly added.
308
- pub fn subtree_format_validity_checks_detailed (
309
- db : & ZebraDb ,
310
- cancel_receiver : & Receiver < CancelFormatChange > ,
311
- ) -> Result < Result < ( ) , String > , CancelFormatChange > {
312
- // This is redundant in some code paths, but not in others. But it's quick anyway.
313
- let quick_result = subtree_format_calculation_pre_checks ( db) ;
314
-
315
- // Check the entire format before returning any errors.
316
- let sapling_result = check_sapling_subtrees ( db, cancel_receiver) ?;
317
- let orchard_result = check_orchard_subtrees ( db, cancel_receiver) ?;
318
-
319
- if quick_result. is_err ( ) || sapling_result. is_err ( ) || orchard_result. is_err ( ) {
320
- let err = Err ( format ! (
321
- "missing or invalid subtree(s): \
322
- quick: {quick_result:?}, sapling: {sapling_result:?}, orchard: {orchard_result:?}"
323
- ) ) ;
324
- warn ! ( ?err) ;
325
- return Ok ( err) ;
326
- }
327
-
328
- Ok ( Ok ( ( ) ) )
329
- }
330
-
331
362
/// Check that Sapling note commitment subtrees were correctly added.
332
363
///
333
364
/// Returns an error if a note commitment subtree is missing or incorrect.
0 commit comments