@devtion/backend 0.0.0-c1f4cbe → 0.0.0-e22d20d

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.
@@ -44,7 +44,7 @@ import { EC2Client } from "@aws-sdk/client-ec2"
44
44
  import { HttpsError } from "firebase-functions/v2/https"
45
45
  import { FinalizeCircuitData, VerifyContributionData } from "../types/index"
46
46
  import { LogLevel } from "../types/enums"
47
- import { COMMON_ERRORS, logAndThrowError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
47
+ import { COMMON_ERRORS, logAndThrowError, makeError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
48
48
  import {
49
49
  createEC2Client,
50
50
  createSSMClient,
@@ -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}`
@@ -462,416 +462,420 @@ const checkIfVMRunning = async (ec2: EC2Client, vmInstanceId: string, attempts =
462
462
  export const verifycontribution = functionsV2.https.onCall(
463
463
  { memory: "16GiB", timeoutSeconds: 3600, region: "europe-west1" },
464
464
  async (request: functionsV2.https.CallableRequest<VerifyContributionData>): Promise<any> => {
465
- if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
466
- logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
467
-
468
- if (
469
- !request.data.ceremonyId ||
470
- !request.data.circuitId ||
471
- !request.data.contributorOrCoordinatorIdentifier ||
472
- !request.data.bucketName
473
- )
474
- logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
475
-
476
- if (
477
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME ||
478
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION ||
479
- !process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH
480
- )
481
- logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
482
-
483
- // Step (0).
484
-
485
- // Prepare and start timer.
486
- const verifyContributionTimer = new Timer({ label: commonTerms.cloudFunctionsNames.verifyContribution })
487
- verifyContributionTimer.start()
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)
488
476
 
489
- // Get DB.
490
- const firestore = admin.firestore()
491
- // Prepare batch of txs.
492
- const batch = firestore.batch()
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
493
521
 
494
- // Extract data.
495
- const { ceremonyId, circuitId, contributorOrCoordinatorIdentifier, bucketName } = request.data
496
- const userId = request.auth?.uid
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
+ )
497
563
 
498
- // Look for the ceremony, circuit and participant document.
499
- const ceremonyDoc = await getDocumentById(commonTerms.collections.ceremonies.name, ceremonyId)
500
- const circuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
501
- const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), userId!)
564
+ const verificationTaskTimer = new Timer({ label: `${ceremonyId}-${circuitId}-${participantDoc.id}` })
502
565
 
503
- if (!ceremonyDoc.data() || !circuitDoc.data() || !participantDoc.data())
504
- logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)
566
+ const completeVerification = async () => {
567
+ // Stop verification task timer.
568
+ printLog("Completing verification", LogLevel.DEBUG)
569
+ verificationTaskTimer.stop()
570
+ verifyCloudFunctionExecutionTime = verificationTaskTimer.ms()
505
571
 
506
- // Extract documents data.
507
- const { state } = ceremonyDoc.data()!
508
- const { status, contributions, verificationStartedAt, contributionStartedAt } = participantDoc.data()!
509
- const { waitingQueue, prefix, avgTimings, verification, files } = circuitDoc.data()!
510
- const { completedContributions, failedContributions } = waitingQueue
511
- const {
512
- contributionComputation: avgContributionComputationTime,
513
- fullContribution: avgFullContributionTime,
514
- verifyCloudFunction: avgVerifyCloudFunctionTime
515
- } = avgTimings
516
- const { cfOrVm, vm } = verification
517
- // we might not have it if the circuit is not using VM.
518
- let vmInstanceId: string = ""
519
- if (vm) vmInstanceId = vm.vmInstanceId
572
+ if (isUsingVM) {
573
+ // Create temporary path.
574
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
575
+ `${circuitId}_${participantDoc.id}.log`
576
+ )
520
577
 
521
- // Define pre-conditions.
522
- const isFinalizing = state === CeremonyState.CLOSED && request.auth && request.auth.token.coordinator // true only when the coordinator verifies the final contributions.
523
- const isContributing = status === ParticipantStatus.CONTRIBUTING
524
- const isUsingVM = cfOrVm === CircuitContributionVerificationMechanism.VM && !!vmInstanceId
525
-
526
- // Prepare state.
527
- let isContributionValid = false
528
- let verifyCloudFunctionExecutionTime = 0 // time spent while executing the verify contribution cloud function.
529
- let verifyCloudFunctionTime = 0 // time spent while executing the core business logic of this cloud function.
530
- let fullContributionTime = 0 // time spent while doing non-verification contributions tasks (download, compute, upload).
531
- let contributionComputationTime = 0 // time spent while computing the contribution.
532
- let lastZkeyBlake2bHash: string = "" // the Blake2B hash of the last zKey.
533
- let verificationTranscriptTemporaryLocalPath: string = "" // the local temporary path for the verification transcript.
534
- let transcriptBlake2bHash: string = "" // the Blake2B hash of the verification transcript.
535
- let commandId: string = "" // the unique identifier of the VM command.
536
-
537
- // Derive necessary data.
538
- const lastZkeyIndex = formatZkeyIndex(completedContributions + 1)
539
- const verificationTranscriptCompleteFilename = `${prefix}_${
540
- isFinalizing
541
- ? `${contributorOrCoordinatorIdentifier}_${finalContributionIndex}_verification_transcript.log`
542
- : `${lastZkeyIndex}_${contributorOrCoordinatorIdentifier}_verification_transcript.log`
543
- }`
544
-
545
- const lastZkeyFilename = `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
546
-
547
- // Prepare state for VM verification (if needed).
548
- const ec2 = await createEC2Client()
549
- const ssm = await createSSMClient()
550
-
551
- // Step (1.A.1).
552
- // Get storage paths.
553
- const verificationTranscriptStoragePathAndFilename = getTranscriptStorageFilePath(
554
- prefix,
555
- verificationTranscriptCompleteFilename
556
- )
557
- // the zKey storage path is required to be sent to the VM api
558
- const lastZkeyStoragePath = getZkeyStorageFilePath(
559
- prefix,
560
- `${prefix}_${isFinalizing ? finalContributionIndex : lastZkeyIndex}.zkey`
561
- )
578
+ await sleep(1000) // wait 1s for file creation.
562
579
 
563
- 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
+ )
564
587
 
565
- const completeVerification = async () => {
566
- // Stop verification task timer.
567
- printLog("Completing verification", LogLevel.DEBUG)
568
- verificationTaskTimer.stop()
569
- 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")
570
590
 
571
- if (isUsingVM) {
572
- // Create temporary path.
573
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(
574
- `${circuitId}_${participantDoc.id}.log`
575
- )
591
+ if (content.includes("ZKey Ok!")) isContributionValid = true
576
592
 
577
- 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, "")
578
597
 
579
- // Download from bucket.
580
- // nb. the transcript MUST be uploaded from the VM by verification commands.
581
- await downloadArtifactFromS3Bucket(
582
- bucketName,
583
- verificationTranscriptStoragePathAndFilename,
584
- verificationTranscriptTemporaryLocalPath
585
- )
598
+ fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
599
+ }
600
+ }
586
601
 
587
- // Read the verification trascript and validate data by checking for core info ("ZKey Ok!").
588
- const content = fs.readFileSync(verificationTranscriptTemporaryLocalPath, "utf-8")
602
+ printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
589
603
 
590
- 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()
591
609
 
592
- // If the contribution is valid, then format and store the trascript.
610
+ // Step (1.A.4).
593
611
  if (isContributionValid) {
594
- // eslint-disable-next-line no-control-regex
595
- const updated = content.replace(/\x1b[[0-9;]*m/g, "")
596
-
597
- fs.writeFileSync(verificationTranscriptTemporaryLocalPath, updated)
598
- }
599
- }
600
-
601
- printLog(`The contribution has been verified - Result ${isContributionValid}`, LogLevel.DEBUG)
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
+ }
602
642
 
603
- // Create a new contribution document.
604
- const contributionDoc = await firestore
605
- .collection(getContributionsCollectionPath(ceremonyId, circuitId))
606
- .doc()
607
- .get()
643
+ // Compute verification transcript hash.
644
+ transcriptBlake2bHash = await blake512FromPath(verificationTranscriptTemporaryLocalPath)
608
645
 
609
- // Step (1.A.4).
610
- if (isContributionValid) {
611
- // Sleep ~3 seconds to wait for verification transcription.
612
- await sleep(3000)
646
+ // Free resources by unlinking transcript temporary file.
647
+ fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
613
648
 
614
- // Step (1.A.4.A.1).
615
- if (isUsingVM) {
616
- // Retrieve the contribution hash from the command output.
617
- lastZkeyBlake2bHash = await retrieveCommandOutput(ssm, vmInstanceId, commandId)
618
-
619
- const hashRegex = /[a-fA-F0-9]{64}/
620
- 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
+ )
621
654
 
622
- 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
+ })
623
684
 
624
- // re upload the formatted verification transcript
625
- await uploadFileToBucket(
626
- bucketName,
627
- verificationTranscriptStoragePathAndFilename,
628
- verificationTranscriptTemporaryLocalPath,
629
- true
630
- )
685
+ verifyContributionTimer.stop()
686
+ verifyCloudFunctionTime = verifyContributionTimer.ms()
631
687
  } else {
632
- // Upload verification transcript.
633
- /// nb. do not use multi-part upload here due to small file size.
634
- await uploadFileToBucket(
635
- bucketName,
636
- verificationTranscriptStoragePathAndFilename,
637
- verificationTranscriptTemporaryLocalPath,
638
- true
639
- )
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
+ })
640
706
  }
641
707
 
642
- // Compute verification transcript hash.
643
- 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
+ }
644
762
 
645
- // Free resources by unlinking transcript temporary file.
646
- fs.unlinkSync(verificationTranscriptTemporaryLocalPath)
763
+ // Step (2).
764
+ await batch.commit()
647
765
 
648
- // Filter participant contributions to find the data related to the one verified.
649
- const participantContributions = contributions.filter(
650
- (contribution: Contribution) =>
651
- !!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
652
773
  )
653
-
654
- /// @dev (there must be only one contribution with an empty 'doc' field).
655
- if (participantContributions.length !== 1)
656
- logAndThrowError(SPECIFIC_ERRORS.SE_VERIFICATION_NO_PARTICIPANT_CONTRIBUTION_DATA)
657
-
658
- // Get contribution computation time.
659
- contributionComputationTime = contributions.at(0).computationTime
660
-
661
- // Step (1.A.4.A.2).
662
- batch.create(contributionDoc.ref, {
663
- participantId: participantDoc.id,
664
- contributionComputationTime,
665
- verificationComputationTime: verifyCloudFunctionExecutionTime,
666
- zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
667
- files: {
668
- transcriptFilename: verificationTranscriptCompleteFilename,
669
- lastZkeyFilename,
670
- transcriptStoragePath: verificationTranscriptStoragePathAndFilename,
671
- lastZkeyStoragePath,
672
- transcriptBlake2bHash,
673
- lastZkeyBlake2bHash
674
- },
675
- verificationSoftware: {
676
- name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
677
- version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
678
- commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
679
- },
680
- valid: isContributionValid,
681
- lastUpdated: getCurrentServerTimestampInMillis()
682
- })
683
-
684
- verifyContributionTimer.stop()
685
- verifyCloudFunctionTime = verifyContributionTimer.ms()
686
- } else {
687
- // Step (1.A.4.B).
688
-
689
- // Free-up storage by deleting invalid contribution.
690
- await deleteObject(bucketName, lastZkeyStoragePath)
691
-
692
- // Step (1.A.4.B.1).
693
- batch.create(contributionDoc.ref, {
694
- participantId: participantDoc.id,
695
- verificationComputationTime: verifyCloudFunctionExecutionTime,
696
- zkeyIndex: isFinalizing ? finalContributionIndex : lastZkeyIndex,
697
- verificationSoftware: {
698
- name: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_NAME),
699
- version: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_VERSION),
700
- commitHash: String(process.env.CUSTOM_CONTRIBUTION_VERIFICATION_SOFTWARE_COMMIT_HASH)
701
- },
702
- valid: isContributionValid,
703
- lastUpdated: getCurrentServerTimestampInMillis()
704
- })
705
774
  }
706
775
 
707
- // Stop VM instance
708
- if (isUsingVM) {
709
- // using try and catch as the VM stopping function can throw
710
- // however we want to continue without stopping as the
711
- // verification was valid, and inform the coordinator
712
- try {
713
- await stopEC2Instance(ec2, vmInstanceId)
714
- } catch (error: any) {
715
- printLog(`Error while stopping VM instance ${vmInstanceId} - Error ${error}`, LogLevel.WARN)
716
- }
717
- }
718
- // Step (1.A.4.C)
719
- if (!isFinalizing) {
720
- // Step (1.A.4.C.1)
721
- // Compute new average contribution/verification time.
722
- fullContributionTime = Number(verificationStartedAt) - Number(contributionStartedAt)
723
-
724
- const newAvgContributionComputationTime =
725
- avgContributionComputationTime > 0
726
- ? (avgContributionComputationTime + contributionComputationTime) / 2
727
- : contributionComputationTime
728
- const newAvgFullContributionTime =
729
- avgFullContributionTime > 0
730
- ? (avgFullContributionTime + fullContributionTime) / 2
731
- : fullContributionTime
732
- const newAvgVerifyCloudFunctionTime =
733
- avgVerifyCloudFunctionTime > 0
734
- ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
735
- : verifyCloudFunctionTime
736
-
737
- // Prepare tx to update circuit average contribution/verification time.
738
- const updatedCircuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
739
- const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data()!
740
- /// @dev this must happen only for valid contributions.
741
- batch.update(circuitDoc.ref, {
742
- avgTimings: {
743
- contributionComputation: isContributionValid
744
- ? newAvgContributionComputationTime
745
- : avgContributionComputationTime,
746
- fullContribution: isContributionValid ? newAvgFullContributionTime : avgFullContributionTime,
747
- verifyCloudFunction: isContributionValid
748
- ? newAvgVerifyCloudFunctionTime
749
- : avgVerifyCloudFunctionTime
750
- },
751
- waitingQueue: {
752
- ...updatedWaitingQueue,
753
- completedContributions: isContributionValid
754
- ? completedContributions + 1
755
- : completedContributions,
756
- failedContributions: isContributionValid ? failedContributions : failedContributions + 1
757
- },
758
- lastUpdated: getCurrentServerTimestampInMillis()
759
- })
760
- }
761
-
762
- // Step (2).
763
- await batch.commit()
764
-
765
- printLog(
766
- `The contribution #${
767
- isFinalizing ? finalContributionIndex : lastZkeyIndex
768
- } of circuit ${circuitId} (ceremony ${ceremonyId}) has been verified as ${
769
- isContributionValid ? "valid" : "invalid"
770
- } for the participant ${participantDoc.id}`,
771
- LogLevel.DEBUG
772
- )
773
- }
776
+ // Step (1).
777
+ if (isContributing || isFinalizing) {
778
+ // Prepare timer.
779
+ verificationTaskTimer.start()
774
780
 
775
- // Step (1).
776
- if (isContributing || isFinalizing) {
777
- // Prepare timer.
778
- verificationTaskTimer.start()
781
+ // Step (1.A.3.0).
782
+ if (isUsingVM) {
783
+ printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
779
784
 
780
- // Step (1.A.3.0).
781
- if (isUsingVM) {
782
- printLog(`Starting the VM mechanism`, LogLevel.DEBUG)
785
+ // Prepare for VM execution.
786
+ let isVMRunning = false // true when the VM is up, otherwise false.
783
787
 
784
- // Prepare for VM execution.
785
- let isVMRunning = false // true when the VM is up, otherwise false.
788
+ // Step (1.A.3.1).
789
+ await startEC2Instance(ec2, vmInstanceId)
786
790
 
787
- // Step (1.A.3.1).
788
- await startEC2Instance(ec2, vmInstanceId)
791
+ await sleep(60000) // nb. wait for VM startup (1 mins + retry).
789
792
 
790
- await sleep(60000) // nb. wait for VM startup (1 mins + retry).
793
+ // Check if the startup is running.
794
+ isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
791
795
 
792
- // Check if the startup is running.
793
- isVMRunning = await checkIfVMRunning(ec2, vmInstanceId)
796
+ printLog(`VM running: ${isVMRunning}`, LogLevel.DEBUG)
794
797
 
795
- 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
+ )
796
805
 
797
- // Step (1.A.3.2).
798
- // Prepare.
799
- const verificationCommand = vmContributionVerificationCommand(
800
- bucketName,
801
- lastZkeyStoragePath,
802
- verificationTranscriptStoragePathAndFilename
803
- )
806
+ // Run.
807
+ commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
804
808
 
805
- // Run.
806
- commandId = await runCommandUsingSSM(ssm, vmInstanceId, verificationCommand)
809
+ printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG)
807
810
 
808
- 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)
809
821
 
810
- // Step (1.A.3.3).
811
- return waitForVMCommandExecution(ssm, vmInstanceId, commandId)
812
- .then(async () => {
813
- // Command execution successfully completed.
814
- printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG)
815
- await completeVerification()
816
- })
817
- .catch((error: any) => {
818
- // Command execution aborted.
819
- printLog(`Command ${commandId} execution has been aborted - Error ${error}`, LogLevel.DEBUG)
822
+ logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
823
+ })
824
+ }
820
825
 
821
- logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION)
822
- })
823
- }
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
+ )
824
847
 
825
- // CF approach.
826
- printLog(`CF mechanism`, LogLevel.DEBUG)
827
-
828
- const potStoragePath = getPotStorageFilePath(files.potFilename)
829
- const firstZkeyStoragePath = getZkeyStorageFilePath(prefix, `${prefix}_${genesisZkeyIndex}.zkey`)
830
- // Prepare temporary file paths.
831
- // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
832
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename)
833
- const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`)
834
- const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`)
835
- const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`)
836
-
837
- // Create and populate transcript.
838
- const transcriptLogger = createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath)
839
- transcriptLogger.info(
840
- `${
841
- isFinalizing ? `Final verification` : `Verification`
842
- } transcript for ${prefix} circuit Phase 2 contribution.\n${
843
- isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`
844
- } (${contributorOrCoordinatorIdentifier})\n`
845
- )
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
+ )
846
860
 
847
- // Step (1.A.2).
848
- await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
849
- await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
850
- await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
851
-
852
- // Step (1.A.4).
853
- isContributionValid = await zKey.verifyFromInit(
854
- firstZkeyTempFilePath,
855
- potTempFilePath,
856
- lastZkeyTempFilePath,
857
- transcriptLogger
858
- )
861
+ // Compute contribution hash.
862
+ lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
859
863
 
860
- // Compute contribution hash.
861
- 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
+ }
862
873
 
863
- // Free resources by unlinking temporary folders.
864
- // Do not free-up verification transcript path here.
865
- try {
866
- fs.unlinkSync(potTempFilePath)
867
- fs.unlinkSync(firstZkeyTempFilePath)
868
- fs.unlinkSync(lastZkeyTempFilePath)
869
- } catch (error: any) {
870
- printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
874
+ await completeVerification()
871
875
  }
872
-
873
- await completeVerification()
874
- }
876
+ } catch (error: any) {
877
+ logAndThrowError(makeError("unknown", error))
878
+ }
875
879
  }
876
880
  )
877
881
 
@@ -883,7 +887,7 @@ export const verifycontribution = functionsV2.https.onCall(
883
887
  export const refreshParticipantAfterContributionVerification = functionsV1
884
888
  .region("europe-west1")
885
889
  .runWith({
886
- memory: "512MB"
890
+ memory: "1GB"
887
891
  })
888
892
  .firestore.document(
889
893
  `/${commonTerms.collections.ceremonies.name}/{ceremony}/${commonTerms.collections.circuits.name}/{circuit}/${commonTerms.collections.contributions.name}/{contributions}`
@@ -966,7 +970,7 @@ export const refreshParticipantAfterContributionVerification = functionsV1
966
970
  export const finalizeCircuit = functionsV1
967
971
  .region("europe-west1")
968
972
  .runWith({
969
- memory: "512MB"
973
+ memory: "1GB"
970
974
  })
971
975
  .https.onCall(async (data: FinalizeCircuitData, context: functionsV1.https.CallableContext) => {
972
976
  if (!context.auth || !context.auth.token.coordinator) logAndThrowError(COMMON_ERRORS.CM_NOT_COORDINATOR_ROLE)