@devtion/backend 0.0.0-8bb9489 → 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.
@@ -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,8 +59,6 @@ import {
57
59
  sleep,
58
60
  uploadFileToBucket
59
61
  } from "../lib/utils"
60
- import { EC2Client } from "@aws-sdk/client-ec2"
61
- import { HttpsError } from "firebase-functions/v2/https"
62
62
 
63
63
  dotenv.config()
64
64
 
@@ -312,7 +312,7 @@ const waitForVMCommandExecution = (ssm: SSMClient, vmInstanceId: string, command
312
312
  export const coordinateCeremonyParticipant = functionsV1
313
313
  .region("europe-west1")
314
314
  .runWith({
315
- memory: "512MB"
315
+ memory: "1GB"
316
316
  })
317
317
  .firestore.document(
318
318
  `${commonTerms.collections.ceremonies.name}/{ceremonyId}/${commonTerms.collections.participants.name}/{participantId}`
@@ -428,10 +428,9 @@ const checkIfVMRunning = async (ec2: EC2Client, vmInstanceId: string, attempts =
428
428
 
429
429
  if (!isVMRunning) {
430
430
  printLog(`VM not running, ${attempts - 1} attempts remaining. Retrying in 1 minute...`, LogLevel.DEBUG)
431
- return await checkIfVMRunning(ec2, vmInstanceId, attempts - 1)
432
- } else {
433
- return true
431
+ return checkIfVMRunning(ec2, vmInstanceId, attempts - 1)
434
432
  }
433
+ return true
435
434
  }
436
435
 
437
436
  /**
@@ -463,416 +462,420 @@ const checkIfVMRunning = async (ec2: EC2Client, vmInstanceId: string, attempts =
463
462
  export const verifycontribution = functionsV2.https.onCall(
464
463
  { memory: "16GiB", timeoutSeconds: 3600, region: "europe-west1" },
465
464
  async (request: functionsV2.https.CallableRequest<VerifyContributionData>): Promise<any> => {
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)
476
-
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).
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)
485
476
 
486
- // Prepare and start timer.
487
- const verifyContributionTimer = new Timer({ label: commonTerms.cloudFunctionsNames.verifyContribution })
488
- verifyContributionTimer.start()
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
489
521
 
490
- // Get DB.
491
- const firestore = admin.firestore()
492
- // Prepare batch of txs.
493
- const batch = firestore.batch()
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
+ )
494
563
 
495
- // Extract data.
496
- const { ceremonyId, circuitId, contributorOrCoordinatorIdentifier, bucketName } = request.data
497
- const userId = request.auth?.uid
564
+ const verificationTaskTimer = new Timer({ label: `${ceremonyId}-${circuitId}-${participantDoc.id}` })
498
565
 
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!)
566
+ const completeVerification = async () => {
567
+ // Stop verification task timer.
568
+ printLog("Completing verification", LogLevel.DEBUG)
569
+ verificationTaskTimer.stop()
570
+ verifyCloudFunctionExecutionTime = verificationTaskTimer.ms()
503
571
 
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
572
+ if (isUsingVM) {
573
+ // Create temporary path.
574
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
575
+ `${circuitId}_${participantDoc.id}.log`
576
+ )
521
577
 
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
- )
578
+ await sleep(1000) // wait 1s for file creation.
563
579
 
564
- const verificationTaskTimer = new Timer({ label: `${ceremonyId}-${circuitId}-${participantDoc.id}` })
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
+ )
565
587
 
566
- const completeVerification = async () => {
567
- // Stop verification task timer.
568
- printLog("Completing verification", LogLevel.DEBUG)
569
- verificationTaskTimer.stop()
570
- verifyCloudFunctionExecutionTime = verificationTaskTimer.ms()
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 (isUsingVM) {
573
- // Create temporary path.
574
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
575
- `${circuitId}_${participantDoc.id}.log`
576
- )
591
+ if (content.includes("ZKey Ok!")) isContributionValid = true
577
592
 
578
- await sleep(1000) // wait 1s for file creation.
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, "")
579
597
 
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
- )
598
+ fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
599
+ }
600
+ }
587
601
 
588
- // Read the verification trascript and validate data by checking for core info ("ZKey Ok!").
589
- const content = fs.readFileSync(verificationTranscriptTemporaryLocalPath, "utf-8")
602
+ printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
590
603
 
591
- if (content.includes("ZKey Ok!")) isContributionValid = true
604
+ // Create a new contribution document.
605
+ const contributionDoc = await firestore
606
+ .collection(getContributionsCollectionPath(ceremonyId, circuitId))
607
+ .doc()
608
+ .get()
592
609
 
593
- // If the contribution is valid, then format and store the trascript.
610
+ // Step (1.A.4).
594
611
  if (isContributionValid) {
595
- // eslint-disable-next-line no-control-regex
596
- const updated = content.replace(/\x1b[[0-9;]*m/g, "")
597
-
598
- fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
599
- }
600
- }
601
-
602
- printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
603
-
604
- // Create a new contribution document.
605
- const contributionDoc = await firestore
606
- .collection(getContributionsCollectionPath(ceremonyId, circuitId))
607
- .doc()
608
- .get()
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
+ }
609
642
 
610
- // Step (1.A.4).
611
- if (isContributionValid) {
612
- // Sleep ~3 seconds to wait for verification transcription.
613
- await sleep(3000)
643
+ // Compute verification transcript hash.
644
+ transcriptBlake2bHash = await blake512FromPath(verificationTranscriptTemporaryLocalPath)
614
645
 
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)
646
+ // Free resources by unlinking transcript temporary file.
647
+ fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
619
648
 
620
- const hashRegex = /[a-fA-F0-9]{64}/
621
- const match = lastZkeyBlake2bHash.match(hashRegex)!
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
653
+ )
622
654
 
623
- lastZkeyBlake2bHash = match.at(0)!
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
+ })
624
684
 
625
- // re upload the formatted verification transcript
626
- await uploadFileToBucket(
627
- bucketName,
628
- verificationTranscriptStoragePathAndFilename,
629
- verificationTranscriptTemporaryLocalPath,
630
- true
631
- )
685
+ verifyContributionTimer.stop()
686
+ verifyCloudFunctionTime = verifyContributionTimer.ms()
632
687
  } 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
- )
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
+ })
641
706
  }
642
707
 
643
- // Compute verification transcript hash.
644
- 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
+ }
645
762
 
646
- // Free resources by unlinking transcript temporary file.
647
- fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
763
+ // Step (2).
764
+ await batch.commit()
648
765
 
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
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
653
773
  )
654
-
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()
687
- } else {
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
- })
706
- }
707
-
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
774
  }
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
- }
762
-
763
- // Step (2).
764
- await batch.commit()
765
775
 
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
773
- )
774
- }
776
+ // Step (1).
777
+ if (isContributing || isFinalizing) {
778
+ // Prepare timer.
779
+ verificationTaskTimer.start()
775
780
 
776
- // Step (1).
777
- if (isContributing || isFinalizing) {
778
- // Prepare timer.
779
- verificationTaskTimer.start()
781
+ // Step (1.A.3.0).
782
+ if (isUsingVM) {
783
+ printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
780
784
 
781
- // Step (1.A.3.0).
782
- if (isUsingVM) {
783
- printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
785
+ // Prepare for VM execution.
786
+ let isVMRunning = false // true when the VM is up, otherwise false.
784
787
 
785
- // Prepare for VM execution.
786
- let isVMRunning = false // true when the VM is up, otherwise false.
788
+ // Step (1.A.3.1).
789
+ await startEC2Instance(ec2, vmInstanceId)
787
790
 
788
- // Step (1.A.3.1).
789
- await startEC2Instance(ec2, vmInstanceId)
791
+ await sleep(60000) // nb. wait for VM startup (1 mins + retry).
790
792
 
791
- await sleep(60000) // nb. wait for VM startup (1 mins + retry).
793
+ // Check if the startup is running.
794
+ isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
792
795
 
793
- // Check if the startup is running.
794
- isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
796
+ printLog(`VM running: ${isVMRunning}`, LogLevel.DEBUG)
795
797
 
796
- 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
+ )
797
805
 
798
- // Step (1.A.3.2).
799
- // Prepare.
800
- const verificationCommand = vmContributionVerificationCommand(
801
- bucketName,
802
- lastZkeyStoragePath,
803
- verificationTranscriptStoragePathAndFilename
804
- )
806
+ // Run.
807
+ commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
805
808
 
806
- // Run.
807
- commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
809
+ printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG)
808
810
 
809
- 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)
810
821
 
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)
822
+ logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
823
+ })
824
+ }
821
825
 
822
- logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
823
- })
824
- }
826
+ // CF approach.
827
+ printLog(`CF mechanism`, LogLevel.DEBUG)
828
+
829
+ const potStoragePath = getPotStorageFilePath(files.potFilename)
830
+ const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
831
+ // Prepare temporary file paths.
832
+ // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
833
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename)
834
+ const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
835
+ const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
836
+ const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
837
+
838
+ // Create and populate transcript.
839
+ const transcriptLogger = createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath)
840
+ transcriptLogger.info(
841
+ `${
842
+ isFinalizing ? `Final verification` : `Verification`
843
+ } transcript for ${prefix} circuit Phase 2 contribution.\n${
844
+ isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
845
+ } (${contributorOrCoordinatorIdentifier})\n`
846
+ )
825
847
 
826
- // CF approach.
827
- printLog(`CF mechanism`, LogLevel.DEBUG)
828
-
829
- const potStoragePath = getPotStorageFilePath(files.potFilename)
830
- const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
831
- // Prepare temporary file paths.
832
- // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
833
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename)
834
- const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
835
- const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
836
- const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
837
-
838
- // Create and populate transcript.
839
- const transcriptLogger = createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath)
840
- transcriptLogger.info(
841
- `${
842
- isFinalizing ? `Final verification` : `Verification`
843
- } transcript for ${prefix} circuit Phase 2 contribution.\n${
844
- isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
845
- } (${contributorOrCoordinatorIdentifier})\n`
846
- )
848
+ // Step (1.A.2).
849
+ await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
850
+ await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
851
+ await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
852
+
853
+ // Step (1.A.4).
854
+ isContributionValid = await zKey.verifyFromInit(
855
+ firstZkeyTempFilePath,
856
+ potTempFilePath,
857
+ lastZkeyTempFilePath,
858
+ transcriptLogger
859
+ )
847
860
 
848
- // Step (1.A.2).
849
- await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
850
- await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
851
- await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
852
-
853
- // Step (1.A.4).
854
- isContributionValid = await zKey.verifyFromInit(
855
- firstZkeyTempFilePath,
856
- potTempFilePath,
857
- lastZkeyTempFilePath,
858
- transcriptLogger
859
- )
861
+ // Compute contribution hash.
862
+ lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
860
863
 
861
- // Compute contribution hash.
862
- lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
864
+ // Free resources by unlinking temporary folders.
865
+ // Do not free-up verification transcript path here.
866
+ try {
867
+ fs.unlinkSync(potTempFilePath)
868
+ fs.unlinkSync(firstZkeyTempFilePath)
869
+ fs.unlinkSync(lastZkeyTempFilePath)
870
+ } catch (error: any) {
871
+ printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
872
+ }
863
873
 
864
- // Free resources by unlinking temporary folders.
865
- // Do not free-up verification transcript path here.
866
- try {
867
- fs.unlinkSync(potTempFilePath)
868
- fs.unlinkSync(firstZkeyTempFilePath)
869
- fs.unlinkSync(lastZkeyTempFilePath)
870
- } catch (error: any) {
871
- printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
874
+ await completeVerification()
872
875
  }
873
-
874
- await completeVerification()
875
- }
876
+ } catch (error: any) {
877
+ logAndThrowError(makeError("unknown", error))
878
+ }
876
879
  }
877
880
  )
878
881
 
@@ -884,7 +887,7 @@ export const verifycontribution = functionsV2.https.onCall(
884
887
  export const refreshParticipantAfterContributionVerification = functionsV1
885
888
  .region("europe-west1")
886
889
  .runWith({
887
- memory: "512MB"
890
+ memory: "1GB"
888
891
  })
889
892
  .firestore.document(
890
893
  `/${commonTerms.collections.ceremonies.name}/{ceremony}/${commonTerms.collections.circuits.name}/{circuit}/${commonTerms.collections.contributions.name}/{contributions}`
@@ -967,7 +970,7 @@ export const refreshParticipantAfterContributionVerification = functionsV1
967
970
  export const finalizeCircuit = functionsV1
968
971
  .region("europe-west1")
969
972
  .runWith({
970
- memory: "512MB"
973
+ memory: "1GB"
971
974
  })
972
975
  .https.onCall(async (data: FinalizeCircuitData, context: functionsV1.https.CallableContext) => {
973
976
  if (!context.auth || !context.auth.token.coordinator) logAndThrowError(COMMON_ERRORS.CM_NOT_COORDINATOR_ROLE)