@devtion/backend 0.0.0-0fd4368 → 0.0.0-142ec0a

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @module @p0tion/backend
3
- * @version 1.0.5
3
+ * @version 1.1.1
4
4
  * @file MPC Phase 2 backend for Firebase services management
5
5
  * @copyright Ethereum Foundation 2022
6
6
  * @license MIT
@@ -11,7 +11,7 @@
11
11
  var admin = require('firebase-admin');
12
12
  var functions = require('firebase-functions');
13
13
  var dotenv = require('dotenv');
14
- var actions = require('@p0tion/actions');
14
+ var actions = require('@devtion/actions');
15
15
  var htmlEntities = require('html-entities');
16
16
  var firestore = require('firebase-admin/firestore');
17
17
  var clientS3 = require('@aws-sdk/client-s3');
@@ -144,7 +144,8 @@ const SPECIFIC_ERRORS = {
144
144
  SE_VM_FAILED_COMMAND_EXECUTION: makeError("failed-precondition", "VM command execution failed", "Please, contact the coordinator if this error persists."),
145
145
  SE_VM_TIMEDOUT_COMMAND_EXECUTION: makeError("deadline-exceeded", "VM command execution took too long and has been timed-out", "Please, contact the coordinator if this error persists."),
146
146
  SE_VM_CANCELLED_COMMAND_EXECUTION: makeError("cancelled", "VM command execution has been cancelled", "Please, contact the coordinator if this error persists."),
147
- SE_VM_DELAYED_COMMAND_EXECUTION: makeError("unavailable", "VM command execution has been delayed since there were no available instance at the moment", "Please, contact the coordinator if this error persists.")
147
+ SE_VM_DELAYED_COMMAND_EXECUTION: makeError("unavailable", "VM command execution has been delayed since there were no available instance at the moment", "Please, contact the coordinator if this error persists."),
148
+ SE_VM_UNKNOWN_COMMAND_STATUS: makeError("unavailable", "VM command execution has failed due to an unknown status code", "Please, contact the coordinator if this error persists.")
148
149
  };
149
150
  /**
150
151
  * A set of common errors.
@@ -287,7 +288,7 @@ const queryOpenedCeremonies = async () => {
287
288
  const getCircuitDocumentByPosition = async (ceremonyId, sequencePosition) => {
288
289
  // Query for all ceremony circuits.
289
290
  const circuits = await getCeremonyCircuits(ceremonyId);
290
- // Apply a filter using the sequence postion.
291
+ // Apply a filter using the sequence position.
291
292
  const matchedCircuits = circuits.filter((circuit) => circuit.data().sequencePosition === sequencePosition);
292
293
  if (matchedCircuits.length !== 1)
293
294
  logAndThrowError(COMMON_ERRORS.CM_NO_CIRCUIT_FOR_GIVEN_SEQUENCE_POSITION);
@@ -328,7 +329,7 @@ const downloadArtifactFromS3Bucket = async (bucketName, objectKey, localFilePath
328
329
  const writeStream = node_fs.createWriteStream(localFilePath);
329
330
  const streamPipeline = node_util.promisify(node_stream.pipeline);
330
331
  await streamPipeline(response.body, writeStream);
331
- writeStream.on('finish', () => {
332
+ writeStream.on("finish", () => {
332
333
  writeStream.end();
333
334
  });
334
335
  };
@@ -452,12 +453,14 @@ const htmlEncodeCircuitData = (circuitDocument) => ({
452
453
  const getGitHubVariables = () => {
453
454
  if (!process.env.GITHUB_MINIMUM_FOLLOWERS ||
454
455
  !process.env.GITHUB_MINIMUM_FOLLOWING ||
455
- !process.env.GITHUB_MINIMUM_PUBLIC_REPOS)
456
+ !process.env.GITHUB_MINIMUM_PUBLIC_REPOS ||
457
+ !process.env.GITHUB_MINIMUM_AGE)
456
458
  logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION);
457
459
  return {
458
460
  minimumFollowers: Number(process.env.GITHUB_MINIMUM_FOLLOWERS),
459
461
  minimumFollowing: Number(process.env.GITHUB_MINIMUM_FOLLOWING),
460
- minimumPublicRepos: Number(process.env.GITHUB_MINIMUM_PUBLIC_REPOS)
462
+ minimumPublicRepos: Number(process.env.GITHUB_MINIMUM_PUBLIC_REPOS),
463
+ minimumAge: Number(process.env.GITHUB_MINIMUM_AGE)
461
464
  };
462
465
  };
463
466
  /**
@@ -467,7 +470,7 @@ const getGitHubVariables = () => {
467
470
  const getAWSVariables = () => {
468
471
  if (!process.env.AWS_ACCESS_KEY_ID ||
469
472
  !process.env.AWS_SECRET_ACCESS_KEY ||
470
- !process.env.AWS_ROLE_ARN ||
473
+ !process.env.AWS_INSTANCE_PROFILE_ARN ||
471
474
  !process.env.AWS_AMI_ID ||
472
475
  !process.env.AWS_SNS_TOPIC_ARN)
473
476
  logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION);
@@ -475,7 +478,7 @@ const getAWSVariables = () => {
475
478
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
476
479
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
477
480
  region: process.env.AWS_REGION || "eu-central-1",
478
- roleArn: process.env.AWS_ROLE_ARN,
481
+ instanceProfileArn: process.env.AWS_INSTANCE_PROFILE_ARN,
479
482
  amiId: process.env.AWS_AMI_ID,
480
483
  snsTopic: process.env.AWS_SNS_TOPIC_ARN
481
484
  };
@@ -544,8 +547,10 @@ const registerAuthUser = functions__namespace
544
547
  const { uid } = user;
545
548
  // Reference to a document using uid.
546
549
  const userRef = firestore.collection(actions.commonTerms.collections.users.name).doc(uid);
547
- // html encode the display name
548
- const encodedDisplayName = htmlEntities.encode(displayName);
550
+ // html encode the display name (or put the ID if the name is not displayed)
551
+ const encodedDisplayName = user.displayName === "Null" || user.displayName === null ? user.uid : htmlEntities.encode(displayName);
552
+ // store the avatar URL of a contributor
553
+ let avatarUrl = "";
549
554
  // we only do reputation check if the user is not a coordinator
550
555
  if (!(email?.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
551
556
  email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)) {
@@ -555,14 +560,18 @@ const registerAuthUser = functions__namespace
555
560
  const vars = getGitHubVariables();
556
561
  // this return true or false
557
562
  try {
558
- const res = await actions.githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos);
559
- if (!res) {
563
+ const { reputable, avatarUrl: avatarURL } = await actions.githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos, vars.minimumAge);
564
+ if (!reputable) {
560
565
  // Delete user
561
566
  await auth.deleteUser(user.uid);
562
567
  // Throw error
563
- logAndThrowError(makeError("permission-denied", "The user is not allowed to sign up because their Github reputation is not high enough.", `The user ${user.displayName} is not allowed to sign up because their Github reputation is not high enough. Please contact the administrator if you think this is a mistake.`));
568
+ logAndThrowError(makeError("permission-denied", "The user is not allowed to sign up because their Github reputation is not high enough.", `The user ${user.displayName === "Null" || user.displayName === null
569
+ ? user.uid
570
+ : user.displayName} is not allowed to sign up because their Github reputation is not high enough. Please contact the administrator if you think this is a mistake.`));
564
571
  }
565
- printLog(`Github reputation check passed for user ${user.displayName}`, LogLevel.DEBUG);
572
+ // store locally
573
+ avatarUrl = avatarURL;
574
+ printLog(`Github reputation check passed for user ${user.displayName === "Null" || user.displayName === null ? user.uid : user.displayName}`, LogLevel.DEBUG);
566
575
  }
567
576
  catch (error) {
568
577
  // Delete user
@@ -572,6 +581,8 @@ const registerAuthUser = functions__namespace
572
581
  }
573
582
  }
574
583
  // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
584
+ // In future releases we might want to loop through the providerData array as we support
585
+ // more providers.
575
586
  await userRef.set({
576
587
  name: encodedDisplayName,
577
588
  encodedDisplayName,
@@ -584,7 +595,13 @@ const registerAuthUser = functions__namespace
584
595
  photoURL: photoURL || "",
585
596
  lastUpdated: getCurrentServerTimestampInMillis()
586
597
  });
598
+ // we want to create a new collection for the users to store the avatars
599
+ const avatarRef = firestore.collection(actions.commonTerms.collections.avatars.name).doc(uid);
600
+ await avatarRef.set({
601
+ avatarUrl: avatarUrl || ""
602
+ });
587
603
  printLog(`Authenticated user document with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
604
+ printLog(`Authenticated user avatar with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
588
605
  });
589
606
  /**
590
607
  * Set custom claims for role-based access control on the newly created user.
@@ -721,7 +738,7 @@ const setupCeremony = functions__namespace
721
738
  // Check if using the VM approach for contribution verification.
722
739
  if (circuit.verification.cfOrVm === "VM" /* CircuitContributionVerificationMechanism.VM */) {
723
740
  // VM command to be run at the startup.
724
- const startupCommand = actions.vmBootstrapCommand(bucketName);
741
+ const startupCommand = actions.vmBootstrapCommand(`${bucketName}/circuits/${circuit.name}`);
725
742
  // Get EC2 client.
726
743
  const ec2Client = await createEC2Client();
727
744
  // Get AWS variables.
@@ -730,7 +747,8 @@ const setupCeremony = functions__namespace
730
747
  const vmCommands = actions.vmDependenciesAndCacheArtifactsCommand(`${bucketName}/${circuit.files?.initialZkeyStoragePath}`, `${bucketName}/${circuit.files?.potStoragePath}`, snsTopic, region);
731
748
  printLog(`Check VM dependencies and cache artifacts commands ${vmCommands.join("\n")}`, LogLevel.DEBUG);
732
749
  // Upload the post-startup commands script file.
733
- await uploadFileToBucketNoFile(bucketName, actions.vmBootstrapScriptFilename, vmCommands.join("\n"));
750
+ printLog(`Uploading VM post-startup commands script file ${actions.vmBootstrapScriptFilename}`, LogLevel.DEBUG);
751
+ await uploadFileToBucketNoFile(bucketName, `circuits/${circuit.name}/${actions.vmBootstrapScriptFilename}`, vmCommands.join("\n"));
734
752
  // Compute the VM disk space requirement (in GB).
735
753
  const vmDiskSize = actions.computeDiskSizeForVM(circuit.zKeySizeInBytes, circuit.metadata?.pot);
736
754
  printLog(`Check VM startup commands ${startupCommand.join("\n")}`, LogLevel.DEBUG);
@@ -824,7 +842,7 @@ const finalizeCeremony = functions__namespace
824
842
  // Get ceremony circuits.
825
843
  const circuits = await getCeremonyCircuits(ceremonyId);
826
844
  // Get final contribution for each circuit.
827
- // nb. the `getFinalContributionDocument` checks the existance of the final contribution document (if not present, throws).
845
+ // nb. the `getFinalContributionDocument` checks the existence of the final contribution document (if not present, throws).
828
846
  // Therefore, we just need to call the method without taking any data to verify the pre-condition of having already computed
829
847
  // the final contributions for each ceremony circuit.
830
848
  for await (const circuit of circuits)
@@ -877,7 +895,7 @@ dotenv.config();
877
895
  * @dev true when the participant can participate (1.A, 3.B, 1.D); otherwise false.
878
896
  */
879
897
  const checkParticipantForCeremony = functions__namespace
880
- .region('europe-west1')
898
+ .region("europe-west1")
881
899
  .runWith({
882
900
  memory: "512MB"
883
901
  })
@@ -948,7 +966,7 @@ const checkParticipantForCeremony = functions__namespace
948
966
  participantDoc.ref.update({
949
967
  status: "EXHUMED" /* ParticipantStatus.EXHUMED */,
950
968
  contributions,
951
- tempContributionData: tempContributionData ? tempContributionData : firestore.FieldValue.delete(),
969
+ tempContributionData: tempContributionData || firestore.FieldValue.delete(),
952
970
  contributionStep: "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */,
953
971
  contributionStartedAt: 0,
954
972
  verificationStartedAt: firestore.FieldValue.delete(),
@@ -981,7 +999,7 @@ const checkParticipantForCeremony = functions__namespace
981
999
  * 2) the participant has just finished the contribution for a circuit (contributionProgress != 0 && status = CONTRIBUTED && contributionStep = COMPLETED).
982
1000
  */
983
1001
  const progressToNextCircuitForContribution = functions__namespace
984
- .region('europe-west1')
1002
+ .region("europe-west1")
985
1003
  .runWith({
986
1004
  memory: "512MB"
987
1005
  })
@@ -1028,7 +1046,7 @@ const progressToNextCircuitForContribution = functions__namespace
1028
1046
  * 5) Completed contribution computation and verification.
1029
1047
  */
1030
1048
  const progressToNextContributionStep = functions__namespace
1031
- .region('europe-west1')
1049
+ .region("europe-west1")
1032
1050
  .runWith({
1033
1051
  memory: "512MB"
1034
1052
  })
@@ -1079,7 +1097,7 @@ const progressToNextContributionStep = functions__namespace
1079
1097
  * @dev enable the current contributor to resume a contribution from where it had left off.
1080
1098
  */
1081
1099
  const permanentlyStoreCurrentContributionTimeAndHash = functions__namespace
1082
- .region('europe-west1')
1100
+ .region("europe-west1")
1083
1101
  .runWith({
1084
1102
  memory: "512MB"
1085
1103
  })
@@ -1121,7 +1139,7 @@ const permanentlyStoreCurrentContributionTimeAndHash = functions__namespace
1121
1139
  * @dev enable the current contributor to resume a multi-part upload from where it had left off.
1122
1140
  */
1123
1141
  const temporaryStoreCurrentContributionMultiPartUploadId = functions__namespace
1124
- .region('europe-west1')
1142
+ .region("europe-west1")
1125
1143
  .runWith({
1126
1144
  memory: "512MB"
1127
1145
  })
@@ -1159,7 +1177,7 @@ const temporaryStoreCurrentContributionMultiPartUploadId = functions__namespace
1159
1177
  * @dev enable the current contributor to resume a multi-part upload from where it had left off.
1160
1178
  */
1161
1179
  const temporaryStoreCurrentContributionUploadedChunkData = functions__namespace
1162
- .region('europe-west1')
1180
+ .region("europe-west1")
1163
1181
  .runWith({
1164
1182
  memory: "512MB"
1165
1183
  })
@@ -1201,7 +1219,7 @@ const temporaryStoreCurrentContributionUploadedChunkData = functions__namespace
1201
1219
  * contributed to every selected ceremony circuits (= DONE).
1202
1220
  */
1203
1221
  const checkAndPrepareCoordinatorForFinalization = functions__namespace
1204
- .region('europe-west1')
1222
+ .region("europe-west1")
1205
1223
  .runWith({
1206
1224
  memory: "512MB"
1207
1225
  })
@@ -1292,6 +1310,7 @@ const coordinate = async (participant, circuit, isSingleParticipantCoordination,
1292
1310
  printLog(`Coordinate - executing scenario A - single - participantResumingAfterTimeoutExpiration`, LogLevel.DEBUG);
1293
1311
  newParticipantStatus = "CONTRIBUTING" /* ParticipantStatus.CONTRIBUTING */;
1294
1312
  newContributionStep = "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */;
1313
+ newCurrentContributorId = participant.id;
1295
1314
  }
1296
1315
  // Scenario (B).
1297
1316
  else if (participantIsNotCurrentContributor) {
@@ -1352,101 +1371,74 @@ const coordinate = async (participant, circuit, isSingleParticipantCoordination,
1352
1371
  * Wait until the command has completed its execution inside the VM.
1353
1372
  * @dev this method implements a custom interval to check 5 times after 1 minute if the command execution
1354
1373
  * has been completed or not by calling the `retrieveCommandStatus` method.
1355
- * @param {any} resolve the promise.
1356
- * @param {any} reject the promise.
1357
1374
  * @param {SSMClient} ssm the SSM client.
1358
1375
  * @param {string} vmInstanceId the unique identifier of the VM instance.
1359
1376
  * @param {string} commandId the unique identifier of the VM command.
1360
1377
  * @returns <Promise<void>> true when the command execution succeed; otherwise false.
1361
1378
  */
1362
- const waitForVMCommandExecution = (resolve, reject, ssm, vmInstanceId, commandId) => {
1363
- const interval = setInterval(async () => {
1379
+ const waitForVMCommandExecution = (ssm, vmInstanceId, commandId) => new Promise((resolve, reject) => {
1380
+ const poll = async () => {
1364
1381
  try {
1365
1382
  // Get command status.
1366
1383
  const cmdStatus = await actions.retrieveCommandStatus(ssm, vmInstanceId, commandId);
1367
1384
  printLog(`Checking command ${commandId} status => ${cmdStatus}`, LogLevel.DEBUG);
1368
- if (cmdStatus === clientSsm.CommandInvocationStatus.SUCCESS) {
1369
- printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG);
1370
- // Resolve the promise.
1371
- resolve();
1372
- }
1373
- else if (cmdStatus === clientSsm.CommandInvocationStatus.FAILED) {
1374
- logAndThrowError(SPECIFIC_ERRORS.SE_VM_FAILED_COMMAND_EXECUTION);
1375
- reject();
1376
- }
1377
- else if (cmdStatus === clientSsm.CommandInvocationStatus.TIMED_OUT) {
1378
- logAndThrowError(SPECIFIC_ERRORS.SE_VM_TIMEDOUT_COMMAND_EXECUTION);
1379
- reject();
1380
- }
1381
- else if (cmdStatus === clientSsm.CommandInvocationStatus.CANCELLED) {
1382
- logAndThrowError(SPECIFIC_ERRORS.SE_VM_CANCELLED_COMMAND_EXECUTION);
1383
- reject();
1385
+ let error;
1386
+ switch (cmdStatus) {
1387
+ case clientSsm.CommandInvocationStatus.CANCELLING:
1388
+ case clientSsm.CommandInvocationStatus.CANCELLED: {
1389
+ error = SPECIFIC_ERRORS.SE_VM_CANCELLED_COMMAND_EXECUTION;
1390
+ break;
1391
+ }
1392
+ case clientSsm.CommandInvocationStatus.DELAYED: {
1393
+ error = SPECIFIC_ERRORS.SE_VM_DELAYED_COMMAND_EXECUTION;
1394
+ break;
1395
+ }
1396
+ case clientSsm.CommandInvocationStatus.FAILED: {
1397
+ error = SPECIFIC_ERRORS.SE_VM_FAILED_COMMAND_EXECUTION;
1398
+ break;
1399
+ }
1400
+ case clientSsm.CommandInvocationStatus.TIMED_OUT: {
1401
+ error = SPECIFIC_ERRORS.SE_VM_TIMEDOUT_COMMAND_EXECUTION;
1402
+ break;
1403
+ }
1404
+ case clientSsm.CommandInvocationStatus.IN_PROGRESS:
1405
+ case clientSsm.CommandInvocationStatus.PENDING: {
1406
+ // wait a minute and poll again
1407
+ setTimeout(poll, 60000);
1408
+ return;
1409
+ }
1410
+ case clientSsm.CommandInvocationStatus.SUCCESS: {
1411
+ printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG);
1412
+ // Resolve the promise.
1413
+ resolve();
1414
+ return;
1415
+ }
1416
+ default: {
1417
+ logAndThrowError(SPECIFIC_ERRORS.SE_VM_UNKNOWN_COMMAND_STATUS);
1418
+ }
1384
1419
  }
1385
- else if (cmdStatus === clientSsm.CommandInvocationStatus.DELAYED) {
1386
- logAndThrowError(SPECIFIC_ERRORS.SE_VM_DELAYED_COMMAND_EXECUTION);
1387
- reject();
1420
+ if (error) {
1421
+ logAndThrowError(error);
1388
1422
  }
1389
1423
  }
1390
1424
  catch (error) {
1391
1425
  printLog(`Invalid command ${commandId} execution`, LogLevel.DEBUG);
1426
+ const ec2 = await createEC2Client();
1427
+ // if it errors out, let's just log it as a warning so the coordinator is aware
1428
+ try {
1429
+ await actions.stopEC2Instance(ec2, vmInstanceId);
1430
+ }
1431
+ catch (error) {
1432
+ printLog(`Error while stopping VM instance ${vmInstanceId} - Error ${error}`, LogLevel.WARN);
1433
+ }
1392
1434
  if (!error.toString().includes(commandId))
1393
1435
  logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION);
1394
1436
  // Reject the promise.
1395
1437
  reject();
1396
1438
  }
1397
- finally {
1398
- // Clear the interval.
1399
- clearInterval(interval);
1400
- }
1401
- }, 60000); // 1 minute.
1402
- };
1403
- /**
1404
- * Wait until the artifacts have been downloaded.
1405
- * @param {any} resolve the promise.
1406
- * @param {any} reject the promise.
1407
- * @param {string} potTempFilePath the tmp path to the locally downloaded pot file.
1408
- * @param {string} firstZkeyTempFilePath the tmp path to the locally downloaded first zkey file.
1409
- * @param {string} lastZkeyTempFilePath the tmp path to the locally downloaded last zkey file.
1410
- */
1411
- const waitForFileDownload = (resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantId) => {
1412
- const maxWaitTime = 5 * 60 * 1000; // 5 minutes
1413
- // every second check if the file download was completed
1414
- const interval = setInterval(async () => {
1415
- printLog(`Verifying that the artifacts were downloaded for circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG);
1416
- try {
1417
- // check if files have been downloaded
1418
- if (!fs.existsSync(potTempFilePath)) {
1419
- printLog(`Pot file not found at ${potTempFilePath}`, LogLevel.DEBUG);
1420
- }
1421
- if (!fs.existsSync(firstZkeyTempFilePath)) {
1422
- printLog(`First zkey file not found at ${firstZkeyTempFilePath}`, LogLevel.DEBUG);
1423
- }
1424
- if (!fs.existsSync(lastZkeyTempFilePath)) {
1425
- printLog(`Last zkey file not found at ${lastZkeyTempFilePath}`, LogLevel.DEBUG);
1426
- }
1427
- // if all files were downloaded
1428
- if (fs.existsSync(potTempFilePath) && fs.existsSync(firstZkeyTempFilePath) && fs.existsSync(lastZkeyTempFilePath)) {
1429
- printLog(`All required files are present on disk.`, LogLevel.INFO);
1430
- // resolve the promise
1431
- resolve();
1432
- }
1433
- }
1434
- catch (error) {
1435
- // if we have an error then we print it as a warning and reject
1436
- printLog(`Error while downloading files: ${error}`, LogLevel.WARN);
1437
- reject();
1438
- }
1439
- finally {
1440
- printLog(`Clearing the interval for file download. Circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG);
1441
- clearInterval(interval);
1442
- }
1443
- }, 5000);
1444
- // we want to clean in 5 minutes in case
1445
- setTimeout(() => {
1446
- clearInterval(interval);
1447
- reject(new Error('Timeout exceeded while waiting for files to be downloaded.'));
1448
- }, maxWaitTime);
1449
- };
1439
+ };
1440
+ setTimeout(poll, 60000);
1441
+ });
1450
1442
  /**
1451
1443
  * This method is used to coordinate the waiting queues of ceremony circuits.
1452
1444
  * @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
@@ -1467,7 +1459,7 @@ const waitForFileDownload = (resolve, reject, potTempFilePath, firstZkeyTempFile
1467
1459
  * - Just completed a contribution or all contributions for each circuit. If yes, coordinate (multi-participant scenario).
1468
1460
  */
1469
1461
  const coordinateCeremonyParticipant = functionsV1__namespace
1470
- .region('europe-west1')
1462
+ .region("europe-west1")
1471
1463
  .runWith({
1472
1464
  memory: "512MB"
1473
1465
  })
@@ -1538,11 +1530,9 @@ const checkIfVMRunning = async (ec2, vmInstanceId, attempts = 5) => {
1538
1530
  const isVMRunning = await actions.checkIfRunning(ec2, vmInstanceId);
1539
1531
  if (!isVMRunning) {
1540
1532
  printLog(`VM not running, ${attempts - 1} attempts remaining. Retrying in 1 minute...`, LogLevel.DEBUG);
1541
- return await checkIfVMRunning(ec2, vmInstanceId, attempts - 1);
1542
- }
1543
- else {
1544
- return true;
1533
+ return checkIfVMRunning(ec2, vmInstanceId, attempts - 1);
1545
1534
  }
1535
+ return true;
1546
1536
  };
1547
1537
  /**
1548
1538
  * Verify the contribution of a participant computed while contributing to a specific circuit of a ceremony.
@@ -1570,7 +1560,7 @@ const checkIfVMRunning = async (ec2, vmInstanceId, attempts = 5) => {
1570
1560
  * 1.A.4.C.1) If true, update circuit waiting for queue and average timings accordingly to contribution verification results;
1571
1561
  * 2) Send all updates atomically to the Firestore database.
1572
1562
  */
1573
- const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB", timeoutSeconds: 3600, region: 'europe-west1' }, async (request) => {
1563
+ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB", timeoutSeconds: 3600, region: "europe-west1" }, async (request) => {
1574
1564
  if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
1575
1565
  logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER);
1576
1566
  if (!request.data.ceremonyId ||
@@ -1681,8 +1671,6 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1681
1671
  lastZkeyBlake2bHash = match.at(0);
1682
1672
  // re upload the formatted verification transcript
1683
1673
  await uploadFileToBucket(bucketName, verificationTranscriptStoragePathAndFilename, verificationTranscriptTemporaryLocalPath, true);
1684
- // Stop VM instance.
1685
- await actions.stopEC2Instance(ec2, vmInstanceId);
1686
1674
  }
1687
1675
  else {
1688
1676
  // Upload verification transcript.
@@ -1743,6 +1731,18 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1743
1731
  lastUpdated: getCurrentServerTimestampInMillis()
1744
1732
  });
1745
1733
  }
1734
+ // Stop VM instance
1735
+ if (isUsingVM) {
1736
+ // using try and catch as the VM stopping function can throw
1737
+ // however we want to continue without stopping as the
1738
+ // verification was valid, and inform the coordinator
1739
+ try {
1740
+ await actions.stopEC2Instance(ec2, vmInstanceId);
1741
+ }
1742
+ catch (error) {
1743
+ printLog(`Error while stopping VM instance ${vmInstanceId} - Error ${error}`, LogLevel.WARN);
1744
+ }
1745
+ }
1746
1746
  // Step (1.A.4.C)
1747
1747
  if (!isFinalizing) {
1748
1748
  // Step (1.A.4.C.1)
@@ -1758,6 +1758,8 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1758
1758
  ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
1759
1759
  : verifyCloudFunctionTime;
1760
1760
  // Prepare tx to update circuit average contribution/verification time.
1761
+ const updatedCircuitDoc = await getDocumentById(actions.getCircuitsCollectionPath(ceremonyId), circuitId);
1762
+ const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data();
1761
1763
  /// @dev this must happen only for valid contributions.
1762
1764
  batch.update(circuitDoc.ref, {
1763
1765
  avgTimings: {
@@ -1770,7 +1772,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1770
1772
  : avgVerifyCloudFunctionTime
1771
1773
  },
1772
1774
  waitingQueue: {
1773
- ...waitingQueue,
1775
+ ...updatedWaitingQueue,
1774
1776
  completedContributions: isContributionValid
1775
1777
  ? completedContributions + 1
1776
1778
  : completedContributions,
@@ -1805,7 +1807,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1805
1807
  commandId = await actions.runCommandUsingSSM(ssm, vmInstanceId, verificationCommand);
1806
1808
  printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG);
1807
1809
  // Step (1.A.3.3).
1808
- return new Promise((resolve, reject) => waitForVMCommandExecution(resolve, reject, ssm, vmInstanceId, commandId))
1810
+ return waitForVMCommandExecution(ssm, vmInstanceId, commandId)
1809
1811
  .then(async () => {
1810
1812
  // Command execution successfully completed.
1811
1813
  printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG);
@@ -1817,52 +1819,38 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1817
1819
  logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION);
1818
1820
  });
1819
1821
  }
1820
- else {
1821
- // CF approach.
1822
- printLog(`CF mechanism`, LogLevel.DEBUG);
1823
- const potStoragePath = actions.getPotStorageFilePath(files.potFilename);
1824
- const firstZkeyStoragePath = actions.getZkeyStorageFilePath(prefix, `${prefix}_${actions.genesisZkeyIndex}.zkey`);
1825
- // Prepare temporary file paths.
1826
- // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
1827
- verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename);
1828
- const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`);
1829
- const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`);
1830
- const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`);
1831
- // Create and populate transcript.
1832
- const transcriptLogger = actions.createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath);
1833
- transcriptLogger.info(`${isFinalizing ? `Final verification` : `Verification`} transcript for ${prefix} circuit Phase 2 contribution.\n${isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`} (${contributorOrCoordinatorIdentifier})\n`);
1834
- // Step (1.A.2).
1835
- await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath);
1836
- await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath);
1837
- await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath);
1838
- await sleep(6000);
1839
- // wait until the files are actually downloaded
1840
- return new Promise((resolve, reject) => waitForFileDownload(resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantDoc.id))
1841
- .then(async () => {
1842
- printLog(`Downloads from AWS S3 bucket completed - ceremony ${ceremonyId} circuit ${circuitId}`, LogLevel.DEBUG);
1843
- // Step (1.A.4).
1844
- isContributionValid = await snarkjs.zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger);
1845
- // Compute contribution hash.
1846
- lastZkeyBlake2bHash = await actions.blake512FromPath(lastZkeyTempFilePath);
1847
- // Free resources by unlinking temporary folders.
1848
- // Do not free-up verification transcript path here.
1849
- try {
1850
- fs.unlinkSync(potTempFilePath);
1851
- fs.unlinkSync(firstZkeyTempFilePath);
1852
- fs.unlinkSync(lastZkeyTempFilePath);
1853
- }
1854
- catch (error) {
1855
- printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
1856
- }
1857
- await completeVerification();
1858
- })
1859
- .catch((error) => {
1860
- // Throw the new error
1861
- const commonError = COMMON_ERRORS.CM_INVALID_REQUEST;
1862
- const additionalDetails = error.toString();
1863
- logAndThrowError(makeError(commonError.code, commonError.message, additionalDetails));
1864
- });
1822
+ // CF approach.
1823
+ printLog(`CF mechanism`, LogLevel.DEBUG);
1824
+ const potStoragePath = actions.getPotStorageFilePath(files.potFilename);
1825
+ const firstZkeyStoragePath = actions.getZkeyStorageFilePath(prefix, `${prefix}_${actions.genesisZkeyIndex}.zkey`);
1826
+ // Prepare temporary file paths.
1827
+ // (nb. these are needed to download the necessary artifacts for verification from AWS S3).
1828
+ verificationTranscriptTemporaryLocalPath = createTemporaryLocalPath(verificationTranscriptCompleteFilename);
1829
+ const potTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}.pot`);
1830
+ const firstZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_genesis.zkey`);
1831
+ const lastZkeyTempFilePath = createTemporaryLocalPath(`${circuitId}_${participantDoc.id}_last.zkey`);
1832
+ // Create and populate transcript.
1833
+ const transcriptLogger = actions.createCustomLoggerForFile(verificationTranscriptTemporaryLocalPath);
1834
+ transcriptLogger.info(`${isFinalizing ? `Final verification` : `Verification`} transcript for ${prefix} circuit Phase 2 contribution.\n${isFinalizing ? `Coordinator ` : `Contributor # ${Number(lastZkeyIndex)}`} (${contributorOrCoordinatorIdentifier})\n`);
1835
+ // Step (1.A.2).
1836
+ await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath);
1837
+ await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath);
1838
+ await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath);
1839
+ // Step (1.A.4).
1840
+ isContributionValid = await snarkjs.zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger);
1841
+ // Compute contribution hash.
1842
+ lastZkeyBlake2bHash = await actions.blake512FromPath(lastZkeyTempFilePath);
1843
+ // Free resources by unlinking temporary folders.
1844
+ // Do not free-up verification transcript path here.
1845
+ try {
1846
+ fs.unlinkSync(potTempFilePath);
1847
+ fs.unlinkSync(firstZkeyTempFilePath);
1848
+ fs.unlinkSync(lastZkeyTempFilePath);
1849
+ }
1850
+ catch (error) {
1851
+ printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
1865
1852
  }
1853
+ await completeVerification();
1866
1854
  }
1867
1855
  });
1868
1856
  /**
@@ -1871,7 +1859,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1871
1859
  * this does not happen if the participant is actually the coordinator who is finalizing the ceremony.
1872
1860
  */
1873
1861
  const refreshParticipantAfterContributionVerification = functionsV1__namespace
1874
- .region('europe-west1')
1862
+ .region("europe-west1")
1875
1863
  .runWith({
1876
1864
  memory: "512MB"
1877
1865
  })
@@ -1932,7 +1920,7 @@ const refreshParticipantAfterContributionVerification = functionsV1__namespace
1932
1920
  * and verification key extracted from the circuit final contribution (as part of the ceremony finalization process).
1933
1921
  */
1934
1922
  const finalizeCircuit = functionsV1__namespace
1935
- .region('europe-west1')
1923
+ .region("europe-west1")
1936
1924
  .runWith({
1937
1925
  memory: "512MB"
1938
1926
  })
@@ -2129,8 +2117,10 @@ const createBucket = functions__namespace
2129
2117
  CORSConfiguration: {
2130
2118
  CORSRules: [
2131
2119
  {
2132
- AllowedMethods: ["GET"],
2133
- AllowedOrigins: ["*"]
2120
+ AllowedMethods: ["GET", "PUT"],
2121
+ AllowedOrigins: ["*"],
2122
+ ExposeHeaders: ["ETag", "Content-Length"],
2123
+ AllowedHeaders: ["*"]
2134
2124
  }
2135
2125
  ]
2136
2126
  }
@@ -2307,7 +2297,8 @@ const startMultiPartUpload = functions__namespace
2307
2297
  const generatePreSignedUrlsParts = functions__namespace
2308
2298
  .region("europe-west1")
2309
2299
  .runWith({
2310
- memory: "512MB"
2300
+ memory: "512MB",
2301
+ timeoutSeconds: 300
2311
2302
  })
2312
2303
  .https.onCall(async (data, context) => {
2313
2304
  if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
@@ -2457,7 +2448,7 @@ const checkAndRemoveBlockingContributor = functions__namespace
2457
2448
  // Get ceremony circuits.
2458
2449
  const circuits = await getCeremonyCircuits(ceremony.id);
2459
2450
  // Extract ceremony data.
2460
- const { timeoutMechanismType, penalty } = ceremony.data();
2451
+ const { timeoutType: timeoutMechanismType, penalty } = ceremony.data();
2461
2452
  for (const circuit of circuits) {
2462
2453
  if (!circuit.data())
2463
2454
  // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
@@ -2523,7 +2514,7 @@ const checkAndRemoveBlockingContributor = functions__namespace
2523
2514
  // Prepare Firestore batch of txs.
2524
2515
  const batch = firestore.batch();
2525
2516
  // Remove current contributor from waiting queue.
2526
- contributors.shift(1);
2517
+ contributors.shift();
2527
2518
  // Check if someone else is ready to start the contribution.
2528
2519
  if (contributors.length > 0) {
2529
2520
  // Step (E.1).
@@ -2607,7 +2598,8 @@ const resumeContributionAfterTimeoutExpiration = functions__namespace
2607
2598
  if (status === "EXHUMED" /* ParticipantStatus.EXHUMED */)
2608
2599
  await participantDoc.ref.update({
2609
2600
  status: "READY" /* ParticipantStatus.READY */,
2610
- lastUpdated: getCurrentServerTimestampInMillis()
2601
+ lastUpdated: getCurrentServerTimestampInMillis(),
2602
+ tempContributionData: {}
2611
2603
  });
2612
2604
  else
2613
2605
  logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_CANNOT_PROGRESS_TO_NEXT_CIRCUIT);