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"]); } }