zebrad/components/zcashd_compat/
config.rs1use std::{
2 net::{IpAddr, SocketAddr},
3 path::PathBuf,
4 time::Duration,
5};
6
7use serde::{de::Error as _, Deserialize, Deserializer, Serialize};
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum ZcashdBinarySource {
13 #[default]
15 Path,
16 Embedded,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
22#[serde(deny_unknown_fields, default)]
23pub struct Config {
24 pub enabled: bool,
29
30 pub manage_zcashd: bool,
34
35 pub zcashd_source: ZcashdBinarySource,
39
40 pub zcashd_path: Option<PathBuf>,
44
45 pub zcashd_datadir: Option<PathBuf>,
49
50 #[serde(default, deserialize_with = "deserialize_zcashd_extra_args")]
64 pub zcashd_extra_args: Vec<String>,
65
66 pub p2p_connect_addr: Option<SocketAddr>,
73
74 #[serde(default, deserialize_with = "deserialize_block_gossip_peer_ips")]
101 pub block_gossip_peer_ips: Vec<IpAddr>,
102
103 #[serde(with = "humantime_serde")]
105 pub startup_delay: Duration,
106
107 #[serde(with = "humantime_serde")]
111 pub restart_backoff: Duration,
112
113 #[serde(with = "humantime_serde")]
117 pub restart_backoff_max: Duration,
118
119 #[serde(with = "humantime_serde")]
121 pub restart_reset_after: Duration,
122
123 #[serde(with = "humantime_serde")]
125 pub shutdown_grace_period: Duration,
126}
127
128impl Default for Config {
129 fn default() -> Self {
135 Self {
136 enabled: false,
137 manage_zcashd: false,
138 zcashd_source: ZcashdBinarySource::Path,
139 zcashd_path: None,
140 zcashd_datadir: None,
141 zcashd_extra_args: Vec::new(),
142 p2p_connect_addr: None,
143 block_gossip_peer_ips: Vec::new(),
144 startup_delay: Duration::from_secs(1),
145 restart_backoff: Duration::from_secs(2),
146 restart_backoff_max: Duration::from_secs(5 * 60),
147 restart_reset_after: Duration::from_secs(60 * 60),
148 shutdown_grace_period: Duration::from_secs(300),
149 }
150 }
151}
152
153fn deserialize_zcashd_extra_args<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
155where
156 D: Deserializer<'de>,
157{
158 #[derive(Deserialize)]
159 #[serde(untagged)]
160 enum ExtraArgsField {
161 Sequence(Vec<String>),
162 JsonString(String),
163 }
164
165 match ExtraArgsField::deserialize(deserializer)? {
166 ExtraArgsField::Sequence(args) => Ok(args),
167 ExtraArgsField::JsonString(args) => {
168 serde_json::from_str(&args).map_err(|error| {
169 D::Error::custom(format!(
170 "zcashd_extra_args must be a sequence or a JSON string array, got: {args:?}. parse error: {error}"
171 ))
172 })
173 }
174 }
175}
176
177fn deserialize_block_gossip_peer_ips<'de, D>(deserializer: D) -> Result<Vec<IpAddr>, D::Error>
182where
183 D: Deserializer<'de>,
184{
185 #[derive(Deserialize)]
186 #[serde(untagged)]
187 enum PeerIpsField {
188 Sequence(Vec<IpAddr>),
189 JsonString(String),
190 }
191
192 match PeerIpsField::deserialize(deserializer)? {
193 PeerIpsField::Sequence(ips) => Ok(ips),
194 PeerIpsField::JsonString(ips) => serde_json::from_str(&ips).map_err(|error| {
195 D::Error::custom(format!(
196 "block_gossip_peer_ips must be a sequence or a JSON string array, got: {ips:?}. parse error: {error}"
197 ))
198 }),
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use std::net::{IpAddr, Ipv4Addr};
205
206 use super::{Config, ZcashdBinarySource};
207
208 #[test]
209 fn defaults_to_unsupervised_path_source_without_explicit_path() {
210 let config = Config::default();
211 assert!(!config.manage_zcashd);
212 assert_eq!(config.zcashd_source, ZcashdBinarySource::Path);
213 assert_eq!(config.zcashd_path, None);
214 assert_eq!(
215 config.restart_reset_after,
216 std::time::Duration::from_secs(60 * 60)
217 );
218 assert_eq!(
219 config.restart_backoff_max,
220 std::time::Duration::from_secs(5 * 60)
221 );
222 }
223
224 #[test]
225 fn default_shutdown_grace_period_allows_zcashd_to_flush_state() {
226 let config = Config::default();
227
228 assert_eq!(
229 config.shutdown_grace_period,
230 std::time::Duration::from_secs(300)
231 );
232 }
233
234 #[test]
235 fn deserialize_restart_reset_after_duration() {
236 let config: Config = toml::from_str(
237 r#"
238 restart_reset_after = "30m"
239 "#,
240 )
241 .expect("restart reset duration should deserialize");
242
243 assert_eq!(
244 config.restart_reset_after,
245 std::time::Duration::from_secs(30 * 60)
246 );
247 }
248
249 #[test]
250 fn deserialize_restart_backoff_max_duration() {
251 let config: Config = toml::from_str(
252 r#"
253 restart_backoff_max = "10m"
254 "#,
255 )
256 .expect("restart backoff cap duration should deserialize");
257
258 assert_eq!(
259 config.restart_backoff_max,
260 std::time::Duration::from_secs(10 * 60)
261 );
262 }
263
264 #[test]
265 fn deserialize_block_gossip_peer_ips() {
266 let config: Config = toml::from_str(
267 r#"
268 block_gossip_peer_ips = ["127.0.0.1"]
269 "#,
270 )
271 .expect("sidecar block gossip peer IPs should deserialize");
272
273 assert_eq!(
274 config.block_gossip_peer_ips,
275 vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]
276 );
277 }
278
279 #[test]
280 fn deserialize_extra_args_from_sequence() {
281 let config: Config = toml::from_str(
282 r#"
283 zcashd_extra_args = ["-conf=/tmp/zcash.conf", "-debug=1"]
284 "#,
285 )
286 .expect("valid sequence should deserialize");
287
288 assert_eq!(
289 config.zcashd_extra_args,
290 vec!["-conf=/tmp/zcash.conf".to_string(), "-debug=1".to_string()]
291 );
292 }
293
294 #[test]
295 fn deserialize_extra_args_from_json_string() {
296 let config: Config = toml::from_str(
297 r#"
298 zcashd_extra_args = "[\"-conf=/tmp/zcash.conf\",\"-debug=1\"]"
299 "#,
300 )
301 .expect("valid JSON string array should deserialize");
302
303 assert_eq!(
304 config.zcashd_extra_args,
305 vec!["-conf=/tmp/zcash.conf".to_string(), "-debug=1".to_string()]
306 );
307 }
308
309 #[test]
310 fn reject_non_array_string_extra_args() {
311 let error = toml::from_str::<Config>(
312 r#"
313 zcashd_extra_args = "-debug=1"
314 "#,
315 )
316 .expect_err("plain strings should be rejected");
317
318 let error_message = error.to_string();
319 assert!(
320 error_message.contains("JSON string array"),
321 "error should explain expected format: {error_message}"
322 );
323 }
324}