|
|
@@ -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 <private-key> --chain <chain-id> | --all-chains <testnet|mainnet>",
|
|
|
+ "Usage: $0 --private-key <private-key> --chain <chain-id> | --all-chains <testnet|mainnet> --nrequests <number> --delay <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"]);
|
|
|
}
|
|
|
}
|
|
|
|