Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 126 additions & 0 deletions src/main/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include <iostream>
#include <lib/clara.hpp>
#include <optional>
#include <thread>

namespace stellar
{
Expand Down Expand Up @@ -1607,6 +1608,121 @@ runReportLastHistoryCheckpoint(CommandLineArgs const& args)
});
}

namespace
{
// Before starting the application, we want to check that the host machine meets
// the minimum system requirements we document (16 GiB RAM, 8 vCPUs). To make
// the check meaningful, we prevent the node from starting when it fails
// (instead of, e.g., writing a log message that may go unread). In the case
// that we aren't able to automatically determine the relevant system
// information, we allow the operator to set config values with their system
// information. Note: we intentionally have these as integer values instead of
// booleans so that there isn't a silent failure if we bump the minimum
// requirements. If the auto-detected (or operator-provided, in the case of
// auto-detection failure) system information doesn't meet the minimum
// requirements, we require the operator to set an additional config value to
// explicitly acknowledge that they are ignoring the warning.

// We want to make the flag value annoying enough to set that the operators have
// to make an intentional and continuous decision to ignore the warning. We use
// the version to make sure that every time they upgrade the package, they have
// to make a new decision to ignore the warning, and we use the public key to
// make sure that the value is unique per node. Notably, we don't use something
// that depends on the current time so that restarts after crashes are handled
// gracefully (assuming the package wasn't upgraded in between).
bool
validateSystemInfo(Config const& cfg)
{
std::string annoyingValue =
fmt::format(FMT_STRING("{}-{}"), STELLAR_CORE_VERSION,
KeyUtils::toStrKey(cfg.NODE_SEED.getPublicKey()));

uint64_t memory = rust_bridge::get_host_total_memory();
if (memory == 0)
{
if (!cfg.SYSCHECK_UNKNOWN_MEMORY_DEFAULT)
{
LOG_ERROR(DEFAULT_LOG,
"Unable to determine total memory of the host; please "
"ensure that the system has at least 16 GiB of RAM. Once "
"confirmed, set SYSCHECK_UNKNOWN_MEMORY_DEFAULT to the "
"size of RAM in KiB.");
return false;
}

LOG_WARNING(DEFAULT_LOG,
"Unable to determine total memory of the host; using "
"SYSCHECK_UNKNOWN_MEMORY_DEFAULT value of {} KiB for "
"checks. Please ensure this is still the correct value.",
cfg.SYSCHECK_UNKNOWN_MEMORY_DEFAULT);

memory = cfg.SYSCHECK_UNKNOWN_MEMORY_DEFAULT;
}

if (memory < static_cast<uint32_t>(16) * 1024 * 1024)
{
if (cfg.SYSCHECK_FORCE_IGNORE_MEMORY != annoyingValue)
{
LOG_ERROR(
Comment on lines +1662 to +1666
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum RAM requirement (16 GiB) is duplicated across the numeric check and multiple log strings. To reduce the chance of inconsistencies if requirements change, consider introducing a named constant (e.g., MIN_VALIDATOR_RAM_KIB) and using it for both the comparison and messaging.

Copilot uses AI. Check for mistakes.
DEFAULT_LOG,
"Host only has {} KiB of RAM; stellar-core may not function "
"properly under heavy load; please ensure that the system has "
"at least 16 GiB of RAM. To force ignore this warning, set "
"SYSCHECK_FORCE_IGNORE_MEMORY to \"{}\". Note that this value "
"differs for every node and version.",
memory, annoyingValue);
return false;
}
LOG_WARNING(
DEFAULT_LOG,
"Host only has {} KiB of RAM; the recommended minimum is 16 GiB",
memory);
}

unsigned int cpus = std::thread::hardware_concurrency();
if (cpus == 0)
{
if (!cfg.SYSCHECK_UNKNOWN_CPU_DEFAULT)
{
LOG_ERROR(
DEFAULT_LOG,
"Unable to determine number of vCPUs of the host; please "
"ensure that the system has at least 8 vCPUs. Once confirmed, "
"set SYSCHECK_UNKNOWN_CPU_DEFAULT to the number of vCPUs.");
return false;
}

LOG_WARNING(DEFAULT_LOG,
"Unable to determine number of vCPUs of the host; using "
"SYSCHECK_UNKNOWN_CPU_DEFAULT value of {} for checks. "
"Please ensure this is still the correct value.",
cfg.SYSCHECK_UNKNOWN_CPU_DEFAULT);

cpus = cfg.SYSCHECK_UNKNOWN_CPU_DEFAULT;
}

if (cpus < 8)
{
if (cfg.SYSCHECK_FORCE_IGNORE_CPU != annoyingValue)
{
LOG_ERROR(DEFAULT_LOG,
Comment on lines +1704 to +1708
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minimum CPU requirement (8 vCPUs) is duplicated across the numeric check and log strings. Consider introducing a named constant (e.g., MIN_VALIDATOR_VCPUS) and reusing it in the comparison + messages to avoid drift when requirements change.

Copilot uses AI. Check for mistakes.
"Host only has {} vCPUs; stellar-core may not function "
"properly under heavy load; please ensure that the "
"system has at least 8 vCPUs. To force ignore this "
"warning, set SYSCHECK_FORCE_IGNORE_CPU to \"{}\". Note "
"that value differs for every node and version.",
cpus, annoyingValue);
return false;
}
LOG_WARNING(DEFAULT_LOG,
"Host only has {} vCPUs; the recommended minimum is 8",
cpus);
}

return true;
}
} // namespace

int
run(CommandLineArgs const& args)
{
Expand Down Expand Up @@ -1682,6 +1798,16 @@ run(CommandLineArgs const& args)
"enabled (for testing only)");
}

if (gIsProductionNetwork && cfg.NODE_IS_VALIDATOR &&
!validateSystemInfo(cfg))
{
LOG_ERROR(
DEFAULT_LOG,
"Host system does not meet the minimum requirements "
"for running stellar-core. Exiting.");
return 1;
}

// Second, setup the app with the final configuration.
clock = std::make_shared<VirtualClock>(clockMode);
app = setupApp(cfg, *clock);
Expand Down
12 changes: 12 additions & 0 deletions src/main/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,18 @@ Config::processConfig(std::shared_ptr<cpptoml::table> t)
{"LOG_COLOR", [&]() { LOG_COLOR = readBool(item); }},
{"BUCKET_DIR_PATH",
[&]() { BUCKET_DIR_PATH = readString(item); }},
{"SYSCHECK_UNKNOWN_MEMORY_DEFAULT",
[&]() {
SYSCHECK_UNKNOWN_MEMORY_DEFAULT = readInt<uint32_t>(item);
}},
{"SYSCHECK_UNKNOWN_CPU_DEFAULT",
[&]() {
SYSCHECK_UNKNOWN_CPU_DEFAULT = readInt<uint32_t>(item);
}},
{"SYSCHECK_FORCE_IGNORE_MEMORY",
[&]() { SYSCHECK_FORCE_IGNORE_MEMORY = readString(item); }},
{"SYSCHECK_FORCE_IGNORE_CPU",
[&]() { SYSCHECK_FORCE_IGNORE_CPU = readString(item); }},
{"FILTERED_SOROBAN_KEYS_PATH",
[&]() {
LOG_WARNING(DEFAULT_LOG,
Expand Down
8 changes: 8 additions & 0 deletions src/main/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,14 @@ class Config : public std::enable_shared_from_this<Config>
bool LOG_COLOR;
std::string BUCKET_DIR_PATH;

// Config parameters controlling startup system info validation (only
// relevant for mainnet validators). See the comment above
// validateSystemInfo in CommandLine.cpp for details.
uint32_t SYSCHECK_UNKNOWN_MEMORY_DEFAULT{0};
uint32_t SYSCHECK_UNKNOWN_CPU_DEFAULT{0};
std::string SYSCHECK_FORCE_IGNORE_MEMORY;
std::string SYSCHECK_FORCE_IGNORE_CPU;
Comment on lines +675 to +681
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config defaults in this codebase appear to be centralized in Config::Config() (e.g., LOG_FILE_PATH, BUCKET_DIR_PATH, etc. are explicitly initialized in Config.cpp). These new SYSCHECK_* members are the only ones using in-class initializers / relying on default construction, which makes default values harder to audit. Consider initializing all SYSCHECK_* defaults in Config::Config() (and removing the in-class {0} initializers) for consistency and to keep defaults in one place.

Copilot uses AI. Check for mistakes.
Comment on lines +675 to +681
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New config parameters (SYSCHECK_UNKNOWN_MEMORY_DEFAULT, SYSCHECK_UNKNOWN_CPU_DEFAULT, SYSCHECK_FORCE_IGNORE_*) were added, but the operator-facing config reference file docs/stellar-core_example.cfg doesn’t include them yet. Since this feature can block startup on mainnet validators, please update the example/config documentation so operators can discover these settings outside of log output.

Copilot uses AI. Check for mistakes.

// Path to Protocol 23 corruption CSV file for testing/recovery
std::string PATH_TO_PROTOCOL_23_CORRUPTION_FILE;

Expand Down
1 change: 1 addition & 0 deletions src/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rustc-simple-version = "=0.1.0"
# NB: this must match the same rand version used by soroban (but the tooling
# will complain if it does not match)
rand = "=0.8.5"
sys-info = "=0.9.1"

itertools = "=0.10.5"
backtrace = { version = "=0.3.76", features = [ "cpp_demangle" ] }
Expand Down
4 changes: 4 additions & 0 deletions src/rust/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ pub(crate) mod rust_bridge {
// Check to see if the XDR files used by different rust dependencies match.
fn check_xdr_version_identities() -> Result<()>;

// Get the total memory available on the host machine, in kibibytes.
// Returns 0 if the value cannot be obtained for any reason.
fn get_host_total_memory() -> u64;

// Computes the resource fee given the transaction resource consumption
// and network configuration.
fn compute_transaction_resource_fee(
Expand Down
4 changes: 4 additions & 0 deletions src/rust/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,7 @@ pub(crate) fn check_xdr_version_identities() -> Result<(), Box<dyn std::error::E
// Add more comparisons between XDR file lists as needed
Ok(())
}

pub(crate) fn get_host_total_memory() -> u64 {
sys_info::mem_info().map(|mem| mem.total).unwrap_or(0)
}
Loading