@devtion/backend 0.0.0-bfc9ee4 → 0.0.0-c749be4
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 +28 -2
- package/dist/src/functions/index.js +152 -104
- package/dist/src/functions/index.mjs +154 -106
- package/dist/types/functions/ceremony.d.ts.map +1 -1
- package/dist/types/functions/circuit.d.ts.map +1 -1
- package/dist/types/functions/storage.d.ts.map +1 -1
- package/dist/types/functions/timeout.d.ts.map +1 -1
- package/dist/types/functions/user.d.ts.map +1 -1
- package/dist/types/lib/errors.d.ts +1 -0
- package/dist/types/lib/errors.d.ts.map +1 -1
- package/dist/types/lib/utils.d.ts +1 -1
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +1 -1
- package/package.json +3 -3
- package/src/functions/ceremony.ts +8 -3
- package/src/functions/circuit.ts +138 -116
- package/src/functions/participant.ts +9 -9
- package/src/functions/storage.ts +7 -4
- package/src/functions/timeout.ts +4 -3
- package/src/functions/user.ts +33 -8
- package/src/lib/errors.ts +5 -0
- package/src/lib/utils.ts +11 -9
- package/src/types/index.ts +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Launching the ready-to-run customized scripts everyone could handle whatever is
|
|
|
51
51
|
|
|
52
52
|
## 🛠 Installation
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**Prerequisites**
|
|
55
55
|
|
|
56
56
|
- Node.js version 16.0 or higher.
|
|
57
57
|
- Yarn version 3.5.0 or higher.
|
|
@@ -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
|
+
5. 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
|
|
@@ -122,7 +148,7 @@ yarn firebase:deploy-firestore
|
|
|
122
148
|
|
|
123
149
|
Firebase provides a [Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) as a set of advanced dev-tools w/ a rich user-interface to build and test apps locally using Firebase services as Cloud Functions, Firestore and Authentication.
|
|
124
150
|
|
|
125
|
-
**
|
|
151
|
+
**Prerequisites**
|
|
126
152
|
|
|
127
153
|
- You will need Java JDK version 11 or higher to run the Firebase Local Emulator.
|
|
128
154
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @p0tion/backend
|
|
3
|
-
* @version 1.0
|
|
3
|
+
* @version 1.1.0
|
|
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('@
|
|
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
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
|
559
|
-
if (!
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
@@ -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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1222
|
+
.region("europe-west1")
|
|
1205
1223
|
.runWith({
|
|
1206
1224
|
memory: "512MB"
|
|
1207
1225
|
})
|
|
@@ -1353,54 +1371,74 @@ const coordinate = async (participant, circuit, isSingleParticipantCoordination,
|
|
|
1353
1371
|
* Wait until the command has completed its execution inside the VM.
|
|
1354
1372
|
* @dev this method implements a custom interval to check 5 times after 1 minute if the command execution
|
|
1355
1373
|
* has been completed or not by calling the `retrieveCommandStatus` method.
|
|
1356
|
-
* @param {any} resolve the promise.
|
|
1357
|
-
* @param {any} reject the promise.
|
|
1358
1374
|
* @param {SSMClient} ssm the SSM client.
|
|
1359
1375
|
* @param {string} vmInstanceId the unique identifier of the VM instance.
|
|
1360
1376
|
* @param {string} commandId the unique identifier of the VM command.
|
|
1361
1377
|
* @returns <Promise<void>> true when the command execution succeed; otherwise false.
|
|
1362
1378
|
*/
|
|
1363
|
-
const waitForVMCommandExecution = (
|
|
1364
|
-
const
|
|
1379
|
+
const waitForVMCommandExecution = (ssm, vmInstanceId, commandId) => new Promise((resolve, reject) => {
|
|
1380
|
+
const poll = async () => {
|
|
1365
1381
|
try {
|
|
1366
1382
|
// Get command status.
|
|
1367
1383
|
const cmdStatus = await actions.retrieveCommandStatus(ssm, vmInstanceId, commandId);
|
|
1368
1384
|
printLog(`Checking command ${commandId} status => ${cmdStatus}`, LogLevel.DEBUG);
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
+
}
|
|
1385
1419
|
}
|
|
1386
|
-
|
|
1387
|
-
logAndThrowError(
|
|
1388
|
-
reject();
|
|
1420
|
+
if (error) {
|
|
1421
|
+
logAndThrowError(error);
|
|
1389
1422
|
}
|
|
1390
1423
|
}
|
|
1391
1424
|
catch (error) {
|
|
1392
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
|
+
}
|
|
1393
1434
|
if (!error.toString().includes(commandId))
|
|
1394
1435
|
logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION);
|
|
1395
1436
|
// Reject the promise.
|
|
1396
1437
|
reject();
|
|
1397
1438
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
}
|
|
1402
|
-
}, 60000); // 1 minute.
|
|
1403
|
-
};
|
|
1439
|
+
};
|
|
1440
|
+
setTimeout(poll, 60000);
|
|
1441
|
+
});
|
|
1404
1442
|
/**
|
|
1405
1443
|
* This method is used to coordinate the waiting queues of ceremony circuits.
|
|
1406
1444
|
* @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
|
|
@@ -1421,7 +1459,7 @@ const waitForVMCommandExecution = (resolve, reject, ssm, vmInstanceId, commandId
|
|
|
1421
1459
|
* - Just completed a contribution or all contributions for each circuit. If yes, coordinate (multi-participant scenario).
|
|
1422
1460
|
*/
|
|
1423
1461
|
const coordinateCeremonyParticipant = functionsV1__namespace
|
|
1424
|
-
.region(
|
|
1462
|
+
.region("europe-west1")
|
|
1425
1463
|
.runWith({
|
|
1426
1464
|
memory: "512MB"
|
|
1427
1465
|
})
|
|
@@ -1492,11 +1530,9 @@ const checkIfVMRunning = async (ec2, vmInstanceId, attempts = 5) => {
|
|
|
1492
1530
|
const isVMRunning = await actions.checkIfRunning(ec2, vmInstanceId);
|
|
1493
1531
|
if (!isVMRunning) {
|
|
1494
1532
|
printLog(`VM not running, ${attempts - 1} attempts remaining. Retrying in 1 minute...`, LogLevel.DEBUG);
|
|
1495
|
-
return
|
|
1496
|
-
}
|
|
1497
|
-
else {
|
|
1498
|
-
return true;
|
|
1533
|
+
return checkIfVMRunning(ec2, vmInstanceId, attempts - 1);
|
|
1499
1534
|
}
|
|
1535
|
+
return true;
|
|
1500
1536
|
};
|
|
1501
1537
|
/**
|
|
1502
1538
|
* Verify the contribution of a participant computed while contributing to a specific circuit of a ceremony.
|
|
@@ -1524,7 +1560,7 @@ const checkIfVMRunning = async (ec2, vmInstanceId, attempts = 5) => {
|
|
|
1524
1560
|
* 1.A.4.C.1) If true, update circuit waiting for queue and average timings accordingly to contribution verification results;
|
|
1525
1561
|
* 2) Send all updates atomically to the Firestore database.
|
|
1526
1562
|
*/
|
|
1527
|
-
const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB", timeoutSeconds: 3600, region:
|
|
1563
|
+
const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB", timeoutSeconds: 3600, region: "europe-west1" }, async (request) => {
|
|
1528
1564
|
if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
|
|
1529
1565
|
logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER);
|
|
1530
1566
|
if (!request.data.ceremonyId ||
|
|
@@ -1635,8 +1671,6 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1635
1671
|
lastZkeyBlake2bHash = match.at(0);
|
|
1636
1672
|
// re upload the formatted verification transcript
|
|
1637
1673
|
await uploadFileToBucket(bucketName, verificationTranscriptStoragePathAndFilename, verificationTranscriptTemporaryLocalPath, true);
|
|
1638
|
-
// Stop VM instance.
|
|
1639
|
-
await actions.stopEC2Instance(ec2, vmInstanceId);
|
|
1640
1674
|
}
|
|
1641
1675
|
else {
|
|
1642
1676
|
// Upload verification transcript.
|
|
@@ -1697,6 +1731,18 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1697
1731
|
lastUpdated: getCurrentServerTimestampInMillis()
|
|
1698
1732
|
});
|
|
1699
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
|
+
}
|
|
1700
1746
|
// Step (1.A.4.C)
|
|
1701
1747
|
if (!isFinalizing) {
|
|
1702
1748
|
// Step (1.A.4.C.1)
|
|
@@ -1711,7 +1757,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1711
1757
|
const newAvgVerifyCloudFunctionTime = avgVerifyCloudFunctionTime > 0
|
|
1712
1758
|
? (avgVerifyCloudFunctionTime + verifyCloudFunctionTime) / 2
|
|
1713
1759
|
: verifyCloudFunctionTime;
|
|
1714
|
-
// Prepare tx to update circuit average contribution/verification time.
|
|
1760
|
+
// Prepare tx to update circuit average contribution/verification time.
|
|
1715
1761
|
const updatedCircuitDoc = await getDocumentById(actions.getCircuitsCollectionPath(ceremonyId), circuitId);
|
|
1716
1762
|
const { waitingQueue: updatedWaitingQueue } = updatedCircuitDoc.data();
|
|
1717
1763
|
/// @dev this must happen only for valid contributions.
|
|
@@ -1761,7 +1807,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1761
1807
|
commandId = await actions.runCommandUsingSSM(ssm, vmInstanceId, verificationCommand);
|
|
1762
1808
|
printLog(`Starting the execution of command ${commandId}`, LogLevel.DEBUG);
|
|
1763
1809
|
// Step (1.A.3.3).
|
|
1764
|
-
return
|
|
1810
|
+
return waitForVMCommandExecution(ssm, vmInstanceId, commandId)
|
|
1765
1811
|
.then(async () => {
|
|
1766
1812
|
// Command execution successfully completed.
|
|
1767
1813
|
printLog(`Command ${commandId} execution has been successfully completed`, LogLevel.DEBUG);
|
|
@@ -1773,40 +1819,38 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1773
1819
|
logAndThrowError(COMMON_ERRORS.CM_INVALID_COMMAND_EXECUTION);
|
|
1774
1820
|
});
|
|
1775
1821
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
fs.unlinkSync(lastZkeyTempFilePath);
|
|
1804
|
-
}
|
|
1805
|
-
catch (error) {
|
|
1806
|
-
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
|
|
1807
|
-
}
|
|
1808
|
-
await completeVerification();
|
|
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);
|
|
1809
1849
|
}
|
|
1850
|
+
catch (error) {
|
|
1851
|
+
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN);
|
|
1852
|
+
}
|
|
1853
|
+
await completeVerification();
|
|
1810
1854
|
}
|
|
1811
1855
|
});
|
|
1812
1856
|
/**
|
|
@@ -1815,7 +1859,7 @@ const verifycontribution = functionsV2__namespace.https.onCall({ memory: "16GiB"
|
|
|
1815
1859
|
* this does not happen if the participant is actually the coordinator who is finalizing the ceremony.
|
|
1816
1860
|
*/
|
|
1817
1861
|
const refreshParticipantAfterContributionVerification = functionsV1__namespace
|
|
1818
|
-
.region(
|
|
1862
|
+
.region("europe-west1")
|
|
1819
1863
|
.runWith({
|
|
1820
1864
|
memory: "512MB"
|
|
1821
1865
|
})
|
|
@@ -1876,7 +1920,7 @@ const refreshParticipantAfterContributionVerification = functionsV1__namespace
|
|
|
1876
1920
|
* and verification key extracted from the circuit final contribution (as part of the ceremony finalization process).
|
|
1877
1921
|
*/
|
|
1878
1922
|
const finalizeCircuit = functionsV1__namespace
|
|
1879
|
-
.region(
|
|
1923
|
+
.region("europe-west1")
|
|
1880
1924
|
.runWith({
|
|
1881
1925
|
memory: "512MB"
|
|
1882
1926
|
})
|
|
@@ -2073,8 +2117,10 @@ const createBucket = functions__namespace
|
|
|
2073
2117
|
CORSConfiguration: {
|
|
2074
2118
|
CORSRules: [
|
|
2075
2119
|
{
|
|
2076
|
-
AllowedMethods: ["GET"],
|
|
2077
|
-
AllowedOrigins: ["*"]
|
|
2120
|
+
AllowedMethods: ["GET", "PUT"],
|
|
2121
|
+
AllowedOrigins: ["*"],
|
|
2122
|
+
ExposeHeaders: ["ETag", "Content-Length"],
|
|
2123
|
+
AllowedHeaders: ["*"]
|
|
2078
2124
|
}
|
|
2079
2125
|
]
|
|
2080
2126
|
}
|
|
@@ -2251,7 +2297,8 @@ const startMultiPartUpload = functions__namespace
|
|
|
2251
2297
|
const generatePreSignedUrlsParts = functions__namespace
|
|
2252
2298
|
.region("europe-west1")
|
|
2253
2299
|
.runWith({
|
|
2254
|
-
memory: "512MB"
|
|
2300
|
+
memory: "512MB",
|
|
2301
|
+
timeoutSeconds: 300
|
|
2255
2302
|
})
|
|
2256
2303
|
.https.onCall(async (data, context) => {
|
|
2257
2304
|
if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
|
|
@@ -2401,7 +2448,7 @@ const checkAndRemoveBlockingContributor = functions__namespace
|
|
|
2401
2448
|
// Get ceremony circuits.
|
|
2402
2449
|
const circuits = await getCeremonyCircuits(ceremony.id);
|
|
2403
2450
|
// Extract ceremony data.
|
|
2404
|
-
const { timeoutMechanismType, penalty } = ceremony.data();
|
|
2451
|
+
const { timeoutType: timeoutMechanismType, penalty } = ceremony.data();
|
|
2405
2452
|
for (const circuit of circuits) {
|
|
2406
2453
|
if (!circuit.data())
|
|
2407
2454
|
// Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
|
|
@@ -2551,7 +2598,8 @@ const resumeContributionAfterTimeoutExpiration = functions__namespace
|
|
|
2551
2598
|
if (status === "EXHUMED" /* ParticipantStatus.EXHUMED */)
|
|
2552
2599
|
await participantDoc.ref.update({
|
|
2553
2600
|
status: "READY" /* ParticipantStatus.READY */,
|
|
2554
|
-
lastUpdated: getCurrentServerTimestampInMillis()
|
|
2601
|
+
lastUpdated: getCurrentServerTimestampInMillis(),
|
|
2602
|
+
tempContributionData: {}
|
|
2555
2603
|
});
|
|
2556
2604
|
else
|
|
2557
2605
|
logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_CANNOT_PROGRESS_TO_NEXT_CIRCUIT);
|