1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
//! Sync configuration.
#[cfg(feature = "wallet_essentials")]
use std::io::{Read, Write};
#[cfg(feature = "wallet_essentials")]
use byteorder::{ReadBytesExt, WriteBytesExt};
/// Performance level.
///
/// The higher the performance level the higher the memory usage and storage.
// TODO: revisit after implementing nullifier refetching
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum PerformanceLevel {
/// - output budget per scan load is quartered
/// - nullifier map only contains chain tip
Low,
/// - nullifier map has a small maximum size
/// - nullifier map only contains chain tip
Medium,
/// - nullifier map has a large maximum size
#[default]
High,
/// - output budget per scan load is quadrupled
/// - nullifier map has no maximum size
///
/// WARNING: this may cause the wallet to become less responsive on slower systems and may use a lot of memory for
/// wallets with a lot of transactions.
Maximum,
}
#[cfg(feature = "wallet_essentials")]
impl PerformanceLevel {
fn serialized_version() -> u8 {
0
}
/// Deserialize into `reader`
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
crate::wallet::serialization::read_version(
&mut reader,
"PerformanceLevel",
Self::serialized_version(),
)?;
Ok(match reader.read_u8()? {
0 => Self::Low,
1 => Self::Medium,
2 => Self::High,
3 => Self::Maximum,
_ => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"failed to read valid performance level",
));
}
})
}
/// Serialize into `writer`
pub fn write<W: Write>(&mut self, mut writer: W) -> std::io::Result<()> {
writer.write_u8(Self::serialized_version())?;
writer.write_u8(match self {
Self::Low => 0,
Self::Medium => 1,
Self::High => 2,
Self::Maximum => 3,
})?;
Ok(())
}
}
impl std::fmt::Display for PerformanceLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => write!(f, "low"),
Self::Medium => write!(f, "medium"),
Self::High => write!(f, "high"),
Self::Maximum => write!(f, "maximum"),
}
}
}
/// Sync configuration.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct SyncConfig {
/// Transparent address discovery configuration.
pub transparent_address_discovery: TransparentAddressDiscovery,
/// Performance level
pub performance_level: PerformanceLevel,
/// Shutdown on completion
///
/// If not set, sync will not shutdown until the consumer sets the `SyncMode` to `Shutdown` variant.
/// The sync engine will regularly check for new blocks mined so the wallet will always be updated to the state
/// of the latest chain.
///
/// If set, sync will still check for any newly mined blocks during scanning. But when the wallet is completely
/// up-to-date with the latest chain, a running sync will shutdown. A sync the consumer has paused stays paused,
/// and shuts down once it is resumed and completes again.
pub shutdown_on_completion: bool,
}
#[cfg(feature = "wallet_essentials")]
impl SyncConfig {
fn serialized_version() -> u8 {
2
}
/// Deserialize into `reader`
pub fn read<R: Read>(mut reader: R) -> std::io::Result<Self> {
let version = crate::wallet::serialization::read_version(
&mut reader,
"SyncConfig",
Self::serialized_version(),
)?;
let gap_limit = reader.read_u8()?;
let scopes = reader.read_u8()?;
let performance_level = if version >= 1 {
PerformanceLevel::read(&mut reader)?
} else {
PerformanceLevel::High
};
let shutdown_on_completion = if version >= 2 {
reader.read_u8()? != 0
} else {
false
};
Ok(Self {
transparent_address_discovery: TransparentAddressDiscovery {
gap_limit,
scopes: TransparentAddressDiscoveryScopes {
external: scopes & 0b1 != 0,
internal: scopes & 0b10 != 0,
refund: scopes & 0b100 != 0,
},
},
performance_level,
shutdown_on_completion,
})
}
/// Serialize into `writer`
pub fn write<W: Write>(&mut self, mut writer: W) -> std::io::Result<()> {
writer.write_u8(Self::serialized_version())?;
writer.write_u8(self.transparent_address_discovery.gap_limit)?;
let mut scopes = 0;
if self.transparent_address_discovery.scopes.external {
scopes |= 0b1;
}
if self.transparent_address_discovery.scopes.internal {
scopes |= 0b10;
}
if self.transparent_address_discovery.scopes.refund {
scopes |= 0b100;
}
writer.write_u8(scopes)?;
self.performance_level.write(&mut writer)?;
writer.write_u8(self.shutdown_on_completion as u8)?;
Ok(())
}
}
/// Transparent address configuration.
///
/// Sets which `scopes` will be searched for addresses in use, scanning relevant transactions, up to a given `gap_limit`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransparentAddressDiscovery {
/// Sets the gap limit for transparent address discovery.
pub gap_limit: u8,
/// Sets the scopes for transparent address discovery.
pub scopes: TransparentAddressDiscoveryScopes,
}
impl Default for TransparentAddressDiscovery {
fn default() -> Self {
Self {
gap_limit: 10,
scopes: TransparentAddressDiscoveryScopes::default(),
}
}
}
impl TransparentAddressDiscovery {
/// Constructs a transparent address discovery config with a gap limit of 1 and ignoring the internal scope.
#[must_use]
pub fn minimal() -> Self {
Self {
gap_limit: 1,
scopes: TransparentAddressDiscoveryScopes::default(),
}
}
/// Constructs a transparent address discovery config with a gap limit of 20 for all scopes.
#[must_use]
pub fn recovery() -> Self {
Self {
gap_limit: 20,
scopes: TransparentAddressDiscoveryScopes::recovery(),
}
}
/// Disables transparent address discovery. Sync will only scan transparent outputs for addresses already in the
/// wallet in transactions that also contain shielded inputs or outputs relevant to the wallet.
#[must_use]
pub fn disabled() -> Self {
Self {
gap_limit: 0,
scopes: TransparentAddressDiscoveryScopes {
external: false,
internal: false,
refund: false,
},
}
}
}
/// Sets the active scopes for transparent address recovery.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransparentAddressDiscoveryScopes {
/// External.
pub external: bool,
/// Internal.
pub internal: bool,
/// Refund.
pub refund: bool,
}
impl Default for TransparentAddressDiscoveryScopes {
fn default() -> Self {
Self {
external: true,
internal: false,
refund: true,
}
}
}
impl TransparentAddressDiscoveryScopes {
/// Constructor with all all scopes active.
#[must_use]
pub fn recovery() -> Self {
Self {
external: true,
internal: true,
refund: true,
}
}
}
#[cfg(all(test, feature = "wallet_essentials"))]
mod tests {
use super::*;
/// Each reader is given only a serialized version above the one its type writes. A reader that refuses the
/// version returns invalid data. A reader that read on would report the end of the input.
#[test]
fn readers_refuse_serialized_versions_above_their_own() {
let newer_sync_config = [SyncConfig::serialized_version() + 1];
let newer_performance_level = [PerformanceLevel::serialized_version() + 1];
for (type_name, read) in [
(
"SyncConfig",
SyncConfig::read(newer_sync_config.as_slice()).map(drop),
),
(
"PerformanceLevel",
PerformanceLevel::read(newer_performance_level.as_slice()).map(drop),
),
] {
assert_eq!(
read.expect_err(type_name).kind(),
std::io::ErrorKind::InvalidData,
"{type_name}"
);
}
}
}