2019-08-29 14:46:54 -07:00
//! Zebrad Abscissa Application
2022-08-30 02:01:33 -07:00
mod entry_point ;
use self ::entry_point ::EntryPoint ;
2022-05-09 20:41:51 -07:00
use std ::{ fmt ::Write as _ , io ::Write as _ , process } ;
2021-10-20 06:57:09 -07:00
2019-08-29 14:46:54 -07:00
use abscissa_core ::{
2022-09-07 00:39:30 -07:00
application ::{ self , AppCell } ,
2021-10-20 06:57:09 -07:00
config ::{ self , Configurable } ,
2022-06-27 17:36:36 -07:00
status_err ,
2021-10-20 06:57:09 -07:00
terminal ::{ component ::Terminal , stderr , stdout , ColorChoice } ,
2022-08-30 02:01:33 -07:00
Application , Component , FrameworkError , Shutdown , StandardPaths , Version ,
2019-08-29 14:46:54 -07:00
} ;
2021-01-29 04:36:33 -08:00
use zebra_network ::constants ::PORT_IN_USE_ERROR ;
2021-06-03 21:42:15 -07:00
use zebra_state ::constants ::{ DATABASE_FORMAT_VERSION , LOCK_FILE_ERROR } ;
2021-01-29 04:36:33 -08:00
2021-10-20 06:57:09 -07:00
use crate ::{ commands ::ZebradCmd , components ::tracing ::Tracing , config ::ZebradConfig } ;
2022-09-07 00:39:30 -07:00
/// See <https://docs.rs/abscissa_core/latest/src/abscissa_core/application/exit.rs.html#7-10>
/// Print a fatal error message and exit
fn fatal_error ( app_name : String , err : & dyn std ::error ::Error ) -> ! {
status_err! ( " {} fatal error: {} " , app_name , err ) ;
process ::exit ( 1 )
}
2019-12-20 11:20:04 -08:00
/// Application state
pub static APPLICATION : AppCell < ZebradApp > = AppCell ::new ( ) ;
2019-08-29 14:46:54 -07:00
/// Obtain a read-only (multi-reader) lock on the application state.
///
/// Panics if the application state has not been initialized.
pub fn app_reader ( ) -> application ::lock ::Reader < ZebradApp > {
APPLICATION . read ( )
}
/// Obtain an exclusive mutable lock on the application state.
pub fn app_writer ( ) -> application ::lock ::Writer < ZebradApp > {
APPLICATION . write ( )
}
/// Obtain a read-only (multi-reader) lock on the application configuration.
///
/// Panics if the application configuration has not been loaded.
pub fn app_config ( ) -> config ::Reader < ZebradApp > {
config ::Reader ::new ( & APPLICATION )
}
2021-04-21 15:14:36 -07:00
/// Returns the zebrad version for this build, in SemVer 2.0 format.
///
/// Includes the git commit and the number of commits since the last version
/// tag, if available.
///
2022-05-30 13:12:11 -07:00
/// For details, see <https://semver.org/>
2021-04-21 15:14:36 -07:00
pub fn app_version ( ) -> Version {
const CARGO_PKG_VERSION : & str = env! ( " CARGO_PKG_VERSION " ) ;
2021-04-25 21:08:58 -07:00
let vergen_git_semver : Option < & str > = option_env! ( " VERGEN_GIT_SEMVER_LIGHTWEIGHT " ) ;
2021-04-21 15:14:36 -07:00
match vergen_git_semver {
// change the git semver format to the semver 2.0 format
Some ( mut vergen_git_semver ) if ! vergen_git_semver . is_empty ( ) = > {
// strip the leading "v", if present
if & vergen_git_semver [ 0 .. 1 ] = = " v " {
vergen_git_semver = & vergen_git_semver [ 1 .. ] ;
}
// split into tag, commit count, hash
let rparts : Vec < _ > = vergen_git_semver . rsplitn ( 3 , '-' ) . collect ( ) ;
match rparts . as_slice ( ) {
// assume it's a cargo package version or a git tag with no hash
[ _ ] | [ _ , _ ] = > vergen_git_semver . parse ( ) . unwrap_or_else ( | _ | {
panic! (
" VERGEN_GIT_SEMVER without a hash {:?} must be valid semver 2.0 " ,
vergen_git_semver
)
} ) ,
// it's the "git semver" format, which doesn't quite match SemVer 2.0
[ hash , commit_count , tag ] = > {
2022-10-27 06:25:18 -07:00
let semver_fix = format! ( " {tag} + {commit_count} . {hash} " ) ;
2021-04-21 15:14:36 -07:00
semver_fix . parse ( ) . unwrap_or_else ( | _ |
panic! ( " Modified VERGEN_GIT_SEMVER {:?} -> {:?} -> {:?} must be valid. Note: CARGO_PKG_VERSION was {:?} . " ,
vergen_git_semver ,
rparts ,
semver_fix ,
CARGO_PKG_VERSION ) )
}
_ = > unreachable! ( " split is limited to 3 parts " ) ,
}
}
_ = > CARGO_PKG_VERSION . parse ( ) . unwrap_or_else ( | _ | {
panic! (
" CARGO_PKG_VERSION {:?} must be valid semver 2.0 " ,
CARGO_PKG_VERSION
)
} ) ,
}
}
2019-08-29 14:46:54 -07:00
/// Zebrad Application
2020-08-06 10:29:31 -07:00
#[ derive(Debug) ]
2019-08-29 14:46:54 -07:00
pub struct ZebradApp {
/// Application configuration.
config : Option < ZebradConfig > ,
/// Application state.
state : application ::State < Self > ,
}
2020-12-01 12:13:20 -08:00
impl ZebradApp {
2020-12-03 17:05:25 -08:00
/// Are standard output and standard error both connected to ttys?
fn outputs_are_ttys ( ) -> bool {
atty ::is ( atty ::Stream ::Stdout ) & & atty ::is ( atty ::Stream ::Stderr )
}
2021-04-19 23:53:04 -07:00
/// Returns the git commit for this build, if available.
///
///
/// # Accuracy
///
/// If the user makes changes, but does not commit them, the git commit will
/// not match the compiled source code.
pub fn git_commit ( ) -> Option < & 'static str > {
2020-12-02 21:18:55 -08:00
const GIT_COMMIT_GCLOUD : Option < & str > = option_env! ( " SHORT_SHA " ) ;
2021-04-19 23:53:04 -07:00
const GIT_COMMIT_VERGEN : Option < & str > = option_env! ( " VERGEN_GIT_SHA_SHORT " ) ;
2020-12-02 21:18:55 -08:00
2021-04-19 23:53:04 -07:00
GIT_COMMIT_GCLOUD . or ( GIT_COMMIT_VERGEN )
2020-12-02 21:18:55 -08:00
}
2020-12-01 12:13:20 -08:00
}
2019-08-29 14:46:54 -07:00
/// Initialize a new application instance.
///
/// By default no configuration is loaded, and the framework state is
/// initialized to a default, empty state (no components, threads, etc).
2021-09-22 06:43:27 -07:00
#[ allow(unknown_lints) ]
#[ allow(clippy::derivable_impls) ]
2019-08-29 14:46:54 -07:00
impl Default for ZebradApp {
fn default ( ) -> Self {
Self {
config : None ,
state : application ::State ::default ( ) ,
}
}
}
impl Application for ZebradApp {
/// Entrypoint command for this application.
2022-08-30 02:01:33 -07:00
type Cmd = EntryPoint ;
2019-08-29 14:46:54 -07:00
/// Application configuration.
type Cfg = ZebradConfig ;
/// Paths to resources within the application.
type Paths = StandardPaths ;
/// Accessor for application configuration.
fn config ( & self ) -> & ZebradConfig {
self . config . as_ref ( ) . expect ( " config not loaded " )
}
/// Borrow the application state immutably.
fn state ( & self ) -> & application ::State < Self > {
& self . state
}
/// Borrow the application state mutably.
fn state_mut ( & mut self ) -> & mut application ::State < Self > {
& mut self . state
}
2020-07-14 23:16:07 -07:00
/// Returns the framework components used by this application.
2020-06-04 19:34:06 -07:00
fn framework_components (
& mut self ,
command : & Self ::Cmd ,
) -> Result < Vec < Box < dyn Component < Self > > > , FrameworkError > {
2020-11-30 17:09:57 -08:00
// Automatically use color if we're outputting to a terminal
//
// The `abcissa` docs claim that abscissa implements `Auto`, but it
// does not - except in `color_backtrace` backtraces.
let mut term_colors = self . term_colors ( command ) ;
if term_colors = = ColorChoice ::Auto {
// We want to disable colors on a per-stream basis, but that feature
// can only be implemented inside the terminal component streams.
// Instead, if either output stream is not a terminal, disable
// colors.
//
// We'd also like to check `config.tracing.use_color` here, but the
// config has not been loaded yet.
2020-12-03 17:05:25 -08:00
if ! Self ::outputs_are_ttys ( ) {
2020-11-30 17:09:57 -08:00
term_colors = ColorChoice ::Never ;
}
}
let terminal = Terminal ::new ( term_colors ) ;
2020-06-04 19:34:06 -07:00
2020-08-06 10:29:31 -07:00
Ok ( vec! [ Box ::new ( terminal ) ] )
2020-06-04 19:34:06 -07:00
}
2019-08-29 14:46:54 -07:00
/// Register all components used by this application.
///
/// If you would like to add additional components to your application
/// beyond the default ones provided by the framework, this is the place
/// to do so.
2022-03-08 01:14:15 -08:00
#[ allow(clippy::print_stderr) ]
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2019-08-29 14:46:54 -07:00
fn register_components ( & mut self , command : & Self ::Cmd ) -> Result < ( ) , FrameworkError > {
2020-02-14 13:38:33 -08:00
use crate ::components ::{
metrics ::MetricsEndpoint , tokio ::TokioComponent , tracing ::TracingEndpoint ,
} ;
2019-09-09 13:05:42 -07:00
let mut components = self . framework_components ( command ) ? ;
2020-08-06 10:29:31 -07:00
2020-08-06 17:22:40 -07:00
// Load config *after* framework components so that we can
// report an error to the terminal if it occurs.
2022-06-27 17:36:36 -07:00
let config = match command . config_path ( ) {
Some ( path ) = > match self . load_config ( & path ) {
Ok ( config ) = > config ,
Err ( e ) = > {
status_err! ( " Zebra could not parse the provided config file. This might mean you are using a deprecated format of the file. You can generate a valid config by running \" zebrad generate \" , and diff it against yours to examine any format inconsistencies. " ) ;
return Err ( e ) ;
}
} ,
None = > ZebradConfig ::default ( ) ,
} ;
2020-08-06 17:22:40 -07:00
let config = command . process_config ( config ) ? ;
2020-12-03 17:05:25 -08:00
let theme = if Self ::outputs_are_ttys ( ) & & config . tracing . use_color {
color_eyre ::config ::Theme ::dark ( )
} else {
color_eyre ::config ::Theme ::new ( )
} ;
2021-04-19 23:53:04 -07:00
// collect the common metadata for the issue URL and panic report,
// skipping any env vars that aren't present
2021-04-25 20:26:06 -07:00
let app_metadata = vec! [
2021-04-19 23:53:04 -07:00
// cargo or git tag + short commit
2021-04-25 20:26:06 -07:00
( " version " , app_version ( ) . to_string ( ) ) ,
// config
( " Zcash network " , config . network . network . to_string ( ) ) ,
2021-06-03 21:42:15 -07:00
// constants
( " state version " , DATABASE_FORMAT_VERSION . to_string ( ) ) ,
2021-04-25 20:26:06 -07:00
] ;
// git env vars can be skipped if there is no `.git` during the
// build, so they must all be optional
let git_metadata : & [ ( _ , Option < _ > ) ] = & [
( " branch " , option_env! ( " VERGEN_GIT_BRANCH " ) ) ,
( " git commit " , Self ::git_commit ( ) ) ,
2021-04-19 23:53:04 -07:00
(
" commit timestamp " ,
2021-04-25 20:26:06 -07:00
option_env! ( " VERGEN_GIT_COMMIT_TIMESTAMP " ) ,
2021-04-25 19:19:55 -07:00
) ,
2021-04-25 20:26:06 -07:00
] ;
// skip missing metadata
let git_metadata : Vec < ( _ , String ) > = git_metadata
. iter ( )
. filter_map ( | ( k , v ) | Some ( ( k , ( * v ) ? ) ) )
. map ( | ( k , v ) | ( * k , v . to_string ( ) ) )
. collect ( ) ;
let build_metadata : Vec < _ > = [
( " target triple " , env! ( " VERGEN_CARGO_TARGET_TRIPLE " ) ) ,
( " build profile " , env! ( " VERGEN_CARGO_PROFILE " ) ) ,
2021-04-19 23:53:04 -07:00
]
. iter ( )
2021-04-25 20:26:06 -07:00
. map ( | ( k , v ) | ( * k , v . to_string ( ) ) )
2021-04-19 23:53:04 -07:00
. collect ( ) ;
2021-01-11 13:46:56 -08:00
2021-04-25 20:26:06 -07:00
let panic_metadata : Vec < _ > = app_metadata
. iter ( )
. chain ( git_metadata . iter ( ) )
. chain ( build_metadata . iter ( ) )
. collect ( ) ;
2021-01-11 13:46:56 -08:00
let mut builder = color_eyre ::config ::HookBuilder ::default ( ) ;
let mut metadata_section = " Metadata: " . to_string ( ) ;
for ( k , v ) in panic_metadata {
2021-04-21 15:14:36 -07:00
builder = builder . add_issue_metadata ( k , v . clone ( ) ) ;
2022-10-27 06:25:18 -07:00
write! ( & mut metadata_section , " \n {k}: {} " , & v )
2022-05-09 20:41:51 -07:00
. expect ( " unexpected failure writing to string " ) ;
2021-01-11 13:46:56 -08:00
}
builder = builder
2020-12-03 17:05:25 -08:00
. theme ( theme )
2022-09-20 14:05:37 -07:00
. panic_section ( metadata_section . clone ( ) )
2020-12-03 17:05:25 -08:00
. issue_url ( concat! ( env! ( " CARGO_PKG_REPOSITORY " ) , " /issues/new " ) )
. issue_filter ( | kind | match kind {
2021-01-29 04:36:33 -08:00
color_eyre ::ErrorKind ::NonRecoverable ( error ) = > {
let error_str = match error . downcast_ref ::< String > ( ) {
Some ( as_string ) = > as_string ,
None = > return true ,
} ;
// listener port conflicts
if PORT_IN_USE_ERROR . is_match ( error_str ) {
return false ;
}
// RocksDB lock file conflicts
if LOCK_FILE_ERROR . is_match ( error_str ) {
return false ;
}
true
}
2020-12-03 17:05:25 -08:00
color_eyre ::ErrorKind ::Recoverable ( error ) = > {
2021-11-15 06:32:18 -08:00
// Type checks should be faster than string conversions.
//
// Don't ask users to create bug reports for timeouts and peer errors.
2020-12-15 14:14:42 -08:00
if error . is ::< tower ::timeout ::error ::Elapsed > ( )
| | error . is ::< tokio ::time ::error ::Elapsed > ( )
2021-11-15 06:32:18 -08:00
| | error . is ::< zebra_network ::PeerError > ( )
| | error . is ::< zebra_network ::SharedPeerError > ( )
| | error . is ::< zebra_network ::HandshakeError > ( )
2020-12-15 14:14:42 -08:00
{
return false ;
}
let error_str = error . to_string ( ) ;
2021-12-09 16:19:52 -08:00
! error_str . contains ( " timed out " )
& & ! error_str . contains ( " duplicate hash " )
& & ! error_str . contains ( " No space left on device " )
2020-12-03 17:05:25 -08:00
}
2020-12-08 15:40:04 -08:00
} ) ;
2021-01-11 13:46:56 -08:00
// This MUST happen after `Terminal::new` to ensure our preferred panic
// handler is the last one installed
2020-12-08 15:40:04 -08:00
let ( panic_hook , eyre_hook ) = builder . into_hooks ( ) ;
2022-06-27 23:22:07 -07:00
eyre_hook . install ( ) . expect ( " eyre_hook.install() error " ) ;
2020-12-08 15:40:04 -08:00
// The Sentry default config pulls in the DSN from the `SENTRY_DSN`
// environment variable.
2022-06-16 12:56:40 -07:00
#[ cfg(feature = " sentry " ) ]
2021-11-02 11:46:57 -07:00
let guard = sentry ::init ( sentry ::ClientOptions {
debug : true ,
release : Some ( app_version ( ) . to_string ( ) . into ( ) ) ,
.. Default ::default ( )
} ) ;
2020-12-08 15:40:04 -08:00
std ::panic ::set_hook ( Box ::new ( move | panic_info | {
let panic_report = panic_hook . panic_report ( panic_info ) ;
2022-10-27 06:25:18 -07:00
eprintln! ( " {panic_report} " ) ;
2020-12-08 15:40:04 -08:00
2022-06-16 12:56:40 -07:00
#[ cfg(feature = " sentry " ) ]
2020-12-08 15:40:04 -08:00
{
let event = crate ::sentry ::panic_event_from ( panic_report ) ;
sentry ::capture_event ( event ) ;
if ! guard . close ( None ) {
warn! ( " unable to flush sentry events during panic " ) ;
}
}
} ) ) ;
2020-12-03 17:05:25 -08:00
2022-07-17 15:43:29 -07:00
// Apply the configured number of threads to the thread pool.
//
// TODO:
// - set rayon panic handler to a function that takes `Box<dyn Any + Send + 'static>`,
// which forwards to sentry. If possible, use eyre's panic report for formatting.
// - do we also need to call this code in `zebra_consensus::init()`,
// when that crate is being used by itself?
rayon ::ThreadPoolBuilder ::new ( )
. num_threads ( config . sync . parallel_cpu_threads )
2022-10-27 06:25:18 -07:00
. thread_name ( | thread_index | format! ( " rayon {thread_index} " ) )
2022-07-17 15:43:29 -07:00
. build_global ( )
. expect ( " unable to initialize rayon thread pool " ) ;
2020-08-06 17:22:40 -07:00
self . config = Some ( config ) ;
2020-08-06 10:29:31 -07:00
let cfg_ref = self
. config
. as_ref ( )
. expect ( " config is loaded before register_components " ) ;
2022-05-05 20:31:52 -07:00
let default_filter = command
. command
. as_ref ( )
2022-11-18 06:24:10 -08:00
. map ( | zcmd | zcmd . default_tracing_filter ( command . verbose , command . help ) )
2022-05-05 20:31:52 -07:00
. unwrap_or ( " warn " ) ;
2020-08-06 10:29:31 -07:00
let is_server = command
. command
. as_ref ( )
. map ( ZebradCmd ::is_server )
. unwrap_or ( false ) ;
2022-05-05 20:31:52 -07:00
// Ignore the configured tracing filter for short-lived utility commands
2021-04-14 19:40:13 -07:00
let mut tracing_config = cfg_ref . tracing . clone ( ) ;
2022-10-24 16:39:00 -07:00
let metrics_config = cfg_ref . metrics . clone ( ) ;
2020-08-06 10:29:31 -07:00
if is_server {
2020-11-30 11:59:40 -08:00
// Override the default tracing filter based on the command-line verbosity.
tracing_config . filter = tracing_config
. filter
. or_else ( | | Some ( default_filter . to_owned ( ) ) ) ;
2020-08-06 10:29:31 -07:00
} else {
2020-11-30 11:59:40 -08:00
// Don't apply the configured filter for short-lived commands.
tracing_config . filter = Some ( default_filter . to_owned ( ) ) ;
tracing_config . flamegraph = None ;
2020-07-16 00:04:42 -07:00
}
2021-04-14 19:40:13 -07:00
components . push ( Box ::new ( Tracing ::new ( tracing_config ) ? ) ) ;
2019-09-09 13:05:42 -07:00
2022-09-20 14:05:37 -07:00
// Log git metadata and platform info when zebrad starts up
if is_server {
tracing ::info! ( " Diagnostic {} " , metadata_section ) ;
}
2021-01-11 13:46:56 -08:00
// Activate the global span, so it's visible when we load the other
// components. Space is at a premium here, so we use an empty message,
// short commit hash, and the unique part of the network name.
2021-04-19 23:53:04 -07:00
let net = & self . config . clone ( ) . unwrap ( ) . network . network . to_string ( ) [ .. 4 ] ;
let global_span = if let Some ( git_commit ) = ZebradApp ::git_commit ( ) {
error_span! ( " " , zebrad = git_commit , net )
} else {
error_span! ( " " , net )
} ;
2021-01-11 13:46:56 -08:00
let global_guard = global_span . enter ( ) ;
// leak the global span, to make sure it stays active
std ::mem ::forget ( global_guard ) ;
2022-07-17 15:43:29 -07:00
tracing ::info! (
num_threads = rayon ::current_num_threads ( ) ,
" initialized rayon thread pool for CPU-bound tasks " ,
) ;
2021-01-11 13:46:56 -08:00
// Launch network and async endpoints only for long-running commands.
if is_server {
components . push ( Box ::new ( TokioComponent ::new ( ) ? ) ) ;
components . push ( Box ::new ( TracingEndpoint ::new ( cfg_ref ) ? ) ) ;
2022-10-24 16:39:00 -07:00
components . push ( Box ::new ( MetricsEndpoint ::new ( & metrics_config ) ? ) ) ;
2021-01-11 13:46:56 -08:00
}
2019-08-29 14:46:54 -07:00
self . state . components . register ( components )
}
2020-08-05 16:35:56 -07:00
/// Load this application's configuration and initialize its components.
2022-06-27 23:22:07 -07:00
#[ allow(clippy::unwrap_in_result) ]
2020-08-05 16:35:56 -07:00
fn init ( & mut self , command : & Self ::Cmd ) -> Result < ( ) , FrameworkError > {
// Create and register components with the application.
// We do this first to calculate a proper dependency ordering before
// application configuration is processed
self . register_components ( command ) ? ;
2021-01-11 13:46:56 -08:00
// Fire callback to signal state in the application lifecycle
2022-06-27 23:22:07 -07:00
let config = self
. config
. take ( )
. expect ( " register_components always populates the config " ) ;
2020-08-06 10:29:31 -07:00
self . after_config ( config ) ? ;
2020-08-05 16:35:56 -07:00
Ok ( ( ) )
}
2019-08-29 14:46:54 -07:00
/// Post-configuration lifecycle callback.
///
/// Called regardless of whether config is loaded to indicate this is the
/// time in app lifecycle when configuration would be loaded if
/// possible.
2020-08-06 10:29:31 -07:00
fn after_config ( & mut self , config : Self ::Cfg ) -> Result < ( ) , FrameworkError > {
2019-08-29 14:46:54 -07:00
// Configure components
self . state . components . after_config ( & config ) ? ;
self . config = Some ( config ) ;
2020-06-04 19:34:06 -07:00
2019-08-29 14:46:54 -07:00
Ok ( ( ) )
}
2020-08-05 16:35:56 -07:00
fn shutdown ( & mut self , shutdown : Shutdown ) -> ! {
2021-10-20 06:57:09 -07:00
// Some OSes require a flush to send all output to the terminal.
// zebrad's logging uses Abscissa, so we flush its streams.
//
// TODO:
// - if this doesn't work, send an empty line as well
// - move this code to the tracing component's `before_shutdown()`
let _ = stdout ( ) . lock ( ) . flush ( ) ;
let _ = stderr ( ) . lock ( ) . flush ( ) ;
2020-08-05 16:35:56 -07:00
if let Err ( e ) = self . state ( ) . components . shutdown ( self , shutdown ) {
2022-09-07 00:39:30 -07:00
let app_name = self . name ( ) . to_string ( ) ;
// Swap out a fake app so we can trigger the destructor on the original
let _ = std ::mem ::take ( self ) ;
fatal_error ( app_name , & e ) ;
2020-08-05 16:35:56 -07:00
}
// Swap out a fake app so we can trigger the destructor on the original
let _ = std ::mem ::take ( self ) ;
match shutdown {
Shutdown ::Graceful = > process ::exit ( 0 ) ,
Shutdown ::Forced = > process ::exit ( 1 ) ,
Shutdown ::Crash = > process ::exit ( 2 ) ,
}
}
2021-04-21 15:14:36 -07:00
fn version ( & self ) -> Version {
app_version ( )
}
2020-06-04 19:34:06 -07:00
}