@@ -815,19 +815,21 @@ pub(crate) struct Progress {
815
815
}
816
816
817
817
pub ( crate ) trait ScanProgress {
818
- fn sapling_scan_progress (
818
+ fn sapling_scan_progress < P : consensus :: Parameters > (
819
819
& self ,
820
820
conn : & rusqlite:: Connection ,
821
+ params : & P ,
821
822
birthday_height : BlockHeight ,
822
823
recover_until_height : Option < BlockHeight > ,
823
824
fully_scanned_height : BlockHeight ,
824
825
chain_tip_height : BlockHeight ,
825
826
) -> Result < Progress , SqliteClientError > ;
826
827
827
828
#[ cfg( feature = "orchard" ) ]
828
- fn orchard_scan_progress (
829
+ fn orchard_scan_progress < P : consensus :: Parameters > (
829
830
& self ,
830
831
conn : & rusqlite:: Connection ,
832
+ params : & P ,
831
833
birthday_height : BlockHeight ,
832
834
recover_until_height : Option < BlockHeight > ,
833
835
fully_scanned_height : BlockHeight ,
@@ -844,6 +846,7 @@ fn subtree_scan_progress(
844
846
table_prefix : & ' static str ,
845
847
output_count_col : & ' static str ,
846
848
shard_height : u8 ,
849
+ pool_activation_height : BlockHeight ,
847
850
birthday_height : BlockHeight ,
848
851
recover_until_height : Option < BlockHeight > ,
849
852
fully_scanned_height : BlockHeight ,
@@ -864,6 +867,11 @@ fn subtree_scan_progress(
864
867
FROM blocks
865
868
WHERE height <= :start_height" ,
866
869
) ) ?;
870
+ let mut stmt_end_tree_size_at = conn. prepare_cached ( & format ! (
871
+ "SELECT {table_prefix}_commitment_tree_size
872
+ FROM blocks
873
+ WHERE height = :height" ,
874
+ ) ) ?;
867
875
868
876
if fully_scanned_height == chain_tip_height {
869
877
// Compute the total blocks scanned since the wallet birthday on either side of
@@ -956,16 +964,13 @@ fn subtree_scan_progress(
956
964
} )
957
965
. transpose ( ) ?;
958
966
959
- // We don't have complete information on how many outputs will exist in the shard at
960
- // the chain tip without having scanned the chain tip block, so we overestimate by
961
- // computing the maximum possible number of notes directly from the shard indices.
962
- //
963
- // TODO: it would be nice to be able to reliably have the size of the commitment tree
964
- // at the chain tip without having to have scanned that block.
965
- let ( min_tree_size, max_tree_size) = conn
967
+ // In case we didn't have information about the tree size at the recover-until
968
+ // height, get the tree size from a nearby subtree. It's fine for this to be
969
+ // approximate; it just shifts the boundary between scan and recover progress.
970
+ let min_tree_size = conn
966
971
. query_row (
967
972
& format ! (
968
- "SELECT MIN(shard_index), MAX(shard_index)
973
+ "SELECT MIN(shard_index)
969
974
FROM {table_prefix}_tree_shards
970
975
WHERE subtree_end_height > :start_height
971
976
OR subtree_end_height IS NULL" ,
@@ -975,13 +980,183 @@ fn subtree_scan_progress(
975
980
let min_tree_size = row
976
981
. get :: < _ , Option < u64 > > ( 0 ) ?
977
982
. map ( |min_idx| min_idx << shard_height) ;
978
- let max_tree_size = row
979
- . get :: < _ , Option < u64 > > ( 1 ) ?
980
- . map ( |max_idx| ( max_idx + 1 ) << shard_height) ;
981
- Ok ( min_tree_size. zip ( max_tree_size) )
983
+ Ok ( min_tree_size)
982
984
} ,
983
985
)
984
- . optional ( ) ?. flatten ( ) . unzip ( ) ;
986
+ . optional ( ) ?. flatten ( ) ;
987
+
988
+ // If we've scanned the block at the chain tip, we know how many notes are
989
+ // currently in the tree.
990
+ let tip_tree_size = match stmt_end_tree_size_at
991
+ . query_row (
992
+ named_params ! { ":height" : u32 :: from( chain_tip_height) } ,
993
+ |row| row. get :: < _ , Option < u64 > > ( 0 ) ,
994
+ )
995
+ . optional ( ) ?
996
+ . flatten ( )
997
+ {
998
+ Some ( tree_size) => Some ( tree_size) ,
999
+ None => {
1000
+ // Estimate the size of the tree by linear extrapolation from available
1001
+ // data closest to the chain tip.
1002
+ //
1003
+ // - If we have scanned blocks within the incomplete subtree, and we know
1004
+ // the tree size for the end of the most recent scanned range, then we
1005
+ // extrapolate from the start of the incomplete subtree:
1006
+ //
1007
+ // subtree
1008
+ // / \
1009
+ // / \
1010
+ // / \
1011
+ // / \
1012
+ // |<--------->| |
1013
+ // | scanned | tip
1014
+ // last_scanned
1015
+ //
1016
+ //
1017
+ // subtree
1018
+ // / \
1019
+ // / \
1020
+ // / \
1021
+ // / \
1022
+ // |<------->| |
1023
+ // | scanned | tip
1024
+ // last_scanned
1025
+ //
1026
+ // - If we don't have scanned blocks within the incomplete subtree, or we
1027
+ // don't know the tree size, then we extrapolate from the block-width of
1028
+ // the last complete subtree.
1029
+ //
1030
+ // This avoids having a sharp discontinuity in the progress percentages
1031
+ // shown to users, and gets more accurate the closer to the chain tip we
1032
+ // have scanned.
1033
+ //
1034
+ // TODO: it would be nice to be able to reliably have the size of the
1035
+ // commitment tree at the chain tip without having to have scanned that
1036
+ // block.
1037
+
1038
+ // Get the last scanned height.
1039
+ let last_scanned = conn
1040
+ . query_row (
1041
+ "SELECT block_range_end
1042
+ FROM scan_queue
1043
+ WHERE priority = :priority
1044
+ ORDER BY block_range_start DESC
1045
+ LIMIT 1" ,
1046
+ named_params ! [ ":priority" : priority_code( & ScanPriority :: Scanned ) ] ,
1047
+ |row| {
1048
+ // Scan ranges are end-exclusive.
1049
+ Ok ( BlockHeight :: from_u32 ( row. get ( 0 ) ?) - 1 )
1050
+ } ,
1051
+ )
1052
+ // `None` if we haven't scanned anything.
1053
+ . optional ( ) ?;
1054
+
1055
+ // Get the tree size at the last scanned height, if known.
1056
+ let last_scanned_tree_size = last_scanned
1057
+ . map ( |last_scanned| {
1058
+ stmt_end_tree_size_at
1059
+ . query_row ( named_params ! { ":height" : u32 :: from( last_scanned) } , |row| {
1060
+ row. get :: < _ , Option < u64 > > ( 0 )
1061
+ } )
1062
+ } )
1063
+ . transpose ( ) ?
1064
+ . flatten ( ) ;
1065
+
1066
+ // Get the last completed subtree.
1067
+ let last_completed_subtree = conn
1068
+ . query_row (
1069
+ & format ! (
1070
+ "SELECT shard_index, subtree_end_height
1071
+ FROM {table_prefix}_tree_shards
1072
+ WHERE subtree_end_height IS NOT NULL
1073
+ ORDER BY shard_index DESC
1074
+ LIMIT 1"
1075
+ ) ,
1076
+ [ ] ,
1077
+ |row| Ok ( ( row. get :: < _ , u64 > ( 0 ) ?, BlockHeight :: from_u32 ( row. get ( 1 ) ?) ) ) ,
1078
+ )
1079
+ // `None` if we have no subtree roots yet.
1080
+ . optional ( ) ?;
1081
+
1082
+ if let Some ( ( last_completed_subtree_index, last_completed_subtree_height) ) =
1083
+ last_completed_subtree
1084
+ {
1085
+ // If we know the tree size at the last scanned height, extrapolate.
1086
+ let tip_tree_size = last_scanned. zip ( last_scanned_tree_size) . and_then (
1087
+ |( last_scanned, last_scanned_tree_size) | {
1088
+ let scanned_notes = last_scanned_tree_size
1089
+ - ( last_completed_subtree_index << shard_height) ;
1090
+ let scanned_range =
1091
+ u64:: from ( last_scanned - last_completed_subtree_height) ;
1092
+ let unscanned_range = u64:: from ( chain_tip_height - last_scanned) ;
1093
+
1094
+ ( scanned_notes * unscanned_range)
1095
+ . checked_div ( scanned_range)
1096
+ . map ( |extrapolated_unscanned_notes| {
1097
+ last_scanned_tree_size + extrapolated_unscanned_notes
1098
+ } )
1099
+ } ,
1100
+ ) ;
1101
+
1102
+ if let Some ( tree_size) = tip_tree_size {
1103
+ Some ( tree_size)
1104
+ } else if let Some ( second_to_last_completed_subtree_height) =
1105
+ last_completed_subtree_index
1106
+ . checked_sub ( 1 )
1107
+ . and_then ( |subtree_index| {
1108
+ conn. query_row (
1109
+ & format ! (
1110
+ "SELECT subtree_end_height
1111
+ FROM {table_prefix}_tree_shards
1112
+ WHERE shard_index = :shard_index"
1113
+ ) ,
1114
+ named_params ! { ":shard_index" : subtree_index} ,
1115
+ |row| {
1116
+ Ok ( row. get :: < _ , Option < _ > > ( 0 ) ?. map ( BlockHeight :: from_u32) )
1117
+ } ,
1118
+ )
1119
+ . transpose ( )
1120
+ } )
1121
+ . transpose ( ) ?
1122
+ {
1123
+ let notes_in_complete_subtrees =
1124
+ ( last_completed_subtree_index + 1 ) << shard_height;
1125
+
1126
+ let subtree_notes = 1 << shard_height;
1127
+ let subtree_range = u64:: from (
1128
+ last_completed_subtree_height - second_to_last_completed_subtree_height,
1129
+ ) ;
1130
+ let unscanned_range =
1131
+ u64:: from ( chain_tip_height - last_completed_subtree_height) ;
1132
+
1133
+ ( subtree_notes * unscanned_range)
1134
+ . checked_div ( subtree_range)
1135
+ . map ( |extrapolated_incomplete_subtree_notes| {
1136
+ notes_in_complete_subtrees + extrapolated_incomplete_subtree_notes
1137
+ } )
1138
+ } else {
1139
+ // There's only one completed subtree; its start height must
1140
+ // be the activation height for this shielded protocol.
1141
+ let scanned_notes = last_completed_subtree_index << shard_height;
1142
+
1143
+ let subtree_range =
1144
+ u64:: from ( last_completed_subtree_height - pool_activation_height) ;
1145
+ let unscanned_range =
1146
+ u64:: from ( chain_tip_height - last_completed_subtree_height) ;
1147
+
1148
+ ( scanned_notes * unscanned_range)
1149
+ . checked_div ( subtree_range)
1150
+ . map ( |extrapolated_unscanned_notes| {
1151
+ scanned_notes + extrapolated_unscanned_notes
1152
+ } )
1153
+ }
1154
+ } else {
1155
+ // We don't have subtree information, so give up. We'll get it soon.
1156
+ None
1157
+ }
1158
+ }
1159
+ } ;
985
1160
986
1161
let recover = recovered_count
987
1162
. zip ( recover_until_size)
@@ -1018,9 +1193,9 @@ fn subtree_scan_progress(
1018
1193
recover_until_size
1019
1194
. unwrap_or ( start_size)
1020
1195
. or ( min_tree_size)
1021
- . zip ( max_tree_size )
1022
- . map ( |( min_tree_size , max_tree_size ) | {
1023
- Ratio :: new ( scanned_count. unwrap_or ( 0 ) , max_tree_size - min_tree_size )
1196
+ . zip ( tip_tree_size )
1197
+ . map ( |( start_size , tip_tree_size ) | {
1198
+ Ratio :: new ( scanned_count. unwrap_or ( 0 ) , tip_tree_size - start_size )
1024
1199
} )
1025
1200
} ;
1026
1201
@@ -1029,10 +1204,11 @@ fn subtree_scan_progress(
1029
1204
}
1030
1205
1031
1206
impl ScanProgress for SubtreeScanProgress {
1032
- #[ tracing:: instrument( skip( conn) ) ]
1033
- fn sapling_scan_progress (
1207
+ #[ tracing:: instrument( skip( conn, params ) ) ]
1208
+ fn sapling_scan_progress < P : consensus :: Parameters > (
1034
1209
& self ,
1035
1210
conn : & rusqlite:: Connection ,
1211
+ params : & P ,
1036
1212
birthday_height : BlockHeight ,
1037
1213
recover_until_height : Option < BlockHeight > ,
1038
1214
fully_scanned_height : BlockHeight ,
@@ -1043,6 +1219,9 @@ impl ScanProgress for SubtreeScanProgress {
1043
1219
SAPLING_TABLES_PREFIX ,
1044
1220
"sapling_output_count" ,
1045
1221
SAPLING_SHARD_HEIGHT ,
1222
+ params
1223
+ . activation_height ( NetworkUpgrade :: Sapling )
1224
+ . expect ( "Sapling activation height must be available." ) ,
1046
1225
birthday_height,
1047
1226
recover_until_height,
1048
1227
fully_scanned_height,
@@ -1051,10 +1230,11 @@ impl ScanProgress for SubtreeScanProgress {
1051
1230
}
1052
1231
1053
1232
#[ cfg( feature = "orchard" ) ]
1054
- #[ tracing:: instrument( skip( conn) ) ]
1055
- fn orchard_scan_progress (
1233
+ #[ tracing:: instrument( skip( conn, params ) ) ]
1234
+ fn orchard_scan_progress < P : consensus :: Parameters > (
1056
1235
& self ,
1057
1236
conn : & rusqlite:: Connection ,
1237
+ params : & P ,
1058
1238
birthday_height : BlockHeight ,
1059
1239
recover_until_height : Option < BlockHeight > ,
1060
1240
fully_scanned_height : BlockHeight ,
@@ -1065,6 +1245,9 @@ impl ScanProgress for SubtreeScanProgress {
1065
1245
ORCHARD_TABLES_PREFIX ,
1066
1246
"orchard_action_count" ,
1067
1247
ORCHARD_SHARD_HEIGHT ,
1248
+ params
1249
+ . activation_height ( NetworkUpgrade :: Nu5 )
1250
+ . expect ( "NU5 activation height must be available." ) ,
1068
1251
birthday_height,
1069
1252
recover_until_height,
1070
1253
fully_scanned_height,
@@ -1104,6 +1287,7 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
1104
1287
1105
1288
let sapling_progress = progress. sapling_scan_progress (
1106
1289
tx,
1290
+ params,
1107
1291
birthday_height,
1108
1292
recover_until_height,
1109
1293
fully_scanned_height,
@@ -1113,6 +1297,7 @@ pub(crate) fn get_wallet_summary<P: consensus::Parameters>(
1113
1297
#[ cfg( feature = "orchard" ) ]
1114
1298
let orchard_progress = progress. orchard_scan_progress (
1115
1299
tx,
1300
+ params,
1116
1301
birthday_height,
1117
1302
recover_until_height,
1118
1303
fully_scanned_height,
0 commit comments