@devtion/devcli 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 +118 -0
- package/dist/.env +41 -0
- package/dist/index.js +3206 -0
- package/dist/types/commands/auth.d.ts +25 -0
- package/dist/types/commands/clean.d.ts +6 -0
- package/dist/types/commands/contribute.d.ts +139 -0
- package/dist/types/commands/finalize.d.ts +51 -0
- package/dist/types/commands/index.d.ts +9 -0
- package/dist/types/commands/listCeremonies.d.ts +5 -0
- package/dist/types/commands/logout.d.ts +6 -0
- package/dist/types/commands/observe.d.ts +22 -0
- package/dist/types/commands/setup.d.ts +86 -0
- package/dist/types/commands/validate.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lib/errors.d.ts +60 -0
- package/dist/types/lib/files.d.ts +64 -0
- package/dist/types/lib/localConfigs.d.ts +110 -0
- package/dist/types/lib/prompts.d.ts +104 -0
- package/dist/types/lib/services.d.ts +31 -0
- package/dist/types/lib/theme.d.ts +42 -0
- package/dist/types/lib/utils.d.ts +158 -0
- package/dist/types/types/index.d.ts +65 -0
- package/package.json +100 -0
- package/src/commands/auth.ts +194 -0
- package/src/commands/clean.ts +49 -0
- package/src/commands/contribute.ts +1090 -0
- package/src/commands/finalize.ts +382 -0
- package/src/commands/index.ts +9 -0
- package/src/commands/listCeremonies.ts +32 -0
- package/src/commands/logout.ts +67 -0
- package/src/commands/observe.ts +193 -0
- package/src/commands/setup.ts +901 -0
- package/src/commands/validate.ts +29 -0
- package/src/index.ts +66 -0
- package/src/lib/errors.ts +77 -0
- package/src/lib/files.ts +102 -0
- package/src/lib/localConfigs.ts +186 -0
- package/src/lib/prompts.ts +748 -0
- package/src/lib/services.ts +191 -0
- package/src/lib/theme.ts +45 -0
- package/src/lib/utils.ts +778 -0
- package/src/types/conf.d.ts +16 -0
- package/src/types/index.ts +70 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import open from "open"
|
|
3
|
+
import {
|
|
4
|
+
isCoordinator,
|
|
5
|
+
getClosedCeremonies,
|
|
6
|
+
getDocumentById,
|
|
7
|
+
getParticipantsCollectionPath,
|
|
8
|
+
checkAndPrepareCoordinatorForFinalization,
|
|
9
|
+
getCeremonyCircuits,
|
|
10
|
+
getVerificationKeyStorageFilePath,
|
|
11
|
+
getBucketName,
|
|
12
|
+
multiPartUpload,
|
|
13
|
+
getVerifierContractStorageFilePath,
|
|
14
|
+
finalizeCeremony,
|
|
15
|
+
generateValidContributionsAttestation,
|
|
16
|
+
commonTerms,
|
|
17
|
+
finalContributionIndex,
|
|
18
|
+
computeSHA256ToHex,
|
|
19
|
+
finalizeCircuit,
|
|
20
|
+
verificationKeyAcronym,
|
|
21
|
+
verifierSmartContractAcronym,
|
|
22
|
+
exportVerifierContract,
|
|
23
|
+
FirebaseDocumentInfo,
|
|
24
|
+
exportVkey
|
|
25
|
+
} from "@p0tion/actions"
|
|
26
|
+
import { Functions } from "firebase/functions"
|
|
27
|
+
import { Firestore } from "firebase/firestore"
|
|
28
|
+
import { dirname } from "path"
|
|
29
|
+
import { fileURLToPath } from "url"
|
|
30
|
+
import { COMMAND_ERRORS, showError } from "../lib/errors.js"
|
|
31
|
+
import {
|
|
32
|
+
customSpinner,
|
|
33
|
+
generateCustomUrlToTweetAboutParticipation,
|
|
34
|
+
handleStartOrResumeContribution,
|
|
35
|
+
publishGist,
|
|
36
|
+
sleep,
|
|
37
|
+
terminate
|
|
38
|
+
} from "../lib/utils.js"
|
|
39
|
+
import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
|
|
40
|
+
import {
|
|
41
|
+
getAttestationLocalFilePath,
|
|
42
|
+
getFinalZkeyLocalFilePath,
|
|
43
|
+
getVerificationKeyLocalFilePath,
|
|
44
|
+
getVerifierContractLocalFilePath,
|
|
45
|
+
localPaths
|
|
46
|
+
} from "../lib/localConfigs.js"
|
|
47
|
+
import theme from "../lib/theme.js"
|
|
48
|
+
import { checkAndMakeNewDirectoryIfNonexistent, writeLocalJsonFile, writeFile } from "../lib/files.js"
|
|
49
|
+
import { promptForCeremonySelection, promptToTypeEntropyOrBeacon } from "../lib/prompts.js"
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Export and store on the ceremony bucket the verification key for the given final contribution.
|
|
53
|
+
* @param cloudFunctions <Functions> - the instance of the Firebase cloud functions for the application.
|
|
54
|
+
* @param bucketName <string> - the name of the ceremony bucket.
|
|
55
|
+
* @param finalZkeyLocalFilePath <string> - the local file path of the final zKey.
|
|
56
|
+
* @param verificationKeyLocalFilePath <string> - the local file path of the verification key.
|
|
57
|
+
* @param verificationKeyStorageFilePath <string> - the storage file path of the verification key.
|
|
58
|
+
*/
|
|
59
|
+
export const handleVerificationKey = async (
|
|
60
|
+
cloudFunctions: Functions,
|
|
61
|
+
bucketName: string,
|
|
62
|
+
finalZkeyLocalFilePath: string,
|
|
63
|
+
verificationKeyLocalFilePath: string,
|
|
64
|
+
verificationKeyStorageFilePath: string
|
|
65
|
+
) => {
|
|
66
|
+
const spinner = customSpinner(`Exporting the verification key...`, "clock")
|
|
67
|
+
spinner.start()
|
|
68
|
+
|
|
69
|
+
// Export the verification key.
|
|
70
|
+
const vKey = await exportVkey(finalZkeyLocalFilePath)
|
|
71
|
+
|
|
72
|
+
spinner.text = "Writing verification key..."
|
|
73
|
+
|
|
74
|
+
// Write the verification key locally.
|
|
75
|
+
writeLocalJsonFile(verificationKeyLocalFilePath, vKey)
|
|
76
|
+
|
|
77
|
+
await sleep(3000) // workaound for file descriptor.
|
|
78
|
+
|
|
79
|
+
// Upload verification key to storage.
|
|
80
|
+
await multiPartUpload(
|
|
81
|
+
cloudFunctions,
|
|
82
|
+
bucketName,
|
|
83
|
+
verificationKeyStorageFilePath,
|
|
84
|
+
verificationKeyLocalFilePath,
|
|
85
|
+
Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
spinner.succeed(`Verification key correctly saved on storage`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Derive and store on the ceremony bucket the Solidity Verifier smart contract for the given final contribution.
|
|
93
|
+
* @param cloudFunctions <Functions> - the instance of the Firebase cloud functions for the application.
|
|
94
|
+
* @param bucketName <string> - the name of the ceremony bucket.
|
|
95
|
+
* @param finalZkeyLocalFilePath <string> - the local file path of the final zKey.
|
|
96
|
+
* @param verifierContractLocalFilePath <string> - the local file path of the verifier smart contract.
|
|
97
|
+
* @param verifierContractStorageFilePath <string> - the storage file path of the verifier smart contract.
|
|
98
|
+
*/
|
|
99
|
+
export const handleVerifierSmartContract = async (
|
|
100
|
+
cloudFunctions: Functions,
|
|
101
|
+
bucketName: string,
|
|
102
|
+
finalZkeyLocalFilePath: string,
|
|
103
|
+
verifierContractLocalFilePath: string,
|
|
104
|
+
verifierContractStorageFilePath: string
|
|
105
|
+
) => {
|
|
106
|
+
const spinner = customSpinner(`Extracting verifier contract...`, `clock`)
|
|
107
|
+
spinner.start()
|
|
108
|
+
|
|
109
|
+
// Verifier path.
|
|
110
|
+
const packagePath = `${dirname(fileURLToPath(import.meta.url))}`
|
|
111
|
+
const verifierPath = packagePath.includes(`src/commands`)
|
|
112
|
+
? `${dirname(
|
|
113
|
+
fileURLToPath(import.meta.url)
|
|
114
|
+
)}/../../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
|
|
115
|
+
: `${dirname(fileURLToPath(import.meta.url))}/../../../node_modules/snarkjs/templates/verifier_groth16.sol.ejs`
|
|
116
|
+
|
|
117
|
+
// Export the Solidity verifier smart contract.
|
|
118
|
+
const verifierCode = await exportVerifierContract(finalZkeyLocalFilePath, verifierPath)
|
|
119
|
+
|
|
120
|
+
spinner.text = `Writing verifier smart contract...`
|
|
121
|
+
|
|
122
|
+
// Write the verification key locally.
|
|
123
|
+
writeFile(verifierContractLocalFilePath, verifierCode)
|
|
124
|
+
|
|
125
|
+
await sleep(3000) // workaound for file descriptor.
|
|
126
|
+
|
|
127
|
+
// Upload verifier smart contract to storage.
|
|
128
|
+
await multiPartUpload(
|
|
129
|
+
cloudFunctions,
|
|
130
|
+
bucketName,
|
|
131
|
+
verifierContractStorageFilePath,
|
|
132
|
+
verifierContractLocalFilePath,
|
|
133
|
+
Number(process.env.CONFIG_STREAM_CHUNK_SIZE_IN_MB)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
spinner.succeed(`Verifier smart contract correctly saved on storage`)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Handle the process of finalizing a ceremony circuit.
|
|
141
|
+
* @dev this process results in the extraction of the final ceremony artifacts for the calculation and verification of proofs.
|
|
142
|
+
* @notice this method must enforce the order among these steps:
|
|
143
|
+
* 1) Compute the final contribution (zKey).
|
|
144
|
+
* 2) Extract the verification key (vKey).
|
|
145
|
+
* 3) Extract the Verifier smart contract (.sol).
|
|
146
|
+
* 4) Upload the artifacts in the AWS S3 storage.
|
|
147
|
+
* 5) Complete the final contribution data w/ artifacts references and hashes (cloud function).
|
|
148
|
+
* @param cloudFunctions <Functions> - the instance of the Firebase cloud functions for the application.
|
|
149
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
150
|
+
* @param ceremony <FirebaseDocumentInfo> - the Firestore document of the ceremony.
|
|
151
|
+
* @param circuit <FirebaseDocumentInfo> - the Firestore document of the ceremony circuit.
|
|
152
|
+
* @param participant <FirebaseDocumentInfo> - the Firestore document of the participant (coordinator).
|
|
153
|
+
* @param beacon <string> - the value used to compute the final contribution while finalizing the ceremony.
|
|
154
|
+
* @param coordinatorIdentifier <string> - the identifier of the coordinator.
|
|
155
|
+
*/
|
|
156
|
+
export const handleCircuitFinalization = async (
|
|
157
|
+
cloudFunctions: Functions,
|
|
158
|
+
firestoreDatabase: Firestore,
|
|
159
|
+
ceremony: FirebaseDocumentInfo,
|
|
160
|
+
circuit: FirebaseDocumentInfo,
|
|
161
|
+
participant: FirebaseDocumentInfo,
|
|
162
|
+
beacon: string,
|
|
163
|
+
coordinatorIdentifier: string
|
|
164
|
+
) => {
|
|
165
|
+
// Step (1).
|
|
166
|
+
await handleStartOrResumeContribution(
|
|
167
|
+
cloudFunctions,
|
|
168
|
+
firestoreDatabase,
|
|
169
|
+
ceremony,
|
|
170
|
+
circuit,
|
|
171
|
+
participant,
|
|
172
|
+
computeSHA256ToHex(beacon),
|
|
173
|
+
coordinatorIdentifier,
|
|
174
|
+
true
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
await sleep(2000) // workaound for descriptors.
|
|
178
|
+
|
|
179
|
+
// Extract data.
|
|
180
|
+
const { prefix: circuitPrefix } = circuit.data
|
|
181
|
+
const { prefix: ceremonyPrefix } = ceremony.data
|
|
182
|
+
|
|
183
|
+
// Prepare local paths.
|
|
184
|
+
const finalZkeyLocalFilePath = getFinalZkeyLocalFilePath(`${circuitPrefix}_${finalContributionIndex}.zkey`)
|
|
185
|
+
const verificationKeyLocalFilePath = getVerificationKeyLocalFilePath(
|
|
186
|
+
`${circuitPrefix}_${verificationKeyAcronym}.json`
|
|
187
|
+
)
|
|
188
|
+
const verifierContractLocalFilePath = getVerifierContractLocalFilePath(
|
|
189
|
+
`${circuitPrefix}_${verifierSmartContractAcronym}.sol`
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// Prepare storage paths.
|
|
193
|
+
const verificationKeyStorageFilePath = getVerificationKeyStorageFilePath(
|
|
194
|
+
circuitPrefix,
|
|
195
|
+
`${circuitPrefix}_${verificationKeyAcronym}.json`
|
|
196
|
+
)
|
|
197
|
+
const verifierContractStorageFilePath = getVerifierContractStorageFilePath(
|
|
198
|
+
circuitPrefix,
|
|
199
|
+
`${circuitPrefix}_${verifierSmartContractAcronym}.sol`
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// Get ceremony bucket.
|
|
203
|
+
const bucketName = getBucketName(ceremonyPrefix, String(process.env.CONFIG_CEREMONY_BUCKET_POSTFIX))
|
|
204
|
+
|
|
205
|
+
// Step (2 & 4).
|
|
206
|
+
await handleVerificationKey(
|
|
207
|
+
cloudFunctions,
|
|
208
|
+
bucketName,
|
|
209
|
+
finalZkeyLocalFilePath,
|
|
210
|
+
verificationKeyLocalFilePath,
|
|
211
|
+
verificationKeyStorageFilePath
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
// Step (3 & 4).
|
|
215
|
+
await handleVerifierSmartContract(
|
|
216
|
+
cloudFunctions,
|
|
217
|
+
bucketName,
|
|
218
|
+
finalZkeyLocalFilePath,
|
|
219
|
+
verifierContractLocalFilePath,
|
|
220
|
+
verifierContractStorageFilePath
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
// Step (5).
|
|
224
|
+
const spinner = customSpinner(`Wrapping up the finalization of the circuit...`, `clock`)
|
|
225
|
+
spinner.start()
|
|
226
|
+
|
|
227
|
+
// Finalize circuit contribution.
|
|
228
|
+
await finalizeCircuit(cloudFunctions, ceremony.id, circuit.id, bucketName, beacon)
|
|
229
|
+
|
|
230
|
+
await sleep(2000)
|
|
231
|
+
|
|
232
|
+
spinner.succeed(`Circuit has been finalized correctly`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Finalize command.
|
|
237
|
+
* @notice The finalize command allows a coordinator to finalize a Trusted Setup Phase 2 ceremony by providing the final beacon,
|
|
238
|
+
* computing the final zKeys and extracting the Verifier Smart Contract + Verification Keys per each ceremony circuit.
|
|
239
|
+
* anyone could use the final zKey to create a proof and everyone else could verify the correctness using the
|
|
240
|
+
* related verification key (off-chain) or Verifier smart contract (on-chain).
|
|
241
|
+
* @dev For proper execution, the command requires the coordinator to be authenticated with a GitHub account (run auth command first) in order to
|
|
242
|
+
* handle sybil-resistance and connect to GitHub APIs to publish the gist containing the final public attestation.
|
|
243
|
+
*/
|
|
244
|
+
const finalize = async () => {
|
|
245
|
+
const { firebaseApp, firebaseFunctions, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
|
|
246
|
+
|
|
247
|
+
// Check for authentication.
|
|
248
|
+
const { user, providerUserId, token: coordinatorAccessToken } = await checkAuth(firebaseApp)
|
|
249
|
+
|
|
250
|
+
// Preserve command execution only for coordinators.
|
|
251
|
+
if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
|
|
252
|
+
|
|
253
|
+
// Retrieve the closed ceremonies (ready for finalization).
|
|
254
|
+
const ceremoniesClosedForFinalization = await getClosedCeremonies(firestoreDatabase)
|
|
255
|
+
|
|
256
|
+
// Gracefully exit if no ceremonies are closed and ready for finalization.
|
|
257
|
+
if (!ceremoniesClosedForFinalization.length) showError(COMMAND_ERRORS.COMMAND_FINALIZED_NO_CLOSED_CEREMONIES, true)
|
|
258
|
+
|
|
259
|
+
console.log(
|
|
260
|
+
`${theme.symbols.warning} The computation of the final contribution could take the bulk of your computational resources and memory based on the size of the circuit ${theme.emojis.fire}\n`
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
// Prompt for ceremony selection.
|
|
264
|
+
const selectedCeremony = await promptForCeremonySelection(ceremoniesClosedForFinalization, true)
|
|
265
|
+
|
|
266
|
+
// Get coordinator participant document.
|
|
267
|
+
let participant = await getDocumentById(
|
|
268
|
+
firestoreDatabase,
|
|
269
|
+
getParticipantsCollectionPath(selectedCeremony.id),
|
|
270
|
+
user.uid
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const isCoordinatorReadyForCeremonyFinalization = await checkAndPrepareCoordinatorForFinalization(
|
|
274
|
+
firebaseFunctions,
|
|
275
|
+
selectedCeremony.id
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if (!isCoordinatorReadyForCeremonyFinalization)
|
|
279
|
+
showError(COMMAND_ERRORS.COMMAND_FINALIZED_NOT_READY_FOR_FINALIZATION, true)
|
|
280
|
+
|
|
281
|
+
// Prompt for beacon.
|
|
282
|
+
const beacon = await promptToTypeEntropyOrBeacon(false)
|
|
283
|
+
// Compute hash
|
|
284
|
+
const beaconHash = computeSHA256ToHex(beacon)
|
|
285
|
+
// Display.
|
|
286
|
+
console.log(`${theme.symbols.info} Beacon SHA256 hash ${theme.text.bold(beaconHash)}`)
|
|
287
|
+
|
|
288
|
+
// Clean directories.
|
|
289
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.output)
|
|
290
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.finalize)
|
|
291
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.finalZkeys)
|
|
292
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.finalPot)
|
|
293
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.finalAttestations)
|
|
294
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.verificationKeys)
|
|
295
|
+
checkAndMakeNewDirectoryIfNonexistent(localPaths.verifierContracts)
|
|
296
|
+
|
|
297
|
+
// Get ceremony circuits.
|
|
298
|
+
const circuits = await getCeremonyCircuits(firestoreDatabase, selectedCeremony.id)
|
|
299
|
+
|
|
300
|
+
// Handle finalization for each ceremony circuit.
|
|
301
|
+
for await (const circuit of circuits)
|
|
302
|
+
await handleCircuitFinalization(
|
|
303
|
+
firebaseFunctions,
|
|
304
|
+
firestoreDatabase,
|
|
305
|
+
selectedCeremony,
|
|
306
|
+
circuit,
|
|
307
|
+
participant,
|
|
308
|
+
beacon,
|
|
309
|
+
providerUserId
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
process.stdout.write(`\n`)
|
|
313
|
+
|
|
314
|
+
const spinner = customSpinner(`Wrapping up the finalization of the ceremony...`, "clock")
|
|
315
|
+
spinner.start()
|
|
316
|
+
|
|
317
|
+
// Finalize the ceremony.
|
|
318
|
+
await finalizeCeremony(firebaseFunctions, selectedCeremony.id)
|
|
319
|
+
|
|
320
|
+
spinner.succeed(
|
|
321
|
+
`Great, you have completed the finalization of the ${theme.text.bold(selectedCeremony.data.title)} ceremony ${
|
|
322
|
+
theme.emojis.tada
|
|
323
|
+
}\n`
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
// Get updated coordinator participant document.
|
|
327
|
+
participant = await getDocumentById(firestoreDatabase, getParticipantsCollectionPath(selectedCeremony.id), user.uid)
|
|
328
|
+
|
|
329
|
+
// Extract updated data.
|
|
330
|
+
const { contributions } = participant.data()!
|
|
331
|
+
const { prefix, title: ceremonyName } = selectedCeremony.data
|
|
332
|
+
|
|
333
|
+
// Generate attestation with final contributions.
|
|
334
|
+
const publicAttestation = await generateValidContributionsAttestation(
|
|
335
|
+
firestoreDatabase,
|
|
336
|
+
circuits,
|
|
337
|
+
selectedCeremony.id,
|
|
338
|
+
participant.id,
|
|
339
|
+
contributions,
|
|
340
|
+
providerUserId,
|
|
341
|
+
ceremonyName,
|
|
342
|
+
true
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
// Write public attestation locally.
|
|
346
|
+
writeFile(
|
|
347
|
+
getAttestationLocalFilePath(
|
|
348
|
+
`${prefix}_${finalContributionIndex}_${commonTerms.foldersAndPathsTerms.attestation}.log`
|
|
349
|
+
),
|
|
350
|
+
Buffer.from(publicAttestation)
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
await sleep(3000) // workaround for file descriptor unexpected close.
|
|
354
|
+
|
|
355
|
+
const gistUrl = await publishGist(coordinatorAccessToken, publicAttestation, ceremonyName, prefix)
|
|
356
|
+
|
|
357
|
+
console.log(
|
|
358
|
+
`\n${
|
|
359
|
+
theme.symbols.info
|
|
360
|
+
} Your public final attestation has been successfully posted as Github Gist (${theme.text.bold(
|
|
361
|
+
theme.text.underlined(gistUrl)
|
|
362
|
+
)})`
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
// Generate a ready to share custom url to tweet about ceremony participation.
|
|
366
|
+
const tweetUrl = generateCustomUrlToTweetAboutParticipation(ceremonyName, gistUrl, true)
|
|
367
|
+
|
|
368
|
+
console.log(
|
|
369
|
+
`${
|
|
370
|
+
theme.symbols.info
|
|
371
|
+
} We encourage you to tweet about the ceremony finalization by clicking the link below\n\n${theme.text.underlined(
|
|
372
|
+
tweetUrl
|
|
373
|
+
)}`
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
// Automatically open a webpage with the tweet.
|
|
377
|
+
await open(tweetUrl)
|
|
378
|
+
|
|
379
|
+
terminate(providerUserId)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export default finalize
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as setup } from "./setup.js"
|
|
2
|
+
export { default as auth } from "./auth.js"
|
|
3
|
+
export { default as contribute } from "./contribute.js"
|
|
4
|
+
export { default as observe } from "./observe.js"
|
|
5
|
+
export { default as finalize } from "./finalize.js"
|
|
6
|
+
export { default as clean } from "./clean.js"
|
|
7
|
+
export { default as logout } from "./logout.js"
|
|
8
|
+
export { default as validate } from "./validate.js"
|
|
9
|
+
export { default as listCeremonies} from "./listCeremonies.js"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { commonTerms, getAllCollectionDocs } from "@p0tion/actions"
|
|
2
|
+
import { showError } from "../lib/errors.js"
|
|
3
|
+
import { bootstrapCommandExecutionAndServices } from "../lib/services.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Validate ceremony setup command.
|
|
7
|
+
*/
|
|
8
|
+
const listCeremonies = async () => {
|
|
9
|
+
try {
|
|
10
|
+
// bootstrap command execution and services
|
|
11
|
+
const { firestoreDatabase } = await bootstrapCommandExecutionAndServices()
|
|
12
|
+
|
|
13
|
+
// get all ceremonies
|
|
14
|
+
const ceremonies = await getAllCollectionDocs(firestoreDatabase, commonTerms.collections.ceremonies.name)
|
|
15
|
+
// store all names
|
|
16
|
+
const names: string[] = []
|
|
17
|
+
|
|
18
|
+
// loop through all ceremonies
|
|
19
|
+
for (const ceremony of ceremonies) names.push(ceremony.data().prefix)
|
|
20
|
+
|
|
21
|
+
// print them to the console
|
|
22
|
+
console.log(names.join(", "))
|
|
23
|
+
process.exit(0)
|
|
24
|
+
|
|
25
|
+
} catch (err: any) {
|
|
26
|
+
showError(`${err.toString()}`, false)
|
|
27
|
+
// we want to exit with a non-zero exit code
|
|
28
|
+
process.exit(1)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default listCeremonies
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getAuth, signOut } from "firebase/auth"
|
|
4
|
+
import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
|
|
5
|
+
import { showError } from "../lib/errors.js"
|
|
6
|
+
import { askForConfirmation } from "../lib/prompts.js"
|
|
7
|
+
import { customSpinner, sleep, terminate } from "../lib/utils.js"
|
|
8
|
+
import theme from "../lib/theme.js"
|
|
9
|
+
import { deleteLocalAccessToken } from "../lib/localConfigs.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Logout command.
|
|
13
|
+
*/
|
|
14
|
+
const logout = async () => {
|
|
15
|
+
try {
|
|
16
|
+
// Initialize services.
|
|
17
|
+
const { firebaseApp } = await bootstrapCommandExecutionAndServices()
|
|
18
|
+
|
|
19
|
+
// Check for authentication.
|
|
20
|
+
const { providerUserId } = await checkAuth(firebaseApp)
|
|
21
|
+
|
|
22
|
+
// Inform the user about deassociation in Github and re run auth
|
|
23
|
+
console.log(
|
|
24
|
+
`${
|
|
25
|
+
theme.symbols.warning
|
|
26
|
+
} The logout could sign you out from Firebase and will delete the access token saved locally on this machine. Therefore, you have to run ${theme.text.bold(
|
|
27
|
+
"phase2cli auth"
|
|
28
|
+
)} to authenticate again.\n${
|
|
29
|
+
theme.symbols.info
|
|
30
|
+
} Remember, we cannot revoke the authorization from your Github account from this CLI! You can do this manually as reported in the official Github documentation ${
|
|
31
|
+
theme.emojis.pointDown
|
|
32
|
+
}\n\n${theme.text.bold(
|
|
33
|
+
theme.text.underlined(
|
|
34
|
+
`https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/reviewing-your-authorized-applications-oauth`
|
|
35
|
+
)
|
|
36
|
+
)}\n`
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
// Ask for confirmation.
|
|
40
|
+
const { confirmation } = await askForConfirmation(
|
|
41
|
+
"Are you sure you want to log out from this machine?",
|
|
42
|
+
"Yes",
|
|
43
|
+
"No"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if (confirmation) {
|
|
47
|
+
const spinner = customSpinner(`Logging out...`, "clock")
|
|
48
|
+
spinner.start()
|
|
49
|
+
|
|
50
|
+
// Sign out.
|
|
51
|
+
const auth = getAuth()
|
|
52
|
+
await signOut(auth)
|
|
53
|
+
|
|
54
|
+
// Delete local token.
|
|
55
|
+
deleteLocalAccessToken()
|
|
56
|
+
|
|
57
|
+
await sleep(3000) // ~3s.
|
|
58
|
+
|
|
59
|
+
spinner.stop()
|
|
60
|
+
console.log(`${theme.symbols.success} Logout successfully completed`)
|
|
61
|
+
} else terminate(providerUserId)
|
|
62
|
+
} catch (err: any) {
|
|
63
|
+
showError(`Something went wrong: ${err.toString()}`, true)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default logout
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FirebaseDocumentInfo,
|
|
5
|
+
getCeremonyCircuits,
|
|
6
|
+
getCircuitContributionsFromContributor,
|
|
7
|
+
getOpenedCeremonies,
|
|
8
|
+
isCoordinator,
|
|
9
|
+
convertToDoubleDigits
|
|
10
|
+
} from "@p0tion/actions"
|
|
11
|
+
import { Firestore } from "firebase/firestore"
|
|
12
|
+
import logSymbols from "log-symbols"
|
|
13
|
+
import readline from "readline"
|
|
14
|
+
import { COMMAND_ERRORS, GENERIC_ERRORS, showError } from "../lib/errors.js"
|
|
15
|
+
import { promptForCeremonySelection } from "../lib/prompts.js"
|
|
16
|
+
import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
|
|
17
|
+
import theme from "../lib/theme.js"
|
|
18
|
+
import {customSpinner, getSecondsMinutesHoursFromMillis, sleep } from "../lib/utils.js"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Clean cursor lines from current position back to root (default: zero).
|
|
22
|
+
* @param currentCursorPos - the current position of the cursor.
|
|
23
|
+
* @returns <number>
|
|
24
|
+
*/
|
|
25
|
+
export const cleanCursorPosBackToRoot = (currentCursorPos: number) => {
|
|
26
|
+
while (currentCursorPos < 0) {
|
|
27
|
+
// Get back and clean line by line.
|
|
28
|
+
readline.cursorTo(process.stdout, 0)
|
|
29
|
+
readline.clearLine(process.stdout, 0)
|
|
30
|
+
readline.moveCursor(process.stdout, -1, -1)
|
|
31
|
+
|
|
32
|
+
currentCursorPos += 1
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return currentCursorPos
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Show the latest updates for the given circuit.
|
|
40
|
+
* @param firestoreDatabase <Firestore> - the Firestore database to query from.
|
|
41
|
+
* @param ceremony <FirebaseDocumentInfo> - the Firebase document containing info about the ceremony.
|
|
42
|
+
* @param circuit <FirebaseDocumentInfo> - the Firebase document containing info about the circuit.
|
|
43
|
+
* @returns Promise<number> return the current position of the cursor (i.e., number of lines displayed).
|
|
44
|
+
*/
|
|
45
|
+
export const displayLatestCircuitUpdates = async (
|
|
46
|
+
firestoreDatabase: Firestore,
|
|
47
|
+
ceremony: FirebaseDocumentInfo,
|
|
48
|
+
circuit: FirebaseDocumentInfo
|
|
49
|
+
): Promise<number> => {
|
|
50
|
+
let observation = theme.text.bold(`- Circuit # ${theme.colors.magenta(circuit.data.sequencePosition)}`) // Observation output.
|
|
51
|
+
let cursorPos = -1 // Current cursor position (nb. decrease every time there's a new line!).
|
|
52
|
+
|
|
53
|
+
const { waitingQueue } = circuit.data
|
|
54
|
+
|
|
55
|
+
// Get info from circuit.
|
|
56
|
+
const { currentContributor } = waitingQueue
|
|
57
|
+
const { completedContributions } = waitingQueue
|
|
58
|
+
|
|
59
|
+
if (!currentContributor) {
|
|
60
|
+
observation += `\n> Nobody's currently waiting to contribute ${theme.emojis.eyes}`
|
|
61
|
+
cursorPos -= 1
|
|
62
|
+
} else {
|
|
63
|
+
// Search for currentContributor' contribution.
|
|
64
|
+
const contributions = await getCircuitContributionsFromContributor(
|
|
65
|
+
firestoreDatabase,
|
|
66
|
+
ceremony.id,
|
|
67
|
+
circuit.id,
|
|
68
|
+
currentContributor
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if (!contributions.length) {
|
|
72
|
+
// The contributor is currently contributing.
|
|
73
|
+
observation += `\n> Participant ${theme.text.bold(`#${completedContributions + 1}`)} (${theme.text.bold(
|
|
74
|
+
currentContributor
|
|
75
|
+
)}) is currently contributing ${theme.emojis.fire}`
|
|
76
|
+
|
|
77
|
+
cursorPos -= 1
|
|
78
|
+
} else {
|
|
79
|
+
// The contributor has contributed.
|
|
80
|
+
observation += `\n> Participant ${theme.text.bold(`#${completedContributions}`)} (${theme.text.bold(
|
|
81
|
+
currentContributor
|
|
82
|
+
)}) has completed the contribution ${theme.emojis.tada}`
|
|
83
|
+
|
|
84
|
+
cursorPos -= 1
|
|
85
|
+
|
|
86
|
+
// The contributor has finished the contribution.
|
|
87
|
+
const contributionData = contributions.at(0)?.data
|
|
88
|
+
|
|
89
|
+
if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
|
|
90
|
+
|
|
91
|
+
// Convert times to seconds.
|
|
92
|
+
const {
|
|
93
|
+
seconds: contributionTimeSeconds,
|
|
94
|
+
minutes: contributionTimeMinutes,
|
|
95
|
+
hours: contributionTimeHours
|
|
96
|
+
} = getSecondsMinutesHoursFromMillis(contributionData?.contributionComputationTime)
|
|
97
|
+
const {
|
|
98
|
+
seconds: verificationTimeSeconds,
|
|
99
|
+
minutes: verificationTimeMinutes,
|
|
100
|
+
hours: verificationTimeHours
|
|
101
|
+
} = getSecondsMinutesHoursFromMillis(contributionData?.verificationComputationTime)
|
|
102
|
+
|
|
103
|
+
observation += `\n> The ${theme.text.bold("computation")} took ${theme.text.bold(
|
|
104
|
+
`${convertToDoubleDigits(contributionTimeHours)}:${convertToDoubleDigits(
|
|
105
|
+
contributionTimeMinutes
|
|
106
|
+
)}:${convertToDoubleDigits(contributionTimeSeconds)}`
|
|
107
|
+
)}`
|
|
108
|
+
observation += `\n> The ${theme.text.bold("verification")} took ${theme.text.bold(
|
|
109
|
+
`${convertToDoubleDigits(verificationTimeHours)}:${convertToDoubleDigits(
|
|
110
|
+
verificationTimeMinutes
|
|
111
|
+
)}:${convertToDoubleDigits(verificationTimeSeconds)}`
|
|
112
|
+
)}`
|
|
113
|
+
observation += `\n> Contribution ${
|
|
114
|
+
contributionData?.valid
|
|
115
|
+
? `${theme.text.bold("VALID")} ${theme.symbols.success}`
|
|
116
|
+
: `${theme.text.bold("INVALID")} ${theme.symbols.error}`
|
|
117
|
+
}`
|
|
118
|
+
|
|
119
|
+
cursorPos -= 3
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Show observation for circuit.
|
|
124
|
+
process.stdout.write(`${observation}\n\n`)
|
|
125
|
+
cursorPos -= 1
|
|
126
|
+
|
|
127
|
+
return cursorPos
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Observe command.
|
|
132
|
+
*/
|
|
133
|
+
const observe = async () => {
|
|
134
|
+
// @todo to be moved as command configuration parameter.
|
|
135
|
+
const observationWaitingTimeInMillis = 3000
|
|
136
|
+
try {
|
|
137
|
+
// Initialize services.
|
|
138
|
+
const { firebaseApp, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
|
|
139
|
+
|
|
140
|
+
// Handle current authenticated user sign in.
|
|
141
|
+
const { user } = await checkAuth(firebaseApp)
|
|
142
|
+
|
|
143
|
+
// Preserve command execution only for coordinators].
|
|
144
|
+
if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
|
|
145
|
+
|
|
146
|
+
// Get running cerimonies info (if any).
|
|
147
|
+
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
|
|
148
|
+
|
|
149
|
+
// Ask to select a ceremony.
|
|
150
|
+
const ceremony = await promptForCeremonySelection(runningCeremoniesDocs, false)
|
|
151
|
+
|
|
152
|
+
console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`)
|
|
153
|
+
|
|
154
|
+
let cursorPos = 0 // Keep track of current cursor position.
|
|
155
|
+
|
|
156
|
+
const spinner = customSpinner(`Getting ready...`, "clock")
|
|
157
|
+
spinner.start()
|
|
158
|
+
|
|
159
|
+
// Get circuit updates every 3 seconds.
|
|
160
|
+
setInterval(async () => {
|
|
161
|
+
// Clean cursor position back to root.
|
|
162
|
+
cursorPos = cleanCursorPosBackToRoot(cursorPos)
|
|
163
|
+
|
|
164
|
+
spinner.stop()
|
|
165
|
+
|
|
166
|
+
spinner.text = `Updating...`
|
|
167
|
+
spinner.start()
|
|
168
|
+
|
|
169
|
+
// Get updates from circuits.
|
|
170
|
+
const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
|
|
171
|
+
|
|
172
|
+
await sleep(observationWaitingTimeInMillis / 10) // Just for a smoother UX/UI experience.
|
|
173
|
+
|
|
174
|
+
spinner.stop()
|
|
175
|
+
|
|
176
|
+
// Observe changes for each circuit
|
|
177
|
+
for await (const circuit of circuits)
|
|
178
|
+
cursorPos += await displayLatestCircuitUpdates(firestoreDatabase, ceremony, circuit)
|
|
179
|
+
|
|
180
|
+
process.stdout.write(`Press CTRL+C to exit`)
|
|
181
|
+
|
|
182
|
+
await sleep(1000) // Just for a smoother UX/UI experience.
|
|
183
|
+
}, observationWaitingTimeInMillis)
|
|
184
|
+
|
|
185
|
+
await sleep(observationWaitingTimeInMillis) // Wait until the first update.
|
|
186
|
+
|
|
187
|
+
spinner.stop()
|
|
188
|
+
} catch (err: any) {
|
|
189
|
+
showError(`Something went wrong: ${err.toString()}`, true)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default observe
|