Skip to content

Commit bde7b66

Browse files
committed
chore(contract-manager) Entropy stress test script
1 parent 0863181 commit bde7b66

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import yargs from "yargs";
2+
import { hideBin } from "yargs/helpers";
3+
import {
4+
DefaultStore,
5+
EvmChain,
6+
EvmEntropyContract,
7+
PrivateKey,
8+
toPrivateKey,
9+
} from "../src";
10+
import { COMMON_DEPLOY_OPTIONS, findEntropyContract } from "./common";
11+
12+
const parser = yargs(hideBin(process.argv))
13+
.usage(
14+
"Requests random numbers from an entropy contract and measures the\n" +
15+
"latency between request submission and fulfillment by the Fortuna keeper service.\n" +
16+
"Usage: $0 --private-key <private-key> --chain <chain-id> | --all-chains <testnet|mainnet> --nrequests <number> --delay <delay>",
17+
)
18+
.options({
19+
chain: {
20+
type: "string",
21+
desc: "test latency for the contract on this chain",
22+
conflicts: "all-chains",
23+
},
24+
"all-chains": {
25+
type: "string",
26+
conflicts: "chain",
27+
choices: ["testnet", "mainnet"],
28+
desc: "test latency for all entropy contracts deployed either on mainnet or testnet",
29+
},
30+
"private-key": COMMON_DEPLOY_OPTIONS["private-key"],
31+
"nrequests": {
32+
type: "number",
33+
desc: "number of requests to make",
34+
default: 1,
35+
},
36+
"delay": {
37+
type: "number",
38+
desc: "delay between requests",
39+
default: 25,
40+
},
41+
});
42+
43+
async function sendRequest(
44+
contract: EvmEntropyContract,
45+
privateKey: PrivateKey,
46+
requestId: number,
47+
): Promise<{ EntropyRequestResponse: any; startTime: number }> {
48+
const fortunaProvider = await contract.getDefaultProvider();
49+
const userRandomNumber = contract.generateUserRandomNumber();
50+
const EntropyRequestResponse = await contract.requestRandomness(
51+
userRandomNumber,
52+
fortunaProvider,
53+
privateKey,
54+
true, // with callback
55+
);
56+
const startTime = Date.now();
57+
console.log(`[Request ${requestId}] Request tx hash : ${EntropyRequestResponse.transactionHash}`);
58+
return { EntropyRequestResponse: EntropyRequestResponse, startTime: startTime };
59+
}
60+
61+
async function waitForCallback(
62+
contract: EvmEntropyContract,
63+
EntropyRequestResponse: any,
64+
startTime: number,
65+
requestId: number,
66+
): Promise<{ success: boolean; latency?: number }> {
67+
const fromBlock = EntropyRequestResponse.blockNumber;
68+
const web3 = contract.chain.getWeb3();
69+
const entropyContract = contract.getContract();
70+
71+
// eslint-disable-next-line no-constant-condition
72+
while (true) {
73+
await new Promise((resolve) => setTimeout(resolve, 1000));
74+
const currentBlock = await web3.eth.getBlockNumber();
75+
if (fromBlock > currentBlock) {
76+
continue;
77+
}
78+
79+
const events = await entropyContract.getPastEvents("RevealedWithCallback", {
80+
fromBlock: fromBlock,
81+
toBlock: currentBlock,
82+
});
83+
84+
const event = events.find(
85+
(event) => event.returnValues.request[1] == EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber,
86+
);
87+
88+
if (event !== undefined) {
89+
console.log(`[Request ${requestId}] Random number : ${event.returnValues.randomNumber}`);
90+
const eventBlockTimestamp = Number(await web3.eth.getBlock(event.blockNumber).then(block => block.timestamp));
91+
const entropyRequestBlockTimestamp = Number(await web3.eth.getBlock(EntropyRequestResponse.blockNumber).then(block => block.timestamp));
92+
const latency = eventBlockTimestamp - entropyRequestBlockTimestamp;
93+
console.log(`[Request ${requestId}] Fortuna Latency : ${latency}ms`);
94+
console.log(
95+
`[Request ${requestId}] Revealed after : ${
96+
event.blockNumber - EntropyRequestResponse.blockNumber
97+
} blocks`,
98+
);
99+
return { success: true, latency };
100+
}
101+
if (Date.now() - startTime > 60000) {
102+
console.log(`[Request ${requestId}] Timeout: 60s passed without the callback being called.`);
103+
return { success: false };
104+
}
105+
}
106+
}
107+
108+
async function testParallelLatency(
109+
contract: EvmEntropyContract,
110+
privateKey: PrivateKey,
111+
numRequests: number,
112+
delay: number,
113+
) {
114+
console.log(`Starting ${numRequests} requests...`);
115+
116+
// First send all requests
117+
const requests: { EntropyRequestResponse: any; startTime: number; requestId: number }[] = [];
118+
for (let i = 0; i < numRequests; i++) {
119+
if (i > 0) {
120+
await new Promise(resolve => setTimeout(resolve, delay));
121+
}
122+
const { EntropyRequestResponse, startTime } = await sendRequest(contract, privateKey, i + 1);
123+
requests.push({ EntropyRequestResponse, startTime, requestId: i + 1 });
124+
}
125+
126+
127+
128+
129+
// Then wait for all callbacks
130+
// The response time won't be accurate here.
131+
const results: { success: boolean; latency?: number }[] = [];
132+
for (const request of requests) {
133+
const sequenceNumber =
134+
request.EntropyRequestResponse.events.RequestedWithCallback.returnValues.sequenceNumber;
135+
console.log(`[Request ${request.requestId}] sequence : ${sequenceNumber}`);
136+
results.push(await waitForCallback(
137+
contract,
138+
request.EntropyRequestResponse,
139+
request.startTime,
140+
request.requestId
141+
));
142+
}
143+
144+
// Calculate statistics
145+
const successfulRequests = results.filter(r => r.success).length;
146+
const failedRequests = numRequests - successfulRequests;
147+
const successRate = (successfulRequests / numRequests) * 100;
148+
149+
// Calculate average latency for successful requests
150+
const successfulLatencies = results
151+
.filter((r): r is { success: true; latency: number } => r.success && r.latency !== undefined)
152+
.map(r => r.latency);
153+
const avgLatency = successfulLatencies.length > 0
154+
? successfulLatencies.reduce((a, b) => a + b, 0) / successfulLatencies.length
155+
: 0;
156+
157+
console.log("\n=== Test Results ===");
158+
console.log(`Total Requests : ${numRequests}`);
159+
console.log(`Successful : ${successfulRequests}`);
160+
console.log(`Failed : ${failedRequests}`);
161+
console.log(`Success Rate : ${successRate.toFixed(2)}%`);
162+
if (successfulLatencies.length > 0) {
163+
console.log(`Average Latency : ${avgLatency.toFixed(2)}ms`);
164+
}
165+
console.log("===================");
166+
}
167+
168+
async function main() {
169+
const argv = await parser.argv;
170+
if (!argv.chain && !argv["all-chains"]) {
171+
throw new Error("Must specify either --chain or --all-chains");
172+
}
173+
const privateKey = toPrivateKey(argv.privateKey);
174+
if (argv["all-chains"]) {
175+
for (const contract of Object.values(DefaultStore.entropy_contracts)) {
176+
if (
177+
contract.getChain().isMainnet() ===
178+
(argv["all-chains"] === "mainnet")
179+
) {
180+
console.log(`Testing latency for ${contract.getId()}...`);
181+
await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]);
182+
}
183+
}
184+
} else if (argv.chain) {
185+
const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain);
186+
const contract = findEntropyContract(chain);
187+
await testParallelLatency(contract, privateKey, argv["nrequests"], argv["delay"]);
188+
}
189+
}
190+
191+
main();

0 commit comments

Comments
 (0)