diff --git a/packages/artillery/configs/execute.yml b/packages/artillery/configs/execute.yml index 4b5c07938..671af02a2 100644 --- a/packages/artillery/configs/execute.yml +++ b/packages/artillery/configs/execute.yml @@ -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 diff --git a/packages/artillery/configs/sign-session-key.yml b/packages/artillery/configs/sign-session-key.yml index 28c46a8f8..d9dbb32f7 100644 --- a/packages/artillery/configs/sign-session-key.yml +++ b/packages/artillery/configs/sign-session-key.yml @@ -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' diff --git a/packages/artillery/project.json b/packages/artillery/project.json index 34b837c52..7bf26f670 100644 --- a/packages/artillery/project.json +++ b/packages/artillery/project.json @@ -22,35 +22,35 @@ "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", - "command": "dotenvx run --env-file=../../.env -- bash -lc 'if [ -n \"$ARTILLERY_KEY\" ]; then artillery run --record --key \"$ARTILLERY_KEY\" configs/pkp-sign.yml; else artillery run configs/pkp-sign.yml; fi'" + "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": "dotenvx run --env-file=../../.env -- bash -lc 'if [ -n \"$ARTILLERY_KEY\" ]; then artillery run --record --key \"$ARTILLERY_KEY\" configs/encrypt-decrypt.yml; else artillery run configs/encrypt-decrypt.yml; fi'" + "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": "dotenvx run --env-file=../../.env -- bash -lc 'if [ -n \"$ARTILLERY_KEY\" ]; then artillery run --record --key \"$ARTILLERY_KEY\" configs/execute.yml; else artillery run configs/execute.yml; fi'" + "command": "artillery run configs/execute.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")" } }, "run:mix": { "executor": "nx:run-commands", "options": { "cwd": "{projectRoot}", - "command": "dotenvx run --env-file=../../.env -- bash -lc 'if [ -n \"$ARTILLERY_KEY\" ]; then artillery run --record --key \"$ARTILLERY_KEY\" configs/mix.yml; else artillery run configs/mix.yml; fi'" + "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": "dotenvx run --env-file=../../.env -- bash -lc 'if [ -n \"$ARTILLERY_KEY\" ]; then artillery run --record --key \"$ARTILLERY_KEY\" configs/sign-session-key.yml; else artillery run configs/sign-session-key.yml; fi'" + "command": "artillery run configs/sign-session-key.yml $([ -n \"$ARTILLERY_KEY\" ] && echo \"--record --key $ARTILLERY_KEY\")" } } }, diff --git a/packages/artillery/src/init.ts b/packages/artillery/src/init.ts index f5c1b063d..f7b31b304 100644 --- a/packages/artillery/src/init.ts +++ b/packages/artillery/src/init.ts @@ -17,7 +17,7 @@ const _network = process.env['NETWORK']; // CONFIGURATIONS const REJECT_BALANCE_THRESHOLD = 0; -const LEDGER_MINIMUM_BALANCE = 20000; +const LEDGER_MINIMUM_BALANCE = 10000; (async () => { // -- Start diff --git a/packages/artillery/src/processors/multi-endpoints.ts b/packages/artillery/src/processors/multi-endpoints.ts index 7a17c8aa5..7757943e8 100644 --- a/packages/artillery/src/processors/multi-endpoints.ts +++ b/packages/artillery/src/processors/multi-endpoints.ts @@ -37,7 +37,7 @@ const ExecuteJsResultSchema = z.object({ }) ), response: z.string(), - logs: z.string(), + logs: z.string().optional(), }); // Global variables to cache expensive operations @@ -112,7 +112,7 @@ const createAuthContextFromState = async () => { ['access-control-condition-decryption', '*'], ], capabilityAuthSigs: [], - expiration: new Date(Date.now() + 1000 * 60 * 15).toISOString(), + expiration: new Date(Date.now() + 1000 * 60 * 30).toISOString(), }, litClient: litClient, }); @@ -140,7 +140,7 @@ export async function runPkpSignTest() { // Perform pkpSign operation const result = await litClient.chain.ethereum.pkpSign({ authContext: authContext, - pubKey: state.masterAccount.pkp.publicKey, + pubKey: state.masterAccount.pkp.pubkey, toSign: `Hello from Artillery! ${Date.now()}`, // Unique message per request // userMaxPrice: 1000000000000000000n, }); @@ -166,7 +166,15 @@ export async function runPkpSignTest() { ); // Throw the error to let Artillery handle it - throw error; + // Handle specific errors to aggregate them + if ( + error instanceof Error && + error.message.includes('unable to get signature share') + ) { + throw new Error('"PKP Sign" failed. unable to get signature share.'); + } else { + throw error; + } } } @@ -235,7 +243,11 @@ export async function runEncryptDecryptTest() { } // test '/web/execute/v2' endpoint -export async function runExecuteJSTest() { +export async function runExecuteJSTest(context: any, _events: any) { + const variant = context.scenario.variables.variant; + + console.log('🔍 variant:', variant); + const startTime = Date.now(); try { @@ -248,32 +260,44 @@ export async function runExecuteJSTest() { // Create auth context const authContext = await createAuthContextFromState(); + // Set up access control conditions requiring wallet ownership + const builder = createAccBuilder(); + let accs: any; + let encryptedData: any; + let dataToEncrypt: string; + if (variant === 'decryptToSingleNode') { + accs = builder + .requireWalletOwnership(authContext.account.address) + .on('ethereum') + .build(); + + // Encrypt data with the access control conditions + dataToEncrypt = 'Hello from encrypt-decrypt test!'; + encryptedData = await litClient.encrypt({ + dataToEncrypt, + unifiedAccessControlConditions: accs, + chain: 'ethereum', + }); + } else { + accs = builder + .requireWalletOwnership(state.masterAccount.pkp.ethAddress) + .on('ethereum') + .build(); + } + // 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 { litActionCode, jsParams } = getLitActionCodeAndJsParams( + variant, + state, + encryptedData, + accs, + await authContext.authNeededCallback() + ); 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, - }, + jsParams, }); // Validate the result using Zod schema @@ -282,9 +306,19 @@ export async function runExecuteJSTest() { const endTime = Date.now(); const duration = endTime - startTime; + if ( + variant === 'decryptToSingleNode' && + // Strip quote marks at the ends + validatedResult.response.substring( + 1, + validatedResult.response.length - 1 + ) !== dataToEncrypt! + ) { + throw new Error('❌ Decrypted data does not match the original data'); + } + console.log(`✅ executeJs successful in ${duration}ms`); console.log('✅ executeJs result:', validatedResult); - // For Artillery, just return - no need to call next() return; } catch (error) { @@ -317,7 +351,7 @@ export async function runSignSessionKeyTest() { const masterAccountPkpAuthContext = await authManager.createPkpAuthContext({ authData: state.masterAccount.authData, - pkpPublicKey: state.masterAccount.pkp.publicKey, + pkpPublicKey: state.masterAccount.pkp.pubkey, authConfig: { resources: [ ['pkp-signing', '*'], @@ -333,7 +367,10 @@ export async function runSignSessionKeyTest() { }, }); - // console.log('✅ Master Account PKP Auth Context:', masterAccountPkpAuthContext); + console.log( + '✅ Sign Session Key successful. Master Account PKP Auth Context:', + masterAccountPkpAuthContext + ); } catch (error) { const endTime = Date.now(); const duration = endTime - startTime; @@ -347,3 +384,137 @@ export async function runSignSessionKeyTest() { throw error; } } + +// String enum for the variant +type Variant = + | 'sign' + | 'broadcastAndCollect' + | 'checkConditionsWithoutAuthSig' + | 'signChildLitAction' + | 'decryptToSingleNode' + | 'runOnce'; + +function getLitActionCodeAndJsParams( + variant: Variant, + state: any, + encryptedData?: any, + accs?: any, + authSig?: any +): { + litActionCode: string; + jsParams: any; +} { + switch (variant) { + case 'broadcastAndCollect': + return { + litActionCode: ` + (async () => { + const resp = await Lit.Actions.broadcastAndCollect({ + name: 'some-name', + value: 'some-value', + }); + Lit.Actions.setResponse({ response: JSON.stringify(resp) }); + })();`, + jsParams: undefined, + }; + case 'checkConditionsWithoutAuthSig': + return { + litActionCode: ` + (async () => { + const { accessControlConditions } = jsParams; + const resp = await Lit.Actions.checkConditions({ + conditions: accessControlConditions, + chain: 'ethereum', + }); + Lit.Actions.setResponse({ response: JSON.stringify(resp.toString()) }); + })();`, + jsParams: { + accessControlConditions: + accs || state.masterAccount.pkp.accessControlConditions, + }, + }; + case 'signChildLitAction': + return { + litActionCode: ` + (async () => { + const { sigName, publicKey } = jsParams; + let utf8Encode = new TextEncoder(); + const toSign = utf8Encode.encode('This message is exactly 32 bytes'); + const _ = await Lit.Actions.call({ ipfsId: 'QmRwN9GKHvCn4Vk7biqtr6adjXMs7PzzYPCzNCRjPFiDjm', params: { + toSign: Array.from(toSign), + publicKey, + sigName + }}); + })();`, + jsParams: { + sigName: 'e2e-test-sig', + publicKey: state.masterAccount.pkp.pubkey, + }, + }; + case 'decryptToSingleNode': + return { + litActionCode: ` + (async () => { + const { accessControlConditions, ciphertext, dataToEncryptHash } = jsParams; + const resp = await Lit.Actions.decryptToSingleNode({ + accessControlConditions, + ciphertext, + dataToEncryptHash, + authSig: null, + chain: 'ethereum', + }); + Lit.Actions.setResponse({ response: JSON.stringify(resp) }); + })();`, + jsParams: { + accessControlConditions: + accs || state.masterAccount.pkp.accessControlConditions, + ciphertext: encryptedData?.ciphertext, + dataToEncryptHash: encryptedData?.dataToEncryptHash, + }, + }; + case 'runOnce': + return { + litActionCode: ` + (async () => { + let temp = await Lit.Actions.runOnce( + { waitForResponse: false, name: 'weather' }, + async () => { + const url = 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'; + const resp = await fetch(url).then((response) => response.json()); + const temp = resp.properties.periods[0].temperature; + return temp; + } + ); + + Lit.Actions.setResponse({ response: JSON.stringify(temp) }); + })();`, + jsParams: undefined, + }; + case 'sign': + return { + 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, + }); + })();`, + jsParams: { + message: 'Test message from e2e executeJs', + sigName: 'e2e-test-sig', + toSign: 'Test message from e2e executeJs', + publicKey: state.masterAccount.pkp.pubkey, + }, + }; + default: + throw new Error(`Unknown variant: ${variant}`); + } +} diff --git a/packages/e2e/src/helper/createEnvVars.ts b/packages/e2e/src/helper/createEnvVars.ts index 1e2c0bb38..0f52852fb 100644 --- a/packages/e2e/src/helper/createEnvVars.ts +++ b/packages/e2e/src/helper/createEnvVars.ts @@ -1,4 +1,9 @@ -const supportedNetworks = ['naga-local', 'naga-test', 'naga-dev'] as const; +const supportedNetworks = [ + 'naga-local', + 'naga-test', + 'naga-staging', + 'naga-dev', +] as const; type EnvName = 'local' | 'live'; export type EnvVars = { @@ -71,7 +76,11 @@ export function createEnvVars(): EnvVars { } // -- live networks - if (network === 'naga-dev' || network === 'naga-test') { + if ( + network === 'naga-dev' || + network === 'naga-test' || + network === 'naga-staging' + ) { const liveRpcUrl = process.env['LIT_YELLOWSTONE_PRIVATE_RPC_URL']; if (liveRpcUrl) {