@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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +151 -0
  3. package/dist/src/functions/index.js +2644 -0
  4. package/dist/src/functions/index.mjs +2596 -0
  5. package/dist/types/functions/ceremony.d.ts +33 -0
  6. package/dist/types/functions/ceremony.d.ts.map +1 -0
  7. package/dist/types/functions/circuit.d.ts +63 -0
  8. package/dist/types/functions/circuit.d.ts.map +1 -0
  9. package/dist/types/functions/index.d.ts +7 -0
  10. package/dist/types/functions/index.d.ts.map +1 -0
  11. package/dist/types/functions/participant.d.ts +58 -0
  12. package/dist/types/functions/participant.d.ts.map +1 -0
  13. package/dist/types/functions/storage.d.ts +37 -0
  14. package/dist/types/functions/storage.d.ts.map +1 -0
  15. package/dist/types/functions/timeout.d.ts +26 -0
  16. package/dist/types/functions/timeout.d.ts.map +1 -0
  17. package/dist/types/functions/user.d.ts +15 -0
  18. package/dist/types/functions/user.d.ts.map +1 -0
  19. package/dist/types/lib/errors.d.ts +75 -0
  20. package/dist/types/lib/errors.d.ts.map +1 -0
  21. package/dist/types/lib/services.d.ts +9 -0
  22. package/dist/types/lib/services.d.ts.map +1 -0
  23. package/dist/types/lib/utils.d.ts +141 -0
  24. package/dist/types/lib/utils.d.ts.map +1 -0
  25. package/dist/types/types/enums.d.ts +13 -0
  26. package/dist/types/types/enums.d.ts.map +1 -0
  27. package/dist/types/types/index.d.ts +130 -0
  28. package/dist/types/types/index.d.ts.map +1 -0
  29. package/package.json +89 -0
  30. package/src/functions/ceremony.ts +333 -0
  31. package/src/functions/circuit.ts +1092 -0
  32. package/src/functions/index.ts +36 -0
  33. package/src/functions/participant.ts +526 -0
  34. package/src/functions/storage.ts +548 -0
  35. package/src/functions/timeout.ts +294 -0
  36. package/src/functions/user.ts +142 -0
  37. package/src/lib/errors.ts +237 -0
  38. package/src/lib/services.ts +28 -0
  39. package/src/lib/utils.ts +472 -0
  40. package/src/types/enums.ts +12 -0
  41. package/src/types/index.ts +140 -0
  42. package/test/index.test.ts +62 -0
@@ -0,0 +1,294 @@
1
+ import * as functions from "firebase-functions"
2
+ import admin from "firebase-admin"
3
+ import dotenv from "dotenv"
4
+ import {
5
+ CeremonyTimeoutType,
6
+ getParticipantsCollectionPath,
7
+ ParticipantContributionStep,
8
+ TimeoutType,
9
+ ParticipantStatus,
10
+ getTimeoutsCollectionPath,
11
+ commonTerms
12
+ } from "@p0tion/actions"
13
+ import {
14
+ getCeremonyCircuits,
15
+ getCurrentServerTimestampInMillis,
16
+ getDocumentById,
17
+ queryOpenedCeremonies
18
+ } from "../lib/utils"
19
+ import { COMMON_ERRORS, logAndThrowError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
20
+ import { LogLevel } from "../types/enums"
21
+
22
+ dotenv.config()
23
+
24
+ /**
25
+ * Check and remove the current contributor if it doesn't complete the contribution on the specified amount of time.
26
+ * @dev since this cloud function is executed every minute, delay problems may occur. See issue #192 (https://github.com/quadratic-funding/mpc-phase2-suite/issues/192).
27
+ * @notice the reasons why a contributor may be considered blocking are many.
28
+ * for example due to network latency, disk availability issues, un/intentional crashes, limited hardware capabilities.
29
+ * the timeout mechanism (fixed/dynamic) could also influence this decision.
30
+ * this cloud function should check each circuit and:
31
+ * A) avoid timeout if there's no current contributor for the circuit.
32
+ * B) avoid timeout if the current contributor is the first for the circuit
33
+ * and timeout mechanism type is dynamic (suggestion: coordinator should be the first contributor).
34
+ * C) check if the current contributor is a potential blocking contributor for the circuit.
35
+ * D) discriminate between blocking contributor (= when downloading, computing, uploading contribution steps)
36
+ * or verification (= verifying contribution step) timeout types.
37
+ * E) execute timeout.
38
+ * E.1) prepare next contributor (if any).
39
+ * E.2) update circuit contributors waiting queue removing the current contributor.
40
+ * E.3) assign timeout to blocking contributor (participant doc update + timeout doc).
41
+ */
42
+ export const checkAndRemoveBlockingContributor = functions
43
+ .region("europe-west1")
44
+ .runWith({
45
+ memory: "512MB"
46
+ })
47
+ .pubsub.schedule("every 1 minutes")
48
+ .onRun(async () => {
49
+ // Prepare Firestore DB.
50
+ const firestore = admin.firestore()
51
+ // Get current server timestamp in milliseconds.
52
+ const currentServerTimestamp = getCurrentServerTimestampInMillis()
53
+
54
+ // Get opened ceremonies.
55
+ const ceremonies = await queryOpenedCeremonies()
56
+
57
+ // For each ceremony.
58
+ for (const ceremony of ceremonies) {
59
+ if (!ceremony.data())
60
+ // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
61
+ printLog(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA.message, LogLevel.WARN)
62
+ else {
63
+ // Get ceremony circuits.
64
+ const circuits = await getCeremonyCircuits(ceremony.id)
65
+
66
+ // Extract ceremony data.
67
+ const { timeoutMechanismType, penalty } = ceremony.data()!
68
+
69
+ for (const circuit of circuits) {
70
+ if (!circuit.data())
71
+ // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
72
+ printLog(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA.message, LogLevel.WARN)
73
+ else {
74
+ // Extract circuit data.
75
+ const { waitingQueue, avgTimings, dynamicThreshold, fixedTimeWindow } = circuit.data()
76
+ const { contributors, currentContributor, failedContributions, completedContributions } =
77
+ waitingQueue
78
+ const {
79
+ fullContribution: avgFullContribution,
80
+ contributionComputation: avgContributionComputation,
81
+ verifyCloudFunction: avgVerifyCloudFunction
82
+ } = avgTimings
83
+
84
+ // Case (A).
85
+ if (!currentContributor)
86
+ // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
87
+ printLog(
88
+ `No current contributor for circuit ${circuit.id} - ceremony ${ceremony.id}`,
89
+ LogLevel.WARN
90
+ )
91
+ else if (
92
+ avgFullContribution === 0 &&
93
+ avgContributionComputation === 0 &&
94
+ avgVerifyCloudFunction === 0 &&
95
+ completedContributions === 0 &&
96
+ timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
97
+ )
98
+ printLog(
99
+ `No timeout will be executed for the first contributor to the circuit ${circuit.id} - ceremony ${ceremony.id}`,
100
+ LogLevel.WARN
101
+ )
102
+ else {
103
+ // Get current contributor document.
104
+ const participant = await getDocumentById(
105
+ getParticipantsCollectionPath(ceremony.id),
106
+ currentContributor
107
+ )
108
+
109
+ if (!participant.data())
110
+ // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
111
+ printLog(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA.message, LogLevel.WARN)
112
+ else {
113
+ // Extract participant data.
114
+ const { contributionStartedAt, verificationStartedAt, contributionStep } =
115
+ participant.data()!
116
+
117
+ // Case (C).
118
+
119
+ // Compute dynamic timeout threshold.
120
+ const timeoutDynamicThreshold =
121
+ timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
122
+ ? (avgFullContribution / 100) * Number(dynamicThreshold)
123
+ : 0
124
+
125
+ // Compute the timeout expiration date (in ms).
126
+ const timeoutExpirationDateInMsForBlockingContributor =
127
+ timeoutMechanismType === CeremonyTimeoutType.DYNAMIC
128
+ ? Number(contributionStartedAt) +
129
+ Number(avgFullContribution) +
130
+ Number(timeoutDynamicThreshold)
131
+ : Number(contributionStartedAt) + Number(fixedTimeWindow) * 60000 // * 60000 = convert minutes to millis.
132
+
133
+ // Case (D).
134
+ const timeoutExpirationDateInMsForVerificationCloudFunction =
135
+ contributionStep === ParticipantContributionStep.VERIFYING &&
136
+ !!verificationStartedAt
137
+ ? Number(verificationStartedAt) + 3540000 // 3540000 = 59 minutes in ms.
138
+ : 0
139
+
140
+ // Assign the timeout type.
141
+ let timeoutType: string = ""
142
+
143
+ if (
144
+ timeoutExpirationDateInMsForBlockingContributor < currentServerTimestamp &&
145
+ (contributionStep === ParticipantContributionStep.DOWNLOADING ||
146
+ contributionStep === ParticipantContributionStep.COMPUTING ||
147
+ contributionStep === ParticipantContributionStep.UPLOADING)
148
+ )
149
+ timeoutType = TimeoutType.BLOCKING_CONTRIBUTION
150
+
151
+ if (
152
+ timeoutExpirationDateInMsForVerificationCloudFunction > 0 &&
153
+ timeoutExpirationDateInMsForVerificationCloudFunction < currentServerTimestamp &&
154
+ contributionStep === ParticipantContributionStep.VERIFYING
155
+ )
156
+ timeoutType = TimeoutType.BLOCKING_CLOUD_FUNCTION
157
+
158
+ printLog(
159
+ `${timeoutType} detected for circuit ${circuit.id} - ceremony ${ceremony.id}`,
160
+ LogLevel.DEBUG
161
+ )
162
+
163
+ if (!timeoutType)
164
+ // Do not use `logAndThrowError` method to avoid the function to exit before checking every ceremony.
165
+ printLog(
166
+ `No timeout for circuit ${circuit.id} - ceremony ${ceremony.id}`,
167
+ LogLevel.WARN
168
+ )
169
+ else {
170
+ // Case (E).
171
+ let nextCurrentContributorId = ""
172
+
173
+ // Prepare Firestore batch of txs.
174
+ const batch = firestore.batch()
175
+
176
+ // Remove current contributor from waiting queue.
177
+ contributors.shift(1)
178
+
179
+ // Check if someone else is ready to start the contribution.
180
+ if (contributors.length > 0) {
181
+ // Step (E.1).
182
+
183
+ // Take the next participant to be current contributor.
184
+ nextCurrentContributorId = contributors.at(0)
185
+
186
+ // Get the document of the next current contributor.
187
+ const nextCurrentContributor = await getDocumentById(
188
+ getParticipantsCollectionPath(ceremony.id),
189
+ nextCurrentContributorId
190
+ )
191
+
192
+ // Prepare next current contributor.
193
+ batch.update(nextCurrentContributor.ref, {
194
+ status: ParticipantStatus.READY,
195
+ lastUpdated: getCurrentServerTimestampInMillis()
196
+ })
197
+ }
198
+
199
+ // Step (E.2).
200
+ // Update accordingly the waiting queue.
201
+ batch.update(circuit.ref, {
202
+ waitingQueue: {
203
+ ...waitingQueue,
204
+ contributors,
205
+ currentContributor: nextCurrentContributorId,
206
+ failedContributions: failedContributions + 1
207
+ },
208
+ lastUpdated: getCurrentServerTimestampInMillis()
209
+ })
210
+
211
+ // Step (E.3).
212
+ batch.update(participant.ref, {
213
+ status: ParticipantStatus.TIMEDOUT,
214
+ lastUpdated: getCurrentServerTimestampInMillis()
215
+ })
216
+
217
+ // Compute the timeout duration (penalty) in milliseconds.
218
+ const timeoutPenaltyInMs = Number(penalty) * 60000 // 60000 = amount of ms x minute.
219
+
220
+ // Prepare an empty doc for timeout (w/ auto-gen uid).
221
+ const timeout = await firestore
222
+ .collection(getTimeoutsCollectionPath(ceremony.id, participant.id))
223
+ .doc()
224
+ .get()
225
+
226
+ // Prepare tx to store info about the timeout.
227
+ batch.create(timeout.ref, {
228
+ type: timeoutType,
229
+ startDate: currentServerTimestamp,
230
+ endDate: currentServerTimestamp + timeoutPenaltyInMs
231
+ })
232
+
233
+ // Send atomic update for Firestore.
234
+ await batch.commit()
235
+
236
+ printLog(
237
+ `The contributor ${participant.id} has been identified as potential blocking contributor. A timeout of type ${timeoutType} has been triggered w/ a penalty of ${timeoutPenaltyInMs} ms`,
238
+ LogLevel.DEBUG
239
+ )
240
+ }
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ })
248
+
249
+ /**
250
+ * Resume the contributor circuit contribution from scratch after the timeout expiration.
251
+ * @dev The participant can resume the contribution if and only if the last timeout in progress was verified as expired (status == EXHUMED).
252
+ */
253
+ export const resumeContributionAfterTimeoutExpiration = functions
254
+ .region("europe-west1")
255
+ .runWith({
256
+ memory: "512MB"
257
+ })
258
+ .https.onCall(async (data: { ceremonyId: string }, context: functions.https.CallableContext): Promise<void> => {
259
+ if (!context.auth || (!context.auth.token.participant && !context.auth.token.coordinator))
260
+ logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
261
+
262
+ if (!data.ceremonyId) logAndThrowError(COMMON_ERRORS.CM_MISSING_OR_WRONG_INPUT_DATA)
263
+
264
+ // Get data.
265
+ const { ceremonyId } = data
266
+ const userId = context.auth?.uid
267
+
268
+ // Look for the ceremony document.
269
+ const ceremonyDoc = await getDocumentById(commonTerms.collections.ceremonies.name, ceremonyId)
270
+ const participantDoc = await getDocumentById(getParticipantsCollectionPath(ceremonyId), userId!)
271
+
272
+ // Prepare documents data.
273
+ const participantData = participantDoc.data()
274
+
275
+ if (!ceremonyDoc.data() || !participantData) logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT_DATA)
276
+
277
+ // Extract data.
278
+ const { contributionProgress, status } = participantData!
279
+
280
+ // Check pre-condition for resumable contribution after timeout expiration.
281
+ if (status === ParticipantStatus.EXHUMED)
282
+ await participantDoc.ref.update({
283
+ status: ParticipantStatus.READY,
284
+ lastUpdated: getCurrentServerTimestampInMillis()
285
+ })
286
+ else logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_CANNOT_PROGRESS_TO_NEXT_CIRCUIT)
287
+
288
+ printLog(
289
+ `Contributor ${userId} can retry the contribution for the circuit in position ${
290
+ contributionProgress + 1
291
+ } after timeout expiration`,
292
+ LogLevel.DEBUG
293
+ )
294
+ })
@@ -0,0 +1,142 @@
1
+ import * as functions from "firebase-functions"
2
+ import { UserRecord } from "firebase-functions/v1/auth"
3
+ import admin from "firebase-admin"
4
+ import dotenv from "dotenv"
5
+ import { commonTerms, githubReputation } from "@p0tion/actions"
6
+ import { encode } from "html-entities"
7
+ import { getGitHubVariables, getCurrentServerTimestampInMillis } from "../lib/utils"
8
+ import { logAndThrowError, makeError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
9
+ import { LogLevel } from "../types/enums"
10
+
11
+ dotenv.config()
12
+ /**
13
+ * Record the authenticated user information inside the Firestore DB upon authentication.
14
+ * @dev the data is recorded in a new document in the `users` collection.
15
+ * @notice this method is automatically triggered upon user authentication in the Firebase app
16
+ * which uses the Firebase Authentication service.
17
+ */
18
+ export const registerAuthUser = functions
19
+ .region("europe-west1")
20
+ .runWith({
21
+ memory: "512MB"
22
+ })
23
+ .auth.user()
24
+ .onCreate(async (user: UserRecord) => {
25
+ // Get DB.
26
+ const firestore = admin.firestore()
27
+ // Get user information.
28
+ if (!user.uid) logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
29
+ // The user object has basic properties such as display name, email, etc.
30
+ const { displayName } = user
31
+ const { email } = user
32
+ const { photoURL } = user
33
+ const { emailVerified } = user
34
+ // Metadata.
35
+ const { creationTime } = user.metadata
36
+ const { lastSignInTime } = user.metadata
37
+ // The user's ID, unique to the Firebase project. Do NOT use
38
+ // this value to authenticate with your backend server, if
39
+ // you have one. Use User.getToken() instead.
40
+ const { uid } = user
41
+ // Reference to a document using uid.
42
+ const userRef = firestore.collection(commonTerms.collections.users.name).doc(uid)
43
+ // html encode the display name
44
+ const encodedDisplayName = encode(displayName)
45
+ // we only do reputation check if the user is not a coordinator
46
+ if (
47
+ !(
48
+ email?.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
49
+ email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN
50
+ )
51
+ ) {
52
+ const auth = admin.auth()
53
+ // if provider == github.com let's use our functions to check the user's reputation
54
+ if (user.providerData[0].providerId === "github.com") {
55
+ const vars = getGitHubVariables()
56
+
57
+ // this return true or false
58
+ try {
59
+ const res = await githubReputation(
60
+ user.providerData[0].uid,
61
+ vars.minimumFollowing,
62
+ vars.minimumFollowers,
63
+ vars.minimumPublicRepos
64
+ )
65
+ if (!res) {
66
+ // Delete user
67
+ await auth.deleteUser(user.uid)
68
+ // Throw error
69
+ logAndThrowError(
70
+ makeError(
71
+ "permission-denied",
72
+ "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.`
74
+ )
75
+ )
76
+ }
77
+ printLog(`Github reputation check passed for user ${user.displayName}`, LogLevel.DEBUG)
78
+ } catch (error: any) {
79
+ // Delete user
80
+ await auth.deleteUser(user.uid)
81
+ logAndThrowError(
82
+ makeError(
83
+ "permission-denied",
84
+ "There was an error while checking the user's Github reputation.",
85
+ `${error}`
86
+ )
87
+ )
88
+ }
89
+ }
90
+ }
91
+ // Set document (nb. we refer to providerData[0] because we use Github OAuth provider only).
92
+ await userRef.set({
93
+ name: encodedDisplayName,
94
+ encodedDisplayName,
95
+ // Metadata.
96
+ creationTime,
97
+ lastSignInTime,
98
+ // Optional.
99
+ email: email || "",
100
+ emailVerified: emailVerified || false,
101
+ photoURL: photoURL || "",
102
+ lastUpdated: getCurrentServerTimestampInMillis()
103
+ })
104
+ printLog(`Authenticated user document with identifier ${uid} has been correctly stored`, LogLevel.DEBUG)
105
+ })
106
+ /**
107
+ * Set custom claims for role-based access control on the newly created user.
108
+ * @notice this method is automatically triggered upon user authentication in the Firebase app
109
+ * which uses the Firebase Authentication service.
110
+ */
111
+ export const processSignUpWithCustomClaims = functions
112
+ .region("europe-west1")
113
+ .runWith({
114
+ memory: "512MB"
115
+ })
116
+ .auth.user()
117
+ .onCreate(async (user: UserRecord) => {
118
+ // Get user information.
119
+ if (!user.uid) logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
120
+ // Prepare state.
121
+ let customClaims: any
122
+ // Check if user meets role criteria to be a coordinator.
123
+ if (
124
+ user.email &&
125
+ (user.email.endsWith(`@${process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN}`) ||
126
+ user.email === process.env.CUSTOM_CLAIMS_COORDINATOR_EMAIL_ADDRESS_OR_DOMAIN)
127
+ ) {
128
+ customClaims = { coordinator: true }
129
+ printLog(`Authenticated user ${user.uid} has been identified as coordinator`, LogLevel.DEBUG)
130
+ } else {
131
+ customClaims = { participant: true }
132
+ printLog(`Authenticated user ${user.uid} has been identified as participant`, LogLevel.DEBUG)
133
+ }
134
+ try {
135
+ // Set custom user claims on this newly created user.
136
+ await admin.auth().setCustomUserClaims(user.uid, customClaims)
137
+ } catch (error: any) {
138
+ const specificError = SPECIFIC_ERRORS.SE_AUTH_SET_CUSTOM_USER_CLAIMS_FAIL
139
+ const additionalDetails = error.toString()
140
+ logAndThrowError(makeError(specificError.code, specificError.message, additionalDetails))
141
+ }
142
+ })
@@ -0,0 +1,237 @@
1
+ import * as functions from "firebase-functions"
2
+ import { FunctionsErrorCode, HttpsError } from "firebase-functions/v1/https"
3
+ import { LogLevel } from "../types/enums"
4
+
5
+ /**
6
+ * Create a new custom HTTPs error for cloud functions.
7
+ * @notice the set of Firebase Functions status codes. The codes are the same at the
8
+ * ones exposed by {@link https://github.com/grpc/grpc/blob/master/doc/statuscodes.md | gRPC}.
9
+ * @param errorCode <FunctionsErrorCode> - the set of possible error codes.
10
+ * @param message <string> - the error messge.
11
+ * @param [details] <string> - the details of the error (optional).
12
+ * @returns <HttpsError>
13
+ */
14
+ export const makeError = (errorCode: FunctionsErrorCode, message: string, details?: string): HttpsError =>
15
+ new functions.https.HttpsError(errorCode, message, details)
16
+
17
+ /**
18
+ * Log a custom message on console using a specific level.
19
+ * @param message <string> - the message to be shown.
20
+ * @param logLevel <LogLevel> - the level of the log to be used to show the message (e.g., debug, error).
21
+ */
22
+ export const printLog = (message: string, logLevel: LogLevel) => {
23
+ switch (logLevel) {
24
+ case LogLevel.INFO:
25
+ functions.logger.info(`[${logLevel}] ${message}`)
26
+ break
27
+ case LogLevel.DEBUG:
28
+ functions.logger.debug(`[${logLevel}] ${message}`)
29
+ break
30
+ case LogLevel.WARN:
31
+ functions.logger.warn(`[${logLevel}] ${message}`)
32
+ break
33
+ case LogLevel.ERROR:
34
+ functions.logger.error(`[${logLevel}] ${message}`)
35
+ break
36
+ case LogLevel.LOG:
37
+ functions.logger.log(`[${logLevel}] ${message}`)
38
+ break
39
+ default:
40
+ console.log(`[${logLevel}] ${message}`)
41
+ break
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Log and throw an HTTPs error.
47
+ * @param error <HttpsError> - the error to be logged and thrown.
48
+ */
49
+ export const logAndThrowError = (error: HttpsError) => {
50
+ printLog(`${error.code}: ${error.message} ${!error.details ? "" : `\ndetails: ${error.details}`}`, LogLevel.ERROR)
51
+ throw error
52
+ }
53
+
54
+ /**
55
+ * A set of Cloud Function specific errors.
56
+ * @notice these are errors that happen only on specific cloud functions.
57
+ */
58
+ export const SPECIFIC_ERRORS = {
59
+ SE_AUTH_NO_CURRENT_AUTH_USER: makeError(
60
+ "failed-precondition",
61
+ "Unable to retrieve the authenticated user.",
62
+ "Authenticated user information could not be retrieved. No document will be created in the relevant collection."
63
+ ),
64
+ SE_AUTH_SET_CUSTOM_USER_CLAIMS_FAIL: makeError(
65
+ "invalid-argument",
66
+ "Unable to set custom claims for authenticated user."
67
+ ),
68
+ SE_AUTH_USER_NOT_REPUTABLE: makeError(
69
+ "permission-denied",
70
+ "The authenticated user is not reputable.",
71
+ "The authenticated user is not reputable. No document will be created in the relevant collection."
72
+ ),
73
+ SE_STORAGE_INVALID_BUCKET_NAME: makeError(
74
+ "already-exists",
75
+ "Unable to create the AWS S3 bucket for the ceremony since the provided name is already in use. Please, provide a different bucket name for the ceremony.",
76
+ "More info about the error could be found at the following link https://docs.aws.amazon.com/simspaceweaver/latest/userguide/troubleshooting_bucket-name-too-long.html"
77
+ ),
78
+ SE_STORAGE_TOO_MANY_BUCKETS: makeError(
79
+ "resource-exhausted",
80
+ "Unable to create the AWS S3 bucket for the ceremony since the are too many buckets already in use. Please, delete 2 or more existing Amazon S3 buckets that you don't need or increase your limits.",
81
+ "More info about the error could be found at the following link https://docs.aws.amazon.com/simspaceweaver/latest/userguide/troubeshooting_too-many-buckets.html"
82
+ ),
83
+ SE_STORAGE_MISSING_PERMISSIONS: makeError(
84
+ "permission-denied",
85
+ "You do not have privileges to perform this operation.",
86
+ "Authenticated user does not have proper permissions on AWS S3."
87
+ ),
88
+ SE_STORAGE_BUCKET_NOT_CONNECTED_TO_CEREMONY: makeError(
89
+ "not-found",
90
+ "Unable to generate a pre-signed url for the given object in the provided bucket.",
91
+ "The bucket is not associated with any valid ceremony document on the Firestore database."
92
+ ),
93
+ SE_STORAGE_WRONG_OBJECT_KEY: makeError(
94
+ "failed-precondition",
95
+ "Unable to interact with a multi-part upload (start, create pre-signed urls or complete).",
96
+ "The object key provided does not match the expected one."
97
+ ),
98
+ SE_STORAGE_CANNOT_INTERACT_WITH_MULTI_PART_UPLOAD: makeError(
99
+ "failed-precondition",
100
+ "Unable to interact with a multi-part upload (start, create pre-signed urls or complete).",
101
+ "Authenticated user is not a current contributor which is currently in the uploading step."
102
+ ),
103
+ SE_STORAGE_DOWNLOAD_FAILED: makeError(
104
+ "failed-precondition",
105
+ "Unable to download the AWS S3 object from the provided ceremony bucket.",
106
+ "This could happen if the file reference stored in the database or bucket turns out to be wrong or if the pre-signed url was not generated correctly."
107
+ ),
108
+ SE_STORAGE_UPLOAD_FAILED: makeError(
109
+ "failed-precondition",
110
+ "Unable to upload the file to the AWS S3 ceremony bucket.",
111
+ "This could happen if the local file or bucket do not exist or if the pre-signed url was not generated correctly."
112
+ ),
113
+ SE_STORAGE_DELETE_FAILED: makeError(
114
+ "failed-precondition",
115
+ "Unable to delete the AWS S3 object from the provided ceremony bucket.",
116
+ "This could happen if the local file or the bucket do not exist."
117
+ ),
118
+ SE_CONTRIBUTE_NO_CEREMONY_CIRCUITS: makeError(
119
+ "not-found",
120
+ "There is no circuit associated with the ceremony.",
121
+ "No documents in the circuits subcollection were found for the selected ceremony."
122
+ ),
123
+ SE_CONTRIBUTE_NO_OPENED_CEREMONIES: makeError("not-found", "There are no ceremonies open to contributions."),
124
+ SE_CONTRIBUTE_CANNOT_PROGRESS_TO_NEXT_CIRCUIT: makeError(
125
+ "failed-precondition",
126
+ "Unable to progress to next circuit for contribution",
127
+ "In order to progress for the contribution the participant must have just been registered for the ceremony or have just finished a contribution."
128
+ ),
129
+ SE_PARTICIPANT_CEREMONY_NOT_OPENED: makeError(
130
+ "failed-precondition",
131
+ "Unable to progress to next contribution step.",
132
+ "The ceremony does not appear to be opened"
133
+ ),
134
+ SE_PARTICIPANT_NOT_CONTRIBUTING: makeError(
135
+ "failed-precondition",
136
+ "Unable to progress to next contribution step.",
137
+ "This may happen due wrong contribution step from participant."
138
+ ),
139
+ SE_PARTICIPANT_CANNOT_STORE_PERMANENT_DATA: makeError(
140
+ "failed-precondition",
141
+ "Unable to store contribution hash and computing time.",
142
+ "This may happen due wrong contribution step from participant or missing coordinator permission (only when finalizing)."
143
+ ),
144
+ SE_PARTICIPANT_CANNOT_STORE_TEMPORARY_DATA: makeError(
145
+ "failed-precondition",
146
+ "Unable to store temporary data to resume a multi-part upload.",
147
+ "This may happen due wrong contribution step from participant."
148
+ ),
149
+ SE_VERIFICATION_NO_PARTICIPANT_CONTRIBUTION_DATA: makeError(
150
+ "not-found",
151
+ `Unable to retrieve current contribution data from participant document.`
152
+ ),
153
+ SE_CEREMONY_CANNOT_FINALIZE_CEREMONY: makeError(
154
+ "failed-precondition",
155
+ `Unable to finalize the ceremony.`,
156
+ `Please, verify to have successfully completed the finalization of each circuit in the ceremony.`
157
+ ),
158
+ SE_FINALIZE_NO_CEREMONY_CONTRIBUTIONS: makeError(
159
+ "not-found",
160
+ "There are no contributions associated with the ceremony circuit.",
161
+ "No documents in the contributions subcollection were found for the selected ceremony circuit."
162
+ ),
163
+ SE_FINALIZE_NO_FINAL_CONTRIBUTION: makeError(
164
+ "not-found",
165
+ "There is no final contribution associated with the ceremony circuit."
166
+ ),
167
+ SE_VM_NOT_RUNNING: makeError("failed-precondition", "The EC2 VM is not running yet"),
168
+ SE_VM_FAILED_COMMAND_EXECUTION: makeError(
169
+ "failed-precondition",
170
+ "VM command execution failed",
171
+ "Please, contact the coordinator if this error persists."
172
+ ),
173
+ SE_VM_TIMEDOUT_COMMAND_EXECUTION: makeError(
174
+ "deadline-exceeded",
175
+ "VM command execution took too long and has been timed-out",
176
+ "Please, contact the coordinator if this error persists."
177
+ ),
178
+ SE_VM_CANCELLED_COMMAND_EXECUTION: makeError(
179
+ "cancelled",
180
+ "VM command execution has been cancelled",
181
+ "Please, contact the coordinator if this error persists."
182
+ ),
183
+ SE_VM_DELAYED_COMMAND_EXECUTION: makeError(
184
+ "unavailable",
185
+ "VM command execution has been delayed since there were no available instance at the moment",
186
+ "Please, contact the coordinator if this error persists."
187
+ )
188
+ }
189
+
190
+ /**
191
+ * A set of common errors.
192
+ * @notice these are errors that happen on multiple cloud functions (e.g., auth, missing data).
193
+ */
194
+ export const COMMON_ERRORS = {
195
+ CM_NOT_COORDINATOR_ROLE: makeError(
196
+ "permission-denied",
197
+ "You do not have privileges to perform this operation.",
198
+ "Authenticated user does not have the coordinator role (missing custom claims)."
199
+ ),
200
+ CM_MISSING_OR_WRONG_INPUT_DATA: makeError(
201
+ "invalid-argument",
202
+ "Unable to perform the operation due to incomplete or incorrect data."
203
+ ),
204
+ CM_WRONG_CONFIGURATION: makeError(
205
+ "failed-precondition",
206
+ "Missing or incorrect configuration.",
207
+ "This may happen due wrong environment configuration for the backend services."
208
+ ),
209
+ CM_NOT_AUTHENTICATED: makeError(
210
+ "failed-precondition",
211
+ "You are not authorized to perform this operation.",
212
+ "You could not perform the requested operation because you are not authenticated on the Firebase Application."
213
+ ),
214
+ CM_INEXISTENT_DOCUMENT: makeError(
215
+ "not-found",
216
+ "Unable to find a document with the given identifier for the provided collection path."
217
+ ),
218
+ CM_INEXISTENT_DOCUMENT_DATA: makeError(
219
+ "not-found",
220
+ "The provided document with the given identifier has no data associated with it.",
221
+ "This problem may occur if the document has not yet been written in the database."
222
+ ),
223
+ CM_INVALID_CEREMONY_FOR_PARTICIPANT: makeError(
224
+ "not-found",
225
+ "The participant does not seem to be related to a ceremony."
226
+ ),
227
+ CM_NO_CIRCUIT_FOR_GIVEN_SEQUENCE_POSITION: makeError(
228
+ "not-found",
229
+ "Unable to find the circuit having the provided sequence position for the given ceremony"
230
+ ),
231
+ CM_INVALID_REQUEST: makeError("unknown", "Failed request."),
232
+ CM_INVALID_COMMAND_EXECUTION: makeError(
233
+ "unknown",
234
+ "There was an error while executing the command on the VM",
235
+ "Please, contact the coordinator if the error persists."
236
+ )
237
+ }