@devtion/backend 0.0.0-92056fa → 0.0.0-9239207

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +7 -7
  2. package/dist/src/functions/index.js +626 -337
  3. package/dist/src/functions/index.mjs +629 -342
  4. package/dist/types/functions/bandada.d.ts +4 -0
  5. package/dist/types/functions/bandada.d.ts.map +1 -0
  6. package/dist/types/functions/ceremony.d.ts.map +1 -1
  7. package/dist/types/functions/circuit.d.ts.map +1 -1
  8. package/dist/types/functions/index.d.ts +2 -0
  9. package/dist/types/functions/index.d.ts.map +1 -1
  10. package/dist/types/functions/siwe.d.ts +4 -0
  11. package/dist/types/functions/siwe.d.ts.map +1 -0
  12. package/dist/types/functions/storage.d.ts.map +1 -1
  13. package/dist/types/functions/timeout.d.ts.map +1 -1
  14. package/dist/types/functions/user.d.ts.map +1 -1
  15. package/dist/types/lib/errors.d.ts +2 -1
  16. package/dist/types/lib/errors.d.ts.map +1 -1
  17. package/dist/types/lib/services.d.ts +7 -0
  18. package/dist/types/lib/services.d.ts.map +1 -1
  19. package/dist/types/lib/utils.d.ts.map +1 -1
  20. package/dist/types/types/index.d.ts +56 -0
  21. package/dist/types/types/index.d.ts.map +1 -1
  22. package/package.json +4 -3
  23. package/src/functions/bandada.ts +155 -0
  24. package/src/functions/ceremony.ts +12 -7
  25. package/src/functions/circuit.ts +408 -382
  26. package/src/functions/index.ts +2 -0
  27. package/src/functions/participant.ts +15 -15
  28. package/src/functions/siwe.ts +77 -0
  29. package/src/functions/storage.ts +11 -8
  30. package/src/functions/timeout.ts +7 -5
  31. package/src/functions/user.ts +22 -12
  32. package/src/lib/errors.ts +6 -1
  33. package/src/lib/services.ts +36 -0
  34. package/src/lib/utils.ts +10 -8
  35. package/src/types/declarations.d.ts +1 -0
  36. package/src/types/index.ts +60 -0
@@ -40,6 +40,8 @@ import {
40
40
  } from "@devtion/actions"
41
41
  import { zKey } from "snarkjs"
42
42
  import { CommandInvocationStatus, SSMClient } from "@aws-sdk/client-ssm"
43
+ import { EC2Client } from "@aws-sdk/client-ec2"
44
+ import { HttpsError } from "firebase-functions/v2/https"
43
45
  import { FinalizeCircuitData, VerifyContributionData } from "../types/index"
44
46
  import { LogLevel } from "../types/enums"
45
47
  import { COMMON_ERRORS, logAndThrowError, makeError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
@@ -57,7 +59,6 @@ import {
57
59
  sleep,
58
60
  uploadFileToBucket
59
61
  } from "../lib/utils"
60
- import { EC2Client } from "@aws-sdk/client-ec2"
61
62
 
62
63
  dotenv.config()
63
64
 
@@ -214,57 +215,80 @@ const coordinate = async (
214
215
  * Wait until the command has completed its execution inside the VM.
215
216
  * @dev this method implements a custom interval to check 5 times after 1 minute if the command execution
216
217
  * has been completed or not by calling the `retrieveCommandStatus` method.
217
- * @param {any} resolve the promise.
218
- * @param {any} reject the promise.
219
218
  * @param {SSMClient} ssm the SSM client.
220
219
  * @param {string} vmInstanceId the unique identifier of the VM instance.
221
220
  * @param {string} commandId the unique identifier of the VM command.
222
221
  * @returns <Promise<void>> true when the command execution succeed; otherwise false.
223
222
  */
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)
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
+ }
236
267
 
237
- if (cmdStatus === CommandInvocationStatus.SUCCESS) {
238
- printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG)
268
+ if (error) {
269
+ logAndThrowError(error)
270
+ }
271
+ } catch (error: any) {
272
+ printLog(`Invalid command ${commandId} execution`, LogLevel.DEBUG)
239
273
 
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)
274
+ const ec2 = await createEC2Client()
257
275
 
258
- if (!error.toString().includes(commandId)) logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
276
+ // if it errors out, let's just log it as a warning so the coordinator is aware
277
+ try {
278
+ await stopEC2Instance(ec2, vmInstanceId)
279
+ } catch (error: any) {
280
+ printLog(`Error while stopping VM instance ${vmInstanceId} - Error ${error}`, LogLevel.WARN)
281
+ }
259
282
 
260
- // Reject the promise.
261
- reject()
262
- } finally {
263
- // Clear the interval.
264
- clearInterval(interval)
283
+ if (!error.toString().includes(commandId)) logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
284
+
285
+ // Reject the promise.
286
+ reject()
287
+ }
265
288
  }
266
- }, 60000) // 1 minute.
267
- }
289
+
290
+ setTimeout(poll, 60000)
291
+ })
268
292
 
269
293
  /**
270
294
  * This method is used to coordinate the waiting queues of ceremony circuits.
@@ -286,9 +310,9 @@ const waitForVMCommandExecution = (
286
310
  * - Just completed a contribution or all contributions for each circuit. If yes, coordinate (multi-participant scenario).
287
311
  */
288
312
  export const coordinateCeremonyParticipant = functionsV1
289
- .region('europe-west1')
313
+ .region("europe-west1")
290
314
  .runWith({
291
- memory: "512MB"
315
+ memory: "1GB"
292
316
  })
293
317
  .firestore.document(
294
318
  `${commonTerms.collections.ceremonies.name}/{ceremonyId}/${commonTerms.collections.participants.name}/{participantId}`
@@ -387,7 +411,6 @@ export const coordinateCeremonyParticipant = functionsV1
387
411
  }
388
412
  })
389
413
 
390
-
391
414
  /**
392
415
  * Recursive function to check whether an EC2 is in a running state
393
416
  * @notice required step to run commands
@@ -396,23 +419,18 @@ export const coordinateCeremonyParticipant = functionsV1
396
419
  * @param attempts <number> - how many times to retry before failing
397
420
  * @returns <Promise<boolean>> - whether the VM was started
398
421
  */
399
- const checkIfVMRunning = async (
400
- ec2: EC2Client,
401
- vmInstanceId: string,
402
- attempts = 5
403
- ): Promise<boolean> => {
422
+ const checkIfVMRunning = async (ec2: EC2Client, vmInstanceId: string, attempts = 5): Promise<boolean> => {
404
423
  // if we tried 5 times, then throw an error
405
424
  if (attempts <= 0) logAndThrowError(SPECIFIC_ERRORS.SE_VM_NOT_RUNNING)
406
425
 
407
- await sleep(60000); // Wait for 1 min
408
- const isVMRunning = await checkIfRunning(ec2, vmInstanceId)
426
+ await sleep(60000) // Wait for 1 min
427
+ const isVMRunning = await checkIfRunning(ec2, vmInstanceId)
409
428
 
410
429
  if (!isVMRunning) {
411
430
  printLog(`VM not running, ${attempts - 1} attempts remaining. Retrying in 1 minute...`, LogLevel.DEBUG)
412
- return await checkIfVMRunning(ec2, vmInstanceId, attempts - 1)
413
- } else {
414
- return true
431
+ return checkIfVMRunning(ec2, vmInstanceId, attempts - 1)
415
432
  }
433
+ return true
416
434
  }
417
435
 
418
436
  /**
@@ -442,361 +460,369 @@ const checkIfVMRunning = async (
442
460
  * 2) Send all updates atomically to the Firestore database.
443
461
  */
444
462
  export const verifycontribution = functionsV2.https.onCall(
445
- { memory: "16GiB", timeoutSeconds: 3600, region: 'europe-west1' },
463
+ { memory: "16GiB", timeoutSeconds: 3600, region: "europe-west1" },
446
464
  async (request: functionsV2.https.CallableRequest<VerifyContributionData>): Promise<any> => {
447
- if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
448
- logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
449
-
450
- if (
451
- !request.data.ceremonyId ||
452
- !request.data.circuitId ||
453
- !request.data.contributorOrCoordinatorIdentifier ||
454
- !request.data.bucketName
455
- )
456
- logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
457
-
458
- if (
459
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME ||
460
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION ||
461
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH
462
- )
463
- logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
464
-
465
- // Step (0).
466
-
467
- // Prepare and start timer.
468
- const verifyContributionTimer = new Timer({ label: commonTerms.cloudFunctionsNames.verifyContribution })
469
- verifyContributionTimer.start()
470
-
471
- // Get DB.
472
- const firestore = admin.firestore()
473
- // Prepare batch of txs.
474
- const batch = firestore.batch()
475
-
476
- // Extract data.
477
- const { ceremonyId, circuitId, contributorOrCoordinatorIdentifier, bucketName } = request.data
478
- const userId = request.auth?.uid
479
-
480
- // Look for the ceremony, circuit and participant document.
481
- const ceremonyDoc = await getDocumentById(commonTerms.collections.ceremonies.name, ceremonyId)
482
- const circuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
483
- const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), userId!)
484
-
485
- if (!ceremonyDoc.data() || !circuitDoc.data() || !participantDoc.data())
486
- logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)
465
+ try {
466
+ if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
467
+ logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
468
+
469
+ if (
470
+ !request.data.ceremonyId ||
471
+ !request.data.circuitId ||
472
+ !request.data.contributorOrCoordinatorIdentifier ||
473
+ !request.data.bucketName
474
+ )
475
+ logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
487
476
 
488
- // Extract documents data.
489
- const { state } = ceremonyDoc.data()!
490
- const { status, contributions, verificationStartedAt, contributionStartedAt } = participantDoc.data()!
491
- const { waitingQueue, prefix, avgTimings, verification, files } = circuitDoc.data()!
492
- const { completedContributions, failedContributions } = waitingQueue
493
- const {
494
- contributionComputation: avgContributionComputationTime,
495
- fullContribution: avgFullContributionTime,
496
- verifyCloudFunction: avgVerifyCloudFunctionTime
497
- } = avgTimings
498
- const { cfOrVm, vm } = verification
499
- // we might not have it if the circuit is not using VM.
500
- let vmInstanceId: string = ""
501
- if (vm) vmInstanceId = vm.vmInstanceId
477
+ if (
478
+ !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME ||
479
+ !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION ||
480
+ !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH
481
+ )
482
+ logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
483
+
484
+ // Step (0).
485
+
486
+ // Prepare and start timer.
487
+ const verifyContributionTimer = new Timer({ label: commonTerms.cloudFunctionsNames.verifyContribution })
488
+ verifyContributionTimer.start()
489
+
490
+ // Get DB.
491
+ const firestore = admin.firestore()
492
+ // Prepare batch of txs.
493
+ const batch = firestore.batch()
494
+
495
+ // Extract data.
496
+ const { ceremonyId, circuitId, contributorOrCoordinatorIdentifier, bucketName } = request.data
497
+ const userId = request.auth?.uid
498
+
499
+ // Look for the ceremony, circuit and participant document.
500
+ const ceremonyDoc = await getDocumentById(commonTerms.collections.ceremonies.name, ceremonyId)
501
+ const circuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
502
+ const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), userId!)
503
+
504
+ if (!ceremonyDoc.data() || !circuitDoc.data() || !participantDoc.data())
505
+ logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)
506
+
507
+ // Extract documents data.
508
+ const { state } = ceremonyDoc.data()!
509
+ const { status, contributions, verificationStartedAt, contributionStartedAt } = participantDoc.data()!
510
+ const { waitingQueue, prefix, avgTimings, verification, files } = circuitDoc.data()!
511
+ const { completedContributions, failedContributions } = waitingQueue
512
+ const {
513
+ contributionComputation: avgContributionComputationTime,
514
+ fullContribution: avgFullContributionTime,
515
+ verifyCloudFunction: avgVerifyCloudFunctionTime
516
+ } = avgTimings
517
+ const { cfOrVm, vm } = verification
518
+ // we might not have it if the circuit is not using VM.
519
+ let vmInstanceId: string = ""
520
+ if (vm) vmInstanceId = vm.vmInstanceId
502
521
 
503
- // Define pre-conditions.
504
- const isFinalizing = state === CeremonyState.CLOSED && request.auth && request.auth.token.coordinator // true only when the coordinator verifies the final contributions.
505
- const isContributing = status === ParticipantStatus.CONTRIBUTING
506
- const isUsingVM = cfOrVm === CircuitContributionVerificationMechanism.VM && !!vmInstanceId
507
-
508
- // Prepare state.
509
- let isContributionValid = false
510
- let verifyCloudFunctionExecutionTime = 0 // time spent while executing the verify contribution cloud function.
511
- let verifyCloudFunctionTime = 0 // time spent while executing the core business logic of this cloud function.
512
- let fullContributionTime = 0 // time spent while doing non-verification contributions tasks (download, compute, upload).
513
- let contributionComputationTime = 0 // time spent while computing the contribution.
514
- let lastZkeyBlake2bHash: string = "" // the Blake2B hash of the last zKey.
515
- let verificationTranscriptTemporaryLocalPath: string = "" // the local temporary path for the verification transcript.
516
- let transcriptBlake2bHash: string = "" // the Blake2B hash of the verification transcript.
517
- let commandId: string = "" // the unique identifier of the VM command.
518
-
519
- // Derive necessary data.
520
- const lastZkeyIndex = formatZkeyIndex(completedContributions + 1)
521
- const verificationTranscriptCompleteFilename = `${prefix}_${
522
- isFinalizing
523
- ? `${contributorOrCoordinatorIdentifier}_${finalContributionIndex}_verification_transcript.log`
524
- : `${lastZkeyIndex}_${contributorOrCoordinatorIdentifier}_verification_transcript.log`
525
- }`
526
-
527
- const lastZkeyFilename = `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
528
-
529
- // Prepare state for VM verification (if needed).
530
- const ec2 = await createEC2Client()
531
- const ssm = await createSSMClient()
532
-
533
- // Step (1.A.1).
534
- // Get storage paths.
535
- const verificationTranscriptStoragePathAndFilename = getTranscriptStorageFilePath(
536
- prefix,
537
- verificationTranscriptCompleteFilename
538
- )
539
- // the zKey storage path is required to be sent to the VM api
540
- const lastZkeyStoragePath = getZkeyStorageFilePath(
541
- prefix,
542
- `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
543
- )
522
+ // Define pre-conditions.
523
+ const isFinalizing = state === CeremonyState.CLOSED && request.auth && request.auth.token.coordinator // true only when the coordinator verifies the final contributions.
524
+ const isContributing = status === ParticipantStatus.CONTRIBUTING
525
+ const isUsingVM = cfOrVm === CircuitContributionVerificationMechanism.VM && !!vmInstanceId
526
+
527
+ // Prepare state.
528
+ let isContributionValid = false
529
+ let verifyCloudFunctionExecutionTime = 0 // time spent while executing the verify contribution cloud function.
530
+ let verifyCloudFunctionTime = 0 // time spent while executing the core business logic of this cloud function.
531
+ let fullContributionTime = 0 // time spent while doing non-verification contributions tasks (download, compute, upload).
532
+ let contributionComputationTime = 0 // time spent while computing the contribution.
533
+ let lastZkeyBlake2bHash: string = "" // the Blake2B hash of the last zKey.
534
+ let verificationTranscriptTemporaryLocalPath: string = "" // the local temporary path for the verification transcript.
535
+ let transcriptBlake2bHash: string = "" // the Blake2B hash of the verification transcript.
536
+ let commandId: string = "" // the unique identifier of the VM command.
537
+
538
+ // Derive necessary data.
539
+ const lastZkeyIndex = formatZkeyIndex(completedContributions + 1)
540
+ const verificationTranscriptCompleteFilename = `${prefix}_${
541
+ isFinalizing
542
+ ? `${contributorOrCoordinatorIdentifier}_${finalContributionIndex}_verification_transcript.log`
543
+ : `${lastZkeyIndex}_${contributorOrCoordinatorIdentifier}_verification_transcript.log`
544
+ }`
545
+
546
+ const lastZkeyFilename = `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
547
+
548
+ // Prepare state for VM verification (if needed).
549
+ const ec2 = await createEC2Client()
550
+ const ssm = await createSSMClient()
551
+
552
+ // Step (1.A.1).
553
+ // Get storage paths.
554
+ const verificationTranscriptStoragePathAndFilename = getTranscriptStorageFilePath(
555
+ prefix,
556
+ verificationTranscriptCompleteFilename
557
+ )
558
+ // the zKey storage path is required to be sent to the VM api
559
+ const lastZkeyStoragePath = getZkeyStorageFilePath(
560
+ prefix,
561
+ `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
562
+ )
544
563
 
545
- const verificationTaskTimer = new Timer({ label: `${ceremonyId}-${circuitId}-${participantDoc.id}` })
564
+ const verificationTaskTimer = new Timer({ label: `${ceremonyId}-${circuitId}-${participantDoc.id}` })
546
565
 
547
- const completeVerification = async () => {
548
- // Stop verification task timer.
549
- printLog("Completing verification", LogLevel.DEBUG)
550
- verificationTaskTimer.stop()
551
- verifyCloudFunctionExecutionTime = verificationTaskTimer.ms()
566
+ const completeVerification = async () => {
567
+ // Stop verification task timer.
568
+ printLog("Completing verification", LogLevel.DEBUG)
569
+ verificationTaskTimer.stop()
570
+ verifyCloudFunctionExecutionTime = verificationTaskTimer.ms()
552
571
 
553
- if (isUsingVM) {
554
- // Create temporary path.
555
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
556
- `${circuitId}_${participantDoc.id}.log`
557
- )
572
+ if (isUsingVM) {
573
+ // Create temporary path.
574
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
575
+ `${circuitId}_${participantDoc.id}.log`
576
+ )
558
577
 
559
- await sleep(1000) // wait 1s for file creation.
578
+ await sleep(1000) // wait 1s for file creation.
560
579
 
561
- // Download from bucket.
562
- // nb. the transcript MUST be uploaded from the VM by verification commands.
563
- await downloadArtifactFromS3Bucket(
564
- bucketName,
565
- verificationTranscriptStoragePathAndFilename,
566
- verificationTranscriptTemporaryLocalPath
567
- )
580
+ // Download from bucket.
581
+ // nb. the transcript MUST be uploaded from the VM by verification commands.
582
+ await downloadArtifactFromS3Bucket(
583
+ bucketName,
584
+ verificationTranscriptStoragePathAndFilename,
585
+ verificationTranscriptTemporaryLocalPath
586
+ )
568
587
 
569
- // Read the verification trascript and validate data by checking for core info ("ZKey Ok!").
570
- const content = fs.readFileSync(verificationTranscriptTemporaryLocalPath, "utf-8")
588
+ // Read the verification trascript and validate data by checking for core info ("ZKey Ok!").
589
+ const content = fs.readFileSync(verificationTranscriptTemporaryLocalPath, "utf-8")
571
590
 
572
- if (content.includes("ZKey Ok!")) isContributionValid = true
591
+ if (content.includes("ZKey Ok!")) isContributionValid = true
573
592
 
574
- // If the contribution is valid, then format and store the trascript.
575
- if (isContributionValid) {
576
- // eslint-disable-next-line no-control-regex
577
- const updated = content.replace(/\x1b[[0-9;]*m/g, "")
593
+ // If the contribution is valid, then format and store the trascript.
594
+ if (isContributionValid) {
595
+ // eslint-disable-next-line no-control-regex
596
+ const updated = content.replace(/\x1b[[0-9;]*m/g, "")
578
597
 
579
- fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
598
+ fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
599
+ }
580
600
  }
581
- }
582
-
583
- printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
584
601
 
585
- // Create a new contribution document.
586
- const contributionDoc = await firestore
587
- .collection(getContributionsCollectionPath(ceremonyId, circuitId))
588
- .doc()
589
- .get()
590
-
591
- // Step (1.A.4).
592
- if (isContributionValid) {
593
- // Sleep ~3 seconds to wait for verification transcription.
594
- await sleep(3000)
595
-
596
- // Step (1.A.4.A.1).
597
- if (isUsingVM) {
598
- // Retrieve the contribution hash from the command output.
599
- lastZkeyBlake2bHash = await retrieveCommandOutput(ssm, vmInstanceId, commandId)
602
+ printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
600
603
 
601
- const hashRegex = /[a-fA-F0-9]{64}/
602
- const match = lastZkeyBlake2bHash.match(hashRegex)!
604
+ // Create a new contribution document.
605
+ const contributionDoc = await firestore
606
+ .collection(getContributionsCollectionPath(ceremonyId, circuitId))
607
+ .doc()
608
+ .get()
603
609
 
604
- lastZkeyBlake2bHash = match.at(0)!
605
-
606
- // re upload the formatted verification transcript
607
- await uploadFileToBucket(
608
- bucketName,
609
- verificationTranscriptStoragePathAndFilename,
610
- verificationTranscriptTemporaryLocalPath,
611
- true
610
+ // Step (1.A.4).
611
+ if (isContributionValid) {
612
+ // Sleep ~3 seconds to wait for verification transcription.
613
+ await sleep(3000)
614
+
615
+ // Step (1.A.4.A.1).
616
+ if (isUsingVM) {
617
+ // Retrieve the contribution hash from the command output.
618
+ lastZkeyBlake2bHash = await retrieveCommandOutput(ssm, vmInstanceId, commandId)
619
+
620
+ const hashRegex = /[a-fA-F0-9]{64}/
621
+ const match = lastZkeyBlake2bHash.match(hashRegex)!
622
+
623
+ lastZkeyBlake2bHash = match.at(0)!
624
+
625
+ // re upload the formatted verification transcript
626
+ await uploadFileToBucket(
627
+ bucketName,
628
+ verificationTranscriptStoragePathAndFilename,
629
+ verificationTranscriptTemporaryLocalPath,
630
+ true
631
+ )
632
+ } else {
633
+ // Upload verification transcript.
634
+ /// nb. do not use multi-part upload here due to small file size.
635
+ await uploadFileToBucket(
636
+ bucketName,
637
+ verificationTranscriptStoragePathAndFilename,
638
+ verificationTranscriptTemporaryLocalPath,
639
+ true
640
+ )
641
+ }
642
+
643
+ // Compute verification transcript hash.
644
+ transcriptBlake2bHash = await blake512FromPath(verificationTranscriptTemporaryLocalPath)
645
+
646
+ // Free resources by unlinking transcript temporary file.
647
+ fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
648
+
649
+ // Filter participant contributions to find the data related to the one verified.
650
+ const participantContributions = contributions.filter(
651
+ (contribution: Contribution) =>
652
+ !!contribution.hash && !!contribution.computationTime && !contribution.doc
612
653
  )
613
654
 
614
- // Stop VM instance.
615
- await stopEC2Instance(ec2, vmInstanceId)
655
+ /// @dev (there must be only one contribution with an empty 'doc' field).
656
+ if (participantContributions.length !== 1)
657
+ logAndThrowError(SPECIFIC_ERRORS.SE_VERIFICATION_NO_PARTICIPANT_CONTRIBUTION_DATA)
658
+
659
+ // Get contribution computation time.
660
+ contributionComputationTime = contributions.at(0).computationTime
661
+
662
+ // Step (1.A.4.A.2).
663
+ batch.create(contributionDoc.ref, {
664
+ participantId: participantDoc.id,
665
+ contributionComputationTime,
666
+ verificationComputationTime: verifyCloudFunctionExecutionTime,
667
+ zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
668
+ files: {
669
+ transcriptFilename: verificationTranscriptCompleteFilename,
670
+ lastZkeyFilename,
671
+ transcriptStoragePath: verificationTranscriptStoragePathAndFilename,
672
+ lastZkeyStoragePath,
673
+ transcriptBlake2bHash,
674
+ lastZkeyBlake2bHash
675
+ },
676
+ verificationSoftware: {
677
+ name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
678
+ version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
679
+ commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
680
+ },
681
+ valid: isContributionValid,
682
+ lastUpdated: getCurrentServerTimestampInMillis()
683
+ })
684
+
685
+ verifyContributionTimer.stop()
686
+ verifyCloudFunctionTime = verifyContributionTimer.ms()
616
687
  } else {
617
- // Upload verification transcript.
618
- /// nb. do not use multi-part upload here due to small file size.
619
- await uploadFileToBucket(
620
- bucketName,
621
- verificationTranscriptStoragePathAndFilename,
622
- verificationTranscriptTemporaryLocalPath,
623
- true
624
- )
688
+ // Step (1.A.4.B).
689
+
690
+ // Free-up storage by deleting invalid contribution.
691
+ await deleteObject(bucketName, lastZkeyStoragePath)
692
+
693
+ // Step (1.A.4.B.1).
694
+ batch.create(contributionDoc.ref, {
695
+ participantId: participantDoc.id,
696
+ verificationComputationTime: verifyCloudFunctionExecutionTime,
697
+ zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
698
+ verificationSoftware: {
699
+ name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
700
+ version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
701
+ commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
702
+ },
703
+ valid: isContributionValid,
704
+ lastUpdated: getCurrentServerTimestampInMillis()
705
+ })
625
706
  }
626
707
 
627
- // Compute verification transcript hash.
628
- transcriptBlake2bHash = await blake512FromPath(verificationTranscriptTemporaryLocalPath)
708
+ // Stop VM instance
709
+ if (isUsingVM) {
710
+ // using try and catch as the VM stopping function can throw
711
+ // however we want to continue without stopping as the
712
+ // verification was valid, and inform the coordinator
713
+ try {
714
+ await stopEC2Instance(ec2, vmInstanceId)
715
+ } catch (error: any) {
716
+ printLog(`Error while stopping VM instance ${vmInstanceId} - Error ${error}`, LogLevel.WARN)
717
+ }
718
+ }
719
+ // Step (1.A.4.C)
720
+ if (!isFinalizing) {
721
+ // Step (1.A.4.C.1)
722
+ // Compute new average contribution/verification time.
723
+ fullContributionTime = Number(verificationStartedAt) - Number(contributionStartedAt)
724
+
725
+ const newAvgContributionComputationTime =
726
+ avgContributionComputationTime > 0
727
+ ? (avgContributionComputationTime + contributionComputationTime) / 2
728
+ : contributionComputationTime
729
+ const newAvgFullContributionTime =
730
+ avgFullContributionTime > 0
731
+ ? (avgFullContributionTime + fullContributionTime) / 2
732
+ : fullContributionTime
733
+ const newAvgVerifyCloudFunctionTime =
734
+ avgVerifyCloudFunctionTime > 0
735
+ ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
736
+ : verifyCloudFunctionTime
737
+
738
+ // Prepare tx to update circuit average contribution/verification time.
739
+ const updatedCircuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
740
+ const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data()!
741
+ /// @dev this must happen only for valid contributions.
742
+ batch.update(circuitDoc.ref, {
743
+ avgTimings: {
744
+ contributionComputation: isContributionValid
745
+ ? newAvgContributionComputationTime
746
+ : avgContributionComputationTime,
747
+ fullContribution: isContributionValid ? newAvgFullContributionTime : avgFullContributionTime,
748
+ verifyCloudFunction: isContributionValid
749
+ ? newAvgVerifyCloudFunctionTime
750
+ : avgVerifyCloudFunctionTime
751
+ },
752
+ waitingQueue: {
753
+ ...updatedWaitingQueue,
754
+ completedContributions: isContributionValid
755
+ ? completedContributions + 1
756
+ : completedContributions,
757
+ failedContributions: isContributionValid ? failedContributions : failedContributions + 1
758
+ },
759
+ lastUpdated: getCurrentServerTimestampInMillis()
760
+ })
761
+ }
629
762
 
630
- // Free resources by unlinking transcript temporary file.
631
- fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
763
+ // Step (2).
764
+ await batch.commit()
632
765
 
633
- // Filter participant contributions to find the data related to the one verified.
634
- const participantContributions = contributions.filter(
635
- (contribution: Contribution) =>
636
- !!contribution.hash && !!contribution.computationTime && !contribution.doc
766
+ printLog(
767
+ `The contribution #${
768
+ isFinalizing ? finalContributionIndex : lastZkeyIndex
769
+ } of circuit ${circuitId} (ceremony ${ceremonyId}) has been verified as ${
770
+ isContributionValid ? "valid" : "invalid"
771
+ } for the participant ${participantDoc.id}`,
772
+ LogLevel.DEBUG
637
773
  )
638
-
639
- /// @dev (there must be only one contribution with an empty 'doc' field).
640
- if (participantContributions.length !== 1)
641
- logAndThrowError(SPECIFIC_ERRORS.SE_VERIFICATION_NO_PARTICIPANT_CONTRIBUTION_DATA)
642
-
643
- // Get contribution computation time.
644
- contributionComputationTime = contributions.at(0).computationTime
645
-
646
- // Step (1.A.4.A.2).
647
- batch.create(contributionDoc.ref, {
648
- participantId: participantDoc.id,
649
- contributionComputationTime,
650
- verificationComputationTime: verifyCloudFunctionExecutionTime,
651
- zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
652
- files: {
653
- transcriptFilename: verificationTranscriptCompleteFilename,
654
- lastZkeyFilename,
655
- transcriptStoragePath: verificationTranscriptStoragePathAndFilename,
656
- lastZkeyStoragePath,
657
- transcriptBlake2bHash,
658
- lastZkeyBlake2bHash
659
- },
660
- verificationSoftware: {
661
- name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
662
- version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
663
- commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
664
- },
665
- valid: isContributionValid,
666
- lastUpdated: getCurrentServerTimestampInMillis()
667
- })
668
-
669
- verifyContributionTimer.stop()
670
- verifyCloudFunctionTime = verifyContributionTimer.ms()
671
- } else {
672
- // Step (1.A.4.B).
673
-
674
- // Free-up storage by deleting invalid contribution.
675
- await deleteObject(bucketName, lastZkeyStoragePath)
676
-
677
- // Step (1.A.4.B.1).
678
- batch.create(contributionDoc.ref, {
679
- participantId: participantDoc.id,
680
- verificationComputationTime: verifyCloudFunctionExecutionTime,
681
- zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
682
- verificationSoftware: {
683
- name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
684
- version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
685
- commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
686
- },
687
- valid: isContributionValid,
688
- lastUpdated: getCurrentServerTimestampInMillis()
689
- })
690
- }
691
-
692
- // Step (1.A.4.C)
693
- if (!isFinalizing) {
694
- // Step (1.A.4.C.1)
695
- // Compute new average contribution/verification time.
696
- fullContributionTime = Number(verificationStartedAt) - Number(contributionStartedAt)
697
-
698
- const newAvgContributionComputationTime =
699
- avgContributionComputationTime > 0
700
- ? (avgContributionComputationTime + contributionComputationTime) / 2
701
- : contributionComputationTime
702
- const newAvgFullContributionTime =
703
- avgFullContributionTime > 0
704
- ? (avgFullContributionTime + fullContributionTime) / 2
705
- : fullContributionTime
706
- const newAvgVerifyCloudFunctionTime =
707
- avgVerifyCloudFunctionTime > 0
708
- ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
709
- : verifyCloudFunctionTime
710
-
711
- // Prepare tx to update circuit average contribution/verification time.
712
- const updatedCircuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
713
- const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data()!
714
- /// @dev this must happen only for valid contributions.
715
- batch.update(circuitDoc.ref, {
716
- avgTimings: {
717
- contributionComputation: isContributionValid
718
- ? newAvgContributionComputationTime
719
- : avgContributionComputationTime,
720
- fullContribution: isContributionValid ? newAvgFullContributionTime : avgFullContributionTime,
721
- verifyCloudFunction: isContributionValid
722
- ? newAvgVerifyCloudFunctionTime
723
- : avgVerifyCloudFunctionTime
724
- },
725
- waitingQueue: {
726
- ...updatedWaitingQueue,
727
- completedContributions: isContributionValid
728
- ? completedContributions + 1
729
- : completedContributions,
730
- failedContributions: isContributionValid ? failedContributions : failedContributions + 1
731
- },
732
- lastUpdated: getCurrentServerTimestampInMillis()
733
- })
734
774
  }
735
775
 
736
- // Step (2).
737
- await batch.commit()
776
+ // Step (1).
777
+ if (isContributing || isFinalizing) {
778
+ // Prepare timer.
779
+ verificationTaskTimer.start()
738
780
 
739
- printLog(
740
- `The contribution #${
741
- isFinalizing ? finalContributionIndex : lastZkeyIndex
742
- } of circuit ${circuitId} (ceremony ${ceremonyId}) has been verified as ${
743
- isContributionValid ? "valid" : "invalid"
744
- } for the participant ${participantDoc.id}`,
745
- LogLevel.DEBUG
746
- )
747
- }
748
-
749
- // Step (1).
750
- if (isContributing || isFinalizing) {
751
- // Prepare timer.
752
- verificationTaskTimer.start()
781
+ // Step (1.A.3.0).
782
+ if (isUsingVM) {
783
+ printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
753
784
 
754
- // Step (1.A.3.0).
755
- if (isUsingVM) {
756
- printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
785
+ // Prepare for VM execution.
786
+ let isVMRunning = false // true when the VM is up, otherwise false.
757
787
 
758
- // Prepare for VM execution.
759
- let isVMRunning = false // true when the VM is up, otherwise false.
788
+ // Step (1.A.3.1).
789
+ await startEC2Instance(ec2, vmInstanceId)
760
790
 
761
- // Step (1.A.3.1).
762
- await startEC2Instance(ec2, vmInstanceId)
791
+ await sleep(60000) // nb. wait for VM startup (1 mins + retry).
763
792
 
764
- await sleep(60000) // nb. wait for VM startup (1 mins + retry).
793
+ // Check if the startup is running.
794
+ isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
765
795
 
766
- // Check if the startup is running.
767
- isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
796
+ printLog(`VM running: ${isVMRunning}`, LogLevel.DEBUG)
768
797
 
769
- printLog(`VM running: ${isVMRunning}`, LogLevel.DEBUG)
798
+ // Step (1.A.3.2).
799
+ // Prepare.
800
+ const verificationCommand = vmContributionVerificationCommand(
801
+ bucketName,
802
+ lastZkeyStoragePath,
803
+ verificationTranscriptStoragePathAndFilename
804
+ )
770
805
 
771
- // Step (1.A.3.2).
772
- // Prepare.
773
- const verificationCommand = vmContributionVerificationCommand(
774
- bucketName,
775
- lastZkeyStoragePath,
776
- verificationTranscriptStoragePathAndFilename
777
- )
806
+ // Run.
807
+ commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
778
808
 
779
- // Run.
780
- commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
809
+ printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG)
781
810
 
782
- printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG)
811
+ // Step (1.A.3.3).
812
+ return waitForVMCommandExecution(ssm, vmInstanceId, commandId)
813
+ .then(async () => {
814
+ // Command execution successfully completed.
815
+ printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG)
816
+ await completeVerification()
817
+ })
818
+ .catch((error: any) => {
819
+ // Command execution aborted.
820
+ printLog(`Command ${commandId} execution has been aborted - Error ${error}`, LogLevel.DEBUG)
783
821
 
784
- // Step (1.A.3.3).
785
- return new Promise<void>((resolve, reject) =>
786
- waitForVMCommandExecution(resolve, reject, ssm, vmInstanceId, commandId)
787
- )
788
- .then(async () => {
789
- // Command execution successfully completed.
790
- printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG)
791
- await completeVerification()
792
- })
793
- .catch((error: any) => {
794
- // Command execution aborted.
795
- printLog(`Command ${commandId} execution has been aborted - Error ${error}`, LogLevel.DEBUG)
822
+ logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
823
+ })
824
+ }
796
825
 
797
- logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
798
- })
799
- } else {
800
826
  // CF approach.
801
827
  printLog(`CF mechanism`, LogLevel.DEBUG)
802
828
 
@@ -804,9 +830,7 @@ export const verifycontribution = functionsV2.https.onCall(
804
830
  const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
805
831
  // Prepare temporary file paths.
806
832
  // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
807
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
808
- verificationTranscriptCompleteFilename
809
- )
833
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename)
810
834
  const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
811
835
  const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
812
836
  const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
@@ -833,7 +857,7 @@ export const verifycontribution = functionsV2.https.onCall(
833
857
  lastZkeyTempFilePath,
834
858
  transcriptLogger
835
859
  )
836
-
860
+
837
861
  // Compute contribution hash.
838
862
  lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
839
863
 
@@ -845,11 +869,13 @@ export const verifycontribution = functionsV2.https.onCall(
845
869
  fs.unlinkSync(lastZkeyTempFilePath)
846
870
  } catch (error: any) {
847
871
  printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
848
- }
872
+ }
849
873
 
850
874
  await completeVerification()
851
875
  }
852
- }
876
+ } catch (error: any) {
877
+ logAndThrowError(makeError("unknown", error))
878
+ }
853
879
  }
854
880
  )
855
881
 
@@ -859,9 +885,9 @@ export const verifycontribution = functionsV2.https.onCall(
859
885
  * this does not happen if the participant is actually the coordinator who is finalizing the ceremony.
860
886
  */
861
887
  export const refreshParticipantAfterContributionVerification = functionsV1
862
- .region('europe-west1')
888
+ .region("europe-west1")
863
889
  .runWith({
864
- memory: "512MB"
890
+ memory: "1GB"
865
891
  })
866
892
  .firestore.document(
867
893
  `/${commonTerms.collections.ceremonies.name}/{ceremony}/${commonTerms.collections.circuits.name}/{circuit}/${commonTerms.collections.contributions.name}/{contributions}`
@@ -942,9 +968,9 @@ export const refreshParticipantAfterContributionVerification = functionsV1
942
968
  * and verification key extracted from the circuit final contribution (as part of the ceremony finalization process).
943
969
  */
944
970
  export const finalizeCircuit = functionsV1
945
- .region('europe-west1')
971
+ .region("europe-west1")
946
972
  .runWith({
947
- memory: "512MB"
973
+ memory: "1GB"
948
974
  })
949
975
  .https.onCall(async (data: FinalizeCircuitData, context: functionsV1.https.CallableContext) => {
950
976
  if (!context.auth || !context.auth.token.coordinator) logAndThrowError(COMMON_ERRORS.CM_NOT_COORDINATOR_ROLE)