Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,68 @@ DIRECTORY_NAME=naga-local
NETWORK=naga-local pnpm run test:e2e all
```

# Artillery Load Testing

Use the standalone Artillery project under `packages/artillery`

## Preparation

```bash
# from the repo root
pnpm install

# pick your target network: naga-dev | naga-staging | naga-test | naga-local
export NETWORK=naga-staging
export LOG_LEVEL=info # optional: debug | debug2 | silent
```

For live networks that read ABI data from the `networks` repo (for example `naga-staging`), run the sync script before firing Artillery so the contracts and addresses are up to date:

```bash
pnpm run sync:contracts # requires GH_API_KEY in your environment
```

Testing a custom local network? Point the runner at your generated `networkContext.json` and RPC URL. (/lit-assets/blockchain/contracts/networkContext.json)

```ts
const networkModule = nagaLocal
.withLocalContext({
networkContextPath:
'/Users/<username>/Projects/lit-assets/blockchain/contracts/networkContext.json',
networkName: 'naga-local',
})
.withOverrides({ rpcUrl: process.env.LOCAL_RPC_URL });
```

If you want Artillery Cloud reports, set `ARTILLERY_KEY=<your-key>` in `.env` before running a scenario.

## One-time initialisation

Master account, auth data and PKP info are written to this file:
`packages/artillery/artillery-state.json`.

```bash
pnpm nx run artillery:init
```

(optional) Check master balances before blasting a load test:

```bash
pnpm nx run artillery:balance-status
```

## Run a workload

Each scenario is exposed as an Nx target. Use the `run:` prefixed name:

```bash
pnpm nx run artillery:run:pkp-sign # PKP signing focus
pnpm nx run artillery:run:encrypt-decrypt # Encryption/decryption focus
pnpm nx run artillery:run:execute # Lit Action execution
pnpm nx run artillery:run:mix # Mixed workload
pnpm nx run artillery:run:sign-session-key # Session key signing
```

# Manual Publishing

```bash
Expand Down
6 changes: 4 additions & 2 deletions packages/artillery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ nx run artillery:sign-session-key

Generating a report required an API key, you can add that to the root `.env` file. You can find your key at [https://app.artillery.io/](https://app.artillery.io/oivpr8dw4i00f)

```jsx
ARTILLERY_KEY = xxx;
```bash
ARTILLERY_KEY=xxx
```

> ℹ️ The Nx run targets pass `ARTILLERY_KEY` to `artillery run` via `--key`, so no extra flags are needed when running the scripts.
40 changes: 39 additions & 1 deletion packages/artillery/configs/execute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,46 @@ config:
processor: '../src/processors/multi-endpoints.ts'

scenarios:
- name: 'Execute JS Stress Test'
- name: 'Execute JS Stress Test - Sign'
weight: 100
variables:
# Access in the script via context.scenario.variables.variant
variant: 'sign'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
- name: 'Execute JS Stress Test - Broadcast and Collect'
weight: 0
variables:
variant: 'broadcastAndCollect'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
- name: 'Execute JS Stress Test - Check Conditions with Auth Sig'
weight: 0
variables:
variant: 'checkConditionsWithoutAuthSig'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
- name: 'Execute JS Stress Test - Sign Child Lit Action'
weight: 0
variables:
variant: 'signChildLitAction'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
- name: 'Execute JS Stress Test - Decrypt to Single Node without Auth Sig'
weight: 0
variables:
variant: 'decryptToSingleNode'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
- name: 'Execute JS Stress Test - Run Once'
weight: 0
variables:
variant: 'runOnce'
flow:
- function: 'runExecuteJSTest'
- think: 0.1
8 changes: 3 additions & 5 deletions packages/artillery/configs/sign-session-key.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ config:
# Over 60s, ramp up to creating 50 vusers per second
- duration: 60
arrivalRate: 5
# rampTo: 50
rampTo: 10
rampTo: 150
name: 'Ramp Up'
# Over 300s, create 50 vusers per second
- duration: 300
# arrivalRate: 50
arrivalRate: 10
arrivalRate: 150
name: 'Sustained Sign Session Key'
# Over 60s, ramp down to creating 5 vusers per second
- duration: 60
arrivalRate: 5
arrivalRate: 20
name: 'Ramp Down'
processor: '../src/processors/multi-endpoints.ts'

Expand Down
10 changes: 5 additions & 5 deletions packages/artillery/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,35 @@
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "artillery run configs/pkp-sign.yml"
"command": "artillery run configs/pkp-sign.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")"
}
},
"run:encrypt-decrypt": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "artillery run configs/encrypt-decrypt.yml"
"command": "artillery run configs/encrypt-decrypt.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")"
}
},
"run:execute": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "artillery run configs/execute.yml"
"command": "artillery run configs/execute.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")"
}
},
"run:mix": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "artillery run configs/mix.yml"
"command": "artillery run configs/mix.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")"
}
},
"run:sign-session-key": {
"executor": "nx:run-commands",
"options": {
"cwd": "{projectRoot}",
"command": "artillery run configs/sign-session-key.yml"
"command": "artillery run configs/sign-session-key.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")"
}
}
},
Expand Down
17 changes: 12 additions & 5 deletions packages/artillery/src/StateManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const readFile = async (): Promise<State> => {

// If content is empty object, write base state
if (Object.keys(content).length === 0) {
await fs.writeFile(FILE_NAME, JSON.stringify(StateObject, null, 2));
await fs.writeFile(FILE_NAME, stringify(StateObject));
return StateObject;
}

Expand All @@ -42,7 +42,7 @@ export const readFile = async (): Promise<State> => {

// create the file if it doesn't exist
export const createFile = async () => {
await fs.writeFile(FILE_NAME, JSON.stringify(StateObject, null, 2));
await fs.writeFile(FILE_NAME, stringify(StateObject));
};

// Type-safe field paths - dynamically derived from State type
Expand Down Expand Up @@ -93,7 +93,7 @@ export const updateField = async <T extends StatePaths>(
state[rootKey] !== null
) {
(state[rootKey] as any)[nestedKey] = value;
await fs.writeFile(FILE_NAME, JSON.stringify(state, null, 2));
await fs.writeFile(FILE_NAME, stringify(state));
} else {
throw new Error(`Invalid path: ${path}`);
}
Expand Down Expand Up @@ -123,7 +123,7 @@ export const getOrUpdate = async <T extends StatePaths>(

// Otherwise, update with default value and return it
(state as any)[path] = defaultValue;
await fs.writeFile(FILE_NAME, JSON.stringify(state, null, 2));
await fs.writeFile(FILE_NAME, stringify(state));
return defaultValue;
} else {
// Nested property
Expand All @@ -144,10 +144,17 @@ export const getOrUpdate = async <T extends StatePaths>(

// Otherwise, update with default value and return it
(state[rootKey] as any)[nestedKey] = defaultValue;
await fs.writeFile(FILE_NAME, JSON.stringify(state, null, 2));
await fs.writeFile(FILE_NAME, stringify(state));
return defaultValue;
} else {
throw new Error(`Invalid path: ${path}`);
}
}
};

const stringify = (value: unknown) =>
JSON.stringify(
value,
(_, val) => (typeof val === 'bigint' ? val.toString() : val),
2
);
Comment on lines +155 to +160
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

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

The stringify function lacks documentation explaining its purpose. Add a JSDoc comment explaining that it serializes objects to JSON with BigInt support, as BigInt values cannot be directly serialized by JSON.stringify.

Copilot uses AI. Check for mistakes.
9 changes: 6 additions & 3 deletions packages/artillery/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import {
} from '@lit-protocol/auth';
import * as StateManager from './StateManager';
import { createLitClient } from '@lit-protocol/lit-client';
import { getOrCreatePkp } from '@lit-protocol/e2e/src/helper/pkp-utils';
import * as NetworkManager from '@lit-protocol/e2e/src/helper/NetworkManager';
import {
getOrCreatePkp,
getLitNetworkModule,
getViemPublicClient,
} from '@lit-protocol/e2e';
import * as AccountManager from '../src/AccountManager';

const _network = process.env['NETWORK'];

// CONFIGURATIONS
const REJECT_BALANCE_THRESHOLD = 0;
const LEDGER_MINIMUM_BALANCE = 20000;
const LEDGER_MINIMUM_BALANCE = 10000;

(async () => {
// -- Start
Expand Down
Loading
Loading