2020-09-09 16:42:52 -07:00
use serde ::{ Deserialize , Serialize } ;
2020-12-01 19:58:43 -08:00
use std ::{ convert ::TryInto , path ::PathBuf } ;
2020-11-19 19:01:19 -08:00
use tempdir ::TempDir ;
2020-09-09 16:42:52 -07:00
use zebra_chain ::parameters ::Network ;
/// Configuration for the state service.
#[ derive(Clone, Debug, Deserialize, Serialize) ]
#[ serde(deny_unknown_fields, default) ]
pub struct Config {
/// The root directory for storing cached data.
///
/// Cached data includes any state that can be replicated from the network
/// (e.g., the chain state, the blocks, the UTXO set, etc.). It does *not*
/// include private data that cannot be replicated from the network, such as
/// wallet data. That data is not handled by `zebra-state`.
///
/// Each network has a separate state, which is stored in "mainnet/state"
/// and "testnet/state" subdirectories.
///
/// The default directory is platform dependent, based on
/// [`dirs::cache_dir()`](https://docs.rs/dirs/3.0.1/dirs/fn.cache_dir.html):
///
/// |Platform | Value | Example |
/// | ------- | ----------------------------------------------- | ---------------------------------- |
/// | Linux | `$XDG_CACHE_HOME/zebra` or `$HOME/.cache/zebra` | /home/alice/.cache/zebra |
/// | macOS | `$HOME/Library/Caches/zebra` | /Users/Alice/Library/Caches/zebra |
/// | Windows | `{FOLDERID_LocalAppData}\zebra` | C:\Users\Alice\AppData\Local\zebra |
/// | Other | `std::env::current_dir()/cache` | |
pub cache_dir : PathBuf ,
/// Whether to use an ephemeral database.
///
2020-11-19 19:01:19 -08:00
/// Ephemeral databases are stored in a temporary directory.
/// They are deleted when Zebra exits successfully.
/// (If Zebra panics or crashes, the ephemeral database won't be deleted.)
2020-09-09 16:42:52 -07:00
///
/// Set to `false` by default. If this is set to `true`, [`cache_dir`] is ignored.
///
/// [`cache_dir`]: struct.Config.html#structfield.cache_dir
pub ephemeral : bool ,
2020-10-27 02:25:29 -07:00
/// Commit blocks to the finalized state up to this height, then exit Zebra.
///
2020-11-17 21:13:01 -08:00
/// Set to `None` by default: Zebra continues syncing indefinitely.
2020-10-27 02:25:29 -07:00
pub debug_stop_at_height : Option < u32 > ,
2020-09-09 16:42:52 -07:00
}
2020-11-19 19:01:19 -08:00
fn gen_temp_path ( prefix : & str ) -> PathBuf {
TempDir ::new ( prefix )
. expect ( " temporary directory is created successfully " )
. into_path ( )
2020-11-18 18:05:06 -08:00
}
2020-09-09 16:42:52 -07:00
impl Config {
2020-12-01 19:58:43 -08:00
/// The ideal open file limit for Zebra
const IDEAL_OPEN_FILE_LIMIT : usize = 1024 ;
/// The minimum number of open files for Zebra to operate normally. Also used
/// as the default open file limit, when the OS doesn't tell us how many
/// files we can use.
///
/// We want 100+ file descriptors for peers, and 100+ for the database.
///
/// On Windows, the default limit is 512 high-level I/O files, and 8192
/// low-level I/O files:
/// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-160#remarks
const MIN_OPEN_FILE_LIMIT : usize = 512 ;
/// The number of files used internally by Zebra.
///
/// Zebra uses file descriptors for OS libraries (10+), polling APIs (10+),
/// stdio (3), and other OS facilities (2+).
const RESERVED_FILE_COUNT : usize = 48 ;
2020-12-01 14:00:05 -08:00
/// Returns the path and database options for the finalized state database
pub ( crate ) fn db_config ( & self , network : Network ) -> ( PathBuf , rocksdb ::Options ) {
2020-09-09 16:42:52 -07:00
let net_dir = match network {
Network ::Mainnet = > " mainnet " ,
Network ::Testnet = > " testnet " ,
} ;
2020-11-18 18:05:06 -08:00
let path = if self . ephemeral {
2020-11-19 19:01:19 -08:00
gen_temp_path ( & format! (
" zebra-state-v{}-{} " ,
crate ::constants ::DATABASE_FORMAT_VERSION ,
net_dir
) )
2020-09-09 16:42:52 -07:00
} else {
2020-11-18 18:05:06 -08:00
self . cache_dir
2020-10-27 14:24:00 -07:00
. join ( " state " )
2020-11-18 18:05:06 -08:00
. join ( format! ( " v {} " , crate ::constants ::DATABASE_FORMAT_VERSION ) )
. join ( net_dir )
} ;
2020-10-27 14:24:00 -07:00
2020-12-01 14:00:05 -08:00
let mut opts = rocksdb ::Options ::default ( ) ;
opts . create_if_missing ( true ) ;
opts . create_missing_column_families ( true ) ;
2020-12-01 19:58:43 -08:00
let open_file_limit = Config ::increase_open_file_limit ( ) ;
let db_file_limit = Config ::get_db_open_file_limit ( open_file_limit ) ;
// If the current limit is very large, set the DB limit using the ideal limit
let db_file_limit = db_file_limit . try_into ( ) . unwrap_or_else ( | _ | {
Config ::get_db_open_file_limit ( Config ::IDEAL_OPEN_FILE_LIMIT )
. try_into ( )
. expect ( " ideal open file limit fits in a config int " )
} ) ;
opts . set_max_open_files ( db_file_limit ) ;
2020-12-01 14:00:05 -08:00
( path , opts )
2020-09-09 16:42:52 -07:00
}
2021-01-18 12:40:15 -08:00
/// Construct a config for an ephemeral database
pub fn ephemeral ( ) -> Config {
Config {
ephemeral : true ,
.. Config ::default ( )
}
2020-09-09 16:42:52 -07:00
}
2020-12-01 19:58:43 -08:00
/// Calculate the database's share of `open_file_limit`
fn get_db_open_file_limit ( open_file_limit : usize ) -> usize {
// Give the DB half the files, and reserve half the files for peers
( open_file_limit - Config ::RESERVED_FILE_COUNT ) / 2
}
/// Increase the open file limit for this process to `IDEAL_OPEN_FILE_LIMIT`.
/// If that fails, try `MIN_OPEN_FILE_LIMIT`.
///
/// If the current limit is above `IDEAL_OPEN_FILE_LIMIT`, leaves it
/// unchanged.
///
/// Returns the current limit, after any successful increases.
///
/// # Panics
///
/// If the open file limit can not be increased to `MIN_OPEN_FILE_LIMIT`.
#[ cfg(unix) ]
fn increase_open_file_limit ( ) -> usize {
use rlimit ::{ getrlimit , Resource } ;
let ( old_limit , hard_rlimit ) = match getrlimit ( Resource ::NOFILE ) {
Ok ( ( soft_limit , hard_rlimit ) ) = > ( soft_limit . try_into ( ) . ok ( ) , Some ( hard_rlimit ) ) ,
Err ( _ ) = > ( None , None ) ,
} ;
// There's no API for reliably setting the soft limit to the lower of the
// hard limit and a desired limit, because:
// * the returned hard limit can be invalid or unrepresentable, and
// * some OS versions (macOS) return larger values than the actual hard
// limit.
// So we try setting the ideal limit, then the minimum limit.
if let Ok ( actual_limit ) =
Config ::set_open_file_limit ( Config ::IDEAL_OPEN_FILE_LIMIT , hard_rlimit , old_limit )
{
return actual_limit ;
}
// Try the hard limit or the minimum, whichever is greater
2021-12-09 06:19:14 -08:00
let min_limit =
if let Some ( hard_limit ) = hard_rlimit . map ( TryInto ::try_into ) . and_then ( Result ::ok ) {
std ::cmp ::max ( Config ::MIN_OPEN_FILE_LIMIT , hard_limit )
} else {
Config ::MIN_OPEN_FILE_LIMIT
} ;
2020-12-01 19:58:43 -08:00
if let Ok ( actual_limit ) = Config ::set_open_file_limit ( min_limit , hard_rlimit , old_limit ) {
tracing ::warn! ( ? actual_limit ,
? hard_rlimit ,
? old_limit ,
min_limit = ? Config ::MIN_OPEN_FILE_LIMIT ,
ideal_limit = ? Config ::IDEAL_OPEN_FILE_LIMIT ,
" the maximum number of open files is below Zebra's ideal limit. Hint: Increase the open file limit to {} before launching Zebra " ,
Config ::IDEAL_OPEN_FILE_LIMIT ) ;
return actual_limit ;
}
panic! ( " open file limit too low: unable to set the number of open files to {} , the minimum number of files required by Zebra. Current soft limit is {:?} and hard limit is {:?} . Hint: Increase the open file limit to {} before launching Zebra " ,
Config ::MIN_OPEN_FILE_LIMIT ,
old_limit ,
hard_rlimit ,
Config ::IDEAL_OPEN_FILE_LIMIT ) ;
}
/// Increase the soft open file limit for this process to `new_limit`,
/// and the hard open file limit to `hard_rlimit`.
///
/// If `hard_rlimit` is `None`, also sets the hard limit to `new_limit`.
///
/// If `old_limit` is already greater than or equal to `new_limit`,
/// returns `Ok(old_limit)`.
///
/// Otherwise, tries to set the limit. Returns `Ok(new_limit)` if the
/// limit is set successfully.
#[ cfg(unix) ]
fn set_open_file_limit (
new_limit : usize ,
hard_rlimit : Option < rlimit ::Rlim > ,
old_limit : Option < usize > ,
) -> Result < usize , ( ) > {
use rlimit ::{ setrlimit , Resource } ;
if let Some ( old_limit ) = old_limit {
if old_limit > = new_limit {
tracing ::info! ( ? new_limit ,
current_limit = ? old_limit ,
? hard_rlimit ,
" the open file limit is at or above the specified limit " ) ;
return Ok ( old_limit ) ;
}
}
let new_rlimit = new_limit
. try_into ( )
. expect ( " new_limit is a valid rlimit value " ) ;
if setrlimit (
Resource ::NOFILE ,
new_rlimit ,
hard_rlimit . unwrap_or ( new_rlimit ) ,
)
. is_ok ( )
{
tracing ::info! (
? new_limit ,
? old_limit ,
? hard_rlimit ,
" set the open file limit for Zebra "
) ;
Ok ( new_limit )
} else {
Err ( ( ) )
}
}
/// Assumes that Zebra can open at least the minimum number of files, and
/// returns `MIN_OPEN_FILE_LIMIT`.
///
/// Increasing the open file limit is not yet implemented on Windows. (And
/// other non-unix platforms).
#[ cfg(not(unix)) ]
fn increase_open_file_limit ( ) -> usize {
// On Windows, the default limit is 512 high-level I/O files, and 8192
// low-level I/O files:
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=msvc-160#remarks
//
// If we need more high-level I/O files on Windows, we could implement
// support for `setmaxstdio` and `getmaxstdio` in the `rlimit` crate:
// https://github.com/Nugine/rlimit/issues/16#issuecomment-723393017
//
// We should panic if `setmaxstdio` fails to set the minimum value,
// and `getmaxstdio` is below the minimum value.
tracing ::info! ( min_limit = ? Config ::MIN_OPEN_FILE_LIMIT ,
ideal_limit = ? Config ::IDEAL_OPEN_FILE_LIMIT ,
" assuming Zebra can open a minimum number of files " ) ;
Config ::MIN_OPEN_FILE_LIMIT
}
2020-09-09 16:42:52 -07:00
}
impl Default for Config {
fn default ( ) -> Self {
let cache_dir = dirs ::cache_dir ( )
. unwrap_or_else ( | | std ::env ::current_dir ( ) . unwrap ( ) . join ( " cache " ) )
. join ( " zebra " ) ;
Self {
cache_dir ,
ephemeral : false ,
2020-10-27 02:25:29 -07:00
debug_stop_at_height : None ,
2020-09-09 16:42:52 -07:00
}
}
}