From bde7b66ac2dd65590b9d77c7fccc2a7920941460 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Wed, 16 Apr 2025 17:17:00 -0400 Subject: [PATCH 1/3] chore(contract-manager) Entropy stress test script --- ...tency_entropy_with_callback_stress_test.ts | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 contract_manager/scripts/latency_entropy_with_callback_stress_test.ts diff --git a/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts b/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts new file mode 100644 index 0000000000..65e643a238 --- /dev/null +++ b/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts @@ -0,0 +1,191 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { + DefaultStore, + EvmChain, + EvmEntropyContract, + PrivateKey, + toPrivateKey, +} from "../src"; +import { COMMON_DEPLOY_OPTIONS, findEntropyContract } from "./common"; + +const parser = yargs(hideBin(process.argv)) + .usage( + "Requests random numbers from an entropy contract and measures the\n" + + "latency between request submission and fulfillment by the Fortuna keeper service.\n" + + "Usage: $0 --private-key --chain | --all-chains --nrequests --delay ", + ) + .options({ + chain: { + type: "string", + desc: "test latency for the contract on this chain", + conflicts: "all-chains", + }, + "all-chains": { + type: "string", + conflicts: "chain", + choices: ["testnet", "mainnet"], + desc: "test latency for all entropy contracts deployed either on mainnet or testnet", + }, + "private-key": COMMON_DEPLOY_OPTIONS["private-key"], + "nrequests": { + type: "number", + desc: "number of requests to make", + default: 1, + }, + "delay": { + type: "number", + desc: "delay between requests", + default: 25, + }, + }); + +async function sendRequest( + contract: EvmEntropyContract, + privateKey: PrivateKey, + requestId: number, +): Promise<{ EntropyRequestResponse: any; startTime: number }> { + const fortunaProvider = await contract.getDefaultProvider(); + const userRandomNumber = contract.generateUserRandomNumber(); + const EntropyRequestResponse = await contract.requestRandomness( + userRandomNumber, + fortunaProvider, + privateKey, + true, // with callback + ); + const startTime = Date.now(); + console.log(`[Request ${requestId}] Request tx hash : ${EntropyRequestResponse.transactionHash}`); + return { EntropyRequestResponse: EntropyRequestResponse, startTime: startTime }; +} + +async function waitForCallback( + contract: EvmEntropyContract, + EntropyRequestResponse: any, + startTime: number, + requestId: number, +): Promise<{ success: boolean; latency?: number }> { + const fromBlock = EntropyRequestResponse.blockNumber; + const web3 = contract.chain.getWeb3(); + const entropyContract = contract.getContract(); + + // eslint-disable-next-line no-constant-condition + while (true) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + const currentBlock = await web3.eth.getBlockNumber(); + if (fromBlock > currentBlock) { + continue; + } + + const events = await entropyContract.getPastEvents("RevealedWithCallback", { + fromBlock: fromBlock, + toBlock: currentBlock, + }); + + const event = events.find( + (event) => event.returnValues.request[1] == EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber, + ); + + if (event !== undefined) { + console.log(`[Request ${requestId}] Random number : ${event.returnValues.randomNumber}`); + const eventBlockTimestamp = Number(await web3.eth.getBlock(event.blockNumber).then(block => block.timestamp)); + const entropyRequestBlockTimestamp = Number(await web3.eth.getBlock(EntropyRequestResponse.blockNumber).then(block => block.timestamp)); + const latency = eventBlockTimestamp - entropyRequestBlockTimestamp; + console.log(`[Request ${requestId}] Fortuna Latency : ${latency}ms`); + console.log( + `[Request ${requestId}] Revealed after : ${ + event.blockNumber - EntropyRequestResponse.blockNumber + } blocks`, + ); + return { success: true, latency }; + } + if (Date.now() - startTime > 60000) { + console.log(`[Request ${requestId}] Timeout: 60s passed without the callback being called.`); + return { success: false }; + } + } +} + +async function testParallelLatency( + contract: EvmEntropyContract, + privateKey: PrivateKey, + numRequests: number, + delay: number, +) { + console.log(`Starting ${numRequests} requests...`); + + // First send all requests + const requests: { EntropyRequestResponse: any; startTime: number; requestId: number }[] = []; + for (let i = 0; i < numRequests; i++) { + if (i > 0) { + await new Promise(resolve => setTimeout(resolve, delay)); + } + const { EntropyRequestResponse, startTime } = await sendRequest(contract, privateKey, i + 1); + requests.push({ EntropyRequestResponse, startTime, requestId: i + 1 }); + } + + + + + // Then wait for all callbacks + // The response time won't be accurate here. + const results: { success: boolean; latency?: number }[] = []; + for (const request of requests) { + const sequenceNumber = + request.EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber; + console.log(`[Request ${request.requestId}] sequence : ${sequenceNumber}`); + results.push(await waitForCallback( + contract, + request.EntropyRequestResponse, + request.startTime, + request.requestId + )); + } + + // Calculate statistics + const successfulRequests = results.filter(r => r.success).length; + const failedRequests = numRequests - successfulRequests; + const successRate = (successfulRequests / numRequests) * 100; + + // Calculate average latency for successful requests + const successfulLatencies = results + .filter((r): r is { success: true; latency: number } => r.success && r.latency !== undefined) + .map(r => r.latency); + const avgLatency = successfulLatencies.length > 0 + ? successfulLatencies.reduce((a, b) => a + b, 0) / successfulLatencies.length + : 0; + + console.log("\n=== Test Results ==="); + console.log(`Total Requests : ${numRequests}`); + console.log(`Successful : ${successfulRequests}`); + console.log(`Failed : ${failedRequests}`); + console.log(`Success Rate : ${successRate.toFixed(2)}%`); + if (successfulLatencies.length > 0) { + console.log(`Average Latency : ${avgLatency.toFixed(2)}ms`); + } + console.log("==================="); +} + +async function main() { + const argv = await parser.argv; + if (!argv.chain && !argv["all-chains"]) { + throw new Error("Must specify either --chain or --all-chains"); + } + const privateKey = toPrivateKey(argv.privateKey); + if (argv["all-chains"]) { + for (const contract of Object.values(DefaultStore.entropy_contracts)) { + if ( + contract.getChain().isMainnet() === + (argv["all-chains"] === "mainnet") + ) { + console.log(`Testing latency for ${contract.getId()}...`); + await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); + } + } + } else if (argv.chain) { + const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain); + const contract = findEntropyContract(chain); + await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); + } +} + +main(); From 9a11f60b802349e0856874de65559e225601dded Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 17 Apr 2025 12:31:59 -0400 Subject: [PATCH 2/3] requested_changes --- .../scripts/latency_entropy_with_callback.ts | 127 ++++++++++++++---- 1 file changed, 101 insertions(+), 26 deletions(-) diff --git a/contract_manager/scripts/latency_entropy_with_callback.ts b/contract_manager/scripts/latency_entropy_with_callback.ts index 63259f1042..1c4966de06 100644 --- a/contract_manager/scripts/latency_entropy_with_callback.ts +++ b/contract_manager/scripts/latency_entropy_with_callback.ts @@ -11,9 +11,9 @@ import { COMMON_DEPLOY_OPTIONS, findEntropyContract } from "./common"; const parser = yargs(hideBin(process.argv)) .usage( - "Requests a random number from an entropy contract and measures the\n" + + "Requests random numbers from an entropy contract and measures the\n" + "latency between request submission and fulfillment by the Fortuna keeper service.\n" + - "Usage: $0 --private-key --chain | --all-chains ", + "Usage: $0 --private-key --chain | --all-chains --nrequests --delay ", ) .options({ chain: { @@ -28,29 +28,43 @@ const parser = yargs(hideBin(process.argv)) desc: "test latency for all entropy contracts deployed either on mainnet or testnet", }, "private-key": COMMON_DEPLOY_OPTIONS["private-key"], + "nrequests": { + type: "number", + desc: "number of requests to make", + default: 1, + }, + "delay": { + type: "number", + desc: "delay between requests in milliseconds", + default: 25, + }, }); -async function testLatency( +async function sendRequest( contract: EvmEntropyContract, privateKey: PrivateKey, -) { - const provider = await contract.getDefaultProvider(); + requestId: number, +): Promise<{ entropyRequestResponse: any; startTime: number }> { + const fortunaProvider = await contract.getDefaultProvider(); const userRandomNumber = contract.generateUserRandomNumber(); - const requestResponse = await contract.requestRandomness( + const entropyRequestResponse = await contract.requestRandomness( userRandomNumber, - provider, + fortunaProvider, privateKey, true, // with callback ); - console.log(`Request tx hash : ${requestResponse.transactionHash}`); - // Read the sequence number for the request from the transaction events. - const sequenceNumber = - requestResponse.events.RequestedWithCallback.returnValues.sequenceNumber; - console.log(`sequence : ${sequenceNumber}`); - const startTime = Date.now(); + console.log(`[Request ${requestId}] Request tx hash : ${entropyRequestResponse.transactionHash}`); + return { entropyRequestResponse, startTime }; +} - const fromBlock = requestResponse.blockNumber; +async function waitForCallback( + contract: EvmEntropyContract, + entropyRequestResponse: any, + startTime: number, + requestId: number, +): Promise<{ success: boolean; latency?: number }> { + const fromBlock = entropyRequestResponse.blockNumber; const web3 = contract.chain.getWeb3(); const entropyContract = contract.getContract(); @@ -58,7 +72,6 @@ async function testLatency( while (true) { await new Promise((resolve) => setTimeout(resolve, 1000)); const currentBlock = await web3.eth.getBlockNumber(); - if (fromBlock > currentBlock) { continue; } @@ -69,25 +82,87 @@ async function testLatency( }); const event = events.find( - (event) => event.returnValues.request[1] == sequenceNumber, + (event) => event.returnValues.request[1] == entropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber, ); if (event !== undefined) { - console.log(`Random number : ${event.returnValues.randomNumber}`); - const endTime = Date.now(); - console.log(`Fortuna Latency : ${endTime - startTime}ms`); + console.log(`[Request ${requestId}] Random number : ${event.returnValues.randomNumber}`); + const eventBlockTimestamp = Number(await web3.eth.getBlock(event.blockNumber).then(block => block.timestamp)); + const entropyRequestBlockTimestamp = Number(await web3.eth.getBlock(entropyRequestResponse.blockNumber).then(block => block.timestamp)); + const latency = eventBlockTimestamp - entropyRequestBlockTimestamp; + console.log(`[Request ${requestId}] Fortuna Latency : ${latency}ms`); console.log( - `Revealed after : ${ - currentBlock - requestResponse.blockNumber + `[Request ${requestId}] Revealed after : ${ + event.blockNumber - entropyRequestResponse.blockNumber } blocks`, ); - break; + return { success: true, latency }; } if (Date.now() - startTime > 60000) { - console.log("Timeout: 60s passed without the callback being called."); - break; + console.log(`[Request ${requestId}] Timeout: 60s passed without the callback being called.`); + return { success: false }; + } + } +} + +async function testParallelLatency( + contract: EvmEntropyContract, + privateKey: PrivateKey, + numRequests: number, + delay: number, +) { + console.log(`Starting ${numRequests} requests...`); + + // First send all requests + const requests: { entropyRequestResponse: any; startTime: number; requestId: number }[] = []; + for (let i = 0; i < numRequests; i++) { + if (i > 0) { + await new Promise(resolve => setTimeout(resolve, delay)); } + const { entropyRequestResponse, startTime } = await sendRequest(contract, privateKey, i + 1); + requests.push({ entropyRequestResponse, startTime, requestId: i + 1 }); + } + + + + + // Then wait for all callbacks + // The response time won't be accurate here. + const results: { success: boolean; latency?: number }[] = []; + for (const request of requests) { + const sequenceNumber = + request.entropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber; + console.log(`[Request ${request.requestId}] sequence : ${sequenceNumber}`); + results.push(await waitForCallback( + contract, + request.entropyRequestResponse, + request.startTime, + request.requestId + )); + } + + // Calculate statistics + const successfulRequests = results.filter(r => r.success).length; + const failedRequests = numRequests - successfulRequests; + const successRate = (successfulRequests / numRequests) * 100; + + // Calculate average latency for successful requests + const successfulLatencies = results + .filter((r): r is { success: true; latency: number } => r.success && r.latency !== undefined) + .map(r => r.latency); + const avgLatency = successfulLatencies.length > 0 + ? successfulLatencies.reduce((a, b) => a + b, 0) / successfulLatencies.length + : 0; + + console.log("\n=== Test Results ==="); + console.log(`Total Requests : ${numRequests}`); + console.log(`Successful : ${successfulRequests}`); + console.log(`Failed : ${failedRequests}`); + console.log(`Success Rate : ${successRate.toFixed(2)}%`); + if (successfulLatencies.length > 0) { + console.log(`Average Latency : ${avgLatency.toFixed(2)}ms`); } + console.log("==================="); } async function main() { @@ -103,13 +178,13 @@ async function main() { (argv["all-chains"] === "mainnet") ) { console.log(`Testing latency for ${contract.getId()}...`); - await testLatency(contract, privateKey); + await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); } } } else if (argv.chain) { const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain); const contract = findEntropyContract(chain); - await testLatency(contract, privateKey); + await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); } } From 4648e6decee1d1a5fa86bd9971d059965430a991 Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 17 Apr 2025 12:32:14 -0400 Subject: [PATCH 3/3] removed --- ...tency_entropy_with_callback_stress_test.ts | 191 ------------------ 1 file changed, 191 deletions(-) delete mode 100644 contract_manager/scripts/latency_entropy_with_callback_stress_test.ts diff --git a/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts b/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts deleted file mode 100644 index 65e643a238..0000000000 --- a/contract_manager/scripts/latency_entropy_with_callback_stress_test.ts +++ /dev/null @@ -1,191 +0,0 @@ -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -import { - DefaultStore, - EvmChain, - EvmEntropyContract, - PrivateKey, - toPrivateKey, -} from "../src"; -import { COMMON_DEPLOY_OPTIONS, findEntropyContract } from "./common"; - -const parser = yargs(hideBin(process.argv)) - .usage( - "Requests random numbers from an entropy contract and measures the\n" + - "latency between request submission and fulfillment by the Fortuna keeper service.\n" + - "Usage: $0 --private-key --chain | --all-chains --nrequests --delay ", - ) - .options({ - chain: { - type: "string", - desc: "test latency for the contract on this chain", - conflicts: "all-chains", - }, - "all-chains": { - type: "string", - conflicts: "chain", - choices: ["testnet", "mainnet"], - desc: "test latency for all entropy contracts deployed either on mainnet or testnet", - }, - "private-key": COMMON_DEPLOY_OPTIONS["private-key"], - "nrequests": { - type: "number", - desc: "number of requests to make", - default: 1, - }, - "delay": { - type: "number", - desc: "delay between requests", - default: 25, - }, - }); - -async function sendRequest( - contract: EvmEntropyContract, - privateKey: PrivateKey, - requestId: number, -): Promise<{ EntropyRequestResponse: any; startTime: number }> { - const fortunaProvider = await contract.getDefaultProvider(); - const userRandomNumber = contract.generateUserRandomNumber(); - const EntropyRequestResponse = await contract.requestRandomness( - userRandomNumber, - fortunaProvider, - privateKey, - true, // with callback - ); - const startTime = Date.now(); - console.log(`[Request ${requestId}] Request tx hash : ${EntropyRequestResponse.transactionHash}`); - return { EntropyRequestResponse: EntropyRequestResponse, startTime: startTime }; -} - -async function waitForCallback( - contract: EvmEntropyContract, - EntropyRequestResponse: any, - startTime: number, - requestId: number, -): Promise<{ success: boolean; latency?: number }> { - const fromBlock = EntropyRequestResponse.blockNumber; - const web3 = contract.chain.getWeb3(); - const entropyContract = contract.getContract(); - - // eslint-disable-next-line no-constant-condition - while (true) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - const currentBlock = await web3.eth.getBlockNumber(); - if (fromBlock > currentBlock) { - continue; - } - - const events = await entropyContract.getPastEvents("RevealedWithCallback", { - fromBlock: fromBlock, - toBlock: currentBlock, - }); - - const event = events.find( - (event) => event.returnValues.request[1] == EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber, - ); - - if (event !== undefined) { - console.log(`[Request ${requestId}] Random number : ${event.returnValues.randomNumber}`); - const eventBlockTimestamp = Number(await web3.eth.getBlock(event.blockNumber).then(block => block.timestamp)); - const entropyRequestBlockTimestamp = Number(await web3.eth.getBlock(EntropyRequestResponse.blockNumber).then(block => block.timestamp)); - const latency = eventBlockTimestamp - entropyRequestBlockTimestamp; - console.log(`[Request ${requestId}] Fortuna Latency : ${latency}ms`); - console.log( - `[Request ${requestId}] Revealed after : ${ - event.blockNumber - EntropyRequestResponse.blockNumber - } blocks`, - ); - return { success: true, latency }; - } - if (Date.now() - startTime > 60000) { - console.log(`[Request ${requestId}] Timeout: 60s passed without the callback being called.`); - return { success: false }; - } - } -} - -async function testParallelLatency( - contract: EvmEntropyContract, - privateKey: PrivateKey, - numRequests: number, - delay: number, -) { - console.log(`Starting ${numRequests} requests...`); - - // First send all requests - const requests: { EntropyRequestResponse: any; startTime: number; requestId: number }[] = []; - for (let i = 0; i < numRequests; i++) { - if (i > 0) { - await new Promise(resolve => setTimeout(resolve, delay)); - } - const { EntropyRequestResponse, startTime } = await sendRequest(contract, privateKey, i + 1); - requests.push({ EntropyRequestResponse, startTime, requestId: i + 1 }); - } - - - - - // Then wait for all callbacks - // The response time won't be accurate here. - const results: { success: boolean; latency?: number }[] = []; - for (const request of requests) { - const sequenceNumber = - request.EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber; - console.log(`[Request ${request.requestId}] sequence : ${sequenceNumber}`); - results.push(await waitForCallback( - contract, - request.EntropyRequestResponse, - request.startTime, - request.requestId - )); - } - - // Calculate statistics - const successfulRequests = results.filter(r => r.success).length; - const failedRequests = numRequests - successfulRequests; - const successRate = (successfulRequests / numRequests) * 100; - - // Calculate average latency for successful requests - const successfulLatencies = results - .filter((r): r is { success: true; latency: number } => r.success && r.latency !== undefined) - .map(r => r.latency); - const avgLatency = successfulLatencies.length > 0 - ? successfulLatencies.reduce((a, b) => a + b, 0) / successfulLatencies.length - : 0; - - console.log("\n=== Test Results ==="); - console.log(`Total Requests : ${numRequests}`); - console.log(`Successful : ${successfulRequests}`); - console.log(`Failed : ${failedRequests}`); - console.log(`Success Rate : ${successRate.toFixed(2)}%`); - if (successfulLatencies.length > 0) { - console.log(`Average Latency : ${avgLatency.toFixed(2)}ms`); - } - console.log("==================="); -} - -async function main() { - const argv = await parser.argv; - if (!argv.chain && !argv["all-chains"]) { - throw new Error("Must specify either --chain or --all-chains"); - } - const privateKey = toPrivateKey(argv.privateKey); - if (argv["all-chains"]) { - for (const contract of Object.values(DefaultStore.entropy_contracts)) { - if ( - contract.getChain().isMainnet() === - (argv["all-chains"] === "mainnet") - ) { - console.log(`Testing latency for ${contract.getId()}...`); - await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); - } - } - } else if (argv.chain) { - const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain); - const contract = findEntropyContract(chain); - await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]); - } -} - -main();