@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,28 @@
|
|
|
1
|
+
import { S3Client } from "@aws-sdk/client-s3"
|
|
2
|
+
import { COMMON_ERRORS, logAndThrowError } from "./errors"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return a configured and connected instance of the AWS S3 client.
|
|
6
|
+
* @dev this method check and utilize the environment variables to configure the connection
|
|
7
|
+
* w/ the S3 client.
|
|
8
|
+
* @returns <Promise<S3Client>> - the instance of the connected S3 Client instance.
|
|
9
|
+
*/
|
|
10
|
+
export const getS3Client = async (): Promise<S3Client> => {
|
|
11
|
+
if (
|
|
12
|
+
!process.env.AWS_ACCESS_KEY_ID ||
|
|
13
|
+
!process.env.AWS_SECRET_ACCESS_KEY ||
|
|
14
|
+
!process.env.AWS_REGION ||
|
|
15
|
+
!process.env.AWS_PRESIGNED_URL_EXPIRATION ||
|
|
16
|
+
!process.env.AWS_CEREMONY_BUCKET_POSTFIX
|
|
17
|
+
)
|
|
18
|
+
logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
|
|
19
|
+
|
|
20
|
+
// Return the connected S3 Client instance.
|
|
21
|
+
return new S3Client({
|
|
22
|
+
credentials: {
|
|
23
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
24
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
|
|
25
|
+
},
|
|
26
|
+
region: process.env.AWS_REGION!
|
|
27
|
+
})
|
|
28
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DocumentData,
|
|
3
|
+
QuerySnapshot,
|
|
4
|
+
DocumentSnapshot,
|
|
5
|
+
QueryDocumentSnapshot,
|
|
6
|
+
Timestamp,
|
|
7
|
+
WhereFilterOp
|
|
8
|
+
} from "firebase-admin/firestore"
|
|
9
|
+
import admin from "firebase-admin"
|
|
10
|
+
import dotenv from "dotenv"
|
|
11
|
+
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
|
|
12
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
|
13
|
+
import { createWriteStream, fstat } from "node:fs"
|
|
14
|
+
import { pipeline } from "node:stream"
|
|
15
|
+
import { promisify } from "node:util"
|
|
16
|
+
import { readFileSync } from "fs"
|
|
17
|
+
import mime from "mime-types"
|
|
18
|
+
import { encode } from "html-entities"
|
|
19
|
+
import { setTimeout } from "timers/promises"
|
|
20
|
+
import {
|
|
21
|
+
commonTerms,
|
|
22
|
+
getCircuitsCollectionPath,
|
|
23
|
+
getContributionsCollectionPath,
|
|
24
|
+
getTimeoutsCollectionPath,
|
|
25
|
+
CeremonyState,
|
|
26
|
+
finalContributionIndex,
|
|
27
|
+
CircuitDocument
|
|
28
|
+
} from "@p0tion/actions"
|
|
29
|
+
import fetch from "@adobe/node-fetch-retry"
|
|
30
|
+
import path from "path"
|
|
31
|
+
import os from "os"
|
|
32
|
+
import { SSMClient } from "@aws-sdk/client-ssm"
|
|
33
|
+
import { EC2Client } from "@aws-sdk/client-ec2"
|
|
34
|
+
import { COMMON_ERRORS, logAndThrowError, SPECIFIC_ERRORS } from "./errors"
|
|
35
|
+
import { getS3Client } from "./services"
|
|
36
|
+
|
|
37
|
+
dotenv.config()
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get a specific document from database.
|
|
41
|
+
* @dev this method differs from the one in the `actions` package because we need to use
|
|
42
|
+
* the admin SDK here; therefore the Firestore instances are not interchangeable between admin
|
|
43
|
+
* and user instance.
|
|
44
|
+
* @param collection <string> - the name of the collection.
|
|
45
|
+
* @param documentId <string> - the unique identifier of the document in the collection.
|
|
46
|
+
* @returns <Promise<DocumentSnapshot<DocumentData>>> - the requested document w/ relative data.
|
|
47
|
+
*/
|
|
48
|
+
export const getDocumentById = async (
|
|
49
|
+
collection: string,
|
|
50
|
+
documentId: string
|
|
51
|
+
): Promise<DocumentSnapshot<DocumentData>> => {
|
|
52
|
+
// Prepare Firestore db instance.
|
|
53
|
+
const firestore = admin.firestore()
|
|
54
|
+
|
|
55
|
+
// Get document.
|
|
56
|
+
const doc = await firestore.collection(collection).doc(documentId).get()
|
|
57
|
+
|
|
58
|
+
// Return only if doc exists; otherwise throw error.
|
|
59
|
+
return doc.exists ? doc : logAndThrowError(COMMON_ERRORS.CM_INEXISTENT_DOCUMENT)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the current server timestamp.
|
|
64
|
+
* @dev the value is in milliseconds.
|
|
65
|
+
* @returns <number> - the timestamp of the server (ms).
|
|
66
|
+
*/
|
|
67
|
+
export const getCurrentServerTimestampInMillis = (): number => Timestamp.now().toMillis()
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Interrupt the current execution for a specified amount of time.
|
|
71
|
+
* @param ms <number> - the amount of time expressed in milliseconds.
|
|
72
|
+
*/
|
|
73
|
+
export const sleep = async (ms: number): Promise<void> => setTimeout(ms)
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Query for ceremony circuits.
|
|
77
|
+
* @notice the order by sequence position is fundamental to maintain parallelism among contributions for different circuits.
|
|
78
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
79
|
+
* @returns Promise<Array<FirebaseDocumentInfo>> - the ceremony' circuits documents ordered by sequence position.
|
|
80
|
+
*/
|
|
81
|
+
export const getCeremonyCircuits = async (ceremonyId: string): Promise<Array<QueryDocumentSnapshot<DocumentData>>> => {
|
|
82
|
+
// Prepare Firestore db instance.
|
|
83
|
+
const firestore = admin.firestore()
|
|
84
|
+
|
|
85
|
+
// Execute query.
|
|
86
|
+
const querySnap = await firestore.collection(getCircuitsCollectionPath(ceremonyId)).get()
|
|
87
|
+
|
|
88
|
+
if (!querySnap.docs) logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_NO_CEREMONY_CIRCUITS)
|
|
89
|
+
|
|
90
|
+
return querySnap.docs.sort(
|
|
91
|
+
(a: DocumentData, b: DocumentData) => a.data().sequencePosition - b.data().sequencePosition
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Query for ceremony circuit contributions.
|
|
97
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
98
|
+
* @param circuitId <string> - the unique identifier of the circuitId.
|
|
99
|
+
* @returns Promise<Array<FirebaseDocumentInfo>> - the contributions of the ceremony circuit.
|
|
100
|
+
*/
|
|
101
|
+
export const getCeremonyCircuitContributions = async (
|
|
102
|
+
ceremonyId: string,
|
|
103
|
+
circuitId: string
|
|
104
|
+
): Promise<Array<QueryDocumentSnapshot<DocumentData>>> => {
|
|
105
|
+
// Prepare Firestore db instance.
|
|
106
|
+
const firestore = admin.firestore()
|
|
107
|
+
|
|
108
|
+
// Execute query.
|
|
109
|
+
const querySnap = await firestore.collection(getContributionsCollectionPath(ceremonyId, circuitId)).get()
|
|
110
|
+
|
|
111
|
+
if (!querySnap.docs) logAndThrowError(SPECIFIC_ERRORS.SE_FINALIZE_NO_CEREMONY_CONTRIBUTIONS)
|
|
112
|
+
|
|
113
|
+
return querySnap.docs
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Query not expired timeouts.
|
|
118
|
+
* @notice a timeout is considered valid (aka not expired) if and only if the timeout end date
|
|
119
|
+
* value is less than current timestamp.
|
|
120
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
121
|
+
* @param participantId <string> - the unique identifier of the participant.
|
|
122
|
+
* @returns <Promise<QuerySnapshot<DocumentData>>>
|
|
123
|
+
*/
|
|
124
|
+
export const queryNotExpiredTimeouts = async (
|
|
125
|
+
ceremonyId: string,
|
|
126
|
+
participantId: string
|
|
127
|
+
): Promise<QuerySnapshot<DocumentData>> => {
|
|
128
|
+
// Prepare Firestore db.
|
|
129
|
+
const firestoreDb = admin.firestore()
|
|
130
|
+
|
|
131
|
+
// Execute and return query result.
|
|
132
|
+
return firestoreDb
|
|
133
|
+
.collection(getTimeoutsCollectionPath(ceremonyId, participantId))
|
|
134
|
+
.where(commonTerms.collections.timeouts.fields.endDate, ">=", getCurrentServerTimestampInMillis())
|
|
135
|
+
.get()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Query for opened ceremonies.
|
|
140
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
141
|
+
* @returns <Promise<Array<FirebaseDocumentInfo>>>
|
|
142
|
+
*/
|
|
143
|
+
export const queryOpenedCeremonies = async (): Promise<Array<QueryDocumentSnapshot<DocumentData>>> => {
|
|
144
|
+
const querySnap = await admin
|
|
145
|
+
.firestore()
|
|
146
|
+
.collection(commonTerms.collections.ceremonies.name)
|
|
147
|
+
.where(commonTerms.collections.ceremonies.fields.state, "==", CeremonyState.OPENED)
|
|
148
|
+
.where(commonTerms.collections.ceremonies.fields.endDate, ">=", getCurrentServerTimestampInMillis())
|
|
149
|
+
.get()
|
|
150
|
+
|
|
151
|
+
if (!querySnap.docs) logAndThrowError(SPECIFIC_ERRORS.SE_CONTRIBUTE_NO_OPENED_CEREMONIES)
|
|
152
|
+
|
|
153
|
+
return querySnap.docs
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get ceremony circuit document by sequence position.
|
|
158
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
159
|
+
* @param sequencePosition <number> - the sequence position of the circuit.
|
|
160
|
+
* @returns Promise<QueryDocumentSnapshot<DocumentData>>
|
|
161
|
+
*/
|
|
162
|
+
export const getCircuitDocumentByPosition = async (
|
|
163
|
+
ceremonyId: string,
|
|
164
|
+
sequencePosition: number
|
|
165
|
+
): Promise<QueryDocumentSnapshot<DocumentData>> => {
|
|
166
|
+
// Query for all ceremony circuits.
|
|
167
|
+
const circuits = await getCeremonyCircuits(ceremonyId)
|
|
168
|
+
|
|
169
|
+
// Apply a filter using the sequence postion.
|
|
170
|
+
const matchedCircuits = circuits.filter(
|
|
171
|
+
(circuit: DocumentData) => circuit.data().sequencePosition === sequencePosition
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (matchedCircuits.length !== 1) logAndThrowError(COMMON_ERRORS.CM_NO_CIRCUIT_FOR_GIVEN_SEQUENCE_POSITION)
|
|
175
|
+
|
|
176
|
+
return matchedCircuits.at(0)!
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Create a temporary file path in the virtual memory of the cloud function.
|
|
181
|
+
* @dev useful when downloading files from AWS S3 buckets for processing within cloud functions.
|
|
182
|
+
* @param completeFilename <string> - the complete file name (name + ext).
|
|
183
|
+
* @returns <string> - the path to the local temporary location.
|
|
184
|
+
*/
|
|
185
|
+
export const createTemporaryLocalPath = (completeFilename: string): string => path.join(os.tmpdir(), completeFilename)
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Download an artifact from the AWS S3 bucket.
|
|
189
|
+
* @dev this method uses streams.
|
|
190
|
+
* @param bucketName <string> - the name of the bucket.
|
|
191
|
+
* @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
|
|
192
|
+
* @param localFilePath <string> - the local path where the file will be stored.
|
|
193
|
+
*/
|
|
194
|
+
export const downloadArtifactFromS3Bucket = async (bucketName: string, objectKey: string, localFilePath: string) => {
|
|
195
|
+
// Prepare AWS S3 client instance.
|
|
196
|
+
const client = await getS3Client()
|
|
197
|
+
|
|
198
|
+
// Prepare command.
|
|
199
|
+
const command = new GetObjectCommand({ Bucket: bucketName, Key: objectKey })
|
|
200
|
+
|
|
201
|
+
// Generate a pre-signed url for downloading the file.
|
|
202
|
+
const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION) })
|
|
203
|
+
|
|
204
|
+
// Execute download request.
|
|
205
|
+
// @ts-ignore
|
|
206
|
+
const response: any = await fetch(url, {
|
|
207
|
+
method: "GET",
|
|
208
|
+
headers: {
|
|
209
|
+
"Access-Control-Allow-Origin": "*"
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
if (response.status !== 200 || !response.ok) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_DOWNLOAD_FAILED)
|
|
214
|
+
|
|
215
|
+
// Write the file locally using streams.
|
|
216
|
+
const writeStream = createWriteStream(localFilePath)
|
|
217
|
+
const streamPipeline = promisify(pipeline)
|
|
218
|
+
await streamPipeline(response.body, writeStream)
|
|
219
|
+
|
|
220
|
+
writeStream.on('finish', () => {
|
|
221
|
+
writeStream.end()
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Upload a new artifact to the AWS S3 bucket.
|
|
227
|
+
* @dev this method uses streams.
|
|
228
|
+
* @param bucketName <string> - the name of the bucket.
|
|
229
|
+
* @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
|
|
230
|
+
* @param localFilePath <string> - the local path where the file to be uploaded is stored.
|
|
231
|
+
*/
|
|
232
|
+
export const uploadFileToBucket = async (
|
|
233
|
+
bucketName: string,
|
|
234
|
+
objectKey: string,
|
|
235
|
+
localFilePath: string,
|
|
236
|
+
isPublic: boolean = false
|
|
237
|
+
) => {
|
|
238
|
+
// Prepare AWS S3 client instance.
|
|
239
|
+
const client = await getS3Client()
|
|
240
|
+
|
|
241
|
+
// Extract content type.
|
|
242
|
+
const contentType = mime.lookup(localFilePath) || ""
|
|
243
|
+
|
|
244
|
+
// Prepare command.
|
|
245
|
+
const command = new PutObjectCommand({
|
|
246
|
+
Bucket: bucketName,
|
|
247
|
+
Key: objectKey,
|
|
248
|
+
ContentType: contentType,
|
|
249
|
+
ACL: isPublic ? "public-read" : "private"
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Generate a pre-signed url for uploading the file.
|
|
253
|
+
const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION) })
|
|
254
|
+
|
|
255
|
+
// Execute upload request.
|
|
256
|
+
// @ts-ignore
|
|
257
|
+
const response = await fetch(url, {
|
|
258
|
+
method: "PUT",
|
|
259
|
+
body: readFileSync(localFilePath),
|
|
260
|
+
headers: { "Content-Type": contentType }
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
if (response.status !== 200 || !response.ok) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_UPLOAD_FAILED)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const uploadFileToBucketNoFile = async (
|
|
267
|
+
bucketName: string,
|
|
268
|
+
objectKey: string,
|
|
269
|
+
data: string,
|
|
270
|
+
isPublic: boolean = false
|
|
271
|
+
) => {
|
|
272
|
+
// Prepare AWS S3 client instance.
|
|
273
|
+
const client = await getS3Client()
|
|
274
|
+
|
|
275
|
+
// Prepare command.
|
|
276
|
+
const command = new PutObjectCommand({
|
|
277
|
+
Bucket: bucketName,
|
|
278
|
+
Key: objectKey,
|
|
279
|
+
ContentType: "text/plain",
|
|
280
|
+
ACL: isPublic ? "public-read" : "private"
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// Generate a pre-signed url for uploading the file.
|
|
284
|
+
const url = await getSignedUrl(client, command, { expiresIn: Number(process.env.AWS_PRESIGNED_URL_EXPIRATION) })
|
|
285
|
+
|
|
286
|
+
// Execute upload request.
|
|
287
|
+
// @ts-ignore
|
|
288
|
+
const response = await fetch(url, {
|
|
289
|
+
method: "PUT",
|
|
290
|
+
body: data,
|
|
291
|
+
headers: { "Content-Type": "text/plain" }
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
if (response.status !== 200 || !response.ok) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_UPLOAD_FAILED)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Upload an artifact from the AWS S3 bucket.
|
|
299
|
+
* @param bucketName <string> - the name of the bucket.
|
|
300
|
+
* @param objectKey <string> - the unique key to identify the object inside the given AWS S3 bucket.
|
|
301
|
+
*/
|
|
302
|
+
export const deleteObject = async (bucketName: string, objectKey: string) => {
|
|
303
|
+
// Prepare AWS S3 client instance.
|
|
304
|
+
const client = await getS3Client()
|
|
305
|
+
|
|
306
|
+
// Prepare command.
|
|
307
|
+
const command = new DeleteObjectCommand({ Bucket: bucketName, Key: objectKey })
|
|
308
|
+
|
|
309
|
+
// Execute command.
|
|
310
|
+
const data = await client.send(command)
|
|
311
|
+
|
|
312
|
+
if (data.$metadata.httpStatusCode !== 204) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_DELETE_FAILED)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Query ceremonies by state and (start/end) date value.
|
|
317
|
+
* @param state <string> - the state of the ceremony.
|
|
318
|
+
* @param needToCheckStartDate <boolean> - flag to discriminate when to check startDate (true) or endDate (false).
|
|
319
|
+
* @param check <WhereFilerOp> - the type of filter (query check - e.g., '<' or '>').
|
|
320
|
+
* @returns <Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>>> - the queried ceremonies after filtering operation.
|
|
321
|
+
*/
|
|
322
|
+
export const queryCeremoniesByStateAndDate = async (
|
|
323
|
+
state: string,
|
|
324
|
+
needToCheckStartDate: boolean,
|
|
325
|
+
check: WhereFilterOp
|
|
326
|
+
): Promise<admin.firestore.QuerySnapshot<admin.firestore.DocumentData>> =>
|
|
327
|
+
admin
|
|
328
|
+
.firestore()
|
|
329
|
+
.collection(commonTerms.collections.ceremonies.name)
|
|
330
|
+
.where(commonTerms.collections.ceremonies.fields.state, "==", state)
|
|
331
|
+
.where(
|
|
332
|
+
needToCheckStartDate
|
|
333
|
+
? commonTerms.collections.ceremonies.fields.startDate
|
|
334
|
+
: commonTerms.collections.ceremonies.fields.endDate,
|
|
335
|
+
check,
|
|
336
|
+
getCurrentServerTimestampInMillis()
|
|
337
|
+
)
|
|
338
|
+
.get()
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Return the document associated with the final contribution for a ceremony circuit.
|
|
342
|
+
* @dev this method is useful during ceremony finalization.
|
|
343
|
+
* @param ceremonyId <string> -
|
|
344
|
+
* @param circuitId <string> -
|
|
345
|
+
* @returns Promise<QueryDocumentSnapshot<DocumentData>> - the final contribution for the ceremony circuit.
|
|
346
|
+
*/
|
|
347
|
+
export const getFinalContribution = async (
|
|
348
|
+
ceremonyId: string,
|
|
349
|
+
circuitId: string
|
|
350
|
+
): Promise<QueryDocumentSnapshot<DocumentData>> => {
|
|
351
|
+
// Get contributions for the circuit.
|
|
352
|
+
const contributions = await getCeremonyCircuitContributions(ceremonyId, circuitId)
|
|
353
|
+
|
|
354
|
+
// Match the final one.
|
|
355
|
+
const matchContribution = contributions.filter(
|
|
356
|
+
(contribution: DocumentData) => contribution.data().zkeyIndex === finalContributionIndex
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
if (!matchContribution) logAndThrowError(SPECIFIC_ERRORS.SE_FINALIZE_NO_FINAL_CONTRIBUTION)
|
|
360
|
+
|
|
361
|
+
// Get the final contribution.
|
|
362
|
+
// nb. there must be only one final contributions x circuit.
|
|
363
|
+
const finalContribution = matchContribution.at(0)!
|
|
364
|
+
|
|
365
|
+
return finalContribution
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Helper function to HTML encode circuit data.
|
|
370
|
+
* @param circuitDocument <CircuitDocument> - the circuit document to be encoded.
|
|
371
|
+
* @returns <CircuitDocument> - the circuit document encoded.
|
|
372
|
+
*/
|
|
373
|
+
export const htmlEncodeCircuitData = (circuitDocument: CircuitDocument): CircuitDocument => ({
|
|
374
|
+
...circuitDocument,
|
|
375
|
+
description: encode(circuitDocument.description),
|
|
376
|
+
name: encode(circuitDocument.name),
|
|
377
|
+
prefix: encode(circuitDocument.prefix)
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Fetch the variables related to GitHub anti-sybil checks
|
|
382
|
+
* @returns <any> - the GitHub variables.
|
|
383
|
+
*/
|
|
384
|
+
export const getGitHubVariables = (): any => {
|
|
385
|
+
if (
|
|
386
|
+
!process.env.GITHUB_MINIMUM_FOLLOWERS ||
|
|
387
|
+
!process.env.GITHUB_MINIMUM_FOLLOWING ||
|
|
388
|
+
!process.env.GITHUB_MINIMUM_PUBLIC_REPOS
|
|
389
|
+
)
|
|
390
|
+
logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
minimumFollowers: Number(process.env.GITHUB_MINIMUM_FOLLOWERS),
|
|
394
|
+
minimumFollowing: Number(process.env.GITHUB_MINIMUM_FOLLOWING),
|
|
395
|
+
minimumPublicRepos: Number(process.env.GITHUB_MINIMUM_PUBLIC_REPOS)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Fetch the variables related to EC2 verification
|
|
401
|
+
* @returns <any> - the AWS EC2 variables.
|
|
402
|
+
*/
|
|
403
|
+
export const getAWSVariables = (): any => {
|
|
404
|
+
if (
|
|
405
|
+
!process.env.AWS_ACCESS_KEY_ID ||
|
|
406
|
+
!process.env.AWS_SECRET_ACCESS_KEY ||
|
|
407
|
+
!process.env.AWS_ROLE_ARN ||
|
|
408
|
+
!process.env.AWS_AMI_ID ||
|
|
409
|
+
!process.env.AWS_SNS_TOPIC_ARN
|
|
410
|
+
)
|
|
411
|
+
logAndThrowError(COMMON_ERRORS.CM_WRONG_CONFIGURATION)
|
|
412
|
+
|
|
413
|
+
return {
|
|
414
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
415
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
416
|
+
region: process.env.AWS_REGION || "eu-central-1",
|
|
417
|
+
roleArn: process.env.AWS_ROLE_ARN!,
|
|
418
|
+
amiId: process.env.AWS_AMI_ID!,
|
|
419
|
+
snsTopic: process.env.AWS_SNS_TOPIC_ARN!
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Create an EC2 client object
|
|
425
|
+
* @returns <Promise<EC2Client>> an EC2 client
|
|
426
|
+
*/
|
|
427
|
+
export const createEC2Client = async (): Promise<EC2Client> => {
|
|
428
|
+
const { accessKeyId, secretAccessKey, region } = getAWSVariables()
|
|
429
|
+
|
|
430
|
+
const ec2: EC2Client = new EC2Client({
|
|
431
|
+
credentials: {
|
|
432
|
+
accessKeyId,
|
|
433
|
+
secretAccessKey
|
|
434
|
+
},
|
|
435
|
+
region
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
return ec2
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Create an SSM client object
|
|
443
|
+
* @returns <Promise<SSMClient>> an SSM client
|
|
444
|
+
*/
|
|
445
|
+
export const createSSMClient = async (): Promise<SSMClient> => {
|
|
446
|
+
const { accessKeyId, secretAccessKey, region } = getAWSVariables()
|
|
447
|
+
|
|
448
|
+
const ssm: SSMClient = new SSMClient({
|
|
449
|
+
credentials: {
|
|
450
|
+
accessKeyId,
|
|
451
|
+
secretAccessKey
|
|
452
|
+
},
|
|
453
|
+
region
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
return ssm
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get the instance id of the EC2 instance associated with the circuit
|
|
461
|
+
* @param circuitId <string> - the circuit id
|
|
462
|
+
* @returns <Promise<string>> - the EC2 instance id
|
|
463
|
+
*/
|
|
464
|
+
export const getEC2InstanceId = async (circuitId: string): Promise<string> => {
|
|
465
|
+
const circuitDoc = await getDocumentById(commonTerms.collections.circuits.name, circuitId)
|
|
466
|
+
|
|
467
|
+
const circuitData = circuitDoc.data()
|
|
468
|
+
|
|
469
|
+
const { vmInstanceId } = circuitData!
|
|
470
|
+
|
|
471
|
+
return vmInstanceId
|
|
472
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { CeremonyInputData, CircuitDocument, ETagWithPartNumber } from "@p0tion/actions"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Group all the necessary data needed for running the `setupCeremony` cloud function.
|
|
5
|
+
* @typedef {Object} SetupCeremonyData
|
|
6
|
+
* @property {CeremonyInputData} ceremonyInputData - the necessary input data for setup a new ceremony.
|
|
7
|
+
* @property {string} ceremonyPrefix - the ceremony prefix.
|
|
8
|
+
* @property {Array<CircuitDocument>} circuits - the necessary input data for setup the related ceremony circuits.
|
|
9
|
+
*/
|
|
10
|
+
export type SetupCeremonyData = {
|
|
11
|
+
ceremonyInputData: CeremonyInputData
|
|
12
|
+
ceremonyPrefix: string
|
|
13
|
+
circuits: Array<CircuitDocument>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Group all the necessary data needed for running the `createBucket` cloud function.
|
|
18
|
+
* @typedef {Object} CreateBucketData
|
|
19
|
+
* @property {string} bucketName - the name of the bucket.
|
|
20
|
+
*/
|
|
21
|
+
export type CreateBucketData = {
|
|
22
|
+
bucketName: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Group all the necessary data needed for running the `checkIfObjectExist` or `generateGetObjectPreSignedUrl` cloud functions.
|
|
27
|
+
* @typedef {Object} BucketAndObjectKeyData
|
|
28
|
+
* @property {string} bucketName - the name of the bucket.
|
|
29
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
30
|
+
*/
|
|
31
|
+
export type BucketAndObjectKeyData = {
|
|
32
|
+
bucketName: string
|
|
33
|
+
objectKey: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Group all the necessary data needed for running the `startMultiPartUpload` cloud function.
|
|
38
|
+
* @typedef {Object} StartMultiPartUploadData
|
|
39
|
+
* @property {string} bucketName - the name of the bucket.
|
|
40
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
41
|
+
* @property {string} ceremonyId - the prefix of the ceremony.
|
|
42
|
+
*/
|
|
43
|
+
export type StartMultiPartUploadData = BucketAndObjectKeyData & {
|
|
44
|
+
ceremonyId?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Group all the necessary data needed for running the `generatePreSignedUrlsParts` cloud function.
|
|
49
|
+
* @typedef {Object} GeneratePreSignedUrlsPartsData
|
|
50
|
+
* @property {string} bucketName - the name of the bucket.
|
|
51
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
52
|
+
* @property {string} uploadId - the identifier of the initiated multi-part upload.
|
|
53
|
+
* @property {number} numberOfParts - the amount of chunks for which pre-signed urls are to be generated.
|
|
54
|
+
* @property {string} ceremonyId - the prefix of the ceremony.
|
|
55
|
+
*/
|
|
56
|
+
export type GeneratePreSignedUrlsPartsData = BucketAndObjectKeyData & {
|
|
57
|
+
uploadId: string
|
|
58
|
+
numberOfParts: number
|
|
59
|
+
ceremonyId?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Group all the necessary data needed for running the `completeMultiPartUpload` cloud function.
|
|
64
|
+
* @typedef {Object} GeneratePreSignedUrlsPartsData
|
|
65
|
+
* @property {string} bucketName - the name of the bucket.
|
|
66
|
+
* @property {string} objectKey - the unique key to identify the object inside the given AWS S3 bucket.
|
|
67
|
+
* @property {string} uploadId - the identifier of the initiated multi-part upload.
|
|
68
|
+
* @property {Array<ETagWithPartNumber>} parts - the chunks of the file related to the multi-part upload.
|
|
69
|
+
* @property {string} [ceremonyId] - the unique identifier of the ceremony.
|
|
70
|
+
*/
|
|
71
|
+
export type CompleteMultiPartUploadData = BucketAndObjectKeyData & {
|
|
72
|
+
uploadId: string
|
|
73
|
+
parts: Array<ETagWithPartNumber>
|
|
74
|
+
ceremonyId?: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Group all the necessary data needed for running the `permanentlyStoreCurrentContributionTimeAndHash` cloud function.
|
|
79
|
+
* @typedef {Object} PermanentlyStoreCurrentContributionTimeAndHash
|
|
80
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
81
|
+
* @property {number} contributionComputationTime - the time spent by the contributor to compute the contribution.
|
|
82
|
+
* @property {string} contributionHash - the hash of the contribution.
|
|
83
|
+
*/
|
|
84
|
+
export type PermanentlyStoreCurrentContributionTimeAndHash = {
|
|
85
|
+
ceremonyId: string
|
|
86
|
+
contributionComputationTime: number
|
|
87
|
+
contributionHash: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Group all the necessary data needed for running the `temporaryStoreCurrentContributionMultiPartUploadId` cloud function.
|
|
92
|
+
* @typedef {Object} TemporaryStoreCurrentContributionMultiPartUploadId
|
|
93
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
94
|
+
* @property {number} uploadId - the unique identifier of the already started multi-part upload.
|
|
95
|
+
*/
|
|
96
|
+
export type TemporaryStoreCurrentContributionMultiPartUploadId = {
|
|
97
|
+
ceremonyId: string
|
|
98
|
+
uploadId: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Group all the necessary data needed for running the `temporaryStoreCurrentContributionUploadedChunkData` cloud function.
|
|
103
|
+
* @typedef {Object} TemporaryStoreCurrentContributionUploadedChunkData
|
|
104
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
105
|
+
* @property {number} uploadId - the unique identifier of the already started multi-part upload.
|
|
106
|
+
*/
|
|
107
|
+
export type TemporaryStoreCurrentContributionUploadedChunkData = {
|
|
108
|
+
ceremonyId: string
|
|
109
|
+
chunk: ETagWithPartNumber
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Group all the necessary data needed for running the `verifycontribution` cloud function.
|
|
114
|
+
* @typedef {Object} VerifyContributionData
|
|
115
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
116
|
+
* @property {string} circuitId - the unique identifier of the circuit.
|
|
117
|
+
* @property {string} bucketName - the name of the bucket.
|
|
118
|
+
* @property {string} contributorOrCoordinatorIdentifier - the identifier of the contributor or coordinator (only when finalizing).
|
|
119
|
+
*/
|
|
120
|
+
export type VerifyContributionData = {
|
|
121
|
+
ceremonyId: string
|
|
122
|
+
circuitId: string
|
|
123
|
+
bucketName: string
|
|
124
|
+
contributorOrCoordinatorIdentifier: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Group all the necessary data needed for running the `finalizeCircuit` cloud function.
|
|
129
|
+
* @typedef {Object} FinalizeCircuitData
|
|
130
|
+
* @property {string} ceremonyId - the unique identifier of the ceremony.
|
|
131
|
+
* @property {string} circuitId - the unique identifier of the circuit.
|
|
132
|
+
* @property {string} bucketName - the name of the bucket.
|
|
133
|
+
* @property {string} beacon - the value used to compute the final contribution while finalizing the ceremony.
|
|
134
|
+
*/
|
|
135
|
+
export type FinalizeCircuitData = {
|
|
136
|
+
ceremonyId: string
|
|
137
|
+
circuitId: string
|
|
138
|
+
bucketName: string
|
|
139
|
+
beacon: string
|
|
140
|
+
}
|