@devtion/backend 0.0.0-7e983e3 → 0.0.0-9c50f66

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.
package/README.md CHANGED
@@ -100,6 +100,32 @@ yarn firebase:init
100
100
 
101
101
  ### Deployment
102
102
 
103
+ #### AWS Infrastructure
104
+
105
+ 0. Login or create a [new AWS Account](https://portal.aws.amazon.com/billing/signup?nc2=h_ct&src=header_signup&redirect_url=https%3A%2F%2Faws.amazon.com%2Fregistration-confirmation#/start/email).
106
+ - The AWS free tier account will cover a good number of requests for ceremonies but there could be some costs based on your ceremony circuits size.
107
+ 1. Create an access key for a user with Admin privileges (__NOT ROOT USER__)
108
+ 2. Setup the `awscli` ([docs](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)) and add the keys for this user.
109
+ 3. Install `terraform` ([docs](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli))
110
+ 4. Decide on an AWS region (by default this is **us-east-1**) - if you want to change you will need to do the following:
111
+ 1. update **aws/lambda/index.mjs** ([exact line](https://github.com/privacy-scaling-explorations/p0tion/blob/dev/packages/backend/aws/lambda/index.mjs#L3)) to the new region
112
+ 2. update **main.tf** ([exact line](https://github.com/privacy-scaling-explorations/p0tion/blob/dev/packages/backend/aws/main.tf#L2)) to the new region
113
+ 5. zip the Lambda folder:
114
+ 1. `cd lambda`
115
+ 2. `zip -r ../lambda.zip .`
116
+ 6. Run terraform:
117
+ 1. `terraform init`
118
+ 2. `terraform plan`
119
+ 3. `terraform apply`
120
+ 4. `terraform output secret_key`
121
+ - To print the secret access key for the IAM user
122
+ 6. Store the other values (sns_topic_arn etc.)
123
+ - These will be needed for the .env file configuration
124
+
125
+ The IAM user created with the steps above can be used for all p0tion's features.
126
+
127
+ #### Firebase
128
+
103
129
  Deploy the current configuration to the `prod` project running
104
130
 
105
131
  ```bash
@@ -544,8 +544,10 @@ const registerAuthUser = functions__namespace
544
544
  const { uid } = user;
545
545
  // Reference to a document using uid.
546
546
  const userRef = firestore.collection(actions.commonTerms.collections.users.name).doc(uid);
547
- // html encode the display name
548
- const encodedDisplayName = htmlEntities.encode(displayName);
547
+ // html encode the display name (or put the ID if the name is not displayed)
548
+ const encodedDisplayName = user.displayName === "Null" || user.displayName === null ? user.uid : htmlEntities.encode(displayName);
549
+ // store the avatar URL of a contributor
550
+ let avatarUrl = "";
549
551
  // we only do reputation check if the user is not a coordinator
550
552
  if (!(email?.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
551
553
  email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)) {
@@ -555,14 +557,16 @@ const registerAuthUser = functions__namespace
555
557
  const vars = getGitHubVariables();
556
558
  // this return true or false
557
559
  try {
558
- const res = await actions.githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos);
559
- if (!res) {
560
+ const { reputable, avatarUrl: avatarURL } = await actions.githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos);
561
+ if (!reputable) {
560
562
  // Delete user
561
563
  await auth.deleteUser(user.uid);
562
564
  // 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.`));
565
+ 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 ? user.uid : 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
566
  }
565
- printLog(`Github reputation check passed for user ${user.displayName}`, LogLevel.DEBUG);
567
+ // store locally
568
+ avatarUrl = avatarURL;
569
+ printLog(`Github reputation check passed for user ${user.displayName === "Null" || user.displayName === null ? user.uid : user.displayName}`, LogLevel.DEBUG);
566
570
  }
567
571
  catch (error) {
568
572
  // Delete user
@@ -572,6 +576,8 @@ const registerAuthUser = functions__namespace
572
576
  }
573
577
  }
574
578
  // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
579
+ // In future releases we might want to loop through the providerData array as we support
580
+ // more providers.
575
581
  await userRef.set({
576
582
  name: encodedDisplayName,
577
583
  encodedDisplayName,
@@ -584,7 +590,13 @@ const registerAuthUser = functions__namespace
584
590
  photoURL: photoURL || "",
585
591
  lastUpdated: getCurrentServerTimestampInMillis()
586
592
  });
593
+ // we want to create a new collection for the users to store the avatars
594
+ const avatarRef = firestore.collection(actions.commonTerms.collections.avatars.name).doc(uid);
595
+ await avatarRef.set({
596
+ avatarUrl: avatarUrl || "",
597
+ });
587
598
  printLog(`Authenticated user document with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
599
+ printLog(`Authenticated user avatar with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
588
600
  });
589
601
  /**
590
602
  * Set custom claims for role-based access control on the newly created user.
@@ -1292,6 +1304,7 @@ const coordinate = async (participant, circuit, isSingleParticipantCoordination,
1292
1304
  printLog(`Coordinate - executing scenario A - single - participantResumingAfterTimeoutExpiration`, LogLevel.DEBUG);
1293
1305
  newParticipantStatus = "CONTRIBUTING" /* ParticipantStatus.CONTRIBUTING */;
1294
1306
  newContributionStep = "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */;
1307
+ newCurrentContributorId = participant.id;
1295
1308
  }
1296
1309
  // Scenario (B).
1297
1310
  else if (participantIsNotCurrentContributor) {
@@ -1400,53 +1413,6 @@ const waitForVMCommandExecution = (resolve, reject, ssm, vmInstanceId, commandId
1400
1413
  }
1401
1414
  }, 60000); // 1 minute.
1402
1415
  };
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
- };
1450
1416
  /**
1451
1417
  * This method is used to coordinate the waiting queues of ceremony circuits.
1452
1418
  * @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
@@ -1757,7 +1723,9 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1757
1723
  const newAvgVerifyCloudFunctionTime = avgVerifyCloudFunctionTime > 0
1758
1724
  ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
1759
1725
  : verifyCloudFunctionTime;
1760
- // Prepare tx to update circuit average contribution/verification time.
1726
+ // Prepare tx to update circuit average contribution/verification time.
1727
+ const updatedCircuitDoc = await getDocumentById(actions.getCircuitsCollectionPath(ceremonyId), circuitId);
1728
+ const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data();
1761
1729
  /// @dev this must happen only for valid contributions.
1762
1730
  batch.update(circuitDoc.ref, {
1763
1731
  avgTimings: {
@@ -1770,7 +1738,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1770
1738
  : avgVerifyCloudFunctionTime
1771
1739
  },
1772
1740
  waitingQueue: {
1773
- ...waitingQueue,
1741
+ ...updatedWaitingQueue,
1774
1742
  completedContributions: isContributionValid
1775
1743
  ? completedContributions + 1
1776
1744
  : completedContributions,
@@ -1835,33 +1803,21 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
1835
1803
  await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath);
1836
1804
  await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath);
1837
1805
  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
- });
1806
+ // Step (1.A.4).
1807
+ isContributionValid = await snarkjs.zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger);
1808
+ // Compute contribution hash.
1809
+ lastZkeyBlake2bHash = await actions.blake512FromPath(lastZkeyTempFilePath);
1810
+ // Free resources by unlinking temporary folders.
1811
+ // Do not free-up verification transcript path here.
1812
+ try {
1813
+ fs.unlinkSync(potTempFilePath);
1814
+ fs.unlinkSync(firstZkeyTempFilePath);
1815
+ fs.unlinkSync(lastZkeyTempFilePath);
1816
+ }
1817
+ catch (error) {
1818
+ printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
1819
+ }
1820
+ await completeVerification();
1865
1821
  }
1866
1822
  }
1867
1823
  });
@@ -2523,7 +2479,7 @@ const checkAndRemoveBlockingContributor = functions__namespace
2523
2479
  // Prepare Firestore batch of txs.
2524
2480
  const batch = firestore.batch();
2525
2481
  // Remove current contributor from waiting queue.
2526
- contributors.shift(1);
2482
+ contributors.shift();
2527
2483
  // Check if someone else is ready to start the contribution.
2528
2484
  if (contributors.length > 0) {
2529
2485
  // Step (E.1).
@@ -19,7 +19,7 @@ import { pipeline } from 'node:stream';
19
19
  import { promisify } from 'node:util';
20
20
  import fs, { readFileSync } from 'fs';
21
21
  import mime from 'mime-types';
22
- import { setTimeout as setTimeout$1 } from 'timers/promises';
22
+ import { setTimeout } from 'timers/promises';
23
23
  import fetch from '@adobe/node-fetch-retry';
24
24
  import path from 'path';
25
25
  import os from 'os';
@@ -191,7 +191,7 @@ const getCurrentServerTimestampInMillis = () => Timestamp.now().toMillis();
191
191
  * Interrupt the current execution for a specified amount of time.
192
192
  * @param ms <number> - the amount of time expressed in milliseconds.
193
193
  */
194
- const sleep = async (ms) => setTimeout$1(ms);
194
+ const sleep = async (ms) => setTimeout(ms);
195
195
  /**
196
196
  * Query for ceremony circuits.
197
197
  * @notice the order by sequence position is fundamental to maintain parallelism among contributions for different circuits.
@@ -521,8 +521,10 @@ const registerAuthUser = functions
521
521
  const { uid } = user;
522
522
  // Reference to a document using uid.
523
523
  const userRef = firestore.collection(commonTerms.collections.users.name).doc(uid);
524
- // html encode the display name
525
- const encodedDisplayName = encode(displayName);
524
+ // html encode the display name (or put the ID if the name is not displayed)
525
+ const encodedDisplayName = user.displayName === "Null" || user.displayName === null ? user.uid : encode(displayName);
526
+ // store the avatar URL of a contributor
527
+ let avatarUrl = "";
526
528
  // we only do reputation check if the user is not a coordinator
527
529
  if (!(email?.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
528
530
  email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)) {
@@ -532,14 +534,16 @@ const registerAuthUser = functions
532
534
  const vars = getGitHubVariables();
533
535
  // this return true or false
534
536
  try {
535
- const res = await githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos);
536
- if (!res) {
537
+ const { reputable, avatarUrl: avatarURL } = await githubReputation(user.providerData[0].uid, vars.minimumFollowing, vars.minimumFollowers, vars.minimumPublicRepos);
538
+ if (!reputable) {
537
539
  // Delete user
538
540
  await auth.deleteUser(user.uid);
539
541
  // Throw error
540
- 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.`));
542
+ 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 ? user.uid : 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.`));
541
543
  }
542
- printLog(`Github reputation check passed for user ${user.displayName}`, LogLevel.DEBUG);
544
+ // store locally
545
+ avatarUrl = avatarURL;
546
+ printLog(`Github reputation check passed for user ${user.displayName === "Null" || user.displayName === null ? user.uid : user.displayName}`, LogLevel.DEBUG);
543
547
  }
544
548
  catch (error) {
545
549
  // Delete user
@@ -549,6 +553,8 @@ const registerAuthUser = functions
549
553
  }
550
554
  }
551
555
  // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
556
+ // In future releases we might want to loop through the providerData array as we support
557
+ // more providers.
552
558
  await userRef.set({
553
559
  name: encodedDisplayName,
554
560
  encodedDisplayName,
@@ -561,7 +567,13 @@ const registerAuthUser = functions
561
567
  photoURL: photoURL || "",
562
568
  lastUpdated: getCurrentServerTimestampInMillis()
563
569
  });
570
+ // we want to create a new collection for the users to store the avatars
571
+ const avatarRef = firestore.collection(commonTerms.collections.avatars.name).doc(uid);
572
+ await avatarRef.set({
573
+ avatarUrl: avatarUrl || "",
574
+ });
564
575
  printLog(`Authenticated user document with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
576
+ printLog(`Authenticated user avatar with identifier ${uid} has been correctly stored`, LogLevel.DEBUG);
565
577
  });
566
578
  /**
567
579
  * Set custom claims for role-based access control on the newly created user.
@@ -1269,6 +1281,7 @@ const coordinate = async (participant, circuit, isSingleParticipantCoordination,
1269
1281
  printLog(`Coordinate - executing scenario A - single - participantResumingAfterTimeoutExpiration`, LogLevel.DEBUG);
1270
1282
  newParticipantStatus = "CONTRIBUTING" /* ParticipantStatus.CONTRIBUTING */;
1271
1283
  newContributionStep = "DOWNLOADING" /* ParticipantContributionStep.DOWNLOADING */;
1284
+ newCurrentContributorId = participant.id;
1272
1285
  }
1273
1286
  // Scenario (B).
1274
1287
  else if (participantIsNotCurrentContributor) {
@@ -1377,53 +1390,6 @@ const waitForVMCommandExecution = (resolve, reject, ssm, vmInstanceId, commandId
1377
1390
  }
1378
1391
  }, 60000); // 1 minute.
1379
1392
  };
1380
- /**
1381
- * Wait until the artifacts have been downloaded.
1382
- * @param {any} resolve the promise.
1383
- * @param {any} reject the promise.
1384
- * @param {string} potTempFilePath the tmp path to the locally downloaded pot file.
1385
- * @param {string} firstZkeyTempFilePath the tmp path to the locally downloaded first zkey file.
1386
- * @param {string} lastZkeyTempFilePath the tmp path to the locally downloaded last zkey file.
1387
- */
1388
- const waitForFileDownload = (resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantId) => {
1389
- const maxWaitTime = 5 * 60 * 1000; // 5 minutes
1390
- // every second check if the file download was completed
1391
- const interval = setInterval(async () => {
1392
- printLog(`Verifying that the artifacts were downloaded for circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG);
1393
- try {
1394
- // check if files have been downloaded
1395
- if (!fs.existsSync(potTempFilePath)) {
1396
- printLog(`Pot file not found at ${potTempFilePath}`, LogLevel.DEBUG);
1397
- }
1398
- if (!fs.existsSync(firstZkeyTempFilePath)) {
1399
- printLog(`First zkey file not found at ${firstZkeyTempFilePath}`, LogLevel.DEBUG);
1400
- }
1401
- if (!fs.existsSync(lastZkeyTempFilePath)) {
1402
- printLog(`Last zkey file not found at ${lastZkeyTempFilePath}`, LogLevel.DEBUG);
1403
- }
1404
- // if all files were downloaded
1405
- if (fs.existsSync(potTempFilePath) && fs.existsSync(firstZkeyTempFilePath) && fs.existsSync(lastZkeyTempFilePath)) {
1406
- printLog(`All required files are present on disk.`, LogLevel.INFO);
1407
- // resolve the promise
1408
- resolve();
1409
- }
1410
- }
1411
- catch (error) {
1412
- // if we have an error then we print it as a warning and reject
1413
- printLog(`Error while downloading files: ${error}`, LogLevel.WARN);
1414
- reject();
1415
- }
1416
- finally {
1417
- printLog(`Clearing the interval for file download. Circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG);
1418
- clearInterval(interval);
1419
- }
1420
- }, 5000);
1421
- // we want to clean in 5 minutes in case
1422
- setTimeout(() => {
1423
- clearInterval(interval);
1424
- reject(new Error('Timeout exceeded while waiting for files to be downloaded.'));
1425
- }, maxWaitTime);
1426
- };
1427
1393
  /**
1428
1394
  * This method is used to coordinate the waiting queues of ceremony circuits.
1429
1395
  * @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
@@ -1734,7 +1700,9 @@ const verifycontribution = functionsV2.https.onCall({ memory: "16GiB", timeoutSe
1734
1700
  const newAvgVerifyCloudFunctionTime = avgVerifyCloudFunctionTime > 0
1735
1701
  ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
1736
1702
  : verifyCloudFunctionTime;
1737
- // Prepare tx to update circuit average contribution/verification time.
1703
+ // Prepare tx to update circuit average contribution/verification time.
1704
+ const updatedCircuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId);
1705
+ const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data();
1738
1706
  /// @dev this must happen only for valid contributions.
1739
1707
  batch.update(circuitDoc.ref, {
1740
1708
  avgTimings: {
@@ -1747,7 +1715,7 @@ const verifycontribution = functionsV2.https.onCall({ memory: "16GiB", timeoutSe
1747
1715
  : avgVerifyCloudFunctionTime
1748
1716
  },
1749
1717
  waitingQueue: {
1750
- ...waitingQueue,
1718
+ ...updatedWaitingQueue,
1751
1719
  completedContributions: isContributionValid
1752
1720
  ? completedContributions + 1
1753
1721
  : completedContributions,
@@ -1812,33 +1780,21 @@ const verifycontribution = functionsV2.https.onCall({ memory: "16GiB", timeoutSe
1812
1780
  await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath);
1813
1781
  await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath);
1814
1782
  await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath);
1815
- await sleep(6000);
1816
- // wait until the files are actually downloaded
1817
- return new Promise((resolve, reject) => waitForFileDownload(resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantDoc.id))
1818
- .then(async () => {
1819
- printLog(`Downloads from AWS S3 bucket completed - ceremony ${ceremonyId} circuit ${circuitId}`, LogLevel.DEBUG);
1820
- // Step (1.A.4).
1821
- isContributionValid = await zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger);
1822
- // Compute contribution hash.
1823
- lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath);
1824
- // Free resources by unlinking temporary folders.
1825
- // Do not free-up verification transcript path here.
1826
- try {
1827
- fs.unlinkSync(potTempFilePath);
1828
- fs.unlinkSync(firstZkeyTempFilePath);
1829
- fs.unlinkSync(lastZkeyTempFilePath);
1830
- }
1831
- catch (error) {
1832
- printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
1833
- }
1834
- await completeVerification();
1835
- })
1836
- .catch((error) => {
1837
- // Throw the new error
1838
- const commonError = COMMON_ERRORS.CM_INVALID_REQUEST;
1839
- const additionalDetails = error.toString();
1840
- logAndThrowError(makeError(commonError.code, commonError.message, additionalDetails));
1841
- });
1783
+ // Step (1.A.4).
1784
+ isContributionValid = await zKey.verifyFromInit(firstZkeyTempFilePath, potTempFilePath, lastZkeyTempFilePath, transcriptLogger);
1785
+ // Compute contribution hash.
1786
+ lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath);
1787
+ // Free resources by unlinking temporary folders.
1788
+ // Do not free-up verification transcript path here.
1789
+ try {
1790
+ fs.unlinkSync(potTempFilePath);
1791
+ fs.unlinkSync(firstZkeyTempFilePath);
1792
+ fs.unlinkSync(lastZkeyTempFilePath);
1793
+ }
1794
+ catch (error) {
1795
+ printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
1796
+ }
1797
+ await completeVerification();
1842
1798
  }
1843
1799
  }
1844
1800
  });
@@ -2500,7 +2456,7 @@ const checkAndRemoveBlockingContributor = functions
2500
2456
  // Prepare Firestore batch of txs.
2501
2457
  const batch = firestore.batch();
2502
2458
  // Remove current contributor from waiting queue.
2503
- contributors.shift(1);
2459
+ contributors.shift();
2504
2460
  // Check if someone else is ready to start the contribution.
2505
2461
  if (contributors.length > 0) {
2506
2462
  // Step (E.1).
@@ -1 +1 @@
1
- {"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../../../src/functions/circuit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,WAAW,MAAM,uBAAuB,CAAA;AAyCpD,OAAO,EAAuB,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAyR5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,6BAA6B,4FAoGpC,CAAA;AA8BN;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kBAAkB,0EAya9B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,+CAA+C,wEA4EtD,CAAA;AAEN;;;;GAIG;AACH,eAAO,MAAM,eAAe,uDA8EtB,CAAA"}
1
+ {"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../../../src/functions/circuit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,WAAW,MAAM,uBAAuB,CAAA;AACpD,OAAO,KAAK,WAAW,MAAM,uBAAuB,CAAA;AAyCpD,OAAO,EAAuB,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAkO5E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,6BAA6B,4FAoGpC,CAAA;AA8BN;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,kBAAkB,0EA0Z9B,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,+CAA+C,wEA4EtD,CAAA;AAEN;;;;GAIG;AACH,eAAO,MAAM,eAAe,uDA8EtB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/functions/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAW/C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,mEAuFvB,CAAA;AACN;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,mEA+BpC,CAAA"}
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../../src/functions/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAA;AAW/C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,mEAsGvB,CAAA;AACN;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,mEA+BpC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devtion/backend",
3
- "version": "0.0.0-7e983e3",
3
+ "version": "0.0.0-9c50f66",
4
4
  "description": "MPC Phase 2 backend for Firebase services management",
5
5
  "repository": "git@github.com:privacy-scaling-explorations/p0tion.git",
6
6
  "homepage": "https://github.com/privacy-scaling-explorations/p0tion",
@@ -67,7 +67,7 @@
67
67
  "@aws-sdk/client-ssm": "^3.357.0",
68
68
  "@aws-sdk/middleware-endpoint": "^3.329.0",
69
69
  "@aws-sdk/s3-request-presigner": "^3.329.0",
70
- "@p0tion/actions": "^1.0.5",
70
+ "@devtion/actions": "latest",
71
71
  "blakejs": "^1.2.1",
72
72
  "dotenv": "^16.0.3",
73
73
  "ethers": "5.7.2",
@@ -85,5 +85,5 @@
85
85
  "publishConfig": {
86
86
  "access": "public"
87
87
  },
88
- "gitHead": "afae72061a3b366b05508de53fe91e9b254cc3d5"
88
+ "gitHead": "1285984f567fe9c2456c6033fad3159d0b4c9393"
89
89
  }
@@ -131,6 +131,7 @@ const coordinate = async (
131
131
 
132
132
  newParticipantStatus = ParticipantStatus.CONTRIBUTING
133
133
  newContributionStep = ParticipantContributionStep.DOWNLOADING
134
+ newCurrentContributorId = participant.id
134
135
  }
135
136
  // Scenario (B).
136
137
  else if (participantIsNotCurrentContributor) {
@@ -265,62 +266,6 @@ const waitForVMCommandExecution = (
265
266
  }, 60000) // 1 minute.
266
267
  }
267
268
 
268
- /**
269
- * Wait until the artifacts have been downloaded.
270
- * @param {any} resolve the promise.
271
- * @param {any} reject the promise.
272
- * @param {string} potTempFilePath the tmp path to the locally downloaded pot file.
273
- * @param {string} firstZkeyTempFilePath the tmp path to the locally downloaded first zkey file.
274
- * @param {string} lastZkeyTempFilePath the tmp path to the locally downloaded last zkey file.
275
- */
276
- const waitForFileDownload = (
277
- resolve: any,
278
- reject: any,
279
- potTempFilePath: string,
280
- firstZkeyTempFilePath: string,
281
- lastZkeyTempFilePath: string,
282
- circuitId: string,
283
- participantId: string
284
- ) => {
285
- const maxWaitTime = 5 * 60 * 1000 // 5 minutes
286
- // every second check if the file download was completed
287
- const interval = setInterval(async () => {
288
- printLog(`Verifying that the artifacts were downloaded for circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG)
289
- try {
290
- // check if files have been downloaded
291
- if (!fs.existsSync(potTempFilePath)) {
292
- printLog(`Pot file not found at ${potTempFilePath}`, LogLevel.DEBUG)
293
- }
294
- if (!fs.existsSync(firstZkeyTempFilePath)) {
295
- printLog(`First zkey file not found at ${firstZkeyTempFilePath}`, LogLevel.DEBUG)
296
- }
297
- if (!fs.existsSync(lastZkeyTempFilePath)) {
298
- printLog(`Last zkey file not found at ${lastZkeyTempFilePath}`, LogLevel.DEBUG)
299
- }
300
-
301
- // if all files were downloaded
302
- if (fs.existsSync(potTempFilePath) && fs.existsSync(firstZkeyTempFilePath) && fs.existsSync(lastZkeyTempFilePath)) {
303
- printLog(`All required files are present on disk.`, LogLevel.INFO)
304
- // resolve the promise
305
- resolve()
306
- }
307
- } catch (error: any) {
308
- // if we have an error then we print it as a warning and reject
309
- printLog(`Error while downloading files: ${error}`, LogLevel.WARN)
310
- reject()
311
- } finally {
312
- printLog(`Clearing the interval for file download. Circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG)
313
- clearInterval(interval)
314
- }
315
- }, 5000)
316
-
317
- // we want to clean in 5 minutes in case
318
- setTimeout(() => {
319
- clearInterval(interval)
320
- reject(new Error('Timeout exceeded while waiting for files to be downloaded.'))
321
- }, maxWaitTime)
322
- }
323
-
324
269
  /**
325
270
  * This method is used to coordinate the waiting queues of ceremony circuits.
326
271
  * @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
@@ -763,7 +708,9 @@ export const verifycontribution = functionsV2.https.onCall(
763
708
  ? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
764
709
  : verifyCloudFunctionTime
765
710
 
766
- // Prepare tx to update circuit average contribution/verification time.
711
+ // Prepare tx to update circuit average contribution/verification time.
712
+ const updatedCircuitDoc = await getDocumentById(getCircuitsCollectionPath(ceremonyId), circuitId)
713
+ const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data()!
767
714
  /// @dev this must happen only for valid contributions.
768
715
  batch.update(circuitDoc.ref, {
769
716
  avgTimings: {
@@ -776,7 +723,7 @@ export const verifycontribution = functionsV2.https.onCall(
776
723
  : avgVerifyCloudFunctionTime
777
724
  },
778
725
  waitingQueue: {
779
- ...waitingQueue,
726
+ ...updatedWaitingQueue,
780
727
  completedContributions: isContributionValid
781
728
  ? completedContributions + 1
782
729
  : completedContributions,
@@ -879,45 +826,28 @@ export const verifycontribution = functionsV2.https.onCall(
879
826
  await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
880
827
  await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
881
828
 
882
- await sleep(6000)
883
-
884
- // wait until the files are actually downloaded
885
- return new Promise<void>((resolve, reject) =>
886
- waitForFileDownload(resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantDoc.id)
829
+ // Step (1.A.4).
830
+ isContributionValid = await zKey.verifyFromInit(
831
+ firstZkeyTempFilePath,
832
+ potTempFilePath,
833
+ lastZkeyTempFilePath,
834
+ transcriptLogger
887
835
  )
888
- .then(async () => {
889
- printLog(`Downloads from AWS S3 bucket completed - ceremony ${ceremonyId} circuit ${circuitId}`, LogLevel.DEBUG)
890
-
891
- // Step (1.A.4).
892
- isContributionValid = await zKey.verifyFromInit(
893
- firstZkeyTempFilePath,
894
- potTempFilePath,
895
- lastZkeyTempFilePath,
896
- transcriptLogger
897
- )
898
-
899
- // Compute contribution hash.
900
- lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
901
-
902
- // Free resources by unlinking temporary folders.
903
- // Do not free-up verification transcript path here.
904
- try {
905
- fs.unlinkSync(potTempFilePath)
906
- fs.unlinkSync(firstZkeyTempFilePath)
907
- fs.unlinkSync(lastZkeyTempFilePath)
908
- } catch (error: any) {
909
- printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
910
- }
911
-
912
- await completeVerification()
913
- })
914
- .catch((error: any) => {
915
- // Throw the new error
916
- const commonError = COMMON_ERRORS.CM_INVALID_REQUEST
917
- const additionalDetails = error.toString()
918
-
919
- logAndThrowError(makeError(commonError.code, commonError.message, additionalDetails))
920
- })
836
+
837
+ // Compute contribution hash.
838
+ lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)
839
+
840
+ // Free resources by unlinking temporary folders.
841
+ // Do not free-up verification transcript path here.
842
+ try {
843
+ fs.unlinkSync(potTempFilePath)
844
+ fs.unlinkSync(firstZkeyTempFilePath)
845
+ fs.unlinkSync(lastZkeyTempFilePath)
846
+ } catch (error: any) {
847
+ printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
848
+ }
849
+
850
+ await completeVerification()
921
851
  }
922
852
  }
923
853
  }
@@ -174,7 +174,7 @@ export const checkAndRemoveBlockingContributor = functions
174
174
  const batch = firestore.batch()
175
175
 
176
176
  // Remove current contributor from waiting queue.
177
- contributors.shift(1)
177
+ contributors.shift()
178
178
 
179
179
  // Check if someone else is ready to start the contribution.
180
180
  if (contributors.length > 0) {
@@ -40,8 +40,11 @@ export const registerAuthUser = functions
40
40
  const { uid } = user
41
41
  // Reference to a document using uid.
42
42
  const userRef = firestore.collection(commonTerms.collections.users.name).doc(uid)
43
- // html encode the display name
44
- const encodedDisplayName = encode(displayName)
43
+ // html encode the display name (or put the ID if the name is not displayed)
44
+ const encodedDisplayName = user.displayName === "Null" || user.displayName === null ? user.uid : encode(displayName)
45
+
46
+ // store the avatar URL of a contributor
47
+ let avatarUrl: string = ""
45
48
  // we only do reputation check if the user is not a coordinator
46
49
  if (
47
50
  !(
@@ -56,13 +59,13 @@ export const registerAuthUser = functions
56
59
 
57
60
  // this return true or false
58
61
  try {
59
- const res = await githubReputation(
62
+ const { reputable, avatarUrl: avatarURL } = await githubReputation(
60
63
  user.providerData[0].uid,
61
64
  vars.minimumFollowing,
62
65
  vars.minimumFollowers,
63
66
  vars.minimumPublicRepos
64
67
  )
65
- if (!res) {
68
+ if (!reputable) {
66
69
  // Delete user
67
70
  await auth.deleteUser(user.uid)
68
71
  // Throw error
@@ -70,11 +73,13 @@ export const registerAuthUser = functions
70
73
  makeError(
71
74
  "permission-denied",
72
75
  "The user is not allowed to sign up because their Github reputation is not high enough.",
73
- `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.`
76
+ `The user ${user.displayName === "Null" || user.displayName === null ? user.uid : 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.`
74
77
  )
75
78
  )
76
- }
77
- printLog(`Github reputation check passed for user ${user.displayName}`, LogLevel.DEBUG)
79
+ }
80
+ // store locally
81
+ avatarUrl = avatarURL
82
+ printLog(`Github reputation check passed for user ${user.displayName === "Null" || user.displayName === null ? user.uid : user.displayName }`, LogLevel.DEBUG)
78
83
  } catch (error: any) {
79
84
  // Delete user
80
85
  await auth.deleteUser(user.uid)
@@ -89,6 +94,8 @@ export const registerAuthUser = functions
89
94
  }
90
95
  }
91
96
  // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
97
+ // In future releases we might want to loop through the providerData array as we support
98
+ // more providers.
92
99
  await userRef.set({
93
100
  name: encodedDisplayName,
94
101
  encodedDisplayName,
@@ -101,7 +108,15 @@ export const registerAuthUser = functions
101
108
  photoURL: photoURL || "",
102
109
  lastUpdated: getCurrentServerTimestampInMillis()
103
110
  })
111
+
112
+ // we want to create a new collection for the users to store the avatars
113
+ const avatarRef = firestore.collection(commonTerms.collections.avatars.name).doc(uid)
114
+ await avatarRef.set({
115
+ avatarUrl: avatarUrl || "",
116
+ })
117
+
104
118
  printLog(`Authenticated user document with identifier ${uid} has been correctly stored`, LogLevel.DEBUG)
119
+ printLog(`Authenticated user avatar with identifier ${uid} has been correctly stored`, LogLevel.DEBUG)
105
120
  })
106
121
  /**
107
122
  * Set custom claims for role-based access control on the newly created user.