Skip to content

chore(contract-manager) Entropy stress test script #2588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 101 additions & 26 deletions contract_manager/scripts/latency_entropy_with_callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -28,37 +28,50 @@ 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();

// 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;
}
Expand All @@ -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() {
Expand All @@ -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"]);
}
}

Expand Down
Loading