Skip to main content

pepper_sync/
config.rs

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