@devtion/backend 0.0.0-7e983e3
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/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/src/functions/index.js +2644 -0
- package/dist/src/functions/index.mjs +2596 -0
- package/dist/types/functions/ceremony.d.ts +33 -0
- package/dist/types/functions/ceremony.d.ts.map +1 -0
- package/dist/types/functions/circuit.d.ts +63 -0
- package/dist/types/functions/circuit.d.ts.map +1 -0
- package/dist/types/functions/index.d.ts +7 -0
- package/dist/types/functions/index.d.ts.map +1 -0
- package/dist/types/functions/participant.d.ts +58 -0
- package/dist/types/functions/participant.d.ts.map +1 -0
- package/dist/types/functions/storage.d.ts +37 -0
- package/dist/types/functions/storage.d.ts.map +1 -0
- package/dist/types/functions/timeout.d.ts +26 -0
- package/dist/types/functions/timeout.d.ts.map +1 -0
- package/dist/types/functions/user.d.ts +15 -0
- package/dist/types/functions/user.d.ts.map +1 -0
- package/dist/types/lib/errors.d.ts +75 -0
- package/dist/types/lib/errors.d.ts.map +1 -0
- package/dist/types/lib/services.d.ts +9 -0
- package/dist/types/lib/services.d.ts.map +1 -0
- package/dist/types/lib/utils.d.ts +141 -0
- package/dist/types/lib/utils.d.ts.map +1 -0
- package/dist/types/types/enums.d.ts +13 -0
- package/dist/types/types/enums.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +130 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/package.json +89 -0
- package/src/functions/ceremony.ts +333 -0
- package/src/functions/circuit.ts +1092 -0
- package/src/functions/index.ts +36 -0
- package/src/functions/participant.ts +526 -0
- package/src/functions/storage.ts +548 -0
- package/src/functions/timeout.ts +294 -0
- package/src/functions/user.ts +142 -0
- package/src/lib/errors.ts +237 -0
- package/src/lib/services.ts +28 -0
- package/src/lib/utils.ts +472 -0
- package/src/types/enums.ts +12 -0
- package/src/types/index.ts +140 -0
- package/test/index.test.ts +62 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { CeremonyInputData, CircuitDocument, ETagWithPartNumber } from "@p0tion/actions";
|
|
2
|
+
/**
|
|
3
|
+
* Group all the necessary data needed for running the `setupCeremony` cloud function.
|
|
4
|
+
* @typedef {Object} SetupCeremonyData
|
|
5
|
+
* @property {CeremonyInputData} ceremonyInputData - the necessary input data for setup a new ceremony.
|
|
6
|
+
* @property {string} ceremonyPrefix - the ceremony prefix.
|
|
7
|
+
* @property {Array<CircuitDocument>} circuits - the necessary input data for setup the related ceremony circuits.
|
|
8
|
+
*/
|
|
9
|
+
export type SetupCeremonyData = {
|
|
10
|
+
ceremonyInputData: CeremonyInputData;
|
|
11
|
+
ceremonyPrefix: string;
|
|
12
|
+
circuits: Array<CircuitDocument>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Group all the necessary data needed for running the `createBucket` cloud function.
|
|
16
|
+
* @typedef {Object} CreateBucketData
|
|
17
|
+
* @property {string} bucketName - the name of the bucket.
|
|
18
|
+
*/
|
|
19
|
+
export type CreateBucketData = {
|
|
20
|
+
bucketName: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Group all the necessary data needed for running the `checkIfObjectExist` or `generateGetObjectPreSignedUrl` cloud functions.
|
|
24
|
+
* @typedef {Object} BucketAndObjectKeyData
|
|
25
|
+
* @property {string} bucketName - the name of the bucket.
|
|
26
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
27
|
+
*/
|
|
28
|
+
export type BucketAndObjectKeyData = {
|
|
29
|
+
bucketName: string;
|
|
30
|
+
objectKey: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Group all the necessary data needed for running the `startMultiPartUpload` cloud function.
|
|
34
|
+
* @typedef {Object} StartMultiPartUploadData
|
|
35
|
+
* @property {string} bucketName - the name of the bucket.
|
|
36
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
37
|
+
* @property {string} ceremonyId - the prefix of the ceremony.
|
|
38
|
+
*/
|
|
39
|
+
export type StartMultiPartUploadData = BucketAndObjectKeyData & {
|
|
40
|
+
ceremonyId?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Group all the necessary data needed for running the `generatePreSignedUrlsParts` cloud function.
|
|
44
|
+
* @typedef {Object} GeneratePreSignedUrlsPartsData
|
|
45
|
+
* @property {string} bucketName - the name of the bucket.
|
|
46
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
47
|
+
* @property {string} uploadId - the identifier of the initiated multi-part upload.
|
|
48
|
+
* @property {number} numberOfParts - the amount of chunks for which pre-signed urls are to be generated.
|
|
49
|
+
* @property {string} ceremonyId - the prefix of the ceremony.
|
|
50
|
+
*/
|
|
51
|
+
export type GeneratePreSignedUrlsPartsData = BucketAndObjectKeyData & {
|
|
52
|
+
uploadId: string;
|
|
53
|
+
numberOfParts: number;
|
|
54
|
+
ceremonyId?: string;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Group all the necessary data needed for running the `completeMultiPartUpload` cloud function.
|
|
58
|
+
* @typedef {Object} GeneratePreSignedUrlsPartsData
|
|
59
|
+
* @property {string} bucketName - the name of the bucket.
|
|
60
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
61
|
+
* @property {string} uploadId - the identifier of the initiated multi-part upload.
|
|
62
|
+
* @property {Array<ETagWithPartNumber>} parts - the chunks of the file related to the multi-part upload.
|
|
63
|
+
* @property {string} [ceremonyId] - the unique identifier of the ceremony.
|
|
64
|
+
*/
|
|
65
|
+
export type CompleteMultiPartUploadData = BucketAndObjectKeyData & {
|
|
66
|
+
uploadId: string;
|
|
67
|
+
parts: Array<ETagWithPartNumber>;
|
|
68
|
+
ceremonyId?: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Group all the necessary data needed for running the `permanentlyStoreCurrentContributionTimeAndHash` cloud function.
|
|
72
|
+
* @typedef {Object} PermanentlyStoreCurrentContributionTimeAndHash
|
|
73
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
74
|
+
* @property {number} contributionComputationTime - the time spent by the contributor to compute the contribution.
|
|
75
|
+
* @property {string} contributionHash - the hash of the contribution.
|
|
76
|
+
*/
|
|
77
|
+
export type PermanentlyStoreCurrentContributionTimeAndHash = {
|
|
78
|
+
ceremonyId: string;
|
|
79
|
+
contributionComputationTime: number;
|
|
80
|
+
contributionHash: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Group all the necessary data needed for running the `temporaryStoreCurrentContributionMultiPartUploadId` cloud function.
|
|
84
|
+
* @typedef {Object} TemporaryStoreCurrentContributionMultiPartUploadId
|
|
85
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
86
|
+
* @property {number} uploadId - the unique identifier of the already started multi-part upload.
|
|
87
|
+
*/
|
|
88
|
+
export type TemporaryStoreCurrentContributionMultiPartUploadId = {
|
|
89
|
+
ceremonyId: string;
|
|
90
|
+
uploadId: string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Group all the necessary data needed for running the `temporaryStoreCurrentContributionUploadedChunkData` cloud function.
|
|
94
|
+
* @typedef {Object} TemporaryStoreCurrentContributionUploadedChunkData
|
|
95
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
96
|
+
* @property {number} uploadId - the unique identifier of the already started multi-part upload.
|
|
97
|
+
*/
|
|
98
|
+
export type TemporaryStoreCurrentContributionUploadedChunkData = {
|
|
99
|
+
ceremonyId: string;
|
|
100
|
+
chunk: ETagWithPartNumber;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Group all the necessary data needed for running the `verifycontribution` cloud function.
|
|
104
|
+
* @typedef {Object} VerifyContributionData
|
|
105
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
106
|
+
* @property {string} circuitId - the unique identifier of the circuit.
|
|
107
|
+
* @property {string} bucketName - the name of the bucket.
|
|
108
|
+
* @property {string} contributorOrCoordinatorIdentifier - the identifier of the contributor or coordinator (only when finalizing).
|
|
109
|
+
*/
|
|
110
|
+
export type VerifyContributionData = {
|
|
111
|
+
ceremonyId: string;
|
|
112
|
+
circuitId: string;
|
|
113
|
+
bucketName: string;
|
|
114
|
+
contributorOrCoordinatorIdentifier: string;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Group all the necessary data needed for running the `finalizeCircuit` cloud function.
|
|
118
|
+
* @typedef {Object} FinalizeCircuitData
|
|
119
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
120
|
+
* @property {string} circuitId - the unique identifier of the circuit.
|
|
121
|
+
* @property {string} bucketName - the name of the bucket.
|
|
122
|
+
* @property {string} beacon - the value used to compute the final contribution while finalizing the ceremony.
|
|
123
|
+
*/
|
|
124
|
+
export type FinalizeCircuitData = {
|
|
125
|
+
ceremonyId: string;
|
|
126
|
+
circuitId: string;
|
|
127
|
+
bucketName: string;
|
|
128
|
+
beacon: string;
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAExF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,iBAAiB,EAAE,iBAAiB,CAAA;IACpC,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;CACnC,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC3B,UAAU,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,sBAAsB,GAAG;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,8BAA8B,GAAG,sBAAsB,GAAG;IAClE,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,2BAA2B,GAAG,sBAAsB,GAAG;IAC/D,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAA;IAChC,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,8CAA8C,GAAG;IACzD,UAAU,EAAE,MAAM,CAAA;IAClB,2BAA2B,EAAE,MAAM,CAAA;IACnC,gBAAgB,EAAE,MAAM,CAAA;CAC3B,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,kDAAkD,GAAG;IAC7D,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,kDAAkD,GAAG;IAC7D,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,kBAAkB,CAAA;CAC5B,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,kCAAkC,EAAE,MAAM,CAAA;CAC7C,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;CACjB,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devtion/backend",
|
|
3
|
+
"version": "0.0.0-7e983e3",
|
|
4
|
+
"description": "MPC Phase 2 backend for Firebase services management",
|
|
5
|
+
"repository": "git@github.com:privacy-scaling-explorations/p0tion.git",
|
|
6
|
+
"homepage": "https://github.com/privacy-scaling-explorations/p0tion",
|
|
7
|
+
"bugs": "https://github.com/privacy-scaling-explorations/p0tion/issues",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"main": "./dist/src/functions/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
"import": "./dist/src/functions/index.mjs",
|
|
12
|
+
"require": "./dist/src/functions/index.js"
|
|
13
|
+
},
|
|
14
|
+
"types": "dist/types/types/index.d.ts",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": "16"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/",
|
|
20
|
+
"src/",
|
|
21
|
+
"test/",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"typescript",
|
|
26
|
+
"zero-knowledge",
|
|
27
|
+
"zk-snarks",
|
|
28
|
+
"phase-2",
|
|
29
|
+
"trusted-setup",
|
|
30
|
+
"ceremony",
|
|
31
|
+
"snarkjs",
|
|
32
|
+
"circom"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "rimraf dist && rollup -c rollup.config.ts --configPlugin typescript",
|
|
36
|
+
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
|
|
37
|
+
"firebase:login": "firebase login",
|
|
38
|
+
"firebase:logout": "firebase logout",
|
|
39
|
+
"firebase:init": "firebase init",
|
|
40
|
+
"firebase:deploy": "yarn firestore:get-indexes && firebase deploy --project prod",
|
|
41
|
+
"firebase:deploy-functions": "firebase deploy --only functions --project prod",
|
|
42
|
+
"firebase:deploy-firestore": "yarn firestore:get-indexes && firebase deploy --only firestore --project prod",
|
|
43
|
+
"firebase:log-functions": "firebase functions:log --project prod",
|
|
44
|
+
"firestore:get-indexes": "firebase firestore:indexes --project prod > firestore.indexes.json",
|
|
45
|
+
"emulator:serve": "yarn build && firebase emulators:start",
|
|
46
|
+
"emulator:serve-functions": "yarn build && firebase emulators:start --only functions",
|
|
47
|
+
"emulator:shell": "yarn build && firebase functions:shell",
|
|
48
|
+
"emulator:exec-test": "firebase --project dev emulators:exec \"yarn test:emulator\"",
|
|
49
|
+
"test:emulator": "jest --config=../../jest.json --detectOpenHandles --forceExit --coverage=false --watchAll=false --no-cache",
|
|
50
|
+
"docs": "typedoc src/**/*.ts --out ../../docs/backend"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@firebase/rules-unit-testing": "^2.0.7",
|
|
54
|
+
"@types/rollup-plugin-auto-external": "^2.0.2",
|
|
55
|
+
"@types/uuid": "^9.0.1",
|
|
56
|
+
"firebase-functions-test": "^3.1.0",
|
|
57
|
+
"firebase-tools": "^12.0.0",
|
|
58
|
+
"rollup-plugin-auto-external": "^2.0.0",
|
|
59
|
+
"rollup-plugin-cleanup": "^3.2.1",
|
|
60
|
+
"rollup-plugin-typescript2": "^0.34.1",
|
|
61
|
+
"typescript": "^5.0.4"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@adobe/node-fetch-retry": "^2.2.0",
|
|
65
|
+
"@aws-sdk/client-ec2": "^3.357.0",
|
|
66
|
+
"@aws-sdk/client-s3": "^3.329.0",
|
|
67
|
+
"@aws-sdk/client-ssm": "^3.357.0",
|
|
68
|
+
"@aws-sdk/middleware-endpoint": "^3.329.0",
|
|
69
|
+
"@aws-sdk/s3-request-presigner": "^3.329.0",
|
|
70
|
+
"@p0tion/actions": "^1.0.5",
|
|
71
|
+
"blakejs": "^1.2.1",
|
|
72
|
+
"dotenv": "^16.0.3",
|
|
73
|
+
"ethers": "5.7.2",
|
|
74
|
+
"firebase-admin": "^11.8.0",
|
|
75
|
+
"firebase-functions": "^4.4.0",
|
|
76
|
+
"html-entities": "^2.3.3",
|
|
77
|
+
"rimraf": "^5.0.0",
|
|
78
|
+
"rollup": "^3.21.6",
|
|
79
|
+
"snarkjs": "^0.6.11",
|
|
80
|
+
"solc": "^0.8.19",
|
|
81
|
+
"timer-node": "^5.0.7",
|
|
82
|
+
"uuid": "^9.0.0",
|
|
83
|
+
"winston": "^3.8.2"
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"access": "public"
|
|
87
|
+
},
|
|
88
|
+
"gitHead": "afae72061a3b366b05508de53fe91e9b254cc3d5"
|
|
89
|
+
}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import * as functions from "firebase-functions"
|
|
2
|
+
import admin from "firebase-admin"
|
|
3
|
+
import dotenv from "dotenv"
|
|
4
|
+
import { DocumentSnapshot, QueryDocumentSnapshot } from "firebase-functions/v1/firestore"
|
|
5
|
+
import {
|
|
6
|
+
CeremonyState,
|
|
7
|
+
ParticipantStatus,
|
|
8
|
+
CeremonyType,
|
|
9
|
+
CircuitWaitingQueue,
|
|
10
|
+
commonTerms,
|
|
11
|
+
getCircuitsCollectionPath,
|
|
12
|
+
getParticipantsCollectionPath,
|
|
13
|
+
createEC2Instance,
|
|
14
|
+
terminateEC2Instance,
|
|
15
|
+
getBucketName,
|
|
16
|
+
CircuitContributionVerificationMechanism,
|
|
17
|
+
computeDiskSizeForVM,
|
|
18
|
+
vmBootstrapCommand,
|
|
19
|
+
vmDependenciesAndCacheArtifactsCommand,
|
|
20
|
+
vmBootstrapScriptFilename
|
|
21
|
+
} from "@p0tion/actions"
|
|
22
|
+
import { encode } from "html-entities"
|
|
23
|
+
import { SetupCeremonyData } from "../types/index"
|
|
24
|
+
import { COMMON_ERRORS, logAndThrowError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
|
|
25
|
+
import {
|
|
26
|
+
queryCeremoniesByStateAndDate,
|
|
27
|
+
getCurrentServerTimestampInMillis,
|
|
28
|
+
getDocumentById,
|
|
29
|
+
getCeremonyCircuits,
|
|
30
|
+
getFinalContribution,
|
|
31
|
+
htmlEncodeCircuitData,
|
|
32
|
+
createEC2Client,
|
|
33
|
+
uploadFileToBucketNoFile,
|
|
34
|
+
getAWSVariables
|
|
35
|
+
} from "../lib/utils"
|
|
36
|
+
import { LogLevel } from "../types/enums"
|
|
37
|
+
|
|
38
|
+
dotenv.config()
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Make a scheduled ceremony open.
|
|
42
|
+
* @dev this function automatically runs every 30 minutes.
|
|
43
|
+
* @todo this methodology for transitioning a ceremony from `scheduled` to `opened` state will be replaced with one
|
|
44
|
+
* that resolves the issues presented in the issue #192 (https://github.com/quadratic-funding/mpc-phase2-suite/issues/192).
|
|
45
|
+
*/
|
|
46
|
+
export const startCeremony = functions
|
|
47
|
+
.region("europe-west1")
|
|
48
|
+
.runWith({
|
|
49
|
+
memory: "512MB"
|
|
50
|
+
})
|
|
51
|
+
.pubsub.schedule(`every 30 minutes`)
|
|
52
|
+
.onRun(async () => {
|
|
53
|
+
// Get ready to be opened ceremonies.
|
|
54
|
+
const scheduledCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.SCHEDULED, true, "<=")
|
|
55
|
+
|
|
56
|
+
if (!scheduledCeremoniesQuerySnap.empty)
|
|
57
|
+
scheduledCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
|
|
58
|
+
// Make state transition to start ceremony.
|
|
59
|
+
await ceremonyDoc.ref.set({ state: CeremonyState.OPENED }, { merge: true })
|
|
60
|
+
|
|
61
|
+
printLog(`Ceremony ${ceremonyDoc.id} is now open`, LogLevel.DEBUG)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Make a scheduled ceremony close.
|
|
67
|
+
* @dev this function automatically runs every 30 minutes.
|
|
68
|
+
* @todo this methodology for transitioning a ceremony from `opened` to `closed` state will be replaced with one
|
|
69
|
+
* that resolves the issues presented in the issue #192 (https://github.com/quadratic-funding/mpc-phase2-suite/issues/192).
|
|
70
|
+
*/
|
|
71
|
+
export const stopCeremony = functions
|
|
72
|
+
.region("europe-west1")
|
|
73
|
+
.runWith({
|
|
74
|
+
memory: "512MB"
|
|
75
|
+
})
|
|
76
|
+
.pubsub.schedule(`every 30 minutes`)
|
|
77
|
+
.onRun(async () => {
|
|
78
|
+
// Get opened ceremonies.
|
|
79
|
+
const runningCeremoniesQuerySnap = await queryCeremoniesByStateAndDate(CeremonyState.OPENED, false, "<=")
|
|
80
|
+
|
|
81
|
+
if (!runningCeremoniesQuerySnap.empty) {
|
|
82
|
+
runningCeremoniesQuerySnap.forEach(async (ceremonyDoc: DocumentSnapshot) => {
|
|
83
|
+
// Make state transition to close ceremony.
|
|
84
|
+
await ceremonyDoc.ref.set({ state: CeremonyState.CLOSED }, { merge: true })
|
|
85
|
+
|
|
86
|
+
printLog(`Ceremony ${ceremonyDoc.id} is now closed`, LogLevel.DEBUG)
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Register all ceremony setup-related documents on the Firestore database.
|
|
93
|
+
* @dev this function will create a new document in the `ceremonies` collection and as needed `circuit`
|
|
94
|
+
* documents in the sub-collection.
|
|
95
|
+
*/
|
|
96
|
+
export const setupCeremony = functions
|
|
97
|
+
.region("europe-west1")
|
|
98
|
+
.runWith({
|
|
99
|
+
memory: "512MB"
|
|
100
|
+
})
|
|
101
|
+
.https.onCall(async (data: SetupCeremonyData, context: functions.https.CallableContext): Promise<any> => {
|
|
102
|
+
// Check if the user has the coordinator claim.
|
|
103
|
+
if (!context.auth || !context.auth.token.coordinator) logAndThrowError(COMMON_ERRORS.CM_NOT_COORDINATOR_ROLE)
|
|
104
|
+
|
|
105
|
+
// Validate the provided data.
|
|
106
|
+
if (!data.ceremonyInputData || !data.ceremonyPrefix || !data.circuits.length)
|
|
107
|
+
logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
|
|
108
|
+
|
|
109
|
+
// Prepare Firestore DB.
|
|
110
|
+
const firestore = admin.firestore()
|
|
111
|
+
const batch = firestore.batch()
|
|
112
|
+
|
|
113
|
+
// Prepare data.
|
|
114
|
+
const { ceremonyInputData, ceremonyPrefix, circuits } = data
|
|
115
|
+
const userId = context.auth?.uid
|
|
116
|
+
|
|
117
|
+
// Create a new ceremony document.
|
|
118
|
+
const ceremonyDoc = await firestore.collection(`${commonTerms.collections.ceremonies.name}`).doc().get()
|
|
119
|
+
|
|
120
|
+
// Prepare tx to write ceremony data.
|
|
121
|
+
batch.create(ceremonyDoc.ref, {
|
|
122
|
+
title: encode(ceremonyInputData.title),
|
|
123
|
+
description: encode(ceremonyInputData.description),
|
|
124
|
+
startDate: new Date(ceremonyInputData.startDate).valueOf(),
|
|
125
|
+
endDate: new Date(ceremonyInputData.endDate).valueOf(),
|
|
126
|
+
prefix: ceremonyPrefix,
|
|
127
|
+
state: CeremonyState.SCHEDULED,
|
|
128
|
+
type: CeremonyType.PHASE2,
|
|
129
|
+
penalty: ceremonyInputData.penalty,
|
|
130
|
+
timeoutType: ceremonyInputData.timeoutMechanismType,
|
|
131
|
+
coordinatorId: userId,
|
|
132
|
+
lastUpdated: getCurrentServerTimestampInMillis()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
// Get the bucket name so we can upload the startup script
|
|
136
|
+
const bucketName = getBucketName(ceremonyPrefix, String(process.env.AWS_CEREMONY_BUCKET_POSTFIX))
|
|
137
|
+
|
|
138
|
+
// Create a new circuit document (circuits ceremony document sub-collection).
|
|
139
|
+
for (let circuit of circuits) {
|
|
140
|
+
// The VM unique identifier (if any).
|
|
141
|
+
let vmInstanceId: string = ""
|
|
142
|
+
|
|
143
|
+
// Get a new circuit document.
|
|
144
|
+
const circuitDoc = await firestore.collection(getCircuitsCollectionPath(ceremonyDoc.ref.id)).doc().get()
|
|
145
|
+
|
|
146
|
+
// Check if using the VM approach for contribution verification.
|
|
147
|
+
if (circuit.verification.cfOrVm === CircuitContributionVerificationMechanism.VM) {
|
|
148
|
+
// VM command to be run at the startup.
|
|
149
|
+
const startupCommand = vmBootstrapCommand(bucketName)
|
|
150
|
+
|
|
151
|
+
// Get EC2 client.
|
|
152
|
+
const ec2Client = await createEC2Client()
|
|
153
|
+
|
|
154
|
+
// Get AWS variables.
|
|
155
|
+
const { snsTopic, region } = getAWSVariables()
|
|
156
|
+
|
|
157
|
+
// Prepare dependencies and cache artifacts command.
|
|
158
|
+
const vmCommands = vmDependenciesAndCacheArtifactsCommand(
|
|
159
|
+
`${bucketName}/${circuit.files?.initialZkeyStoragePath!}`,
|
|
160
|
+
`${bucketName}/${circuit.files?.potStoragePath!}`,
|
|
161
|
+
snsTopic,
|
|
162
|
+
region
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
printLog(`Check VM dependencies and cache artifacts commands ${vmCommands.join("\n")}`, LogLevel.DEBUG)
|
|
166
|
+
|
|
167
|
+
// Upload the post-startup commands script file.
|
|
168
|
+
await uploadFileToBucketNoFile(bucketName, vmBootstrapScriptFilename, vmCommands.join("\n"))
|
|
169
|
+
|
|
170
|
+
// Compute the VM disk space requirement (in GB).
|
|
171
|
+
const vmDiskSize = computeDiskSizeForVM(circuit.zKeySizeInBytes!, circuit.metadata?.pot!)
|
|
172
|
+
|
|
173
|
+
printLog(`Check VM startup commands ${startupCommand.join("\n")}`, LogLevel.DEBUG)
|
|
174
|
+
|
|
175
|
+
// Configure and instantiate a new VM based on the coordinator input.
|
|
176
|
+
const instance = await createEC2Instance(
|
|
177
|
+
ec2Client,
|
|
178
|
+
startupCommand,
|
|
179
|
+
circuit.verification.vm?.vmConfigurationType!,
|
|
180
|
+
vmDiskSize,
|
|
181
|
+
circuit.verification.vm?.vmDiskType!
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// Get the VM instance identifier.
|
|
185
|
+
vmInstanceId = instance.instanceId
|
|
186
|
+
|
|
187
|
+
// Update the circuit document info accordingly.
|
|
188
|
+
circuit = {
|
|
189
|
+
...circuit,
|
|
190
|
+
verification: {
|
|
191
|
+
cfOrVm: circuit.verification.cfOrVm,
|
|
192
|
+
vm: {
|
|
193
|
+
vmConfigurationType: circuit.verification.vm?.vmConfigurationType!,
|
|
194
|
+
vmDiskSize,
|
|
195
|
+
vmInstanceId
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Encode circuit data.
|
|
202
|
+
const encodedCircuit = htmlEncodeCircuitData(circuit)
|
|
203
|
+
|
|
204
|
+
// Prepare tx to write circuit data.
|
|
205
|
+
batch.create(circuitDoc.ref, {
|
|
206
|
+
...encodedCircuit,
|
|
207
|
+
lastUpdated: getCurrentServerTimestampInMillis()
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Send txs in a batch (to avoid race conditions).
|
|
212
|
+
await batch.commit()
|
|
213
|
+
|
|
214
|
+
printLog(`Setup completed for ceremony ${ceremonyDoc.id}`, LogLevel.DEBUG)
|
|
215
|
+
|
|
216
|
+
return ceremonyDoc.id
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Prepare all the necessary information needed for initializing the waiting queue of a circuit.
|
|
221
|
+
* @dev this function will add a new field `waitingQueue` in the newly created circuit document.
|
|
222
|
+
*/
|
|
223
|
+
export const initEmptyWaitingQueueForCircuit = functions
|
|
224
|
+
.region("europe-west1")
|
|
225
|
+
.runWith({
|
|
226
|
+
memory: "512MB"
|
|
227
|
+
})
|
|
228
|
+
.firestore.document(
|
|
229
|
+
`/${commonTerms.collections.ceremonies.name}/{ceremony}/${commonTerms.collections.circuits.name}/{circuit}`
|
|
230
|
+
)
|
|
231
|
+
.onCreate(async (doc: QueryDocumentSnapshot) => {
|
|
232
|
+
// Prepare Firestore DB.
|
|
233
|
+
const firestore = admin.firestore()
|
|
234
|
+
|
|
235
|
+
// Get circuit document identifier and data.
|
|
236
|
+
const circuitId = doc.id
|
|
237
|
+
// Get parent ceremony collection path.
|
|
238
|
+
const parentCollectionPath = doc.ref.parent.path // == /ceremonies/{ceremony}/circuits/.
|
|
239
|
+
|
|
240
|
+
// Define an empty waiting queue.
|
|
241
|
+
const emptyWaitingQueue: CircuitWaitingQueue = {
|
|
242
|
+
contributors: [],
|
|
243
|
+
currentContributor: "",
|
|
244
|
+
completedContributions: 0,
|
|
245
|
+
failedContributions: 0
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Update the circuit document.
|
|
249
|
+
await firestore.collection(parentCollectionPath).doc(circuitId).set(
|
|
250
|
+
{
|
|
251
|
+
waitingQueue: emptyWaitingQueue,
|
|
252
|
+
lastUpdated: getCurrentServerTimestampInMillis()
|
|
253
|
+
},
|
|
254
|
+
{ merge: true }
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
printLog(
|
|
258
|
+
`An empty waiting queue has been successfully initialized for circuit ${circuitId} which belongs to ceremony ${doc.id}`,
|
|
259
|
+
LogLevel.DEBUG
|
|
260
|
+
)
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Conclude the finalization of the ceremony.
|
|
265
|
+
* @dev checks that the ceremony is closed (= CLOSED), the coordinator is finalizing and has already
|
|
266
|
+
* provided the final contribution for each ceremony circuit.
|
|
267
|
+
*/
|
|
268
|
+
export const finalizeCeremony = functions
|
|
269
|
+
.region("europe-west1")
|
|
270
|
+
.runWith({
|
|
271
|
+
memory: "512MB"
|
|
272
|
+
})
|
|
273
|
+
.https.onCall(async (data: { ceremonyId: string }, context: functions.https.CallableContext): Promise<any> => {
|
|
274
|
+
if (!context.auth || !context.auth.token.coordinator) logAndThrowError(COMMON_ERRORS.CM_NOT_COORDINATOR_ROLE)
|
|
275
|
+
|
|
276
|
+
if (!data.ceremonyId) logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
|
|
277
|
+
|
|
278
|
+
// Prepare Firestore DB.
|
|
279
|
+
const firestore = admin.firestore()
|
|
280
|
+
const batch = firestore.batch()
|
|
281
|
+
|
|
282
|
+
// Extract data.
|
|
283
|
+
const { ceremonyId } = data
|
|
284
|
+
const userId = context.auth?.uid
|
|
285
|
+
|
|
286
|
+
// Look for the ceremony document.
|
|
287
|
+
const ceremonyDoc = await getDocumentById(commonTerms.collections.ceremonies.name, ceremonyId)
|
|
288
|
+
const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), userId!)
|
|
289
|
+
|
|
290
|
+
if (!ceremonyDoc.data() || !participantDoc.data()) logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)
|
|
291
|
+
|
|
292
|
+
// Get ceremony circuits.
|
|
293
|
+
const circuits = await getCeremonyCircuits(ceremonyId)
|
|
294
|
+
|
|
295
|
+
// Get final contribution for each circuit.
|
|
296
|
+
// nb. the `getFinalContributionDocument` checks the existance of the final contribution document (if not present, throws).
|
|
297
|
+
// Therefore, we just need to call the method without taking any data to verify the pre-condition of having already computed
|
|
298
|
+
// the final contributions for each ceremony circuit.
|
|
299
|
+
for await (const circuit of circuits) await getFinalContribution(ceremonyId, circuit.id)
|
|
300
|
+
|
|
301
|
+
// Extract data.
|
|
302
|
+
const { state } = ceremonyDoc.data()!
|
|
303
|
+
const { status } = participantDoc.data()!
|
|
304
|
+
|
|
305
|
+
// Pre-conditions: verify the ceremony is closed and coordinator is finalizing.
|
|
306
|
+
if (state === CeremonyState.CLOSED && status === ParticipantStatus.FINALIZING) {
|
|
307
|
+
// Prepare txs for updates.
|
|
308
|
+
batch.update(ceremonyDoc.ref, { state: CeremonyState.FINALIZED })
|
|
309
|
+
batch.update(participantDoc.ref, {
|
|
310
|
+
status: ParticipantStatus.FINALIZED
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
// Check for VM termination (if any).
|
|
314
|
+
for (const circuit of circuits) {
|
|
315
|
+
const circuitData = circuit.data()
|
|
316
|
+
const { verification } = circuitData
|
|
317
|
+
|
|
318
|
+
if (verification.cfOrVm === CircuitContributionVerificationMechanism.VM) {
|
|
319
|
+
// Prepare EC2 client.
|
|
320
|
+
const ec2Client = await createEC2Client()
|
|
321
|
+
|
|
322
|
+
const { vm } = verification
|
|
323
|
+
|
|
324
|
+
await terminateEC2Instance(ec2Client, vm.vmInstanceId)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Send txs.
|
|
329
|
+
await batch.commit()
|
|
330
|
+
|
|
331
|
+
printLog(`Ceremony ${ceremonyDoc.id} correctly finalized - Coordinator ${participantDoc.id}`, LogLevel.INFO)
|
|
332
|
+
} else logAndThrowError(SPECIFIC_ERRORS.SE_CEREMONY_CANNOT_FINALIZE_CEREMONY)
|
|
333
|
+
})
|