Skip to content

Sei-load: configurable load generators - #3917

Open
kollegian wants to merge 7 commits into
mainfrom
sei-load/scenario-additions
Open

Sei-load: configurable load generators#3917
kollegian wants to merge 7 commits into
mainfrom
sei-load/scenario-additions

Conversation

@kollegian

@kollegian kollegian commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Describe your changes and provide context

Integrates configurable load runners for sei load.

Testing performed to validate your change

No source code change.

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large integration-test surface that signs and broadcasts real transactions when EXECUTE=1; misconfigured RPC/chain IDs or partition indexes could fund or stress the wrong accounts, though safety ceilings and dry-run defaults mitigate accidental high-rate runs.

Overview
Turns the Pacific replay package into a multi-mode load generator with a unified npm run load CLI (setup, provision, run) for defi, tokenops, nativetransfers, and simulate (existing capture/replay). Synthetic runs use rate-limited scheduling with per-worker pending caps, optional LOAD_MIX weights, partitioned user pools (PARTITION_INDEX / USERS_PER_PARTITION), Prometheus metrics, rotating transactions.jsonl audits, and per-run summary.json.

Targets no longer hard-code arctic/atlantic: TARGET_NETWORK, chain IDs, and RPC URLs are all required env vars. Deployment manifests move to schema v5 (adds TestERC1155 and related fixture wiring). Funding uses exact decimal FUND_SEI via seiToUsei instead of JS floats.

Adds Docker (Dockerfile, healthcheck on :9465), GH Actions to test and push ghcr.io/.../sei-load-generator, load:prepare-account for isolated runner mnemonics, shared Cosmos funding helper, and refactors deploy/provision/replay entrypoints for reuse from the container (LOADGEN_COMPILED, dist/runLoad.js).

Reviewed by Cursor Bugbot for commit 183de2d. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 14, 2026, 3:10 PM

Comment thread integration_test/load_generator/Dockerfile Outdated
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.50%. Comparing base (fa71d9e) to head (183de2d).
⚠️ Report is 9 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3917      +/-   ##
==========================================
- Coverage   59.51%   58.50%   -1.02%     
==========================================
  Files        2325     2229      -96     
  Lines      198686   188054   -10632     
==========================================
- Hits       118251   110016    -8235     
+ Misses      69217    67661    -1556     
+ Partials    11218    10377     -841     
Flag Coverage Δ
sei-db 70.41% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.
see 96 files with indirect coverage changes

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

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Large but well-structured addition of configurable generated load modes (defi/tokenops/nativetransfers) plus a Docker image and Helm chart for the standalone load generator; the code is defensive (EXECUTE gating, safety ceilings, manifest validation, exact-decimal funding) and adds real unit coverage. No blockers, but a few operational sharp edges: reusing a RUN_ID makes ERC-721 mints collide, the Helm runner can start against a stale fixture manifest, and the switch from process.exit(1) to process.exitCode = 1 can hang failing entry points.

Findings: 0 blocking | 8 non-blocking | 4 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • package.json adds overrides: { "protobufjs": "$protobufjs" }, which silently forces @confio/ics23 (a deprecated package whose static codec was generated against protobufjs 6) from ^6.8.8 up to 8.7.1, and drops all @protobufjs/* and long@4 entries from the lockfile. @cosmjs/stargate imports ics23 eagerly, so an incompatibility would surface at module load on every command. Worth a sentence in the PR body or a smoke assertion recording why the override is needed (presumably the protobufjs 6.11 advisory) and that ics23 still loads.
  • LoadMetrics.listen installs an async request handler and does response.end(await this.registry.metrics()); if registry.metrics() rejects (a default-metrics collector failing to read /proc, for example) the rejection is unhandled and, under Node's default policy, terminates a long-running load run on a scrape. Wrapping the handler body in try/catch and returning 500 keeps a metrics hiccup from killing the run.
  • Helm object names are not length-guarded: the runner Deployment/Service names (fullname-<runner>) are built with no trunc, and sei-load-generator.fixtureSetupName truncates to 63 after appending -<revision>. With a long release or fixture-set name the former is rejected by the API server, and the latter can truncate the revision suffix away so the new setup Job collides with the previous revision's (immutable) Job and the upgrade fails. Truncate-then-suffix, or append a short hash.
  • 4 suggestion(s)/nit(s) flagged inline on specific lines.
  • 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.

Pre-existing issues

  • [suggestion] No GitHub workflow runs the integration_test/load_generator suites (npm test / npm run test:fixtures / npm run typecheck) — .github/workflows/integration-test.yml only covers rpc_tests. The ~260 lines of new mocha specs and the new hardhat tokenOps.spec.ts this PR adds are therefore not exercised on CI, so regressions in the load generator land silently.

evmOperation('erc20_burn', 8, token, ERC20, 'burn', GAS_LIMIT, () => [AMOUNT / 20n]),
evmOperation('erc721_mint', 14, nft, ERC721, 'safeMint', GAS_LIMIT, (worker, sequence) => [
worker.evmAddress,
uniqueTokenId(context.runId, worker, sequence),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] erc721_mint derives its token ID from (runId, workerAddress, sequence), and chooseOperation is driven by seededRandom(config.runId), so two runs with the same RUN_ID replay an identical operation stream and mint the identical set of token IDs. loadConfig only requires RUN_ID to be non-empty, never unique, so:

  • The README's own example, run twice (EXECUTE=1 RUN_ID=tokens-50 npm run load -- run --type tokenops --tps 50), makes every erc721_mint (weight 14 of ~102) revert with already minted for the whole second run.
  • Under the Helm chart --run-id is $(POD_NAME), which is stable across container restarts within a pod while sequence resets to 0 — so any crash/restart of a tokenops runner permanently degrades that share of its load into included_failed.

Mixing a per-process component into the ID (process start time, or the value recorded in run.json) would make reruns and restarts collision-free without giving up the determinism the new test asserts.

command:
- sh
- -ec
- timeout 1800 sh -c 'until test -s /fixtures/deployment.json; do echo "waiting for fixtures"; sleep 2; done'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] wait-for-fixtures treats any non-empty /fixtures/deployment.json as ready, but the fixture PVC outlives Helm revisions while the setup Job is recreated per revision and runs concurrently with this Deployment. On upgrade the runner therefore starts against whatever manifest the previous revision left behind, before the new setup Job has validated or replaced it.

It is not silently wrong — validateManifests in runSynthetic rejects a mismatched schemaVersion/network/chainId — but the observable behaviour is a crash-loop plus a wasted provision-users funding pass on every restart, and it never converges if the setup Job itself fails (e.g. verifyExisting refusing a manifest from a different network, which then needs a manual FORCE_DEPLOY=1). Gating on a marker file that setup writes only after success (schema- or revision-stamped) makes the ordering an invariant instead of a race.

if (require.main === module) {
runReplayMain().catch(error => {
console.error('Fatal:', error instanceof Error ? error.message : error);
process.exitCode = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] Switching from process.exit(1) to process.exitCode = 1 means the process only exits once the event loop drains, and this entry point creates handles outside the try/finally that owns cleanup: liveMetrics.listen (line 187) and the JsonRpcProvider are established before the try at line 217, while liveMetrics.close() / provider.destroy() live in that block's finally. A failure in between — e.g. bucketAudit.initialize() on a non-writable replay dir — leaves the metrics server listening and the process alive indefinitely, with /healthz still answering 200 so a Kubernetes liveness probe won't recycle it.

The same pattern is now in runLoad.ts, runBuffered.ts (which may still hold a child that ignored SIGTERM), deployFixtures.ts (provider.destroy() only on the two success paths, and the setup Job has no activeDeadlineSeconds), and provisionUsers.ts. Either move handle creation inside the guarded region or keep process.exit(1) after logging.

emit Transfer(address(0), to, value);
}

function burn(uint256 value) external {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[nit] This copy of TestERC20 now diverges from integration_test/rpc_tests/contracts/TestERC20.sol (byte-identical before this PR), while the file's own header still says it is "the canonical contract across the RPC suite" and is "[d]eployed identically on Sei and the geth reference so contract-touching parity specs see the same layout on both chains." Either mirror burn into the rpc_tests copy or drop that claim from this one, so the next reader doesn't rely on a guarantee the two files no longer honour.

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

Love that AI has made @kollegian into a SUPER USER! awesome end-to-end work. the only blocker for me is to simply move pieces of this work into different repos and leave the code here to be purely about the implementation of the load generation itself

We also need to figure out where the containers for this are being published and pulled from.

Super thanks to @bdchatham for helping out on this one 🙌

@@ -0,0 +1,42 @@
{{- define "sei-load-generator.name" -}}

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.

I would just move all of these to platform repo (and probably use kustomize) to save the hassle of having to set up helm repository and build process.

Comment thread integration_test/load_generator/changes.md Outdated
Comment thread integration_test/load_generator/runtime/load-runs/defi-test-1786550775/run.json Outdated
Comment thread .gitignore Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 183de2d. Configure here.

{
startedAt,
completedAt: completedAt.toISOString(),
durationSeconds: (completedAt.getTime() - Date.parse(startedAt)) / 1_000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Summary TPS includes setup time

Medium Severity

reportRun computes durationSeconds from the timestamp taken before RPC checks, worker creation, and fixture preparation through post-run drain. summarizeRun uses that span as the TPS denominator, so achieved rate is diluted by setup and teardown rather than the scheduled load window.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 183de2d. Configure here.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants