Skip to content
Closed
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -502,22 +502,37 @@ export class EtherscanVerifyApiService implements WStorageService {
return;
}

let response: EtherscanRpcResponse;
try {
response = await this.submitVerification(verification, apiBaseUrl);
} catch (error: any) {
// Store the external verification result if we have job data
if (jobData) {
this.storeExternalVerificationResult(jobData, {
error: error.message,
});
}
logger.error("Failed to submit verification to explorer", {
...submissionContext,
error,
let response = await this.handleEtherscanApiVerification(
verification,
apiBaseUrl,
submissionContext,
jobData,
);

// Case in which Etherscan explorer hasn't indexed the contract yet
const maxRetries = 6;
const retryDelayMs = 5000;
let retries = 0;
while (
response.message.startsWith("Unable to locate ContractCode at") &&
retries < maxRetries
) {
retries += 1;
logger.info(
"Explorer has not indexed contract yet; retrying verification submission",
{
...submissionContext,
attempt: retries,
maxRetries,
},
);
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
response = await this.handleEtherscanApiVerification(
verification,
apiBaseUrl,
});
throw error;
submissionContext,
jobData,
);
}

logger.info("Submitted verification to explorer", {
Expand Down Expand Up @@ -609,6 +624,36 @@ export class EtherscanVerifyApiService implements WStorageService {
return (await response.json()) as EtherscanRpcResponse;
}

private async handleEtherscanApiVerification(
verification: VerificationExport,
apiBaseUrl: string,
submissionContext: {
identifier: string;
chainId: number;
address: string;
},
jobData?: {
verificationId: string;
finishTime: Date;
},
): Promise<EtherscanRpcResponse> {
try {
return await this.submitVerification(verification, apiBaseUrl);
} catch (error: any) {
if (jobData) {
this.storeExternalVerificationResult(jobData, {
error: error.message,
});
}
logger.error("Failed to submit verification to explorer", {
...submissionContext,
apiBaseUrl,
error,
});
throw error;
}
}

private async handleBlockscoutVyperVerification(
verification: VerificationExport,
apiBaseUrl: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,51 @@ describe("EtherscanVerifyApiService", function () {
},
);
});

it("retries submission when contract is not yet indexed", async () => {
const clock = sandbox.useFakeTimers();
const baseUrl = "https://etherscan.example/api";
const upsertStub = sandbox.stub().resolves();
const service = createService(
WStorageIdentifiers.EtherscanVerify,
baseUrl,
upsertStub,
);
const jobData = {
verificationId: "verification-job-id",
finishTime: new Date(),
};

const unindexedResponse = mockFetchResponse({
status: "0",
message: "Unable to locate ContractCode at 0xabc",
result: "N/A",
});
const successResponse = mockFetchResponse({
status: "1",
message: "OK",
result: "receipt-789",
});

fetchStub.onCall(0).resolves(unindexedResponse);
fetchStub.onCall(1).resolves(unindexedResponse);
fetchStub.onCall(2).resolves(successResponse);

const verification = structuredClone(MockVerificationExport);
const storePromise = service.storeVerification(verification, jobData);

await clock.tickAsync(10000);
await storePromise;

expect(fetchStub.callCount).to.equal(3);
expect(clock.now).to.equal(10000);
sinon.assert.calledOnceWithExactly(
upsertStub,
jobData.verificationId,
WStorageIdentifiers.EtherscanVerify,
{
verificationId: "receipt-789",
},
);
});
});