stress

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 8, 2026 License: MIT Imports: 20 Imported by: 0

README

Clearnode Stress Testing Tool

Built-in stress testing tool for validating clearnode performance, correctness, and stability under load.

Quick Start

# Set target
export STRESS_WS_URL=ws://localhost:7824/ws

# Read-only test (no wallet needed)
clearnode stress-test ping:1000:10

# State-mutating test (funded wallet required)
export STRESS_PRIVATE_KEY=<hex-encoded-private-key>
clearnode stress-test transfer-roundtrip:10:20:usdc

Architecture

The stress tool is compiled into the clearnode binary and invoked via the stress-test subcommand. It connects to a running clearnode instance over WebSocket using the Go SDK.

clearnode stress-test <spec>
        │
        ├── Read-only methods ──► Connection pool (N parallel WebSocket clients)
        │                              │
        │                              └── Distributes requests round-robin across connections
        │
        └── State-mutating methods ──► Custom orchestration
                                          │
                                          ├── transfer-roundtrip: 3-phase fund/stress/collect
                                          └── app-session-lifecycle: create/deposit/operate/close

Key design decisions:

  • Each WebSocket connection sends requests sequentially (waits for response before sending next)
  • Parallelism is achieved through multiple connections
  • Connection pool tolerates individual failures — test runs with whatever connections succeeded
  • Results include per-request latency, percentile distribution, and error breakdown

Configuration

All configuration is via environment variables.

Variable Required Default Description
STRESS_WS_URL Yes - WebSocket URL of the target clearnode
STRESS_PRIVATE_KEY No ephemeral Hex-encoded ECDSA private key
STRESS_CONNECTIONS No 10 Default parallel connections per test
STRESS_TIMEOUT No 10m Overall test timeout
STRESS_MAX_ERROR_RATE No 0.01 Error rate threshold (0.01 = 1%)

When STRESS_PRIVATE_KEY is not set, an ephemeral key is generated. This works for read-only methods but state-mutating methods require a funded wallet.

Spec Format

method:total_requests[:connections[:extra_params...]]
  • method — test method name
  • total_requests — total number of operations to execute
  • connections — parallel WebSocket connections (optional, falls back to STRESS_CONNECTIONS)
  • extra_params — method-specific parameters (asset, amount, wallet address, etc.)

Available Methods

Read-Only Methods

These methods test read path performance. They use a shared connection pool and do not modify server state. An ephemeral wallet is used if STRESS_PRIVATE_KEY is not set.

Method Extra Params Description
ping none WebSocket ping/pong roundtrip
get-config none Fetch server configuration
get-blockchains none List available blockchains
get-assets [chain_id] List assets, optionally filtered by chain
get-balances [wallet] Get wallet balances
get-transactions [wallet] Fetch transactions (paginated, limit 20)
get-home-channel asset or wallet:asset Get home channel for wallet+asset
get-escrow-channel channel_id Get escrow channel by ID
get-latest-state asset or wallet:asset Get latest channel state
get-channel-key-states [wallet] Get last channel key states
get-app-sessions [wallet] Query app sessions (paginated)
get-app-key-states [wallet] Get last app key states

Examples:

# 1000 pings over 10 connections
clearnode stress-test ping:1000:10

# 500 config fetches over 5 connections
clearnode stress-test get-config:500:5

# 2000 balance queries over 20 connections
clearnode stress-test get-balances:2000:20:0x1234...

# 1000 home channel lookups
clearnode stress-test get-home-channel:1000:10:usdc

# Asset queries filtered by chain ID
clearnode stress-test get-assets:500:5:84532
State-Mutating Methods

These methods test write path performance. They require STRESS_PRIVATE_KEY with a funded wallet.

transfer-roundtrip

Spec: transfer-roundtrip:rounds:wallets:asset[:amount]

Param Description
rounds Back-and-forth transfer rounds per wallet pair
wallets Number of derived wallets (rounded up to even)
asset Asset symbol (e.g., usdc)
amount Transfer amount per operation (default: 0.000001)

Three-phase execution:

  1. Fund — Sender distributes amount to each derived wallet
  2. Stress — Wallet pairs (0,1), (2,3), ... transfer back and forth in parallel for rounds iterations
  3. Collect — All wallets return funds to sender

Wallet keys are deterministically derived from the master key using SHA-256: masterKey:receiver:<index>.

Total measured operations = wallets * rounds (phase 2 only).

Examples:

# 10 rounds, 20 wallets (10 pairs), usdc, default amount
clearnode stress-test transfer-roundtrip:10:20:usdc

# 50 rounds, 100 wallets (50 pairs), custom amount
clearnode stress-test transfer-roundtrip:50:100:usdc:0.0001
app-session-lifecycle

Spec: app-session-lifecycle:sessions:participants:operates:asset[:amount]

Param Description
sessions Number of concurrent app session lifecycles
participants Wallets per session (quorum = all must sign)
operates Number of operate state updates per session
asset Asset symbol (e.g., usdc)
amount Deposit amount per session (default: 0.000003)

Per-session lifecycle:

  1. Create — Create app session with all participants
  2. Deposit — First participant deposits funds into session
  3. Operate — Submit N state updates with rotating fund allocations
  4. Close — Close session with final allocation matching last operate

All signatures are pre-generated before the stress phase begins. Each session is driven by its first participant ("pipe lead") over a dedicated WebSocket connection.

Wallet keys are derived using SHA-256: masterKey:appsession:<pipeIdx>:<walletIdx>.

Total measured operations = sessions * (operates + 3).

Examples:

# 10 sessions, 5 participants each, 3 operates per session
clearnode stress-test app-session-lifecycle:10:5:3:usdc

# 50 sessions, 3 participants, 10 operates, custom amount
clearnode stress-test app-session-lifecycle:50:3:10:usdc:0.000005

Report Output

Every test produces a standardized report:

Stress Test Report
==================
Method:          ping
Total Requests:  1000
Connections:     10
Duration:        2.345s

Results
-------
Successful:      998 (99.8%)
Failed:          2 (0.2%)
Requests/sec:    426.44

Latency
-------
Min:             1.2ms
Max:             45.3ms
Average:         2.3ms
Median (p50):    2.1ms
P95:             4.5ms
P99:             12.8ms

Errors
------
  context deadline exceeded                                          2

Pass/fail criteria: The test exits with code 0 (PASS) if the error rate is within STRESS_MAX_ERROR_RATE, or code 1 (FAIL) if exceeded.

Helm Integration

The stress tool is integrated as a Helm test. When enabled, helm test creates a Pod that runs the stress spec against the in-cluster clearnode service.

values.yaml:

stressTest:
  enabled: true
  specs:
    - "ping:100000:100"
  privateKey: "<hex-key>"  # optional, for state-mutating tests
  connections: 10
  timeout: "10m"
  maxErrorRate: "0.01"

Run:

helm test <release-name>

The WebSocket URL defaults to the in-cluster service (ws://<release>-clearnode:7824/ws). Override with stressTest.wsURL for external targets.

Testing Strategy

Phase 1: Read Path Baseline

Validate read performance under increasing load. No funded wallet needed.

export STRESS_WS_URL=ws://target:7824/ws

# Baseline latency
clearnode stress-test ping:100:1
clearnode stress-test get-config:100:1

# Scale connections
clearnode stress-test ping:10000:10
clearnode stress-test ping:100000:100
clearnode stress-test get-balances:10000:50:0xWALLET
Phase 2: Write Path Stress

Test state mutation throughput. Requires funded wallet.

export STRESS_PRIVATE_KEY=<key>

# Small scale
clearnode stress-test transfer-roundtrip:5:4:usdc

# Production scale
clearnode stress-test transfer-roundtrip:50:100:usdc:0.0001
Phase 3: App Session Lifecycle

Test multi-participant coordination.

# Small scale
clearnode stress-test app-session-lifecycle:5:3:3:usdc

# Production scale
clearnode stress-test app-session-lifecycle:50:5:10:usdc:0.000005
Phase 4: Sustained Load

Run extended tests to detect resource leaks and degradation.

# High volume read
clearnode stress-test ping:1000000:100

# Extended write
clearnode stress-test transfer-roundtrip:500:100:usdc:0.000001

Troubleshooting

Symptom Cause Fix
STRESS_WS_URL is required Missing environment variable Set STRESS_WS_URL
failed to open any connections Target unreachable or refusing connections Verify URL and that clearnode is running
WARNING: Only N/M connections established Server under load or connection limits Reduce connection count or check server capacity
STRESS_PRIVATE_KEY is required State-mutating method without key Set STRESS_PRIVATE_KEY with a funded wallet
fund wallet X: insufficient balance Sender wallet not funded Transfer funds to the wallet address printed at startup
High error rate in transfer tests Database contention or deadlocks Check server logs for deadlock traces
context deadline exceeded Test exceeded STRESS_TIMEOUT Increase timeout or reduce test scope

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloseClientPool

func CloseClientPool(clients []*sdk.Client)

CloseClientPool closes all clients in the pool.

func CreateClientPool

func CreateClientPool(wsURL, privateKey string, n int) ([]*sdk.Client, error)

CreateClientPool opens up to n WebSocket connections to the clearnode. It tolerates individual connection failures and returns whatever connections succeeded. Returns an error only if zero connections could be established.

func MethodRegistry

func MethodRegistry() map[string]Runner

MethodRegistry returns all available stress test methods.

func PrintReport

func PrintReport(report Report)

PrintReport prints a formatted stress test report to stdout.

func Run

func Run(args []string) int

Run is the main entry point for the stress-test subcommand. It receives os.Args[2:] (everything after "stress-test"). Returns exit code: 0 if pass, 1 if fail.

Types

type Config

type Config struct {
	WsURL        string        `env:"STRESS_WS_URL,required"`
	PrivateKey   string        `env:"STRESS_PRIVATE_KEY"`
	Connections  int           `env:"STRESS_CONNECTIONS" env-default:"10"`
	Timeout      time.Duration `env:"STRESS_TIMEOUT" env-default:"10m"`
	MaxErrorRate float64       `env:"STRESS_MAX_ERROR_RATE" env-default:"0.01"`
}

Config holds all stress test settings, read from environment variables.

func ReadConfig

func ReadConfig() (*Config, error)

ReadConfig reads the stress test configuration from environment variables. If STRESS_PRIVATE_KEY is not set, an ephemeral key is generated.

func (*Config) WalletAddress

func (c *Config) WalletAddress() (string, error)

WalletAddress derives the wallet address from the configured private key. Returns empty string if no private key was explicitly configured.

type Factory

type Factory func(args []string, walletAddress string) (MethodFunc, error)

Factory parses method-specific args and returns a MethodFunc.

type MethodFunc

type MethodFunc func(ctx context.Context, client *sdk.Client) error

MethodFunc is the signature for a single stress test request against one client.

type Report

type Report struct {
	Method      string
	TotalReqs   int
	Connections int
	Successful  int
	Failed      int
	TotalTime   time.Duration

	MinLatency    time.Duration
	MaxLatency    time.Duration
	AvgLatency    time.Duration
	MedianLatency time.Duration
	P95Latency    time.Duration
	P99Latency    time.Duration

	RequestsPerSec float64
	ErrorBreakdown map[string]int
}

Report contains the full aggregated report after a stress test run.

func ComputeReport

func ComputeReport(method string, totalReqs, connections int, results []Result, totalTime time.Duration) Report

ComputeReport aggregates stress test results into a report.

func RunAppSessionLifecycleStress

func RunAppSessionLifecycleStress(ctx context.Context, cfg *Config, spec TestSpec) (Report, error)

RunAppSessionLifecycleStress runs a parallel app session lifecycle stress test.

Spec format: app-session-lifecycle:app_sessions:participants:operates:asset[:amount]

  • app_sessions: number of concurrent app session lifecycles
  • participants: wallets per pipe (weight 1 each, quorum = all must sign)
  • operates: number of operate state updates per session

Each pipe = create + deposit + N operates + close = N + 3 operations.

func RunTransferStress

func RunTransferStress(ctx context.Context, cfg *Config, spec TestSpec) (Report, error)

RunTransferStress runs a parallel transfer stress test.

Spec format: transfer-roundtrip:rounds:wallets:asset[:amount]

  • wallets: number of derived wallets (must be even, each pair runs in parallel)
  • rounds: number of back-and-forth rounds per pair

Phases:

  1. Fund distribution: sender sends amount to each derived wallet
  2. Stress test: pairs of wallets transfer back and forth in parallel
  3. Fund collection: all wallets return funds to sender

type Result

type Result struct {
	Duration time.Duration
	Err      error
}

Result captures the outcome of a single request.

func RunTest

func RunTest(ctx context.Context, totalReqs int, clients []*sdk.Client, fn MethodFunc) ([]Result, time.Duration)

RunTest executes totalReqs calls of fn distributed across the client pool. Each connection sends requests sequentially — waits for a response before sending the next one. Multiple connections run in parallel.

type Runner

type Runner func(ctx context.Context, cfg *Config, spec TestSpec) (Report, error)

Runner is the unified signature for executing a stress test. Every method in the registry uses this — pool-based or custom.

type TestSpec

type TestSpec struct {
	Method      string
	TotalReqs   int
	Connections int // 0 means use default from config
	ExtraArgs   []string
}

TestSpec represents a parsed test specification from CLI args.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL