diff --git a/e2e/artillery/configs/encrypt-decrypt.yml b/e2e/artillery/configs/encrypt-decrypt.yml new file mode 100644 index 000000000..4e9855ba8 --- /dev/null +++ b/e2e/artillery/configs/encrypt-decrypt.yml @@ -0,0 +1,24 @@ +config: + target: "dummy" + phases: + # Over 60s, ramp up to creating 50 vusers per second + - duration: 60 + arrivalRate: 5 + rampTo: 150 + name: "Ramp Up" + # Over 300s, create 50 vusers per second + - duration: 300 + arrivalRate: 150 + name: "Sustained Encrypt & Decrypt" + # Over 60s, ramp down to creating 5 vusers per second + - duration: 60 + arrivalRate: 20 + name: "Ramp Down" + processor: "../src/processors/multi-endpoints.ts" + +scenarios: + - name: "Encrypt & Decrypt Stress Test" + weight: 100 + flow: + - function: "runEncryptDecryptTest" + - think: 0.1 \ No newline at end of file diff --git a/e2e/artillery/configs/execute.yml b/e2e/artillery/configs/execute.yml new file mode 100644 index 000000000..50e549c8e --- /dev/null +++ b/e2e/artillery/configs/execute.yml @@ -0,0 +1,24 @@ +config: + target: "dummy" + phases: + # Over 60s, ramp up to creating 50 vusers per second + - duration: 60 + arrivalRate: 5 + rampTo: 100 + name: "Ramp Up" + # Over 300s, create 50 vusers per second + - duration: 300 + arrivalRate: 100 + name: "Sustained Encrypt & Decrypt" + # Over 60s, ramp down to creating 5 vusers per second + - duration: 60 + arrivalRate: 20 + name: "Ramp Down" + processor: "../src/processors/multi-endpoints.ts" + +scenarios: + - name: "Execute JS Stress Test" + weight: 100 + flow: + - function: "runExecuteJSTest" + - think: 0.1 \ No newline at end of file diff --git a/e2e/artillery/configs/mix.yml b/e2e/artillery/configs/mix.yml new file mode 100644 index 000000000..41c35f44e --- /dev/null +++ b/e2e/artillery/configs/mix.yml @@ -0,0 +1,34 @@ +config: + target: "dummy" + phases: + # Over 60s, ramp up to creating 50 vusers per second + - duration: 60 + arrivalRate: 5 + rampTo: 75 + name: "Ramp Up" + # Over 300s, create 50 vusers per second + - duration: 300 + arrivalRate: 75 + name: "Sustained Encrypt & Decrypt" + # Over 60s, ramp down to creating 5 vusers per second + - duration: 60 + arrivalRate: 20 + name: "Ramp Down" + processor: "../src/processors/multi-endpoints.ts" + +scenarios: + - name: "PKP Sign Stress Test" + weight: 50 + flow: + - function: "runPkpSignTest" + - think: 0.1 + - name: "Encrypt & Decrypt Stress Test" + weight: 25 + flow: + - function: "runEncryptDecryptTest" + - think: 0.1 + - name: "Execute JS Stress Test" + weight: 25 + flow: + - function: "runExecuteJSTest" + - think: 0.1 \ No newline at end of file diff --git a/e2e/artillery/configs/pkp-sign.yml b/e2e/artillery/configs/pkp-sign.yml index 8738b31f8..c915a79b2 100644 --- a/e2e/artillery/configs/pkp-sign.yml +++ b/e2e/artillery/configs/pkp-sign.yml @@ -4,17 +4,15 @@ config: # Over 60s, ramp up to creating 50 vusers per second - duration: 60 arrivalRate: 5 - # rampTo: 50 - rampTo: 10 - name: 'Ramp Up' + rampTo: 80 + name: "Ramp Up" # Over 300s, create 50 vusers per second - duration: 300 - # arrivalRate: 50 - arrivalRate: 10 - name: 'Sustained PKP Signing' + arrivalRate: 80 + name: "Sustained PKP Signing" # 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' diff --git a/e2e/artillery/src/init.ts b/e2e/artillery/src/init.ts index d9f4c4c55..4534971af 100644 --- a/e2e/artillery/src/init.ts +++ b/e2e/artillery/src/init.ts @@ -14,7 +14,7 @@ const _network = process.env['NETWORK']; // CONFIGURATIONS const REJECT_BALANCE_THRESHOLD = 0; -const LEDGER_MINIMUM_BALANCE = 10000; +const LEDGER_MINIMUM_BALANCE = 20000; (async () => { // -- Start diff --git a/e2e/artillery/src/processors/multi-endpoints.ts b/e2e/artillery/src/processors/multi-endpoints.ts index 5044e1077..dde80ae0d 100644 --- a/e2e/artillery/src/processors/multi-endpoints.ts +++ b/e2e/artillery/src/processors/multi-endpoints.ts @@ -4,6 +4,7 @@ import { z } from 'zod'; import * as StateManager from '../StateManager'; import * as NetworkManager from '../../../src/helper/NetworkManager'; import * as AccountManager from '../AccountManager'; +import { createAccBuilder } from '@lit-protocol/access-control-conditions'; // PKP Sign Result Schema const PkpSignResultSchema = z.object({ @@ -17,6 +18,28 @@ const PkpSignResultSchema = z.object({ sigType: z.string().min(1, 'Signature type cannot be empty'), }); +// Execute JS Result Schema +const ExecuteJsResultSchema = z.object({ + success: z.boolean(), + signatures: z.record( + z.string(), + z.object({ + signature: z.string().regex(/^0x[a-fA-F0-9]+$/, 'Invalid hex signature'), + verifyingKey: z + .string() + .regex(/^0x[a-fA-F0-9]+$/, 'Invalid hex verifying key'), + signedData: z + .string() + .regex(/^0x[a-fA-F0-9]+$/, 'Invalid hex signed data'), + recoveryId: z.number().int().min(0).max(3, 'Recovery ID must be 0-3'), + publicKey: z.string().regex(/^0x[a-fA-F0-9]+$/, 'Invalid hex public key'), + sigType: z.string().min(1, 'Signature type cannot be empty'), + }) + ), + response: z.string(), + logs: z.string(), +}); + // Global variables to cache expensive operations let litClient: LitClientType; let authManager: any = null; @@ -147,6 +170,137 @@ export async function runPkpSignTest() { } } +// test '/web/encryption/sign/v2' endpoint +export async function runEncryptDecryptTest() { + const startTime = Date.now(); + + try { + // 1. Initialise shared resources (only happens once) + await initialiseSharedResources(); + + // 2. Read state + const state = await StateManager.readFile(); + + // Create auth context + const authContext = await createAuthContextFromState(); + + // Set up access control conditions requiring wallet ownership + const addressToUse = authContext.account.address; + const builder = createAccBuilder(); + const accs = builder + .requireWalletOwnership(addressToUse) + .on('ethereum') + .build(); + + // Encrypt data with the access control conditions + const dataToEncrypt = 'Hello from PKP encrypt-decrypt test!'; + const encryptedData = await litClient.encrypt({ + dataToEncrypt, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + + // Decrypt the data using the appropriate auth context + const decryptedData = await litClient.decrypt({ + data: encryptedData, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + authContext, + }); + + // Assert that the decrypted data is the same as the original data + if (decryptedData.convertedData !== dataToEncrypt) { + throw new Error('❌ Decrypted data does not match the original data'); + } + + const endTime = Date.now(); + const duration = endTime - startTime; + + console.log(`✅ encrypt & decrypt successful in ${duration}ms`); + + // For Artillery, just return - no need to call next() + return; + } catch (error) { + const endTime = Date.now(); + const duration = endTime - startTime; + + console.error( + `❌ encrypt & decrypt failed in ${duration}ms:`, + error instanceof Error ? error.message : String(error) + ); + + // Throw the error to let Artillery handle it + throw error; + } +} + +// test '/web/execute/v2' endpoint +export async function runExecuteJSTest() { + const startTime = Date.now(); + + try { + // 1. Initialise shared resources (only happens once) + await initialiseSharedResources(); + + // 2. Read state + const state = await StateManager.readFile(); + + // Create auth context + const authContext = await createAuthContextFromState(); + + // Perform executeJs operation + const litActionCode = ` + (async () => { + const { sigName, toSign, publicKey } = jsParams; + const { keccak256, arrayify } = ethers.utils; + + const toSignBytes = new TextEncoder().encode(toSign); + const toSignBytes32 = keccak256(toSignBytes); + const toSignBytes32Array = arrayify(toSignBytes32); + + const sigShare = await Lit.Actions.signEcdsa({ + toSign: toSignBytes32Array, + publicKey, + sigName, + }); + })();`; + + const result = await litClient.executeJs({ + code: litActionCode, + authContext, + jsParams: { + message: 'Test message from e2e executeJs', + sigName: 'e2e-test-sig', + toSign: 'Test message from e2e executeJs', + publicKey: state.masterAccount.pkp.publicKey, + }, + }); + + // Validate the result using Zod schema + const validatedResult = ExecuteJsResultSchema.parse(result); + + const endTime = Date.now(); + const duration = endTime - startTime; + + console.log(`✅ executeJs successful in ${duration}ms`); + console.log('✅ executeJs result:', validatedResult); + + // For Artillery, just return - no need to call next() + return; + } catch (error) { + const endTime = Date.now(); + const duration = endTime - startTime; + + console.error( + `❌ executeJs failed in ${duration}ms:`, + error instanceof Error ? error.message : String(error) + ); + + // Throw the error to let Artillery handle it + throw error; + } +} + // test '/web/sign_session_key' endpoint export async function runSignSessionKeyTest() { // ❗️ IT'S IMPORTANT TO SET THIS TO FALSE FOR TESTING @@ -170,7 +324,8 @@ export async function runSignSessionKeyTest() { ['lit-action-execution', '*'], ['access-control-condition-decryption', '*'], ], - expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + // 30m expiration + expiration: new Date(Date.now() + 1000 * 60 * 30).toISOString(), }, litClient: litClient, cache: { diff --git a/e2e/src/init.ts b/e2e/src/init.ts index 8f0bd3db6..5b8075672 100644 --- a/e2e/src/init.ts +++ b/e2e/src/init.ts @@ -224,7 +224,8 @@ export const init = async ( ['lit-action-execution', '*'], ['access-control-condition-decryption', '*'], ], - expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + // 30m expiration + expiration: new Date(Date.now() + 1000 * 60 * 30).toISOString(), }, litClient: litClient, }); diff --git a/package.json b/package.json index 2dd802d1d..ae85b044b 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "artillery:init": "bun run ./e2e/artillery/src/init.ts", "artillery:balance-status": "LOG_LEVEL=silent bun run ./e2e/artillery/src/balance-status.ts", "artillery:pkp-sign": "DEBUG_HTTP=true LOG_LEVEL=silent dotenvx run --env-file=.env -- sh -c 'artillery run ./e2e/artillery/configs/pkp-sign.yml ${ARTILLERY_KEY:+--record --key $ARTILLERY_KEY}'", + "artillery:encrypt-decrypt": "DEBUG_HTTP=true LOG_LEVEL=silent dotenvx run --env-file=.env -- sh -c 'artillery run ./e2e/artillery/configs/encrypt-decrypt.yml ${ARTILLERY_KEY:+--record --key $ARTILLERY_KEY}'", + "artillery:execute-js": "DEBUG_HTTP=true LOG_LEVEL=silent dotenvx run --env-file=.env -- sh -c 'artillery run ./e2e/artillery/configs/execute.yml ${ARTILLERY_KEY:+--record --key $ARTILLERY_KEY}'", + "artillery:mix": "DEBUG_HTTP=true LOG_LEVEL=silent dotenvx run --env-file=.env -- sh -c 'artillery run ./e2e/artillery/configs/mix.yml ${ARTILLERY_KEY:+--record --key $ARTILLERY_KEY}'", "artillery:sign-session-key": "DEBUG_HTTP=true LOG_LEVEL=silent dotenvx run --env-file=.env -- sh -c 'artillery run ./e2e/artillery/configs/sign-session-key.yml ${ARTILLERY_KEY:+--record --key $ARTILLERY_KEY}'" }, "private": true,