Skip to content

Commit cc475a0

Browse files
committed
fix: don't reject on first iteration of waitForVMCommandExecutioncution
1 parent 2249f7f commit cc475a0

File tree

2 files changed

+115
-97
lines changed

2 files changed

+115
-97
lines changed

packages/backend/src/functions/circuit.ts

+110-97
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
uploadFileToBucket
5959
} from "../lib/utils"
6060
import { EC2Client } from "@aws-sdk/client-ec2"
61+
import { HttpsError } from "firebase-functions/v2/https"
6162

6263
dotenv.config()
6364

@@ -214,57 +215,71 @@ const coordinate = async (
214215
* Wait until the command has completed its execution inside the VM.
215216
* @dev this method implements a custom interval to check 5 times after 1 minute if the command execution
216217
* has been completed or not by calling the `retrieveCommandStatus` method.
217-
* @param {any} resolve the promise.
218-
* @param {any} reject the promise.
219218
* @param {SSMClient} ssm the SSM client.
220219
* @param {string} vmInstanceId the unique identifier of the VM instance.
221220
* @param {string} commandId the unique identifier of the VM command.
222221
* @returns <Promise<void>> true when the command execution succeed; otherwise false.
223222
*/
224-
const waitForVMCommandExecution = (
225-
resolve: any,
226-
reject: any,
227-
ssm: SSMClient,
228-
vmInstanceId: string,
229-
commandId: string
230-
) => {
231-
const interval = setInterval(async () => {
232-
try {
233-
// Get command status.
234-
const cmdStatus = await retrieveCommandStatus(ssm, vmInstanceId, commandId)
235-
printLog(`Checking command ${commandId} status => ${cmdStatus}`, LogLevel.DEBUG)
236-
237-
if (cmdStatus === CommandInvocationStatus.SUCCESS) {
238-
printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG)
239-
240-
// Resolve the promise.
241-
resolve()
242-
} else if (cmdStatus === CommandInvocationStatus.FAILED) {
243-
logAndThrowError(SPECIFIC_ERRORS.SE_VM_FAILED_COMMAND_EXECUTION)
244-
reject()
245-
} else if (cmdStatus === CommandInvocationStatus.TIMED_OUT) {
246-
logAndThrowError(SPECIFIC_ERRORS.SE_VM_TIMEDOUT_COMMAND_EXECUTION)
247-
reject()
248-
} else if (cmdStatus === CommandInvocationStatus.CANCELLED) {
249-
logAndThrowError(SPECIFIC_ERRORS.SE_VM_CANCELLED_COMMAND_EXECUTION)
250-
reject()
251-
} else if (cmdStatus === CommandInvocationStatus.DELAYED) {
252-
logAndThrowError(SPECIFIC_ERRORS.SE_VM_DELAYED_COMMAND_EXECUTION)
253-
reject()
254-
}
255-
} catch (error: any) {
256-
printLog(`Invalid command ${commandId} execution`, LogLevel.DEBUG)
223+
const waitForVMCommandExecution = (ssm: SSMClient, vmInstanceId: string, commandId: string): Promise<void> =>
224+
new Promise((resolve, reject) => {
225+
const poll = async () => {
226+
try {
227+
// Get command status.
228+
const cmdStatus = await retrieveCommandStatus(ssm, vmInstanceId, commandId)
229+
printLog(`Checking command ${commandId} status => ${cmdStatus}`, LogLevel.DEBUG)
230+
231+
let error: HttpsError | undefined
232+
switch (cmdStatus) {
233+
case CommandInvocationStatus.CANCELLING:
234+
case CommandInvocationStatus.CANCELLED: {
235+
error = SPECIFIC_ERRORS.SE_VM_CANCELLED_COMMAND_EXECUTION
236+
break
237+
}
238+
case CommandInvocationStatus.DELAYED: {
239+
error = SPECIFIC_ERRORS.SE_VM_DELAYED_COMMAND_EXECUTION
240+
break
241+
}
242+
case CommandInvocationStatus.FAILED: {
243+
error = SPECIFIC_ERRORS.SE_VM_FAILED_COMMAND_EXECUTION
244+
break
245+
}
246+
case CommandInvocationStatus.TIMED_OUT: {
247+
error = SPECIFIC_ERRORS.SE_VM_TIMEDOUT_COMMAND_EXECUTION
248+
break
249+
}
250+
case CommandInvocationStatus.IN_PROGRESS:
251+
case CommandInvocationStatus.PENDING: {
252+
// wait a minute and poll again
253+
setTimeout(poll, 60000)
254+
return
255+
}
256+
case CommandInvocationStatus.SUCCESS: {
257+
printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG)
258+
259+
// Resolve the promise.
260+
resolve()
261+
return
262+
}
263+
default: {
264+
logAndThrowError(SPECIFIC_ERRORS.SE_VM_UNKNOWN_COMMAND_STATUS)
265+
}
266+
}
257267

258-
if (!error.toString().includes(commandId)) logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
268+
if (error) {
269+
logAndThrowError(error)
270+
}
271+
} catch (error: any) {
272+
printLog(`Invalid command ${commandId} execution`, LogLevel.DEBUG)
273+
274+
if (!error.toString().includes(commandId)) logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
259275

260-
// Reject the promise.
261-
reject()
262-
} finally {
263-
// Clear the interval.
264-
clearInterval(interval)
276+
// Reject the promise.
277+
reject()
278+
}
265279
}
266-
}, 60000) // 1 minute.
267-
}
280+
281+
setTimeout(poll, 60000);
282+
})
268283

269284
/**
270285
* This method is used to coordinate the waiting queues of ceremony circuits.
@@ -783,9 +798,7 @@ export const verifycontribution = functionsV2.https.onCall(
783798
printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG)
784799

785800
// Step (1.A.3.3).
786-
return new Promise<void>((resolve, reject) =>
787-
waitForVMCommandExecution(resolve, reject, ssm, vmInstanceId, commandId)
788-
)
801+
return waitForVMCommandExecution(ssm, vmInstanceId, commandId)
789802
.then(async () => {
790803
// Command execution successfully completed.
791804
printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG)
@@ -797,59 +810,59 @@ export const verifycontribution = functionsV2.https.onCall(
797810

798811
logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
799812
})
800-
} else {
801-
// CF approach.
802-
printLog(`CF mechanism`, LogLevel.DEBUG)
813+
}
803814

804-
const potStoragePath = getPotStorageFilePath(files.potFilename)
805-
const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
806-
// Prepare temporary file paths.
807-
// (nb. these are needed to download the necessary artifacts for verification from AWS S3).
808-
verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
809-
verificationTranscriptCompleteFilename
810-
)
811-
const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
812-
const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
813-
const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
814-
815-
// Create and populate transcript.
816-
const transcriptLogger = createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath)
817-
transcriptLogger.info(
818-
`${
819-
isFinalizing ? `Final verification` : `Verification`
820-
} transcript for ${prefix} circuit Phase 2 contribution.\n${
821-
isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
822-
} (${contributorOrCoordinatorIdentifier})\n`
823-
)
815+
// CF approach.
816+
printLog(`CF mechanism`, LogLevel.DEBUG)
824817

825-
// Step (1.A.2).
826-
await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
827-
await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
828-
await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
829-
830-
// Step (1.A.4).
831-
isContributionValid = await zKey.verifyFromInit(
832-
firstZkeyTempFilePath,
833-
potTempFilePath,
834-
lastZkeyTempFilePath,
835-
transcriptLogger
836-
)
837-
838-
// Compute contribution hash.
839-
lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
840-
841-
// Free resources by unlinking temporary folders.
842-
// Do not free-up verification transcript path here.
843-
try {
844-
fs.unlinkSync(potTempFilePath)
845-
fs.unlinkSync(firstZkeyTempFilePath)
846-
fs.unlinkSync(lastZkeyTempFilePath)
847-
} catch (error: any) {
848-
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
849-
}
850-
851-
await completeVerification()
852-
}
818+
const potStoragePath = getPotStorageFilePath(files.potFilename)
819+
const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
820+
// Prepare temporary file paths.
821+
// (nb. these are needed to download the necessary artifacts for verification from AWS S3).
822+
verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
823+
verificationTranscriptCompleteFilename
824+
)
825+
const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
826+
const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
827+
const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
828+
829+
// Create and populate transcript.
830+
const transcriptLogger = createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath)
831+
transcriptLogger.info(
832+
`${
833+
isFinalizing ? `Final verification` : `Verification`
834+
} transcript for ${prefix} circuit Phase 2 contribution.\n${
835+
isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
836+
} (${contributorOrCoordinatorIdentifier})\n`
837+
)
838+
839+
// Step (1.A.2).
840+
await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
841+
await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
842+
await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
843+
844+
// Step (1.A.4).
845+
isContributionValid = await zKey.verifyFromInit(
846+
firstZkeyTempFilePath,
847+
potTempFilePath,
848+
lastZkeyTempFilePath,
849+
transcriptLogger
850+
)
851+
852+
// Compute contribution hash.
853+
lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
854+
855+
// Free resources by unlinking temporary folders.
856+
// Do not free-up verification transcript path here.
857+
try {
858+
fs.unlinkSync(potTempFilePath)
859+
fs.unlinkSync(firstZkeyTempFilePath)
860+
fs.unlinkSync(lastZkeyTempFilePath)
861+
} catch (error: any) {
862+
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
863+
}
864+
865+
await completeVerification()
853866
}
854867
}
855868
)

packages/backend/src/lib/errors.ts

+5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ export const SPECIFIC_ERRORS = {
184184
"unavailable",
185185
"VM command execution has been delayed since there were no available instance at the moment",
186186
"Please, contact the coordinator if this error persists."
187+
),
188+
SE_VM_UNKNOWN_COMMAND_STATUS: makeError(
189+
"unavailable",
190+
"VM command execution has failed due to an unknown status code",
191+
"Please, contact the coordinator if this error persists."
187192
)
188193
}
189194

0 commit comments

Comments
 (0)