Skip to content

test: add governance proposal validator fuzz target#7166

Closed
thepastaclaw wants to merge 5 commits intodashpay:developfrom
thepastaclaw:fuzz/governance-proposal-validator
Closed

test: add governance proposal validator fuzz target#7166
thepastaclaw wants to merge 5 commits intodashpay:developfrom
thepastaclaw:fuzz/governance-proposal-validator

Conversation

@thepastaclaw
Copy link

@thepastaclaw thepastaclaw commented Feb 22, 2026

Summary

  • add a new Dash-specific functional fuzz target: governance_proposal_validator
  • exercise CProposalValidator with both structured and random proposal payloads
  • cover key validator pathways including:
    • JSON parsing failures and malformed payloads
    • oversized data handling (MAX_DATA_SIZE path)
    • script vs non-script payment addresses (fAllowScript toggle)
    • expiration toggles (fCheckExpiration true/false)
    • URL edge cases including bracket mismatch checks
  • wire the new target into src/Makefile.test.include

Testing

  • ./configure --enable-fuzz-binary --disable-tests --without-gui --disable-bench --disable-stacktraces
  • make -C src test/fuzz/fuzz -j8

Tracker: thepastaclaw/tracker#129
Epic: thepastaclaw/tracker#108

Validation

What was tested:

  • ./configure --enable-fuzz-binary --disable-tests --without-gui --disable-bench --disable-stacktraces + make -C src test/fuzz/fuzz -j8 — built the fuzz binary including the new governance_proposal_validator target
  • Ran target with empty corpus to confirm it starts up and exercises the validator paths without immediate crashes

Results:

  • governance_proposal_validator fuzz target compiled and linked successfully
  • Target ran for a short seed corpus pass with no crashes or sanitizer errors
  • CI full suite: all checks passed
    • linux64-build — pass; linux64-test — pass
    • linux64_nowallet-build — pass; linux64_nowallet-test — pass
    • linux64_sqlite-build — pass; linux64_sqlite-test — pass
    • linux64_tsan-build — pass; linux64_tsan-test — pass
    • linux64_ubsan-build — pass; linux64_ubsan-test — pass
    • mac-build / Build source — pass
    • win64-build / Build source — pass

Environment: Local macOS arm64 (fuzz build + empty corpus run); GitHub Actions CI (linux64 + tsan + ubsan + mac + win64 matrix)

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Walkthrough

Adds a new C++ fuzz test target, governance_proposal_validator, which builds and hex-encodes JSON governance proposals (valid, malformed, oversized, random), initializes mainnet parameters, and exercises the governance proposal validator across flag combinations to exercise parsing, validation, and error paths. The fuzz target source was added to the fuzz test build list. Separately, a test data listing entry was modified resulting in a concatenated/malformed test path string in the test util listing.

Sequence Diagram

sequenceDiagram
    participant FuzzEngine as Fuzz Engine
    participant TestInit as Test Initialization
    participant InputBuilder as Input Builder
    participant Validator as Governance Proposal Validator
    participant ErrorHandler as Error Handler

    FuzzEngine->>TestInit: Start fuzz target
    TestInit->>TestInit: initialize_governance_proposal_validator() (select mainnet params)
    TestInit->>InputBuilder: Prepare presets (addresses, URLs, ranges)

    loop For each fuzz iteration
        InputBuilder->>InputBuilder: Generate fields (type,name,epochs,payment,address,URL)
        InputBuilder->>InputBuilder: Create JSON variants (valid, malformed, oversized, random)
        InputBuilder->>InputBuilder: Hex-encode payloads
        InputBuilder->>FuzzEngine: Supply encoded payloads

        loop For each flag combination (allow_script, check_expiration)
            FuzzEngine->>Validator: Validate(payload, flags)
            Validator->>Validator: Parse and validate JSON proposal
            alt valid
                Validator-->>FuzzEngine: Return success/result
            else invalid/malformed
                Validator-->>ErrorHandler: Throw/propagate error
                ErrorHandler-->>FuzzEngine: Return error message
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: adding a new governance proposal validator fuzz target test.
Description check ✅ Passed The description is comprehensive and clearly relates to the changeset, covering summary, testing methodology, validation results, and environment details.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 90-91: Clamp or safely compute end_epoch to avoid signed overflow
when adding the offset from fuzzed_data_provider: read the offset via
ConsumeIntegralInRange<int64_t> into a variable, then compute end_epoch from
start_epoch using a saturated/checked addition (e.g., if start_epoch >
std::numeric_limits<int64_t>::max() - offset set end_epoch =
std::numeric_limits<int64_t>::max(); if start_epoch <
std::numeric_limits<int64_t>::min() - offset set end_epoch =
std::numeric_limits<int64_t>::min(); else end_epoch = start_epoch + offset).
Apply this logic around the existing use of fuzzed_data_provider, start_epoch
and end_epoch so the harness itself cannot trigger signed integer UB.

Address CodeRabbit feedback: adding offset [-4, 1024] to an
unconstrained int64_t start_epoch can overflow, which is UB
under UBSan. Clamp to safe range and add explicit <limits> include.
Required for clang-format CI enforcement on Dash-specific files.
Copy link

@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.

🧹 Nitpick comments (1)
src/test/fuzz/governance_proposal_validator.cpp (1)

19-29: Consider using the existing HexStr utility from util/strencodings.h instead of the custom implementation.

The Dash codebase provides HexStr() which converts byte spans to hex strings. Other fuzz tests (e.g., rpc.cpp, decode_tx.cpp) use this utility for consistency. To use it here, add #include <util/strencodings.h> and replace the HexEncodeString function calls with HexStr().

The current custom implementation is correct — the nibble extraction logic (ch >> 4 for high nibble, ch & 0x0f for low nibble) is sound.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/fuzz/governance_proposal_validator.cpp` around lines 19 - 29,
Replace the custom HexEncodeString implementation with the project's standard
HexStr utility: add `#include` <util/strencodings.h>, remove or stop using
HexEncodeString, and replace calls to HexEncodeString(...) with HexStr(...)
(ensuring the argument type matches the HexStr overload, e.g., pass a
std::string or span/byte vector as required). This keeps behavior consistent
with other fuzz tests like rpc.cpp and decode_tx.cpp and reuses the existing hex
encoding utility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 91-95: The clamping of start_epoch via
fuzzed_data_provider.ConsumeIntegralInRange ensures start_epoch + offset cannot
overflow; keep the change as shown (use the bounds
std::numeric_limits<int64_t>::min() + 1024 and
std::numeric_limits<int64_t>::max() - 1024) and leave the computation of
end_epoch (start_epoch +
fuzzed_data_provider.ConsumeIntegralInRange<int64_t>(-4, 1024)) intact; ensure
the explanatory comment ("Clamp start_epoch to avoid signed overflow UB when
adding offset") remains alongside the start_epoch and end_epoch uses to clarify
the intent for future readers and reviewers (references: start_epoch, end_epoch,
fuzzed_data_provider).

---

Nitpick comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 19-29: Replace the custom HexEncodeString implementation with the
project's standard HexStr utility: add `#include` <util/strencodings.h>, remove or
stop using HexEncodeString, and replace calls to HexEncodeString(...) with
HexStr(...) (ensuring the argument type matches the HexStr overload, e.g., pass
a std::string or span/byte vector as required). This keeps behavior consistent
with other fuzz tests like rpc.cpp and decode_tx.cpp and reuses the existing hex
encoding utility.

src/stats/*.h
src/test/block_reward_reallocation_tests.cpp
src/test/bls_tests.cpp
src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp
Copy link
Member

Choose a reason for hiding this comment

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

seems you have some new lines missed here

@thepastaclaw thepastaclaw force-pushed the fuzz/governance-proposal-validator branch from b2e7273 to 3db377c Compare February 23, 2026 03:55
Copy link

@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)
src/test/fuzz/governance_proposal_validator.cpp (1)

19-29: Consider using the existing HexStr utility instead of a local re-implementation.

HexEncodeString duplicates the functionality of HexStr from util/strencodings.h. Using the canonical helper improves consistency and reduces local maintenance burden. HexStr is already widely used throughout fuzz targets in the codebase.

♻️ Proposed refactor

Add the include at the top:

+#include <util/strencodings.h>

Then remove HexEncodeString and replace the call sites at lines 110–111 and 113:

-const std::string json_hex = HexEncodeString(
-    MakeProposalJson(type, random_name, start_epoch, end_epoch, payment_amount, payment_address, url));
+const std::string json_hex = HexStr(MakeByteSpan(
+    MakeProposalJson(type, random_name, start_epoch, end_epoch, payment_amount, payment_address, url)));
-const std::string malformed_json_hex = HexEncodeString("{" + fuzzed_data_provider.ConsumeRandomLengthString(128));
+const std::string malformed_json_hex = HexStr(MakeByteSpan("{" + fuzzed_data_provider.ConsumeRandomLengthString(128)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/fuzz/governance_proposal_validator.cpp` around lines 19 - 29,
Replace the local HexEncodeString implementation with the canonical HexStr
utility: add `#include` "util/strencodings.h" at the top, remove the
HexEncodeString function, and change all call sites that use
HexEncodeString(some_string) to HexStr(some_string). Ensure the replacement uses
the same input type (std::string or Span) as existing HexStr overloads and
update any includes or using directives if necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/util/data/non-backported.txt`:
- Line 53: The file non-backported.txt contains a concatenated entry
"src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp" which
merges two distinct paths; split them into two separate lines so each path is on
its own line: "src/test/fuzz/governance_proposal_validator.cpp" and
"src/test/bls_tests.cpp". Update the single offending line by inserting a
newline between the two filenames so both entries are valid glob targets for
clang-format CI.

---

Duplicate comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 77-79: The change ensures start_epoch (via
fuzzed_data_provider.ConsumeIntegralInRange) is clamped to [INT64_MIN+1024,
INT64_MAX-1024] and end_epoch is computed as start_epoch + an offset from
ConsumeIntegralInRange(-4, 1024), preventing signed overflow; keep the existing
bounds and the use of start_epoch and end_epoch as-is (no further changes
required to fuzzed_data_provider usage or the addition).

---

Nitpick comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 19-29: Replace the local HexEncodeString implementation with the
canonical HexStr utility: add `#include` "util/strencodings.h" at the top, remove
the HexEncodeString function, and change all call sites that use
HexEncodeString(some_string) to HexStr(some_string). Ensure the replacement uses
the same input type (std::string or Span) as existing HexStr overloads and
update any includes or using directives if necessary.

src/stats/*.h
src/test/block_reward_reallocation_tests.cpp
src/test/bls_tests.cpp
src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the missing newline — both entries are concatenated into a single invalid glob.

Line 53 reads src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp, merging two distinct entries without a line break. As a result, neither src/test/fuzz/governance_proposal_validator.cpp nor src/test/bls_tests.cpp is recognized, breaking clang-format CI enforcement for both files.

🐛 Proposed fix
-src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp
+src/test/fuzz/governance_proposal_validator.cpp
+src/test/bls_tests.cpp
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp
src/test/fuzz/governance_proposal_validator.cpp
src/test/bls_tests.cpp
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/util/data/non-backported.txt` at line 53, The file non-backported.txt
contains a concatenated entry
"src/test/fuzz/governance_proposal_validator.cppsrc/test/bls_tests.cpp" which
merges two distinct paths; split them into two separate lines so each path is on
its own line: "src/test/fuzz/governance_proposal_validator.cpp" and
"src/test/bls_tests.cpp". Update the single offending line by inserting a
newline between the two filenames so both entries are valid glob targets for
clang-format CI.

Copy link

@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.

🧹 Nitpick comments (2)
src/test/fuzz/governance_proposal_validator.cpp (2)

116-116: random_hex is a misleading name — it contains raw bytes, not hex.

Every other payload variable (json_hex, malformed_json_hex, oversized_hex) is genuinely hex-encoded. random_hex is raw fuzzer bytes, which tests the hex-decode failure path of the validator. Rename to random_bytes (or raw_payload) to make the intent clear and avoid confusion with the adjacent hex-encoded variants.

♻️ Proposed rename
-    const std::string random_hex = fuzzed_data_provider.ConsumeRandomLengthString(2048);
+    const std::string raw_payload = fuzzed_data_provider.ConsumeRandomLengthString(2048);
 
     for (const bool allow_script : {false, true}) {
         for (const bool check_expiration : {false, true}) {
             RunValidatorCase(json_hex, allow_script, check_expiration);
             RunValidatorCase(malformed_json_hex, allow_script, check_expiration);
             RunValidatorCase(oversized_hex, allow_script, check_expiration);
-            RunValidatorCase(random_hex, allow_script, check_expiration);
+            RunValidatorCase(raw_payload, allow_script, check_expiration);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/fuzz/governance_proposal_validator.cpp` at line 116, The variable
random_hex actually holds raw fuzzer bytes, not hex, so rename random_hex to
random_bytes (or raw_payload) wherever it's declared and used in
governance_proposal_validator.cpp to clarify intent and avoid confusion with
json_hex/malformed_json_hex/oversized_hex; update the declaration (currently
using fuzzed_data_provider.ConsumeRandomLengthString(2048)) and all subsequent
references in the test helper or validation calls to use the new identifier
(random_bytes or raw_payload) so the code clearly reflects that this value tests
the hex-decode failure path.

19-29: HexEncodeString duplicates the existing HexStr utility.

util/strencodings.h in Dash Core provides HexStr(), which converts a string to lowercase hexadecimal — identical to this custom helper. Replace the custom function with:

`#include` <util/strencodings.h>

Then use HexStr(s) at lines 110 and 113 instead of HexEncodeString(s).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/fuzz/governance_proposal_validator.cpp` around lines 19 - 29, The
file defines a redundant HexEncodeString function; remove that function, add
`#include` <util/strencodings.h>, and replace uses of HexEncodeString(s) in
governance_proposal_validator.cpp with HexStr(s) (which yields the same
lowercase hex). Ensure you delete the HexEncodeString definition and update the
two call sites where HexEncodeString is used to call HexStr instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Around line 76-79: The clamp on start_epoch using
fuzzed_data_provider.ConsumeIntegralInRange<int64_t> to the range [INT64_MIN +
1024, INT64_MAX - 1024] properly prevents signed overflow when adding the offset
from ConsumeIntegralInRange<int64_t>(-4, 1024) to produce end_epoch, so no
further changes to the start_epoch/end_epoch logic in
governance_proposal_validator.cpp are required.

---

Nitpick comments:
In `@src/test/fuzz/governance_proposal_validator.cpp`:
- Line 116: The variable random_hex actually holds raw fuzzer bytes, not hex, so
rename random_hex to random_bytes (or raw_payload) wherever it's declared and
used in governance_proposal_validator.cpp to clarify intent and avoid confusion
with json_hex/malformed_json_hex/oversized_hex; update the declaration
(currently using fuzzed_data_provider.ConsumeRandomLengthString(2048)) and all
subsequent references in the test helper or validation calls to use the new
identifier (random_bytes or raw_payload) so the code clearly reflects that this
value tests the hex-decode failure path.
- Around line 19-29: The file defines a redundant HexEncodeString function;
remove that function, add `#include` <util/strencodings.h>, and replace uses of
HexEncodeString(s) in governance_proposal_validator.cpp with HexStr(s) (which
yields the same lowercase hex). Ensure you delete the HexEncodeString definition
and update the two call sites where HexEncodeString is used to call HexStr
instead.

@thepastaclaw
Copy link
Author

Re: newlines in non-backported.txt — fixed in f306b73, pushed right around when you commented.

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

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

utACK 3db377c

@thepastaclaw
Copy link
Author

Superseded by consolidated fuzz branch in #7173 to avoid merge conflicts.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants