1use std::{
80 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
81 path::Path,
82 sync::Arc,
83};
84
85use abscissa_core::{config, Command, FrameworkError};
86use color_eyre::eyre::{eyre, Report};
87use futures::FutureExt;
88use tokio::{
89 pin, select,
90 sync::{oneshot, watch},
91};
92use tower::{builder::ServiceBuilder, util::BoxService, ServiceExt};
93use tracing_futures::Instrument;
94
95use zebra_chain::block::genesis::regtest_genesis_block;
96use zebra_consensus::router::BackgroundTaskHandles;
97use zebra_rpc::{methods::RpcImpl, server::RpcServer, SubmitBlockChannel};
98
99use crate::{
100 application::{build_version, user_agent, LAST_WARN_ERROR_LOG_SENDER},
101 components::{
102 health,
103 inbound::{self, InboundSetupData, MAX_INBOUND_RESPONSE_TIME},
104 mempool::{self, Mempool},
105 notify::{self, BlockNotifyError},
106 sync::{self, show_block_chain_progress, VERIFICATION_PIPELINE_SCALING_MULTIPLIER},
107 tokio::{RuntimeRun, TokioComponent},
108 zcashd_compat, ChainSync, Inbound,
109 },
110 config::ZebradConfig,
111 prelude::*,
112};
113
114#[cfg(feature = "internal-miner")]
115use crate::components;
116
117#[derive(Command, Debug, Default, clap::Parser)]
119pub struct StartCmd {
120 #[clap(help = "tracing filters which override the zebrad.toml config")]
122 filters: Vec<String>,
123
124 #[clap(long)]
126 zcashd_compat: bool,
127
128 #[clap(long = "unsafe-low-specs")]
130 unsafe_low_specs: bool,
131}
132
133#[cfg(target_os = "linux")]
138fn check_tcp_slow_start_after_idle() {
139 const PATH: &str = "/proc/sys/net/ipv4/tcp_slow_start_after_idle";
140
141 let raw = match std::fs::read_to_string(PATH) {
142 Ok(raw) => raw,
143 Err(error) => {
144 debug!(
145 ?error,
146 path = PATH,
147 "could not read TCP sysctl, skipping check"
148 );
149 return;
150 }
151 };
152
153 if raw.trim() == "0" {
154 return;
155 }
156
157 warn!(
158 setting = "net.ipv4.tcp_slow_start_after_idle",
159 "TCP slow-start-after-idle is enabled, which resets TCP's congestion window \
160 between block requests and significantly reduces single-peer throughput for \
161 block propagation. \
162 Hint: set `net.ipv4.tcp_slow_start_after_idle=0` via sysctl. \
163 See https://zebra.zfnd.org/user/troubleshooting.html#linux-tcp-tuning-for-block-propagation"
164 );
165}
166
167#[cfg(not(target_os = "linux"))]
168fn check_tcp_slow_start_after_idle() {}
169
170impl StartCmd {
171 const ZCASHD_COMPAT_SHUTDOWN_TIMEOUT_MARGIN: std::time::Duration =
176 std::time::Duration::from_secs(30);
177
178 fn zcashd_compat_p2p_connect_addr(
184 config: &ZebradConfig,
185 local_listener: SocketAddr,
186 ) -> SocketAddr {
187 if let Some(addr) = config.zcashd_compat.p2p_connect_addr {
188 return addr;
189 }
190
191 if local_listener.ip().is_unspecified() {
192 match local_listener.ip() {
195 IpAddr::V4(_) => SocketAddr::from(([127, 0, 0, 1], local_listener.port())),
196 IpAddr::V6(_) => {
197 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), local_listener.port())
198 }
199 }
200 } else {
201 local_listener
202 }
203 }
204
205 fn zcashd_compat_default_block_gossip_peer_ips() -> Vec<IpAddr> {
208 vec![
209 IpAddr::V4(Ipv4Addr::LOCALHOST),
210 IpAddr::V6(Ipv6Addr::LOCALHOST),
211 ]
212 }
213
214 fn zcashd_compat_supervisor_shutdown_timeout(
220 config: &ZebradConfig,
221 ) -> Option<std::time::Duration> {
222 (config.zcashd_compat.enabled && config.zcashd_compat.manage_zcashd).then_some(
223 config
224 .zcashd_compat
225 .shutdown_grace_period
226 .saturating_add(Self::ZCASHD_COMPAT_SHUTDOWN_TIMEOUT_MARGIN),
227 )
228 }
229
230 fn zcashd_compat_supervisor_should_exit(
232 zcashd_compat_result: Result<Result<(), Report>, tokio::task::JoinError>,
233 ) -> bool {
234 zcashd_compat::set_supervision_unexpectedly_disabled_metrics();
235
236 match zcashd_compat_result {
237 Ok(Ok(())) => {
238 warn!(
239 "zcashd-compat supervisor task exited unexpectedly in supervision mode; \
240 continuing without zcashd supervision"
241 );
242 }
243 Ok(Err(err)) => {
244 warn!(
245 ?err,
246 "zcashd-compat supervisor task failed in supervision mode; \
247 continuing without zcashd supervision"
248 );
249 }
250 Err(join_err) => {
251 warn!(
252 ?join_err,
253 "zcashd-compat supervisor task panicked in supervision mode; \
254 continuing without zcashd supervision"
255 );
256 }
257 }
258
259 false
260 }
261
262 async fn start(&self) -> Result<(), Report> {
263 check_tcp_slow_start_after_idle();
264
265 let config = APPLICATION.config();
266 let is_regtest = config.network.network.is_regtest();
267
268 let config = if is_regtest {
269 Arc::new(ZebradConfig {
270 mempool: mempool::Config {
271 debug_enable_at_height: Some(0),
272 ..config.mempool
273 },
274 ..Arc::unwrap_or_clone(config)
275 })
276 } else {
277 config
278 };
279
280 let zcashd_compat_block_gossip_peer_ips = if config.zcashd_compat.enabled {
281 if config.zcashd_compat.block_gossip_peer_ips.is_empty() {
282 if config
288 .zcashd_compat
289 .p2p_connect_addr
290 .is_some_and(|addr| !addr.ip().is_loopback())
291 {
292 warn!(
293 p2p_connect_addr = ?config.zcashd_compat.p2p_connect_addr,
294 "zcashd_compat.p2p_connect_addr is not loopback, but \
295 zcashd_compat.block_gossip_peer_ips defaults to loopback only; \
296 if the sidecar connects from a non-loopback IP, set \
297 block_gossip_peer_ips to that IP or it will not receive \
298 pinned block gossip"
299 );
300 }
301
302 Self::zcashd_compat_default_block_gossip_peer_ips()
303 } else {
304 config.zcashd_compat.block_gossip_peer_ips.clone()
305 }
306 } else {
307 Vec::new()
308 };
309
310 if config.zcashd_compat.enabled {
311 let preflight_config = config.clone();
314 let unsafe_low_specs = self.unsafe_low_specs;
315 tokio::task::spawn_blocking(move || {
316 zcashd_compat::run_preflight(&preflight_config, unsafe_low_specs)
317 })
318 .await
319 .map_err(|err| eyre!("failed to join zcashd-compat preflight task: {err}"))??;
320 }
321
322 let resolved_zcashd_path = if config.zcashd_compat.enabled
323 && config.zcashd_compat.manage_zcashd
324 {
325 let zcashd_compat_config = config.zcashd_compat.clone();
326 let state_cache_dir = config.state.cache_dir.clone();
327 Some(
328 tokio::task::spawn_blocking(move || {
329 zcashd_compat::resolve_zcashd_binary_path(
330 &zcashd_compat_config,
331 &state_cache_dir,
332 )
333 })
334 .await
335 .map_err(|err| eyre!("failed to join managed zcashd binary resolver: {err}"))??,
336 )
337 } else {
338 None
339 };
340
341 info!("initializing node state");
342 let (_, max_checkpoint_height) = zebra_consensus::router::init_checkpoint_list(
343 config.consensus.clone(),
344 &config.network.network,
345 );
346
347 info!("opening database, this may take a few minutes");
348
349 let (state_service, read_only_state_service, latest_chain_tip, chain_tip_change) =
350 zebra_state::init(
351 config.state.clone(),
352 &config.network.network,
353 max_checkpoint_height,
354 config.sync.checkpoint_verify_concurrency_limit
355 * (VERIFICATION_PIPELINE_SCALING_MULTIPLIER + 1),
356 )
357 .await;
358
359 info!("logging database metrics on startup");
360 read_only_state_service.log_db_metrics();
361
362 let state = ServiceBuilder::new()
363 .buffer(Self::state_buffer_bound())
364 .service(state_service);
365
366 info!("initializing network");
367 let (setup_tx, setup_rx) = oneshot::channel();
378 let inbound = ServiceBuilder::new()
379 .load_shed()
380 .buffer(inbound::downloads::MAX_INBOUND_CONCURRENCY)
381 .timeout(MAX_INBOUND_RESPONSE_TIME)
382 .service(Inbound::new(
383 config.sync.full_verify_concurrency_limit,
384 setup_rx,
385 ));
386
387 let (peer_set, address_book, misbehavior_sender) =
388 zebra_network::init_with_block_gossip_peer_ips(
389 config.network.clone(),
390 inbound,
391 latest_chain_tip.clone(),
392 user_agent(),
393 zcashd_compat_block_gossip_peer_ips,
394 )
395 .await;
396
397 info!("initializing verifiers");
400 let (tx_verifier_setup_tx, tx_verifier_setup_rx) = oneshot::channel();
401 let (block_verifier_router, tx_verifier, consensus_task_handles, max_checkpoint_height) =
402 zebra_consensus::router::init(
403 config.consensus.clone(),
404 &config.network.network,
405 state.clone(),
406 tx_verifier_setup_rx,
407 )
408 .await;
409
410 info!("initializing syncer");
411 let (mut syncer, sync_status) = ChainSync::new(
412 &config,
413 max_checkpoint_height,
414 peer_set.clone(),
415 block_verifier_router.clone(),
416 state.clone(),
417 latest_chain_tip.clone(),
418 misbehavior_sender.clone(),
419 );
420
421 info!("initializing mempool");
422 let (mempool, mempool_transaction_subscriber) = Mempool::new(
423 &config.mempool,
424 peer_set.clone(),
425 state.clone(),
426 tx_verifier,
427 sync_status.clone(),
428 latest_chain_tip.clone(),
429 chain_tip_change.clone(),
430 misbehavior_sender.clone(),
431 );
432 let mempool = BoxService::new(mempool);
433 let mempool = ServiceBuilder::new()
434 .buffer(mempool::downloads::MAX_INBOUND_CONCURRENCY)
435 .service(mempool);
436
437 if tx_verifier_setup_tx.send(mempool.clone()).is_err() {
438 warn!("error setting up the transaction verifier with a handle to the mempool service");
439 };
440
441 info!("fully initializing inbound peer request handler");
442 let setup_data = InboundSetupData {
444 address_book: address_book.clone(),
445 block_download_peer_set: peer_set.clone(),
446 block_verifier: block_verifier_router.clone(),
447 mempool: mempool.clone(),
448 state: state.clone(),
449 latest_chain_tip: latest_chain_tip.clone(),
450 misbehavior_sender,
451 };
452 setup_tx
453 .send(setup_data)
454 .map_err(|_| eyre!("could not send setup data to inbound service"))?;
455 tokio::task::yield_now().await;
457
458 let submit_block_channel = SubmitBlockChannel::new();
460
461 let (rpc_impl, mut rpc_tx_queue_handle) = RpcImpl::new(
463 config.network.network.clone(),
464 config.mining.clone(),
465 config.rpc.debug_force_finished_sync,
466 build_version(),
467 user_agent(),
468 mempool.clone(),
469 state.clone(),
470 read_only_state_service.clone(),
471 block_verifier_router.clone(),
472 sync_status.clone(),
473 latest_chain_tip.clone(),
474 address_book.clone(),
475 LAST_WARN_ERROR_LOG_SENDER.subscribe(),
476 Some(submit_block_channel.sender()),
477 );
478
479 let rpc_task_handle = if config.rpc.listen_addr.is_some() {
480 RpcServer::start(rpc_impl.clone(), config.rpc.clone())
481 .await
482 .expect("server should start")
483 } else {
484 tokio::spawn(std::future::pending().in_current_span())
485 };
486
487 let zcashd_compat_shutdown_timeout =
488 Self::zcashd_compat_supervisor_shutdown_timeout(&config);
489 let (zcashd_compat_shutdown_tx, zcashd_compat_shutdown_rx) = watch::channel(false);
490 let mut zcashd_compat_task_handle = if let Some(resolved_zcashd_path) = resolved_zcashd_path
491 {
492 let local_listener = address_book
493 .lock()
494 .expect("unexpected panic in address book mutex guard")
495 .local_listener_socket_addr();
496 let supervisor_config = zcashd_compat::SupervisorConfig::new(
497 &config.zcashd_compat,
498 resolved_zcashd_path,
499 &config.state.cache_dir,
500 config.network.network.kind(),
501 Self::zcashd_compat_p2p_connect_addr(&config, local_listener),
502 );
503
504 info!(
505 connect = %supervisor_config.zebra_p2p_addr,
506 "zcashd-compat mode enabled"
507 );
508
509 tokio::spawn(
510 zcashd_compat::run_supervisor(supervisor_config, zcashd_compat_shutdown_rx)
511 .in_current_span(),
512 )
513 } else {
514 if config.zcashd_compat.enabled {
515 zcashd_compat::set_supervision_config_disabled_metrics();
516 info!("zcashd-compat mode enabled: zcashd supervision disabled");
517 }
518
519 tokio::spawn(std::future::pending().in_current_span())
520 };
521
522 let indexer_rpc_task_handle = {
525 if let Some(indexer_listen_addr) = config.rpc.indexer_listen_addr {
526 info!("spawning indexer RPC server");
527 let (indexer_rpc_task_handle, _listen_addr) = zebra_rpc::indexer::server::init(
528 indexer_listen_addr,
529 read_only_state_service.clone(),
530 latest_chain_tip.clone(),
531 mempool_transaction_subscriber.clone(),
532 )
533 .await
534 .map_err(|err| eyre!(err))?;
535
536 indexer_rpc_task_handle
537 } else {
538 warn!("configure an indexer_listen_addr to start the indexer RPC server");
539 tokio::spawn(std::future::pending().in_current_span())
540 }
541 };
542
543 info!("spawning block gossip task");
545 let block_gossip_task_handle = tokio::spawn(
546 sync::gossip_best_tip_block_hashes(
547 sync_status.clone(),
548 chain_tip_change.clone(),
549 peer_set.clone(),
550 Some(submit_block_channel.receiver()),
551 )
552 .in_current_span(),
553 );
554
555 info!("spawning block notify task");
556 let block_notify_task_handle: tokio::task::JoinHandle<Result<(), BlockNotifyError>> =
557 if let Some(command) = config.notify.block_notify_command.clone() {
558 tokio::spawn(
559 notify::run_block_notify(
560 command,
561 sync_status.clone(),
562 chain_tip_change.clone(),
563 )
564 .in_current_span(),
565 )
566 } else {
567 tokio::spawn(std::future::pending().in_current_span())
568 };
569
570 info!("spawning mempool queue checker task");
571 let mempool_queue_checker_task_handle = mempool::QueueChecker::spawn(mempool.clone());
572
573 info!("spawning mempool transaction gossip task");
574 let tx_gossip_task_handle = tokio::spawn(
575 mempool::gossip_mempool_transaction_id(
576 mempool_transaction_subscriber.subscribe(),
577 peer_set.clone(),
578 )
579 .in_current_span(),
580 );
581
582 info!("spawning delete old databases task");
583 let mut old_databases_task_handle = zebra_state::check_and_delete_old_state_databases(
584 &config.state,
585 &config.network.network,
586 );
587
588 info!("spawning progress logging task");
589 let (chain_tip_metrics_sender, chain_tip_metrics_receiver) =
590 health::ChainTipMetrics::channel();
591 let progress_task_handle = tokio::spawn(
592 show_block_chain_progress(
593 config.network.network.clone(),
594 latest_chain_tip.clone(),
595 sync_status.clone(),
596 chain_tip_metrics_sender,
597 )
598 .in_current_span(),
599 );
600
601 info!("initializing health endpoints");
603 let (health_task_handle, _) = health::init(
604 config.health.clone(),
605 config.network.network.clone(),
606 chain_tip_metrics_receiver,
607 sync_status.clone(),
608 address_book.clone(),
609 )
610 .await;
611
612 info!("spawning end of support checking task");
614 let end_of_support_task_handle = tokio::spawn(
615 sync::end_of_support::start(config.network.network.clone(), latest_chain_tip.clone())
616 .in_current_span(),
617 );
618
619 tokio::task::yield_now().await;
623
624 info!("spawning mempool crawler task");
626 let mempool_crawler_task_handle = mempool::Crawler::spawn(
627 &config.mempool,
628 peer_set,
629 mempool.clone(),
630 sync_status.clone(),
631 chain_tip_change.clone(),
632 );
633
634 info!("spawning syncer task");
635 if is_regtest
641 && !syncer
642 .state_contains(config.network.network.genesis_hash())
643 .await?
644 {
645 let genesis_hash = block_verifier_router
646 .clone()
647 .oneshot(zebra_consensus::Request::Commit(regtest_genesis_block()))
648 .await
649 .expect("should validate Regtest genesis block");
650
651 assert_eq!(
652 genesis_hash,
653 config.network.network.genesis_hash(),
654 "validated block hash should match network genesis hash"
655 )
656 }
657 let syncer_task_handle = tokio::spawn(syncer.sync().in_current_span());
658
659 #[cfg(feature = "internal-miner")]
663 let miner_task_handle = if config.mining.is_internal_miner_enabled() {
664 info!("spawning Zcash miner");
665 components::miner::spawn_init(&config.metrics, rpc_impl)
666 } else {
667 tokio::spawn(std::future::pending().in_current_span())
668 };
669
670 #[cfg(not(feature = "internal-miner"))]
671 let miner_task_handle: tokio::task::JoinHandle<Result<(), Report>> =
673 tokio::spawn(std::future::pending().in_current_span());
674
675 info!("spawned initial Zebra tasks");
676
677 pin!(rpc_task_handle);
681 pin!(indexer_rpc_task_handle);
682 pin!(syncer_task_handle);
683 pin!(block_gossip_task_handle);
684 pin!(block_notify_task_handle);
685 pin!(mempool_crawler_task_handle);
686 pin!(mempool_queue_checker_task_handle);
687 pin!(tx_gossip_task_handle);
688 pin!(progress_task_handle);
689 pin!(end_of_support_task_handle);
690 pin!(miner_task_handle);
691
692 let BackgroundTaskHandles {
694 mut state_checkpoint_verify_handle,
695 } = consensus_task_handles;
696
697 let state_checkpoint_verify_handle_fused = (&mut state_checkpoint_verify_handle).fuse();
698 pin!(state_checkpoint_verify_handle_fused);
699
700 let old_databases_task_handle_fused = (&mut old_databases_task_handle).fuse();
701 pin!(old_databases_task_handle_fused);
702
703 let mut zcashd_compat_task_finished = false;
706 let zcashd_compat_task_handle_fused = (&mut zcashd_compat_task_handle).fuse();
707 pin!(zcashd_compat_task_handle_fused);
708
709 let exit_status = loop {
711 let mut exit_when_task_finishes = true;
712
713 let result = select! {
714 rpc_join_result = &mut rpc_task_handle => {
715 let rpc_server_result = rpc_join_result
716 .expect("unexpected panic in the rpc task");
717 info!(?rpc_server_result, "rpc task exited");
718 Ok(())
719 }
720
721 rpc_tx_queue_result = &mut rpc_tx_queue_handle => {
722 rpc_tx_queue_result
723 .expect("unexpected panic in the rpc transaction queue task");
724 info!("rpc transaction queue task exited");
725 Ok(())
726 }
727
728 indexer_rpc_join_result = &mut indexer_rpc_task_handle => {
729 let indexer_rpc_server_result = indexer_rpc_join_result
730 .expect("unexpected panic in the indexer task");
731 info!(?indexer_rpc_server_result, "indexer rpc task exited");
732 Ok(())
733 }
734
735 sync_result = &mut syncer_task_handle => sync_result
736 .expect("unexpected panic in the syncer task")
737 .map(|_| info!("syncer task exited")),
738
739 block_gossip_result = &mut block_gossip_task_handle => block_gossip_result
740 .expect("unexpected panic in the chain tip block gossip task")
741 .map(|_| info!("chain tip block gossip task exited"))
742 .map_err(|e| eyre!(e)),
743
744 block_notify_result = &mut block_notify_task_handle => block_notify_result
745 .expect("unexpected panic in the block notify task")
746 .map(|_| info!("block notify task exited"))
747 .map_err(|e| eyre!(e)),
748
749 mempool_crawl_result = &mut mempool_crawler_task_handle => mempool_crawl_result
750 .expect("unexpected panic in the mempool crawler")
751 .map(|_| info!("mempool crawler task exited"))
752 .map_err(|e| eyre!(e)),
753
754 mempool_queue_result = &mut mempool_queue_checker_task_handle => mempool_queue_result
755 .expect("unexpected panic in the mempool queue checker")
756 .map(|_| info!("mempool queue checker task exited"))
757 .map_err(|e| eyre!(e)),
758
759 tx_gossip_result = &mut tx_gossip_task_handle => tx_gossip_result
760 .expect("unexpected panic in the transaction gossip task")
761 .map(|_| info!("transaction gossip task exited"))
762 .map_err(|e| eyre!(e)),
763
764 progress_result = &mut progress_task_handle => {
767 info!("chain progress task exited");
768 progress_result
769 .expect("unexpected panic in the chain progress task");
770 }
771
772 end_of_support_result = &mut end_of_support_task_handle => end_of_support_result
773 .expect("unexpected panic in the end of support task")
774 .map(|_| info!("end of support task exited")),
775
776 state_checkpoint_verify_result = &mut state_checkpoint_verify_handle_fused => {
778 state_checkpoint_verify_result
779 .unwrap_or_else(|_| panic!(
780 "unexpected panic checking previous state followed the best chain"));
781
782 exit_when_task_finishes = false;
783 Ok(())
784 }
785
786 old_databases_result = &mut old_databases_task_handle_fused => {
788 old_databases_result
789 .unwrap_or_else(|_| panic!(
790 "unexpected panic deleting old database directories"));
791
792 exit_when_task_finishes = false;
793 Ok(())
794 }
795
796 miner_result = &mut miner_task_handle => miner_result
797 .expect("unexpected panic in the miner task")
798 .map(|_| info!("miner task exited")),
799
800 zcashd_compat_result = &mut zcashd_compat_task_handle_fused => {
801 zcashd_compat_task_finished = true;
802 exit_when_task_finishes =
803 Self::zcashd_compat_supervisor_should_exit(zcashd_compat_result);
804 Ok(())
805 },
806 };
807
808 if let Err(err) = result {
811 break Err(err);
812 }
813
814 if exit_when_task_finishes {
815 break Ok(());
816 }
817 };
818
819 info!("exiting Zebra because an ongoing task exited: asking other tasks to stop");
820
821 rpc_task_handle.abort();
823 rpc_tx_queue_handle.abort();
824 health_task_handle.abort();
825 syncer_task_handle.abort();
826 block_gossip_task_handle.abort();
827 block_notify_task_handle.abort();
828 mempool_crawler_task_handle.abort();
829 mempool_queue_checker_task_handle.abort();
830 tx_gossip_task_handle.abort();
831 progress_task_handle.abort();
832 end_of_support_task_handle.abort();
833 miner_task_handle.abort();
834 if zcashd_compat_task_finished {
835 debug!("zcashd-compat supervisor task already exited before shutdown");
836 } else if let Some(zcashd_compat_shutdown_timeout) = zcashd_compat_shutdown_timeout {
837 info!(
838 ?zcashd_compat_shutdown_timeout,
839 "requesting zcashd-compat supervisor shutdown"
840 );
841 if zcashd_compat_shutdown_tx.send(true).is_err() {
842 warn!("zcashd-compat supervisor shutdown request was not delivered");
843 }
844 if tokio::time::timeout(
845 zcashd_compat_shutdown_timeout,
846 &mut zcashd_compat_task_handle,
847 )
848 .await
849 .is_err()
850 {
851 warn!(
852 ?zcashd_compat_shutdown_timeout,
853 "zcashd-compat supervisor did not finish before shutdown timeout; \
854 abandoning child process handle"
855 );
856 zcashd_compat_task_handle.abort();
860 }
861 } else {
862 debug!("aborting zcashd-compat supervisor task without managed child shutdown");
863 zcashd_compat_task_handle.abort();
864 }
865
866 state_checkpoint_verify_handle.abort();
868 old_databases_task_handle.abort();
869
870 info!(
871 "exiting Zebra: all tasks have been asked to stop, waiting for remaining tasks to finish"
872 );
873
874 exit_status
875 }
876
877 fn state_buffer_bound() -> usize {
880 let config = APPLICATION.config();
881
882 [
887 config.sync.download_concurrency_limit,
888 config.sync.full_verify_concurrency_limit,
889 inbound::downloads::MAX_INBOUND_CONCURRENCY,
890 mempool::downloads::MAX_INBOUND_CONCURRENCY,
891 ]
892 .into_iter()
893 .max()
894 .unwrap()
895 }
896}
897
898impl Runnable for StartCmd {
899 fn run(&self) {
901 info!("Starting zebrad");
902 let rt = APPLICATION
903 .state()
904 .components_mut()
905 .get_downcast_mut::<TokioComponent>()
906 .expect("TokioComponent should be available")
907 .rt
908 .take();
909
910 rt.expect("runtime should not already be taken")
911 .run(self.start());
912
913 info!("stopping zebrad");
914 }
915}
916
917impl config::Override<ZebradConfig> for StartCmd {
918 fn override_config(&self, mut config: ZebradConfig) -> Result<ZebradConfig, FrameworkError> {
922 if !self.filters.is_empty() {
923 config.tracing.filter = Some(self.filters.join(","));
924 }
925
926 if self.zcashd_compat {
930 config.zcashd_compat.enabled = true;
931 }
932
933 if !config.zcashd_compat.enabled && !config.zcashd_compat.block_gossip_peer_ips.is_empty() {
934 return Err(std::io::Error::other(
935 "zcashd_compat.block_gossip_peer_ips requires zcashd_compat.enabled = true",
936 )
937 .into());
938 }
939
940 if config.zcashd_compat.enabled && config.zcashd_compat.manage_zcashd {
941 zcashd_compat::reject_peer_selection_extra_args(
942 &config.zcashd_compat.zcashd_extra_args,
943 )
944 .map_err(|err| std::io::Error::other(err.to_string()))?;
945
946 match zcashd_compat::effective_zcashd_source(&config.zcashd_compat) {
947 Ok(zcashd_compat::ZcashdBinarySource::Path(path))
948 if !zcashd_compat::is_command_resolvable(Path::new(&path)) =>
949 {
950 return Err(std::io::Error::other(format!(
951 "zcashd-compat mode could not resolve zcashd_path={}",
952 path.display()
953 ))
954 .into());
955 }
956 Ok(_) => {}
957 Err(err) => return Err(std::io::Error::other(err.to_string()).into()),
958 }
959 }
960
961 Ok(config)
962 }
963}
964
965#[cfg(test)]
966mod tests {
967 use abscissa_core::config::Override;
968 use color_eyre::eyre::eyre;
969
970 use super::StartCmd;
971 use crate::components::zcashd_compat;
972 use crate::config::ZebradConfig;
973
974 #[test]
975 fn zcashd_compat_flag_enables_mode() {
976 let cmd = StartCmd {
977 filters: Vec::new(),
978 zcashd_compat: true,
979 unsafe_low_specs: false,
980 };
981 let mut config = ZebradConfig::default();
982 config.zcashd_compat.manage_zcashd = false;
983
984 let config = cmd
985 .override_config(config)
986 .expect("zcashd-compat override config should succeed");
987
988 assert!(config.zcashd_compat.enabled);
989 }
990
991 #[test]
992 fn zcashd_compat_config_enables_mode() {
993 let cmd = StartCmd {
994 filters: Vec::new(),
995 zcashd_compat: false,
996 unsafe_low_specs: false,
997 };
998 let mut config = ZebradConfig::default();
999 config.zcashd_compat.enabled = true;
1000 config.zcashd_compat.manage_zcashd = false;
1001
1002 let config = cmd
1003 .override_config(config)
1004 .expect("zcashd-compat override config should succeed");
1005
1006 assert!(config.zcashd_compat.enabled);
1007 }
1008
1009 #[test]
1010 fn block_gossip_peer_ips_require_zcashd_compat() {
1011 let cmd = StartCmd {
1012 filters: Vec::new(),
1013 zcashd_compat: false,
1014 unsafe_low_specs: false,
1015 };
1016 let mut config = ZebradConfig::default();
1017 config.zcashd_compat.block_gossip_peer_ips =
1018 vec![std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)];
1019
1020 let error = cmd
1021 .override_config(config)
1022 .expect_err("block gossip peers should require zcashd-compat");
1023
1024 assert!(
1025 error
1026 .to_string()
1027 .contains("zcashd_compat.block_gossip_peer_ips requires"),
1028 "error should explain the zcashd-compat requirement: {error}"
1029 );
1030 }
1031
1032 #[test]
1033 fn zcashd_compat_config_rejects_peer_selection_extra_args() {
1034 let cmd = StartCmd {
1035 filters: Vec::new(),
1036 zcashd_compat: false,
1037 unsafe_low_specs: false,
1038 };
1039 let mut config = ZebradConfig::default();
1040 config.zcashd_compat.enabled = true;
1041 config.zcashd_compat.manage_zcashd = true;
1042 config.zcashd_compat.zcashd_source = zcashd_compat::ConfigZcashdBinarySource::Embedded;
1043 config.zcashd_compat.zcashd_extra_args = vec!["-addnode=1.2.3.4".to_string()];
1044
1045 let error = cmd
1046 .override_config(config)
1047 .expect_err("peer-selection extra args should be rejected");
1048 assert!(
1049 error.to_string().contains("peer-selection"),
1050 "unexpected error: {error}"
1051 );
1052 }
1053
1054 #[test]
1055 fn zcashd_compat_manage_zcashd_requires_resolvable_path() {
1056 let cmd = StartCmd {
1057 filters: Vec::new(),
1058 zcashd_compat: true,
1059 unsafe_low_specs: false,
1060 };
1061 let mut config = ZebradConfig::default();
1062 config.zcashd_compat.manage_zcashd = true;
1063 config.zcashd_compat.zcashd_path = Some("/definitely/missing/zcashd-compat".into());
1064
1065 let error = cmd
1066 .override_config(config)
1067 .expect_err("zcashd-compat override should fail for an unresolvable zcashd path");
1068
1069 assert!(
1070 error
1071 .to_string()
1072 .contains("zcashd-compat mode could not resolve zcashd_path"),
1073 "unexpected error: {error}"
1074 );
1075 }
1076
1077 #[test]
1078 fn zcashd_compat_path_source_requires_explicit_path() {
1079 let cmd = StartCmd {
1080 filters: Vec::new(),
1081 zcashd_compat: true,
1082 unsafe_low_specs: false,
1083 };
1084 let mut config = ZebradConfig::default();
1085 config.zcashd_compat.manage_zcashd = true;
1086 config.zcashd_compat.zcashd_source = zcashd_compat::ConfigZcashdBinarySource::Path;
1087 config.zcashd_compat.zcashd_path = None;
1088
1089 let error = cmd
1090 .override_config(config)
1091 .expect_err("path source should require explicit zcashd_path");
1092 assert!(
1093 error.to_string().contains("zcashd_source=path"),
1094 "unexpected error: {error}"
1095 );
1096 }
1097
1098 #[test]
1099 fn zcashd_compat_embedded_source_allows_missing_local_path() {
1100 let cmd = StartCmd {
1101 filters: Vec::new(),
1102 zcashd_compat: true,
1103 unsafe_low_specs: false,
1104 };
1105 let mut config = ZebradConfig::default();
1106 config.zcashd_compat.manage_zcashd = true;
1107 config.zcashd_compat.zcashd_source = zcashd_compat::ConfigZcashdBinarySource::Embedded;
1108 config.zcashd_compat.zcashd_path = None;
1109
1110 cmd.override_config(config)
1111 .expect("embedded source should be validated at runtime, not override-time");
1112 }
1113
1114 #[test]
1115 fn zcashd_compat_config_manage_zcashd_requires_resolvable_path() {
1116 let cmd = StartCmd {
1117 filters: Vec::new(),
1118 zcashd_compat: false,
1119 unsafe_low_specs: false,
1120 };
1121 let mut config = ZebradConfig::default();
1122 config.zcashd_compat.enabled = true;
1123 config.zcashd_compat.manage_zcashd = true;
1124 config.zcashd_compat.zcashd_path = Some("/definitely/missing/zcashd-compat".into());
1125
1126 let error = cmd
1127 .override_config(config)
1128 .expect_err("zcashd-compat config should fail for an unresolvable zcashd path");
1129
1130 assert!(
1131 error
1132 .to_string()
1133 .contains("zcashd-compat mode could not resolve zcashd_path"),
1134 "unexpected error: {error}"
1135 );
1136 }
1137
1138 #[test]
1139 fn zcashd_compat_supervisor_shutdown_timeout_matches_config() {
1140 let mut config = ZebradConfig::default();
1141
1142 config.zcashd_compat.enabled = true;
1143 config.zcashd_compat.manage_zcashd = true;
1144 config.zcashd_compat.shutdown_grace_period = std::time::Duration::from_secs(42);
1145 assert_eq!(
1146 StartCmd::zcashd_compat_supervisor_shutdown_timeout(&config),
1147 Some(
1148 std::time::Duration::from_secs(42)
1149 + StartCmd::ZCASHD_COMPAT_SHUTDOWN_TIMEOUT_MARGIN
1150 ),
1151 "outer supervisor wait must exceed the child grace period so task \
1152 abort cannot preempt graceful termination",
1153 );
1154
1155 config.zcashd_compat.manage_zcashd = false;
1156 assert_eq!(
1157 StartCmd::zcashd_compat_supervisor_shutdown_timeout(&config),
1158 None
1159 );
1160
1161 config.zcashd_compat.enabled = false;
1162 config.zcashd_compat.manage_zcashd = true;
1163 assert_eq!(
1164 StartCmd::zcashd_compat_supervisor_shutdown_timeout(&config),
1165 None
1166 );
1167 }
1168
1169 #[test]
1170 fn zcashd_compat_supervisor_ok_exit_does_not_exit_zebra() {
1171 assert!(!StartCmd::zcashd_compat_supervisor_should_exit(Ok(Ok(()))));
1172 }
1173
1174 #[test]
1175 fn zcashd_compat_supervisor_error_does_not_exit_zebra() {
1176 assert!(!StartCmd::zcashd_compat_supervisor_should_exit(Ok(Err(
1177 eyre!("simulated zcashd supervisor runtime failure"),
1178 ))));
1179 }
1180
1181 #[tokio::test]
1182 async fn zcashd_compat_supervisor_panic_does_not_exit_zebra() {
1183 let join_err = tokio::spawn(async {
1184 panic!("simulated zcashd supervisor panic");
1185 })
1186 .await
1187 .expect_err("task should panic");
1188
1189 assert!(!StartCmd::zcashd_compat_supervisor_should_exit(Err(
1190 join_err
1191 )));
1192 }
1193}