|
|
@@ -11,9 +11,12 @@ import yargs from "yargs";
|
|
|
import { hideBin } from "yargs/helpers";
|
|
|
import { z } from "zod";
|
|
|
|
|
|
+const DEFAULT_RETRIES = 3;
|
|
|
+
|
|
|
type LoadedConfig = {
|
|
|
contract: EvmEntropyContract;
|
|
|
interval: number;
|
|
|
+ retries: number;
|
|
|
};
|
|
|
|
|
|
function timeToSeconds(timeStr: string): number {
|
|
|
@@ -46,6 +49,7 @@ async function loadConfig(configPath: string): Promise<LoadedConfig[]> {
|
|
|
"chain-id": z.string(),
|
|
|
interval: z.string(),
|
|
|
"rpc-endpoint": z.string().optional(),
|
|
|
+ retries: z.number().default(DEFAULT_RETRIES),
|
|
|
}),
|
|
|
);
|
|
|
const configContent = (await import(configPath, {
|
|
|
@@ -78,7 +82,7 @@ async function loadConfig(configPath: string): Promise<LoadedConfig[]> {
|
|
|
evmChain.networkId,
|
|
|
);
|
|
|
}
|
|
|
- return { contract: firstContract, interval };
|
|
|
+ return { contract: firstContract, interval, retries: config.retries };
|
|
|
});
|
|
|
return loadedConfigs;
|
|
|
}
|
|
|
@@ -188,31 +192,63 @@ export const main = function () {
|
|
|
privateKeyFileContent.replace("0x", "").trimEnd(),
|
|
|
);
|
|
|
logger.info("Running");
|
|
|
- const promises = configs.map(async ({ contract, interval }) => {
|
|
|
- const child = logger.child({ chain: contract.chain.getId() });
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
|
- while (true) {
|
|
|
- try {
|
|
|
- await Promise.race([
|
|
|
- testLatency(contract, privateKey, child),
|
|
|
- new Promise((_, reject) =>
|
|
|
- setTimeout(() => {
|
|
|
- reject(
|
|
|
- new Error(
|
|
|
- "Timeout: 120s passed but testLatency function was not resolved",
|
|
|
- ),
|
|
|
+ const promises = configs.map(
|
|
|
+ async ({ contract, interval, retries }) => {
|
|
|
+ const child = logger.child({ chain: contract.chain.getId() });
|
|
|
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
|
+ while (true) {
|
|
|
+ let lastError: Error | undefined;
|
|
|
+ let success = false;
|
|
|
+
|
|
|
+ for (let attempt = 1; attempt <= retries; attempt++) {
|
|
|
+ try {
|
|
|
+ await Promise.race([
|
|
|
+ testLatency(contract, privateKey, child),
|
|
|
+ new Promise((_, reject) =>
|
|
|
+ setTimeout(() => {
|
|
|
+ reject(
|
|
|
+ new Error(
|
|
|
+ "Timeout: 120s passed but testLatency function was not resolved",
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }, 120_000),
|
|
|
+ ),
|
|
|
+ ]);
|
|
|
+ success = true;
|
|
|
+ break;
|
|
|
+ } catch (error) {
|
|
|
+ lastError = error as Error;
|
|
|
+ child.warn(
|
|
|
+ { attempt, maxRetries: retries, error: error },
|
|
|
+ `Attempt ${attempt.toString()}/${retries.toString()} failed, ${attempt < retries ? "retrying..." : "all retries exhausted"}`,
|
|
|
+ );
|
|
|
+
|
|
|
+ if (attempt < retries) {
|
|
|
+ // Wait a bit before retrying (exponential backoff, max 10s)
|
|
|
+ const backoffDelay = Math.min(
|
|
|
+ 2000 * Math.pow(2, attempt - 1),
|
|
|
+ 10_000,
|
|
|
+ );
|
|
|
+ await new Promise((resolve) =>
|
|
|
+ setTimeout(resolve, backoffDelay),
|
|
|
);
|
|
|
- }, 120_000),
|
|
|
- ),
|
|
|
- ]);
|
|
|
- } catch (error) {
|
|
|
- child.error(error, "Error testing latency");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!success && lastError) {
|
|
|
+ child.error(
|
|
|
+ { error: lastError, retriesExhausted: retries },
|
|
|
+ "All retries exhausted, callback was not called.",
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ await new Promise((resolve) =>
|
|
|
+ setTimeout(resolve, interval * 1000),
|
|
|
+ );
|
|
|
}
|
|
|
- await new Promise((resolve) =>
|
|
|
- setTimeout(resolve, interval * 1000),
|
|
|
- );
|
|
|
- }
|
|
|
- });
|
|
|
+ },
|
|
|
+ );
|
|
|
await Promise.all(promises);
|
|
|
},
|
|
|
)
|