Skip to content

feat(dash-spv): consolidate devnet config into DevnetConfig struct#788

Merged
QuantumExplorer merged 5 commits into
devfrom
feat/devnet-config-struct
May 28, 2026
Merged

feat(dash-spv): consolidate devnet config into DevnetConfig struct#788
QuantumExplorer merged 5 commits into
devfrom
feat/devnet-config-struct

Conversation

@xdustinface
Copy link
Copy Markdown
Collaborator

@xdustinface xdustinface commented May 27, 2026

Supersedes #786. Groups every devnet-only ClientConfig knob (name, llmq_params, three LLMQ routing overrides) into one Option<DevnetConfig> field. Validation becomes one biconditional: devnet.is_some() == (network == Network::Devnet).

Lands the three Dash Core routing flags (-llmqchainlocks, -llmqinstantsenddip0024, -llmqplatform) with rotation invariants enforced per chainparams.cpp: ChainLocks must not rotate, ISD24 must rotate. Setters accept all 8 devnet-registered LLMQ types, and enabled_llmq_types(Devnet) expands to match so DKG scheduling tracks any legal override target.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added devnet configuration support with customizable quorum type routing.
    • New CLI flags for specifying ChainLocks, InstantSend, and Platform quorum types in devnet environments.
  • Improvements

    • Enhanced devnet parameter validation and configuration handling.
    • Improved CLI structure for devnet-specific settings.

Review Change Stack

`From<u8> for LLMQType` was missing the `107 => LlmqtypeDevnetPlatform` arm, so wire decoders silently mapped Platform quorum messages to `LlmqtypeUnknown`. Add it so devnet Platform routing can be observed by SPV when overrides are exercised.
Mirror Dash Core's `-llmqchainlocks`, `-llmqinstantsenddip0024`, and `-llmqplatform` flags with process-global `OnceLock` setters and getters. Wire them into `NetworkLLMQExt::{chain_locks_type, isd_llmq_type, platform_type}` so the `Network::Devnet` arms honor any active override.

Setters reject types outside Dash Core's devnet allowlist (`50_60`, `60_75`, `400_60`, `400_85`, `100_67`, `devnet`, `devnet_dip0024`, `devnet_platform`) and enforce the rotation invariants Core applies in `chainparams.cpp`: ChainLocks must not rotate, InstantSend DIP24 must rotate, Platform has no rotation constraint. Setters are idempotent on identical re-set and error on conflict, matching the existing `set_llmq_devnet_params` contract.

`enabled_llmq_types(Devnet)` expands to all eight devnet-registered LLMQ types so DKG window scheduling tracks any legal routing target.

`devnet_llmq_type_from_name` parses the Dash-Core name set, accepting `llmq_dev_platform` as a defensive alias for this crate's local spelling of `LLMQ_DEV_PLATFORM`.
Replace `ClientConfig.llmq_devnet_params` and the free-floating `--devnet-name` CLI flag with a single `Option<DevnetConfig>` field on `ClientConfig`. The struct collects every devnet-only setting in one place and surfaces the routing overrides added in the previous commit (`-llmqchainlocks`, `-llmqinstantsenddip0024`, `-llmqplatform`).

`ClientConfig::validate` collapses to a biconditional: `devnet.is_some() == (network == Network::Devnet)`. The devnet user-agent suffix (`/rust-dash-spv:VERSION(devnet.devnet-NAME)/`) moves off `main.rs` and onto `DevnetConfig::user_agent`, so library consumers building a devnet client get the same handshake string without re-implementing it.

`main.rs` gains `--llmq-chainlocks`, `--llmq-instantsend-dip0024`, `--llmq-platform` flags and extracts `build_client_config(&Args, PathBuf)` so the `Args` → `ClientConfig` mapping is unit-testable.
`config_test.rs` adds round-trip, user-agent format, table-driven validation matrix, empty-name rejection, and a single-test forwarding check that exercises all four `apply_global_overrides` slots in one shot. The forwarding test owns the process-global `OnceLock`s for the whole binary, comment block calls that out so future tests don't accidentally collide.

`main.rs` gains a `tests` module that drives the extracted `build_devnet_config`, `build_client_config`, and `parse_llmq_devnet_params` helpers via clap's `try_parse_from`: covers missing `--devnet-name`, devnet flags rejected on non-devnet networks, minimal devnet build, full five-flag compose, parse-error shapes, and unknown quorum-name rejection.
Hoist three duplicate `use crate::sml::llmq_type::network::NetworkLLMQExt;` lines out of individual lifecycle tests in `dash/src/sml/llmq_type/mod.rs` into the `mod tests` module top, and pull `use dashcore::sml::llmq_type::{...}` out of `test_apply_global_overrides_forwards_all_slots` in `dash-spv/src/client/config_test.rs`.

Replace inline fully-qualified paths in `dash-spv/src/main.rs`: `dash_spv::ValidationMode` and `dash_spv::LLMQType::*` become normal imports; `tempfile::TempDir::new()` in the test module uses `use tempfile::TempDir;` instead.

Trim cross-referencing comments that named sibling tests or caller flows. Fold `test_devnet_llmq_type_from_name_rejects_unknown_names` into `test_devnet_llmq_type_from_name`. Swap a `;` clause separator for `.` in a `DevnetConfig` doc comment.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

📝 Walkthrough

Walkthrough

This PR adds a complete devnet LLMQ routing override system. It introduces DevnetConfig to encapsulate devnet-specific configuration (name and optional LLMQ overrides), integrates it into ClientConfig, adds process-global override state in dashcore with setter/getter functions, wires CLI flags for quorum type rerouting, and ensures network routing consults the overrides when present.

Changes

Devnet LLMQ Routing Override System

Layer / File(s) Summary
DevnetConfig type and builder API
dash-spv/src/client/devnet.rs, dash-spv/src/client/mod.rs
DevnetConfig encapsulates devnet name and optional overrides for LLMQ params and three quorum routing types (ChainLocks, InstantSend DIP024, Platform). Provides fluent builders, user-agent suffix generation (/(devnet.devnet-<name>)/), validation (non-empty name, no / character), and apply_global_overrides() to invoke dashcore setters. Exported at crate client module level.
ClientConfig integration and validation
dash-spv/src/client/config.rs
Replaces llmq_devnet_params: Option<...> field with devnet: Option<DevnetConfig>. Default initializes to None. Builder adds with_devnet(DevnetConfig). Validation enforces biconditional: Network::Devnet requires devnet to be Some (and valid); other networks require devnet to be None. apply_global_overrides() delegates to devnet.apply_global_overrides() when set.
Crate-level public API
dash-spv/src/lib.rs
Re-exports DevnetConfig, EventHandler, and LlmqDevnetParams at crate root via pub use statements, making them available to downstream crates.
Dashcore devnet override infrastructure
dash/src/sml/llmq_type/mod.rs
Introduces process-global OnceLock state for ChainLocks, InstantSend DIP024, and Platform overrides. Adds devnet_llmq_type_from_name(name) parser converting quorum names to LLMQType (with llmq_dev_platform alias support). Shared setter validates LLMQType is devnet-registered and satisfies per-target rotation constraints (non-rotating for ChainLocks, rotating for InstantSend DIP024, unconstrained for Platform). Public setter/getter functions enable idempotent override behavior (same value accepted; conflicting value rejected). Corrects u8 discriminator mapping for LlmqtypeDevnetPlatform.
Network routing integration
dash/src/sml/llmq_type/network.rs
NetworkLLMQExt methods isd_llmq_type, chain_locks_type, and platform_type now consult devnet override getters (falling back to defaults via unwrap_or) when Network::Devnet. enabled_llmq_types explicitly enumerates standard devnet LLMQ types before devnet-specific ones.
CLI argument parsing and config building
dash-spv/src/main.rs
Extends Args with three new devnet-only flags (--llmq-chainlocks, --llmq-instantsend-dip0024, --llmq-platform) for rerouting quorum types. Refactors config construction: run() calls build_client_config(), validates config, applies devnet user-agent when present. build_client_config() assembles storage path, validation mode, start-height ("now"u32::MAX sentinel), peers, and devnet configuration. build_devnet_config() enforces devnet-only flag validity, constructs DevnetConfig from --devnet-name, applies LLMQ param and quorum type overrides. parse_llmq_devnet_params() parses SIZE:THRESHOLD pair into LlmqDevnetParams.
Configuration, override, and integration testing
dash-spv/src/client/config_test.rs, dash/src/sml/llmq_type/mod.rs, dash-spv/src/main.rs
Validates devnet field is None for non-devnet networks; tests DevnetConfig round-trip through ClientConfig; checks user-agent formatting and validation across network/devnet matrix; verifies apply_global_overrides is no-op when devnet absent and forwards all slots with idempotent re-application. Dashcore tests validate quorum name parsing (aliases, errors), routing setter constraint enforcement (devnet registration, rotation requirements), and lifecycle of each override (defaults, application, idempotent reset, conflicting values, network isolation). CLI tests verify devnet flag requirements, rejection on non-devnet networks, full config composition, parsing errors, unknown quorum names, and correct build_client_config behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

This PR spans multiple files across two crates with heterogeneous logic: new struct definition, configuration integration with validation, process-global override state with constraint enforcement, network routing updates, CLI parsing and composition, and comprehensive multi-layered testing. The changes introduce new concepts (DevnetConfig, devnet override setters/getters) and require understanding their interaction across module boundaries, though the implementation is straightforward and well-tested.

Possibly related issues

Possibly related PRs

  • dashpay/rust-dashcore#784: Related refactor of the same dash-spv ClientConfig devnet override mechanism, replacing llmq_devnet_params with devnet: Option<DevnetConfig> and extending override capabilities.

Suggested labels

ready-for-review

Suggested reviewers

  • ZocoLini
  • QuantumExplorer

Poem

🐰 Devnets now route with grace,
No more hardcoded quorum face!
CLI flags set the path,
Global state feels the wrath,
Override logic finds its place!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main consolidation change: moving devnet configuration fields into a dedicated DevnetConfig struct, which is the central architectural change across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/devnet-config-struct

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
dash/src/sml/llmq_type/mod.rs (1)

304-330: 💤 Low value

Minor: TOCTOU race can produce misleading error message (acceptable for CLI use).

If two threads concurrently call a setter with the same valid value, one will succeed but the other may receive an error stating "already set to a different value" due to the gap between slot.get() and slot.set(). The actual behavior is correct (value gets set properly), but the error message is misleading in this edge case.

Given this is intended for single-threaded CLI configuration at startup, this is acceptable. If library-level concurrent access becomes a use case, consider rechecking the value inside the Err branch of slot.set().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dash/src/sml/llmq_type/mod.rs` around lines 304 - 330, The TOCTOU can make
set_devnet_routing_override return a misleading "already set to a different
value" when slot.set() fails but the concurrent setter wrote the same llmq_type;
modify the Err branch of slot.set() so that on set error you immediately recheck
slot.get() — if Some(&existing) == llmq_type return Ok(()) else keep returning
Err(format!("-{} already set to a different value", flag_name)); this touches
the OnceLock usage in set_devnet_routing_override and uses slot.get()/slot.set()
and the LLMQType comparison to disambiguate the error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dash-spv/src/client/devnet.rs`:
- Around line 79-105: Replace ad-hoc Result<(), String> error handling in
DevnetConfig::validate and DevnetConfig::apply_global_overrides
(dash-spv/src/client/devnet.rs) with a typed thiserror enum (e.g., DevnetError)
that has variants for validation failures (EmptyName, ContainsSlash) and for
each override failure wrapping the underlying dashcore error (e.g.,
LlmqParamsError(#[from] dashcore::LlmqParamsError), ChainLocksTypeError(#[from]
dashcore::ChainLocksTypeError), ...). Change
DevnetConfig::apply_global_overrides to return Result<(), DevnetError> and
remove map_err(|e| e.to_string()) by returning the underlying error via the From
conversions; update DevnetConfig::validate to return Result<(), DevnetError>.
Then update ClientConfig::validate and ClientConfig::apply_global_overrides
(dash-spv/src/client/config.rs) to return a new higher-level ConfigError (also a
thiserror enum) that can wrap DevnetError (via #[from]) so callers can
pattern-match on structured errors instead of strings.

---

Nitpick comments:
In `@dash/src/sml/llmq_type/mod.rs`:
- Around line 304-330: The TOCTOU can make set_devnet_routing_override return a
misleading "already set to a different value" when slot.set() fails but the
concurrent setter wrote the same llmq_type; modify the Err branch of slot.set()
so that on set error you immediately recheck slot.get() — if Some(&existing) ==
llmq_type return Ok(()) else keep returning Err(format!("-{} already set to a
different value", flag_name)); this touches the OnceLock usage in
set_devnet_routing_override and uses slot.get()/slot.set() and the LLMQType
comparison to disambiguate the error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 90b58fe2-c543-493a-9f6f-bc1ba50b04f4

📥 Commits

Reviewing files that changed from the base of the PR and between 9655ea3 and e6c078e.

📒 Files selected for processing (8)
  • dash-spv/src/client/config.rs
  • dash-spv/src/client/config_test.rs
  • dash-spv/src/client/devnet.rs
  • dash-spv/src/client/mod.rs
  • dash-spv/src/lib.rs
  • dash-spv/src/main.rs
  • dash/src/sml/llmq_type/mod.rs
  • dash/src/sml/llmq_type/network.rs

Comment thread dash-spv/src/client/devnet.rs
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 90.83156% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.44%. Comparing base (58d61ea) to head (e6c078e).
⚠️ Report is 1 commits behind head on dev.

Files with missing lines Patch % Lines
dash-spv/src/main.rs 82.22% 32 Missing ⚠️
dash-spv/src/client/devnet.rs 90.38% 5 Missing ⚠️
dash/src/sml/llmq_type/network.rs 37.50% 5 Missing ⚠️
dash/src/sml/llmq_type/mod.rs 99.27% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #788      +/-   ##
==========================================
+ Coverage   72.21%   72.44%   +0.22%     
==========================================
  Files         321      322       +1     
  Lines       70467    70898     +431     
==========================================
+ Hits        50890    51361     +471     
+ Misses      19577    19537      -40     
Flag Coverage Δ
core 76.47% <95.89%> (+0.13%) ⬆️
ffi 46.21% <ø> (+0.01%) ⬆️
rpc 20.00% <ø> (ø)
spv 90.05% <88.54%> (+0.56%) ⬆️
wallet 71.13% <ø> (-0.09%) ⬇️
Files with missing lines Coverage Δ
dash-spv/src/client/config.rs 94.49% <100.00%> (+6.03%) ⬆️
dash-spv/src/client/config_test.rs 100.00% <100.00%> (ø)
dash-spv/src/client/mod.rs 100.00% <ø> (ø)
dash-spv/src/lib.rs 46.66% <ø> (ø)
dash/src/sml/llmq_type/mod.rs 81.20% <99.27%> (+9.55%) ⬆️
dash-spv/src/client/devnet.rs 90.38% <90.38%> (ø)
dash/src/sml/llmq_type/network.rs 73.52% <37.50%> (+0.33%) ⬆️
dash-spv/src/main.rs 54.25% <82.22%> (+54.25%) ⬆️

... and 12 files with indirect coverage changes

@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label May 28, 2026
@QuantumExplorer QuantumExplorer merged commit eb889af into dev May 28, 2026
42 checks passed
@QuantumExplorer QuantumExplorer deleted the feat/devnet-config-struct branch May 28, 2026 00:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants