Skip to content

feat: document-throughput load-test tool - #4376

Open
shumkov wants to merge 2 commits into
v4.2-devfrom
feat/rs-scripts-load-test-tool
Open

feat: document-throughput load-test tool#4376
shumkov wants to merge 2 commits into
v4.2-devfrom
feat/rs-scripts-load-test-tool

Conversation

@shumkov

@shumkov shumkov commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

Platform had no in-repo tool to generate sustained, honest document-throughput
load against a running network for consensus stress measurement (block
interval / throughput / round stability). This adds one to rs-scripts as
dev/ops tooling. It was used to produce the consensus-DoS "Gate C" stress
datapoints and the testnet post-roll fair-share flood/load pass.

What was done?

A new load-test bin in packages/rs-scripts, alongside the existing
register-contract / check-contract-properties:

  • load-test — drives DocumentCreate state transitions from a single
    funded identity, fanned out across N cloned data-contract variants. Nonce
    uniqueness comes from the SDK's shared per-(identity, contract) nonce
    cache; the fan-out keeps each contract's in-flight, not-yet-committed nonces
    under the consensus missing-revisions window so fire-and-forget creates are
    not rejected for running too far ahead of the committed nonce. Paced by a
    token interval + semaphore; fire-and-forget (broadcast to mempool, retries
    off) so the measured rate reflects real acceptance. Two quorum-key sources
    for proof verification: --quorum-http (external client;
    TrustedHttpContextProvider, e.g. testnet/mainnet) or --core-* (devnets
    with no HTTP quorum endpoint, via Core RPC).
  • docs/load-tool/load-contract.json — a cheap, non-unique-index "person"
    document-type fixture that is freely spammable.
  • packages/rs-scripts/Cargo.toml — one [[bin]] entry plus the feature
    flags the bin needs (dpp: random-documents; tokio: sync, time) and
    rand.

The bin takes an already-funded identity (id + private key) and only talks to
DAPI. Identity bootstrap/funding is intentionally out of scope for this PR and
will land separately on platform-wallet's proven asset-lock funding path.

How Has This Been Tested?

Exercised end-to-end against live networks:

  • devnet-moutai (protocol 12): combined-stress ("Gate C") — honest
    ~15 docs/s held at round 0; rounds held 0 up to ~55 tx/s committed;
    single-client ceiling ~30 tx/s (DAPI-gateway bound, not chain).
  • testnet (dash-testnet-51, protocol 13), as an external client via
    --quorum-http across 13 HPMN DAPI nodes:
    • Honest-only, 10 min: 8,658 ok / 8 err (0.09%), 14.44 docs/s, every block at
      round 0, node memory flat.
    • Combined with a 100-lane consensus-flood on a validator's p2p port: honest
      8,719 ok / 2 err (0.023%) — unaffected; round 0 throughout; tenderdash
      memory flat 128–155 MiB; the flooded node recovered identically to a
      non-flooded peer.
  • Local CI mirror on v4.2-dev: cargo build -p rs-scripts, cargo clippy --workspace --all-features, and cargo fmt --all --check — all clean.
    Degenerate-input guards (--rate 0, --connections 0, --contracts < --connections) verified to fail loud instead of hanging.

This is an operational/measurement bin in an internal scripts crate (like the
existing rs-scripts bins) exercised against live networks; it carries no unit
tests.

Breaking Changes

None — additive new bin in an internal scripts crate; no existing code changed.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests — n/a, ops tooling exercised against live networks (see above)
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any — n/a, no breaking changes
  • I have made corresponding changes to the documentation if needed

Summary by CodeRabbit

  • New Features
    • Added a load-testing tool for measuring sustained document throughput across Dash Platform environments.
    • Supports configurable network, endpoint, identity, contract, request rate, concurrency, duration, and dry-run settings.
    • Registers contract variants and broadcasts generated documents with bounded concurrency and pacing.
    • Provides progress reporting, success and failure counts, final throughput metrics, and descriptive configuration or connectivity errors.
    • Added a sample document contract defining validated person metadata, including required names and age fields.

Add a `load-test` bin to rs-scripts for driving and measuring sustained
document load against a running network (block-interval / throughput /
round-stability stress measurement, not a max-TPS benchmark).

It drives DocumentCreate state transitions from a single funded identity,
fanned out across N cloned data-contract variants so each contract's in-flight,
not-yet-committed nonces stay well under the consensus missing-revisions window
(nonce uniqueness itself comes from the SDK's shared per-(identity,contract)
nonce cache). Requests are paced by a token interval + semaphore and are
fire-and-forget (broadcast to mempool, retries off) so the measured rate
reflects real acceptance. `--quorum-http` selects an external-client context
provider (TrustedHttpContextProvider) for testnet/mainnet; `--core-*` uses Core
RPC for devnets with no HTTP quorum endpoint. Ships a cheap, spammable
person-doc-type fixture (docs/load-tool/load-contract.json).

The bin takes an already-funded identity (id + private key) and only talks to
DAPI; identity bootstrap/funding is intentionally out of scope and will land
separately on platform-wallet's asset-lock funding path.

Additive only: one new bin in an internal scripts crate, no existing code
changed.

Exercised end-to-end on testnet (dash-testnet-51, protocol 13) as an external
client: sustained ~15 docs/s at round 0 across 13 DAPI nodes, with honest
document service unaffected (0.02% rejects) under a concurrent 100-lane
consensus-flood.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 30f336c3-58b3-44eb-a813-909bfdd29276

📥 Commits

Reviewing files that changed from the base of the PR and between 02e481f and 808e1a0.

📒 Files selected for processing (1)
  • packages/rs-scripts/src/bin/load_test.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-scripts/src/bin/load_test.rs

📝 Walkthrough

Walkthrough

Adds a load-test binary, a typed person contract, configurable SDK connectivity, contract registration, random document generation, bounded broadcast concurrency, progress reporting, and throughput statistics.

Changes

Load-test execution

Layer / File(s) Summary
Load contract and command setup
docs/load-tool/load-contract.json, packages/rs-scripts/Cargo.toml, packages/rs-scripts/src/bin/load_test.rs
Defines the person schema and adds CLI parsing, validation, network selection, SDK construction, and identity loading.
Identity validation and contract registration
packages/rs-scripts/src/bin/load_test.rs
Selects a compatible signing key and registers cloned contract variants with nonce sequencing, confirmation handling, retries, and progress reporting.
Paced document broadcasting
packages/rs-scripts/src/bin/load_test.rs
Generates random documents, submits them across variants with bounded concurrency, tracks results, drains requests, and reports throughput.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant load_test
  participant DAPI_or_Core_RPC
  participant Identity
  participant Dash_Platform
  Operator->>load_test: Provide load-test options
  load_test->>DAPI_or_Core_RPC: Build SDK through quorum data
  load_test->>Identity: Fetch identity and select signing key
  load_test->>Dash_Platform: Register contract variants
  load_test->>Dash_Platform: Broadcast generated documents
  Dash_Platform-->>load_test: Return submission results
  load_test-->>Operator: Report progress and throughput
Loading

Suggested reviewers: quantumexplorer

🚥 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 clearly and concisely describes the new document-throughput load-test tool added by the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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/rs-scripts-load-test-tool

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

@shumkov shumkov changed the title feat(rs-scripts): document-throughput load-test tool feat: document-throughput load-test tool Aug 11, 2026
@thepastaclaw

thepastaclaw commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit 808e1a0)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
packages/rs-scripts/src/bin/load_test.rs (3)

278-285: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Give the explicit flag precedence over the environment variable.

std::env::var("CORE_RPC_PASSWORD").ok().or(args.core_password.clone()) uses the environment value even when the user passes --core-password. An exported variable then silently overrides an explicit flag. Reverse the order.

♻️ Proposed change
-        let core_password = std::env::var("CORE_RPC_PASSWORD")
-            .ok()
-            .or(args.core_password.clone())
+        let core_password = args
+            .core_password
+            .clone()
+            .or_else(|| std::env::var("CORE_RPC_PASSWORD").ok())
🤖 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 `@packages/rs-scripts/src/bin/load_test.rs` around lines 278 - 285, Update the
core_password resolution in the load-test argument handling to prefer
args.core_password from the explicit --core-password flag before falling back to
CORE_RPC_PASSWORD, while preserving the existing missing-password error message
and behavior.

543-549: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Return a failure signal when every broadcast fails.

run_document_load returns (), so run returns Ok(()) and the process exits with code 0 even when all broadcasts fail. Automated runs cannot detect a total failure. Return the counters and map an all-error run to ExitCode::FAILURE.

🤖 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 `@packages/rs-scripts/src/bin/load_test.rs` around lines 543 - 549, Update
run_document_load to return its success and failure counters after draining the
semaphore, then have run inspect those counters and return ExitCode::FAILURE
when every broadcast fails; preserve the successful exit behavior when at least
one broadcast succeeds.

374-393: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Use exponential backoff for the registration retry.

The retry uses a fixed 4 s delay for 8 attempts. During a validator restart, this fails after 32 s. Exponential backoff tolerates longer outages with the same attempt count.

Note also that a failure after acceptance (a lost response or a wait timeout) leaves an orphan contract on chain, because the retry consumes the next identity nonce. This does not break the run. Record it as a known behavior in the module docs.

🤖 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 `@packages/rs-scripts/src/bin/load_test.rs` around lines 374 - 393, The retry
loop around put_to_platform_and_wait_for_response should use an exponential
backoff rather than a fixed 4-second delay, increasing the wait between attempts
while preserving the existing attempt limit and error handling. Also update the
module documentation to record that failures after on-chain acceptance can
orphan a contract because retries consume the next identity nonce.
packages/rs-scripts/Cargo.toml (1)

40-40: 🗄️ Data Integrity & Integration | 🔵 Trivial

Keep rand = "0.8" and run the required workspace checks. rs-scripts and dpp both resolve to rand 0.8.6; the workspace has no shared rand dependency. Run workspace-wide cargo test, cargo check, cargo clippy, and cargo fmt.

🤖 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 `@packages/rs-scripts/Cargo.toml` at line 40, Keep the rand dependency at
version 0.8 in the rs-scripts Cargo manifest, and run the required
workspace-wide cargo test, cargo check, cargo clippy, and cargo fmt checks.

Source: Coding guidelines

🤖 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 `@packages/rs-scripts/src/bin/load_test.rs`:
- Around line 550-562: Update the completion reporting around the load-test loop
to calculate throughput from successfully accepted documents only (`ok /
measured_elapsed_seconds`), using wall-clock elapsed time that includes
semaphore backpressure and draining after the deadline. Change the output label
and target comparison to reflect accepted docs per second, while preserving the
existing success/error counts.
- Around line 112-116: Update the private_key clap argument to support
LOAD_TEST_PRIVATE_KEY via env and hide its environment value by adding the
requested env and hide_env_values settings. Also enable clap’s env feature in
the package dependency configuration while preserving existing CLI argument
behavior.

---

Nitpick comments:
In `@packages/rs-scripts/Cargo.toml`:
- Line 40: Keep the rand dependency at version 0.8 in the rs-scripts Cargo
manifest, and run the required workspace-wide cargo test, cargo check, cargo
clippy, and cargo fmt checks.

In `@packages/rs-scripts/src/bin/load_test.rs`:
- Around line 278-285: Update the core_password resolution in the load-test
argument handling to prefer args.core_password from the explicit --core-password
flag before falling back to CORE_RPC_PASSWORD, while preserving the existing
missing-password error message and behavior.
- Around line 543-549: Update run_document_load to return its success and
failure counters after draining the semaphore, then have run inspect those
counters and return ExitCode::FAILURE when every broadcast fails; preserve the
successful exit behavior when at least one broadcast succeeds.
- Around line 374-393: The retry loop around
put_to_platform_and_wait_for_response should use an exponential backoff rather
than a fixed 4-second delay, increasing the wait between attempts while
preserving the existing attempt limit and error handling. Also update the module
documentation to record that failures after on-chain acceptance can orphan a
contract because retries consume the next identity nonce.
🪄 Autofix

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 Plus

Run ID: 0cc1713a-78e6-41d5-b9c2-7c90cc70d941

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb65fe and 02e481f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • docs/load-tool/load-contract.json
  • packages/rs-scripts/Cargo.toml
  • packages/rs-scripts/src/bin/load_test.rs

Comment thread packages/rs-scripts/src/bin/load_test.rs
Comment thread packages/rs-scripts/src/bin/load_test.rs
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.80%. Comparing base (3bb65fe) to head (808e1a0).
⚠️ Report is 3 commits behind head on v4.2-dev.

Additional details and impacted files
@@            Coverage Diff            @@
##           v4.2-dev    #4376   +/-   ##
=========================================
  Coverage     87.80%   87.80%           
=========================================
  Files          2641     2641           
  Lines        336510   336510           
=========================================
  Hits         295468   295468           
  Misses        41042    41042           
Components Coverage Δ
dpp 88.86% <ø> (ø)
drive 86.25% <ø> (ø)
drive-abci 89.66% <ø> (ø)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.02% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex/Sol only (Phase 2 disabled)

The new binary is isolated from consensus-critical paths, but it has four measurement/bootstrap correctness issues and one secret-handling issue: deadline waits can submit after the configured window, accepted throughput counts failures, extreme timer inputs can panic or yield a meaningless run, ambiguous registration retries can create paid duplicate contracts, and the private key lacks a non-argv input path. These are suggestions rather than consensus-blocking defects, so the appropriate action is COMMENT.
Source: reviewers codex general (gpt-5.6-sol) and codex rust-quality (gpt-5.6-sol); final verifier codex (gpt-5.6-sol). openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 5 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/rs-scripts/src/bin/load_test.rs`:
- [SUGGESTION] packages/rs-scripts/src/bin/load_test.rs:461-467: Stop deadline is not enforced while waiting for a tick or permit
  The deadline is checked only before awaiting the ticker and semaphore. Either wait can finish after the configured duration, after which another document is still submitted. For example, `--rate 0.1 --time 1` submits on the interval's immediate first tick, waits until the ten-second second tick, and submits again even though the run was requested to last one second. Semaphore backpressure can produce the same behavior for up to the request timeout. Race both waits against a sleep ending at `deadline`, and exit without spawning when the deadline wins.
- [SUGGESTION] packages/rs-scripts/src/bin/load_test.rs:377-385: Retrying the combined put-and-wait call can register duplicate contracts
  `put_to_platform_and_wait_for_response` first obtains a fresh identity nonce and broadcasts the resulting transition, then separately waits for its confirmation proof. If the broadcast succeeds but confirmation waiting times out or loses its response, this retry loop invokes the whole operation again. The SDK then allocates another nonce, deriving a different contract ID, so several paid contracts can be committed while only the final confirmed one is tracked. Construct and broadcast each contract transition once, retain that transition and contract ID, and retry only confirmation/querying after an ambiguous post-broadcast failure; fresh-transition retries should be limited to errors known to occur before broadcast acceptance.
- [SUGGESTION] packages/rs-scripts/src/bin/load_test.rs:188-190: Timer validation still permits panic and zero-duration cases
  Positive infinity and sufficiently large finite rates pass the current check. Their reciprocal becomes a zero `Duration`, and `tokio::time::interval` panics because its period must be nonzero; this occurs only after contract variants have been registered and fees may have been spent. Separately, `--time 0` performs paid contract registration, broadcasts nothing, and later computes `0 / 0`, reporting `NaN docs/s` with a successful exit. Reject non-finite rates, rates whose effective interval is zero, and zero run durations before any network side effects.
- [SUGGESTION] packages/rs-scripts/src/bin/load_test.rs:550-560: Accepted throughput is calculated from all attempts
  The summary labels the result as `docs/s accepted`, but its numerator is `ok + err`, so failed broadcasts inflate the tool's primary metric. It also divides by the configured duration even when ticker/semaphore waits and draining extend the actual run. Use successful acceptances as the numerator and measured elapsed wall-clock time as the denominator; the attempted and error counts are already reported separately.
- [SUGGESTION] packages/rs-scripts/src/bin/load_test.rs:112-116: Provide a non-argv input path for the identity private key
  The identity private key can currently be supplied only through `--private-key`, exposing it in the process argument list and commonly in shell history. Add support for a dedicated environment variable such as `LOAD_TEST_PRIVATE_KEY` while preserving the CLI option, hide the environment value in clap-generated output, and enable clap's `env` feature in `packages/rs-scripts/Cargo.toml`.

Comment thread packages/rs-scripts/src/bin/load_test.rs
Comment thread packages/rs-scripts/src/bin/load_test.rs
Comment thread packages/rs-scripts/src/bin/load_test.rs Outdated
Comment thread packages/rs-scripts/src/bin/load_test.rs Outdated
Comment thread packages/rs-scripts/src/bin/load_test.rs

@thephez thephez left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This tool serves a different purpose than the strategy tests (https://github.com/dashpay/platform/tree/v4.2-dev/packages/strategy-tests)? I thought it did a similar thing (although I haven't personally used it).

Address PR review on the load-test bin:
- Reject non-finite (+inf/NaN) and too-high --rate whose reciprocal rounds to
  a zero tick period (previously panicked tokio's interval, but only after
  contract variants were already registered and paid for), and reject --time 0.
- Base reported accepted-throughput on successful acceptances over measured
  wall-clock (ok / start.elapsed()) instead of (ok + err) over the nominal
  --time, so failed broadcasts and post-deadline drain no longer inflate the
  tool's headline metric.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants