Skip to content

feat(config): migrate wallet to use a layered configuration#194

Open
gustavovalverde wants to merge 12 commits intozcash:mainfrom
gustavovalverde:feat-layered-config
Open

feat(config): migrate wallet to use a layered configuration#194
gustavovalverde wants to merge 12 commits intozcash:mainfrom
gustavovalverde:feat-layered-config

Conversation

@gustavovalverde
Copy link
Contributor

Motivation

This pull request migrates Zallet's configuration system from Abscissa to config-rs, to unify the configuration approach across the Z3 stack (Zebra, Zaino, and Zallet).

This work follows the same pattern used in Zebra's configuration migration (Zebra PR #9768) and Zaino's migration (Zaino PR #469).

Solution

The new implementation uses config-rs to load configuration from three sources in order of precedence:

  1. Hard-coded defaults (lowest precedence)
  2. Optional zallet.toml configuration file
  3. Environment variables with ZALLET_ prefix (highest precedence)

Key technical improvements:

  • Native error handling: Uses config::ConfigError directly, providing structured error information with file paths, keys, and type mismatches
  • Nested environment variables: Supports ZALLET_SECTION__KEY format for complex configuration structures
  • Array parsing: Comma-separated lists for arrays (e.g., ZALLET_RPC__BIND=127.0.0.1:8080,127.0.0.1:8081)

The Abscissa Configurable trait is retained for application lifecycle integration, CLI argument injection (like --datadir), and command-specific configuration overrides, but no longer handles configuration file loading.

Implementation Details

  • Added #[serde(default)] to all nested configuration sections to ensure partial configurations work correctly
  • Configuration loading logic moved to config.rs for better organization
  • Error conversion from config::ConfigError to FrameworkError happens only in process_config()
  • CLI arguments like --datadir and --config are properly integrated with the configuration system

Tests

A test suite has been added at wallet/zallet/tests/config.rs covering:

  • Default value loading and TOML file parsing
  • Environment variable overrides with nested structures
  • Configuration source precedence (env > file > defaults)
  • Error handling for invalid TOML, missing files, and type mismatches
  • Array parsing from environment variables (both comma-separated and JSON formats)

Specifications & References

@y4ssi
Copy link
Contributor

y4ssi commented Aug 12, 2025

LGTM, this is essential for the whole containerization process.

Copy link
Collaborator

@str4d str4d left a comment

Choose a reason for hiding this comment

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

I am currently on PTO, but I want to ensure this is blocked on my review.


/// Returns true if a leaf key name should be considered sensitive and blocked
/// from environment variable overrides.
fn is_sensitive_leaf_key(leaf_key: &str) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Collaborator

@str4d str4d left a comment

Choose a reason for hiding this comment

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

Reviewed ca41f6e.

Please squash all undos into the first commit.

}

/// Zallet Configuration Filename
pub const CONFIG_FILE: &str = "zallet.toml";
Copy link
Collaborator

Choose a reason for hiding this comment

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

We place constants at the top of a file (between the imports, and functions/structs/etc), so that we don't lose track of them. Move this to the equivalent location of where it was in zallet/src/commands.rs.

EDIT: after my review comment that EntryPoint::config_path should be un-deleted, this move should instead be undone.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in c30ceed (#194)

Cargo.toml Outdated
rust-embed = "8"

# Parsing and serialization
config = { version = "0.15.13", features = ["toml", "json"] }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing cargo vet audit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in ef32182 (#194)

Comment on lines +131 to +130
// Add filtered environment variables (highest precedence)
builder = builder.add_source(
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the group chat, we discussed only supporting env-var setting in Docker images specifically. If we take that approach, this would be where we configure it:

Suggested change
// Add filtered environment variables (highest precedence)
builder = builder.add_source(
// Add filtered environment variables (highest precedence)
#[cfg(zallet_docker_image)]
builder = builder.add_source(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In Zebra we're removing this feature gates that were specific to Docker (unrelated to config) because it requires us to build a specific image just for the sake of testing and confirming that feature is behaving as expected. It also requires to publish an image just with this feature enabled so it's easier for users.

What would be the benefit here? I'm not completely sure what we're trying to avoid. From my perspective—not adding the feature gate—would allow a consistent way to configure Zallet (containerized or not).

Copy link
Contributor

@y4ssi y4ssi Aug 21, 2025

Choose a reason for hiding this comment

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

We shouldn’t have different releases, since by avoiding the definition of sensitive parameters as environment variables, I believe there’s no longer a need to maintain two releases

gustavovalverde and others added 5 commits August 21, 2025 17:17
This commit replaces the Abscissa-based configuration system in Zallet with
the `config-rs` crate, providing a standard layered configuration approach
that unifies the Z3 stack configuration strategy across Zebra, Zaino, and Zallet

This change introduces a clear precedence for configuration sources:
1. Hard-coded defaults (lowest precedence)
2. Optional `zallet.toml` configuration file
3. Environment variables with `ZALLET_` prefix (highest precedence)

The Abscissa `Configurable` trait remains for application lifecycle integration,
CLI argument injection, and command-specific configuration overrides.
- Add is_sensitive_leaf_key() to filter ZALLET_* env vars by leaf key (case-insensitive)
- Block env overrides ending with: password, secret, token, cookie, private_key
- Pre-filter env vars before passing to config::Environment via .source(Some(filtered_env))
- Preserve existing prefix/separator logic and list parsing for rpc.bind
- Keep non-sensitive fields overridable (validator_address, validator_cookie_path, etc.)

Aligns with Zebra and Zaino security model for Z3 stack consistency.
Co-authored-by: Jack Grigg <thestr4d@gmail.com>
Co-authored-by: Jack Grigg <thestr4d@gmail.com>
- Move CONFIG_FILE constant from config.rs to commands.rs
- Import functions directly in config.rs instead of using fully qualified paths
- Move resolve_config_path method from ZalletConfig to EntryPoint::config_path
- Position config_path method after datadir method as requested
- Fix test compilation errors with mutable env variables as some tests failed because env variables needed mut to call env.set_var()
- Trust Ed Page for config crate dependency (already trusted by major orgs)
Update ZalletConfig documentation to explain layered configuration approach:
- Document Option<T> field usage for default vs explicit value distinction
- Explain serde(default) section header UX balance
- Clarify config generation vs parsing behavior differences
…ntly ignoring

Replace silent ignoring of sensitive environment variables with explicit errors to prevent:
- Silent misconfigurations where users think secrets are configured but aren't
- Process table exposure of sensitive values even when ignored
@gustavovalverde
Copy link
Contributor Author

Please squash all undos into the first commit.

Once we're done I'll squash. In the meanwhile I'll keep them separate for clarity

@gustavovalverde gustavovalverde requested a review from str4d August 21, 2025 18:59
@gustavovalverde
Copy link
Contributor Author

I've fixed the merge conflicts. Let me know how'd like to proceed @str4d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-packaging Area: Packaging

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants