Add validation to detect when CLI storage flags differ from persisted settings, preventing silent configuration mismatches that could confuse users. Closes #20482
6.6 KiB
Issue #20482: Add Configuration Validation for RocksDB Flags at Startup
Problem
Reth uses StorageSettings to control where data is stored (MDBX vs RocksDB vs static files). These flags are:
receipts_in_static_filestransaction_senders_in_static_filesstorages_history_in_rocksdbtransaction_hash_numbers_in_rocksdbaccount_history_in_rocksdb
The bug: Users can change these flags via CLI after initial sync, but the node silently loads the persisted settings from the database, ignoring CLI flags. This creates confusion:
- User syncs with
--static-files.receipts=false(receipts in MDBX) - User restarts with
--static-files.receipts=true(expecting receipts in static files) - Node loads persisted settings from DB → still uses MDBX
- User thinks receipts are in static files, but they're not
Worse scenario: If CLI flags were honored on restart, old data would be in one location, new data in another → data corruption.
Design Philosophy
Following John Ousterhout's A Philosophy of Software Design, we chose to "Define Errors Out of Existence" rather than adding complex error handling in the storage layer.
Why Not Add Validation in ProviderFactory::new()?
The original approach was to:
- Add
expected_settingsparameter toProviderFactory::new() - Add
StorageSettingsMismatcherror variant - Return error if settings don't match
Problems with this approach:
- Adds complexity to a core type (
ProviderFactory) - Forces all callers to handle a new error type
- The "error" has no meaningful recovery - it's really a configuration mistake
Better Approach: Validate at CLI Layer
Instead, we:
- Keep
ProviderFactory::new()simple - it just loads settings from DB - Validate settings mismatch in CLI code where user intent is clear
- Return a clear, actionable error message using
eyre::eyre!
Solution Architecture
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLI LAYER │
│ │
│ 1. Create ProviderFactory (loads settings from DB) │
│ 2. Compare CLI settings vs stored settings │
│ 3. If mismatch → return eyre::eyre! with clear message │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STORAGE LAYER │
│ │
│ ProviderFactory::new() - UNCHANGED │
│ • Loads settings from DB │
│ • Falls back to legacy defaults if none exist │
│ • No validation, no new parameters │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Implementation
Part 1: CLI Validation (crates/cli/commands/src/common.rs)
In create_provider_factory(), after creating the factory:
// Error if CLI settings differ from stored settings
let cli_settings = self.static_files.to_settings();
if let Some(stored_settings) = factory.storage_settings()? {
if stored_settings != cli_settings {
return Err(eyre::eyre!(
"Storage settings mismatch!\n\n\
Stored in DB: {stored_settings:?}\n\
From CLI: {cli_settings:?}\n\n\
Storage flags cannot be changed after genesis.\n\
Either remove the conflicting CLI flags, or delete the database and re-sync."
));
}
}
Part 2: Node Builder Validation (crates/node/builder/src/launch/common.rs)
Same pattern in create_provider_factory():
// Error if CLI settings differ from stored settings
let cli_settings = self.node_config().static_files.to_settings();
if let Some(stored_settings) = factory.storage_settings()? {
if stored_settings != cli_settings {
return Err(eyre::eyre!(
"Storage settings mismatch!\n\n\
Stored in DB: {stored_settings:?}\n\
From CLI: {cli_settings:?}\n\n\
Storage flags cannot be changed after genesis.\n\
Either remove the conflicting CLI flags, or delete the database and re-sync."
));
}
}
Files Modified
| File | Change |
|---|---|
crates/cli/commands/src/common.rs |
Add settings mismatch check |
crates/node/builder/src/launch/common.rs |
Add settings mismatch check |
Files NOT Modified (Key Insight!)
| File | Why Not |
|---|---|
crates/storage/errors/src/provider.rs |
No new error type needed |
crates/storage/provider/src/providers/database/mod.rs |
ProviderFactory::new() stays simple |
crates/storage/provider/src/providers/database/builder.rs |
No new builder methods |
Benefits of This Approach
- Simpler core types -
ProviderFactorydoesn't need to know about CLI settings - No new error variants - Uses existing
eyrefor CLI errors - Clear error messages - Actionable guidance at the user-facing layer
- Follows single responsibility - Storage layer stores, CLI layer validates user input
Testing
The validation is straightforward and tested by:
- Starting a node with settings A
- Restarting with settings B
- Observing the clear error message
No unit tests needed in the storage layer since no logic was added there.
PR
- Title:
feat(cli): validate storage settings match persisted settings at startup - Closes #20482