@devtion/actions 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 +83 -0
- package/dist/index.mjs +2608 -0
- package/dist/index.node.js +2714 -0
- package/dist/types/hardhat.config.d.ts +6 -0
- package/dist/types/hardhat.config.d.ts.map +1 -0
- package/dist/types/src/helpers/authentication.d.ts +21 -0
- package/dist/types/src/helpers/authentication.d.ts.map +1 -0
- package/dist/types/src/helpers/constants.d.ts +194 -0
- package/dist/types/src/helpers/constants.d.ts.map +1 -0
- package/dist/types/src/helpers/contracts.d.ts +57 -0
- package/dist/types/src/helpers/contracts.d.ts.map +1 -0
- package/dist/types/src/helpers/crypto.d.ts +27 -0
- package/dist/types/src/helpers/crypto.d.ts.map +1 -0
- package/dist/types/src/helpers/database.d.ts +105 -0
- package/dist/types/src/helpers/database.d.ts.map +1 -0
- package/dist/types/src/helpers/functions.d.ts +145 -0
- package/dist/types/src/helpers/functions.d.ts.map +1 -0
- package/dist/types/src/helpers/security.d.ts +10 -0
- package/dist/types/src/helpers/security.d.ts.map +1 -0
- package/dist/types/src/helpers/services.d.ts +38 -0
- package/dist/types/src/helpers/services.d.ts.map +1 -0
- package/dist/types/src/helpers/storage.d.ts +121 -0
- package/dist/types/src/helpers/storage.d.ts.map +1 -0
- package/dist/types/src/helpers/tasks.d.ts +2 -0
- package/dist/types/src/helpers/tasks.d.ts.map +1 -0
- package/dist/types/src/helpers/utils.d.ts +139 -0
- package/dist/types/src/helpers/utils.d.ts.map +1 -0
- package/dist/types/src/helpers/verification.d.ts +95 -0
- package/dist/types/src/helpers/verification.d.ts.map +1 -0
- package/dist/types/src/helpers/vm.d.ts +112 -0
- package/dist/types/src/helpers/vm.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +15 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/types/enums.d.ts +133 -0
- package/dist/types/src/types/enums.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +603 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/package.json +87 -0
- package/src/helpers/authentication.ts +37 -0
- package/src/helpers/constants.ts +312 -0
- package/src/helpers/contracts.ts +268 -0
- package/src/helpers/crypto.ts +55 -0
- package/src/helpers/database.ts +221 -0
- package/src/helpers/functions.ts +438 -0
- package/src/helpers/security.ts +86 -0
- package/src/helpers/services.ts +83 -0
- package/src/helpers/storage.ts +329 -0
- package/src/helpers/tasks.ts +56 -0
- package/src/helpers/utils.ts +743 -0
- package/src/helpers/verification.ts +354 -0
- package/src/helpers/vm.ts +392 -0
- package/src/index.ts +162 -0
- package/src/types/enums.ts +141 -0
- package/src/types/index.ts +650 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { Contract, ContractFactory, Signer } from "ethers"
|
|
2
|
+
import { utils as ffUtils } from "ffjavascript"
|
|
3
|
+
import { Firestore, where } from "firebase/firestore"
|
|
4
|
+
import { Functions } from "firebase/functions"
|
|
5
|
+
import fs from "fs"
|
|
6
|
+
import solc from "solc"
|
|
7
|
+
import {
|
|
8
|
+
downloadAllCeremonyArtifacts,
|
|
9
|
+
exportVerifierAndVKey,
|
|
10
|
+
generateGROTH16Proof,
|
|
11
|
+
generateZkeyFromScratch,
|
|
12
|
+
getFinalContributionBeacon,
|
|
13
|
+
verifyGROTH16Proof,
|
|
14
|
+
verifyZKey
|
|
15
|
+
} from "./verification"
|
|
16
|
+
import { compareHashes } from "./crypto"
|
|
17
|
+
import { commonTerms, finalContributionIndex, verificationKeyAcronym, verifierSmartContractAcronym } from "./constants"
|
|
18
|
+
import { fromQueryToFirebaseDocumentInfo, queryCollection } from "./database"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Formats part of a GROTH16 SNARK proof
|
|
22
|
+
* @link adapted from SNARKJS p256 function
|
|
23
|
+
* @param proofPart <any> a part of a proof to be formatted
|
|
24
|
+
* @returns <string> the formatted proof part
|
|
25
|
+
*/
|
|
26
|
+
export const p256 = (proofPart: any) => {
|
|
27
|
+
let nProofPart = proofPart.toString(16)
|
|
28
|
+
while (nProofPart.length < 64) nProofPart = `0${nProofPart}`
|
|
29
|
+
nProofPart = `0x${nProofPart}`
|
|
30
|
+
return nProofPart
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This function formats the calldata for Solidity
|
|
35
|
+
* @link adapted from SNARKJS formatSolidityCalldata function
|
|
36
|
+
* @dev this function is supposed to be called with
|
|
37
|
+
* @dev the output of generateGROTH16Proof
|
|
38
|
+
* @param circuitInput <string[]> Inputs to the circuit
|
|
39
|
+
* @param _proof <object> Proof
|
|
40
|
+
* @returns <SolidityCalldata> The calldata formatted for Solidity
|
|
41
|
+
*/
|
|
42
|
+
export const formatSolidityCalldata = (circuitInput: string[], _proof: any): any => {
|
|
43
|
+
try {
|
|
44
|
+
const proof = ffUtils.unstringifyBigInts(_proof)
|
|
45
|
+
// format the public inputs to the circuit
|
|
46
|
+
const formattedCircuitInput = []
|
|
47
|
+
for (const cInput of circuitInput) {
|
|
48
|
+
formattedCircuitInput.push(p256(ffUtils.unstringifyBigInts(cInput)))
|
|
49
|
+
}
|
|
50
|
+
// construct calldata
|
|
51
|
+
const calldata = {
|
|
52
|
+
arg1: [p256(proof.pi_a[0]), p256(proof.pi_a[1])],
|
|
53
|
+
arg2: [
|
|
54
|
+
[p256(proof.pi_b[0][1]), p256(proof.pi_b[0][0])],
|
|
55
|
+
[p256(proof.pi_b[1][1]), p256(proof.pi_b[1][0])]
|
|
56
|
+
],
|
|
57
|
+
arg3: [p256(proof.pi_c[0]), p256(proof.pi_c[1])],
|
|
58
|
+
arg4: formattedCircuitInput
|
|
59
|
+
}
|
|
60
|
+
return calldata
|
|
61
|
+
} catch (error: any) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"There was an error while formatting the calldata. Please make sure that you are calling this function with the output of the generateGROTH16Proof function, and then please try again."
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Verify a GROTH16 SNARK proof on chain
|
|
70
|
+
* @param contract <Contract> The contract instance
|
|
71
|
+
* @param proof <SolidityCalldata> The calldata formatted for Solidity
|
|
72
|
+
* @returns <Promise<boolean>> Whether the proof is valid or not
|
|
73
|
+
*/
|
|
74
|
+
export const verifyGROTH16ProofOnChain = async (contract: any, proof: any): Promise<boolean> => {
|
|
75
|
+
const res = await contract.verifyProof(proof.arg1, proof.arg2, proof.arg3, proof.arg4)
|
|
76
|
+
return res
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compiles a contract given a path
|
|
81
|
+
* @param contractPath <string> path to the verifier contract
|
|
82
|
+
* @returns <Promise<any>> the compiled contract
|
|
83
|
+
*/
|
|
84
|
+
export const compileContract = async (contractPath: string): Promise<any> => {
|
|
85
|
+
if (!fs.existsSync(contractPath))
|
|
86
|
+
throw new Error(
|
|
87
|
+
"The contract path does not exist. Please make sure that you are passing a valid path to the contract and try again."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const data = fs.readFileSync(contractPath).toString()
|
|
91
|
+
const input = {
|
|
92
|
+
language: "Solidity",
|
|
93
|
+
sources: {
|
|
94
|
+
Verifier: { content: data }
|
|
95
|
+
},
|
|
96
|
+
settings: {
|
|
97
|
+
outputSelection: {
|
|
98
|
+
"*": {
|
|
99
|
+
"*": ["*"]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const compiled = JSON.parse(solc.compile(JSON.stringify(input), { import: { contents: "" } }))
|
|
107
|
+
return compiled.contracts.Verifier.Verifier
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
"There was an error while compiling the smart contract. Please check that the file is not corrupted and try again."
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Deploy the verifier contract
|
|
117
|
+
* @param contractFactory <ContractFactory> The contract factory
|
|
118
|
+
* @returns <Promise<Contract>> The contract instance
|
|
119
|
+
*/
|
|
120
|
+
export const deployVerifierContract = async (contractPath: string, signer: Signer): Promise<Contract> => {
|
|
121
|
+
const compiledContract = await compileContract(contractPath)
|
|
122
|
+
// connect to hardhat node running locally
|
|
123
|
+
const contractFactory = new ContractFactory(compiledContract.abi, compiledContract.evm.bytecode.object, signer)
|
|
124
|
+
const contract = await contractFactory.deploy()
|
|
125
|
+
await contract.deployed()
|
|
126
|
+
return contract
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Verify a ceremony validity
|
|
131
|
+
* 1. Download all artifacts
|
|
132
|
+
* 2. Verify that the zkeys are valid
|
|
133
|
+
* 3. Extract the verifier and the vKey
|
|
134
|
+
* 4. Generate a proof and verify it locally
|
|
135
|
+
* 5. Deploy Verifier contract and verify the proof on-chain
|
|
136
|
+
* @param functions <Functions> firebase functions instance
|
|
137
|
+
* @param firestore <Firestore> firebase firestore instance
|
|
138
|
+
* @param ceremonyPrefix <string> ceremony prefix
|
|
139
|
+
* @param outputDirectory <string> output directory where to store the ceremony artifacts
|
|
140
|
+
* @param circuitInputsPath <string> path to the circuit inputs file
|
|
141
|
+
* @param verifierTemplatePath <string> path to the verifier template file
|
|
142
|
+
* @param signer <Signer> signer for contract interaction
|
|
143
|
+
* @param logger <any> logger for printing snarkjs output
|
|
144
|
+
*/
|
|
145
|
+
export const verifyCeremony = async (
|
|
146
|
+
functions: Functions,
|
|
147
|
+
firestore: Firestore,
|
|
148
|
+
ceremonyPrefix: string,
|
|
149
|
+
outputDirectory: string,
|
|
150
|
+
circuitInputsPath: string,
|
|
151
|
+
verifierTemplatePath: string,
|
|
152
|
+
signer: Signer,
|
|
153
|
+
logger?: any
|
|
154
|
+
): Promise<void> => {
|
|
155
|
+
// 1. download all ceremony artifacts
|
|
156
|
+
const ceremonyArtifacts = await downloadAllCeremonyArtifacts(functions, firestore, ceremonyPrefix, outputDirectory)
|
|
157
|
+
// if there are no ceremony artifacts, we throw an error
|
|
158
|
+
if (ceremonyArtifacts.length === 0)
|
|
159
|
+
throw new Error(
|
|
160
|
+
"There was an error while downloading all ceremony artifacts. Please review your ceremony prefix and try again."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
// extract the circuit inputs
|
|
164
|
+
if (!fs.existsSync(circuitInputsPath))
|
|
165
|
+
throw new Error("The circuit inputs file does not exist. Please check the path and try again.")
|
|
166
|
+
const circuitsInputs = JSON.parse(fs.readFileSync(circuitInputsPath).toString())
|
|
167
|
+
|
|
168
|
+
// find the ceremony given the prefix
|
|
169
|
+
const ceremonyQuery = await queryCollection(firestore, commonTerms.collections.ceremonies.name, [
|
|
170
|
+
where(commonTerms.collections.ceremonies.fields.prefix, "==", ceremonyPrefix)
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
// get the ceremony data - no need to do an existence check as
|
|
174
|
+
// we already checked that the ceremony exists in downloafAllCeremonyArtifacts
|
|
175
|
+
const ceremonyData = fromQueryToFirebaseDocumentInfo(ceremonyQuery.docs)
|
|
176
|
+
const ceremony = ceremonyData.at(0)
|
|
177
|
+
// this is required to re-generate the final zKey
|
|
178
|
+
const { coordinatorId } = ceremony!.data
|
|
179
|
+
const ceremonyId = ceremony!.id
|
|
180
|
+
|
|
181
|
+
// we verify each circuit separately
|
|
182
|
+
for (const ceremonyArtifact of ceremonyArtifacts) {
|
|
183
|
+
// get the index of the circuit in the list of circuits
|
|
184
|
+
const inputIndex = ceremonyArtifacts.indexOf(ceremonyArtifact)
|
|
185
|
+
|
|
186
|
+
// 2. verify the final zKey
|
|
187
|
+
const isValid = await verifyZKey(
|
|
188
|
+
ceremonyArtifact.r1csLocalFilePath,
|
|
189
|
+
ceremonyArtifact.finalZkeyLocalFilePath,
|
|
190
|
+
ceremonyArtifact.potLocalFilePath,
|
|
191
|
+
logger
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if (!isValid)
|
|
195
|
+
throw new Error(
|
|
196
|
+
`The zkey for Circuit ${ceremonyArtifact.circuitPrefix} is not valid. Please check that the artifact is correct. If not, you might have to re run the final contribution to compute a valid final zKey.`
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
// 3. get the final contribution beacon
|
|
200
|
+
const contributionBeacon = await getFinalContributionBeacon(
|
|
201
|
+
firestore,
|
|
202
|
+
ceremonyId,
|
|
203
|
+
ceremonyArtifact.circuitId,
|
|
204
|
+
coordinatorId
|
|
205
|
+
)
|
|
206
|
+
const generatedFinalZkeyPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${finalContributionIndex}_verification.zkey`
|
|
207
|
+
// 4. re generate the zkey using the beacon and check hashes
|
|
208
|
+
await generateZkeyFromScratch(
|
|
209
|
+
true,
|
|
210
|
+
ceremonyArtifact.r1csLocalFilePath,
|
|
211
|
+
ceremonyArtifact.potLocalFilePath,
|
|
212
|
+
generatedFinalZkeyPath,
|
|
213
|
+
logger,
|
|
214
|
+
ceremonyArtifact.lastZkeyLocalFilePath,
|
|
215
|
+
coordinatorId,
|
|
216
|
+
contributionBeacon
|
|
217
|
+
)
|
|
218
|
+
const zKeysMatching = await compareHashes(generatedFinalZkeyPath, ceremonyArtifact.finalZkeyLocalFilePath)
|
|
219
|
+
if (!zKeysMatching)
|
|
220
|
+
throw new Error(
|
|
221
|
+
`The final zkey for the Circuit ${ceremonyArtifact.circuitPrefix} does not match the one generated from the beacon. Please confirm manually by downloading from the S3 bucket.`
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
// 5. extract the verifier and the vKey
|
|
225
|
+
const verifierLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verifierSmartContractAcronym}_verification.sol`
|
|
226
|
+
const vKeyLocalPath = `${ceremonyArtifact.directoryRoot}/${ceremonyArtifact.circuitPrefix}_${verificationKeyAcronym}_verification.json`
|
|
227
|
+
await exportVerifierAndVKey(
|
|
228
|
+
ceremonyArtifact.finalZkeyLocalFilePath,
|
|
229
|
+
verifierLocalPath,
|
|
230
|
+
vKeyLocalPath,
|
|
231
|
+
verifierTemplatePath
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// 6. verify that the generated verifier and vkey match the ones downloaded from S3
|
|
235
|
+
const verifierMatching = await compareHashes(verifierLocalPath, ceremonyArtifact.verifierLocalFilePath)
|
|
236
|
+
if (!verifierMatching)
|
|
237
|
+
throw new Error(
|
|
238
|
+
`The verifier contract for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`
|
|
239
|
+
)
|
|
240
|
+
const vKeyMatching = await compareHashes(vKeyLocalPath, ceremonyArtifact.verificationKeyLocalFilePath)
|
|
241
|
+
if (!vKeyMatching)
|
|
242
|
+
throw new Error(
|
|
243
|
+
`The verification key for the Contract ${ceremonyArtifact.circuitPrefix} does not match the one downloaded from S3. Please confirm manually by downloading from the S3 bucket.`
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// 7. generate a proof and verify it locally (use either of the downloaded or generated as the hashes will have matched at this point)
|
|
247
|
+
const { proof, publicSignals } = await generateGROTH16Proof(
|
|
248
|
+
circuitsInputs[inputIndex],
|
|
249
|
+
ceremonyArtifact.finalZkeyLocalFilePath,
|
|
250
|
+
ceremonyArtifact.wasmLocalFilePath,
|
|
251
|
+
logger
|
|
252
|
+
)
|
|
253
|
+
const isProofValid = await verifyGROTH16Proof(vKeyLocalPath, publicSignals, proof)
|
|
254
|
+
if (!isProofValid)
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Could not verify the proof for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
// 8. deploy Verifier contract and verify the proof on-chain
|
|
260
|
+
const verifierContract = await deployVerifierContract(verifierLocalPath, signer)
|
|
261
|
+
const formattedProof = await formatSolidityCalldata(publicSignals, proof)
|
|
262
|
+
const isProofValidOnChain = await verifyGROTH16ProofOnChain(verifierContract, formattedProof)
|
|
263
|
+
if (!isProofValidOnChain)
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Could not verify the proof on-chain for Circuit ${ceremonyArtifact.circuitPrefix}. Please check that the artifacts are correct as well as the inputs to the circuit, and try again.`
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
import crypto from "crypto"
|
|
3
|
+
import blake from "blakejs"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @hidden
|
|
7
|
+
*/
|
|
8
|
+
const toHexByte = (byte: number) => (byte < 0x10 ? `0${byte.toString(16)}` : byte.toString(16))
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Converts Uint8Array to hexadecimal string.
|
|
12
|
+
* @param buffer arbritrary length of data
|
|
13
|
+
* @returns hexadecimal string
|
|
14
|
+
*/
|
|
15
|
+
export const toHex = (buffer: Uint8Array): string => Array.from(buffer).map(toHexByte).join("")
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get 512 bit blake hash of the contents of given path.
|
|
19
|
+
* @param data buffer or hexadecimal string
|
|
20
|
+
* @returns 64 byte hexadecimal string
|
|
21
|
+
*/
|
|
22
|
+
export const blake512FromPath = async (path: fs.PathLike): Promise<string> => {
|
|
23
|
+
const context = blake.blake2bInit(64, undefined)
|
|
24
|
+
|
|
25
|
+
const hash: string = await new Promise((resolve) => {
|
|
26
|
+
fs.createReadStream(path)
|
|
27
|
+
.on("data", (chunk: Buffer) => {
|
|
28
|
+
blake.blake2bUpdate(context, chunk)
|
|
29
|
+
})
|
|
30
|
+
.on("end", () => {
|
|
31
|
+
resolve(toHex(blake.blake2bFinal(context)))
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
return hash
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Return the SHA256 hash (HEX format) of a given value
|
|
39
|
+
* @param value <string> - the value to be hashed.
|
|
40
|
+
* @returns <string> - the HEX format of the SHA256 hash of the given value
|
|
41
|
+
*/
|
|
42
|
+
export const computeSHA256ToHex = (value: string): string => crypto.createHash("sha256").update(value).digest("hex")
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Helper function that can be used to compare whether two files' hashes are equal or not.
|
|
46
|
+
* @param path1 <string> Path to the first file.
|
|
47
|
+
* @param path2 <string> Path to the second file.
|
|
48
|
+
* @returns <Promise<boolean>> Whether the files are equal or not.
|
|
49
|
+
*/
|
|
50
|
+
export const compareHashes = async (path1: string, path2: string): Promise<boolean> => {
|
|
51
|
+
const hash1 = await blake512FromPath(path1)
|
|
52
|
+
const hash2 = await blake512FromPath(path2)
|
|
53
|
+
|
|
54
|
+
return hash1 === hash2
|
|
55
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collection as collectionRef,
|
|
3
|
+
doc,
|
|
4
|
+
DocumentData,
|
|
5
|
+
DocumentSnapshot,
|
|
6
|
+
Firestore,
|
|
7
|
+
getDoc,
|
|
8
|
+
getDocs,
|
|
9
|
+
query,
|
|
10
|
+
QueryConstraint,
|
|
11
|
+
QueryDocumentSnapshot,
|
|
12
|
+
QuerySnapshot,
|
|
13
|
+
Timestamp,
|
|
14
|
+
where
|
|
15
|
+
} from "firebase/firestore"
|
|
16
|
+
import { CeremonyState } from "../types/enums"
|
|
17
|
+
import { FirebaseDocumentInfo } from "../types/index"
|
|
18
|
+
import { commonTerms } from "./constants"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get participants collection path for database reference.
|
|
22
|
+
* @notice all participants related documents are store under `ceremonies/<ceremonyId>/participants` collection path.
|
|
23
|
+
* nb. This is a rule that must be satisfied. This is NOT an optional convention.
|
|
24
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
25
|
+
* @returns <string> - the participants collection path.
|
|
26
|
+
*/
|
|
27
|
+
export const getParticipantsCollectionPath = (ceremonyId: string): string =>
|
|
28
|
+
`${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.participants.name}`
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get circuits collection path for database reference.
|
|
32
|
+
* @notice all circuits related documents are store under `ceremonies/<ceremonyId>/circuits` collection path.
|
|
33
|
+
* nb. This is a rule that must be satisfied. This is NOT an optional convention.
|
|
34
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
35
|
+
* @returns <string> - the participants collection path.
|
|
36
|
+
*/
|
|
37
|
+
export const getCircuitsCollectionPath = (ceremonyId: string): string =>
|
|
38
|
+
`${commonTerms.collections.ceremonies.name}/${ceremonyId}/${commonTerms.collections.circuits.name}`
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get contributions collection path for database reference.
|
|
42
|
+
* @notice all contributions related documents are store under `ceremonies/<ceremonyId>/circuits/<circuitId>/contributions` collection path.
|
|
43
|
+
* nb. This is a rule that must be satisfied. This is NOT an optional convention.
|
|
44
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
45
|
+
* @param circuitId <string> - the unique identifier of the circuit.
|
|
46
|
+
* @returns <string> - the contributions collection path.
|
|
47
|
+
*/
|
|
48
|
+
export const getContributionsCollectionPath = (ceremonyId: string, circuitId: string): string =>
|
|
49
|
+
`${getCircuitsCollectionPath(ceremonyId)}/${circuitId}/${commonTerms.collections.contributions.name}`
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get timeouts collection path for database reference.
|
|
53
|
+
* @notice all timeouts related documents are store under `ceremonies/<ceremonyId>/participants/<participantId>/timeouts` collection path.
|
|
54
|
+
* nb. This is a rule that must be satisfied. This is NOT an optional convention.
|
|
55
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
56
|
+
* @param participantId <string> - the unique identifier of the participant.
|
|
57
|
+
* @returns <string> - the timeouts collection path.
|
|
58
|
+
*/
|
|
59
|
+
export const getTimeoutsCollectionPath = (ceremonyId: string, participantId: string): string =>
|
|
60
|
+
`${getParticipantsCollectionPath(ceremonyId)}/${participantId}/${commonTerms.collections.timeouts.name}`
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Helper for query a collection based on certain constraints.
|
|
64
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
65
|
+
* @param collection <string> - the name of the collection.
|
|
66
|
+
* @param queryConstraints <Array<QueryConstraint>> - a sequence of where conditions.
|
|
67
|
+
* @returns <Promise<QuerySnapshot<DocumentData>>> - return the matching documents (if any).
|
|
68
|
+
*/
|
|
69
|
+
export const queryCollection = async (
|
|
70
|
+
firestoreDatabase: Firestore,
|
|
71
|
+
collection: string,
|
|
72
|
+
queryConstraints: Array<QueryConstraint>
|
|
73
|
+
): Promise<QuerySnapshot<DocumentData>> => {
|
|
74
|
+
// Make a query.
|
|
75
|
+
const q = query(collectionRef(firestoreDatabase, collection), ...queryConstraints)
|
|
76
|
+
|
|
77
|
+
// Get docs.
|
|
78
|
+
const snap = await getDocs(q)
|
|
79
|
+
|
|
80
|
+
return snap
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Helper for obtaining uid and data for query document snapshots.
|
|
85
|
+
* @param queryDocSnap <Array<QueryDocumentSnapshot>> - the array of query document snapshot to be converted.
|
|
86
|
+
* @returns Array<FirebaseDocumentInfo>
|
|
87
|
+
*/
|
|
88
|
+
export const fromQueryToFirebaseDocumentInfo = (
|
|
89
|
+
queryDocSnap: Array<QueryDocumentSnapshot>
|
|
90
|
+
): Array<FirebaseDocumentInfo> =>
|
|
91
|
+
queryDocSnap.map((document: QueryDocumentSnapshot<DocumentData>) => ({
|
|
92
|
+
id: document.id,
|
|
93
|
+
ref: document.ref,
|
|
94
|
+
data: document.data()
|
|
95
|
+
}))
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Fetch for all documents in a collection.
|
|
99
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
100
|
+
* @param collection <string> - the name of the collection.
|
|
101
|
+
* @returns <Promise<Array<QueryDocumentSnapshot<DocumentData>>>> - return all documents (if any).
|
|
102
|
+
*/
|
|
103
|
+
export const getAllCollectionDocs = async (
|
|
104
|
+
firestoreDatabase: Firestore,
|
|
105
|
+
collection: string
|
|
106
|
+
): Promise<Array<QueryDocumentSnapshot<DocumentData>>> =>
|
|
107
|
+
(await getDocs(collectionRef(firestoreDatabase, collection))).docs
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get a specific document from database.
|
|
111
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
112
|
+
* @param collection <string> - the name of the collection.
|
|
113
|
+
* @param documentId <string> - the unique identifier of the document in the collection.
|
|
114
|
+
* @returns <Promise<DocumentSnapshot<DocumentData>>> - return the document from Firestore.
|
|
115
|
+
*/
|
|
116
|
+
export const getDocumentById = async (
|
|
117
|
+
firestoreDatabase: Firestore,
|
|
118
|
+
collection: string,
|
|
119
|
+
documentId: string
|
|
120
|
+
): Promise<DocumentSnapshot<DocumentData>> => {
|
|
121
|
+
const docRef = doc(firestoreDatabase, collection, documentId)
|
|
122
|
+
|
|
123
|
+
return getDoc(docRef)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Query for opened ceremonies.
|
|
128
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
129
|
+
* @returns <Promise<Array<FirebaseDocumentInfo>>>
|
|
130
|
+
*/
|
|
131
|
+
export const getOpenedCeremonies = async (firestoreDatabase: Firestore): Promise<Array<FirebaseDocumentInfo>> => {
|
|
132
|
+
const runningStateCeremoniesQuerySnap = await queryCollection(
|
|
133
|
+
firestoreDatabase,
|
|
134
|
+
commonTerms.collections.ceremonies.name,
|
|
135
|
+
[
|
|
136
|
+
where(commonTerms.collections.ceremonies.fields.state, "==", CeremonyState.OPENED),
|
|
137
|
+
where(commonTerms.collections.ceremonies.fields.endDate, ">=", Date.now())
|
|
138
|
+
]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return fromQueryToFirebaseDocumentInfo(runningStateCeremoniesQuerySnap.docs)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Query for ceremony circuits.
|
|
146
|
+
* @notice the order by sequence position is fundamental to maintain parallelism among contributions for different circuits.
|
|
147
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
148
|
+
* @param ceremonyId <string> - the ceremony unique identifier.
|
|
149
|
+
* @returns Promise<Array<FirebaseDocumentInfo>> - the ceremony' circuits documents ordered by sequence position.
|
|
150
|
+
*/
|
|
151
|
+
export const getCeremonyCircuits = async (
|
|
152
|
+
firestoreDatabase: Firestore,
|
|
153
|
+
ceremonyId: string
|
|
154
|
+
): Promise<Array<FirebaseDocumentInfo>> =>
|
|
155
|
+
fromQueryToFirebaseDocumentInfo(
|
|
156
|
+
await getAllCollectionDocs(firestoreDatabase, getCircuitsCollectionPath(ceremonyId))
|
|
157
|
+
).sort((a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition)
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Query for a specific ceremony' circuit contribution from a given contributor (if any).
|
|
161
|
+
* @notice if the caller is a coordinator, there could be more than one contribution (= the one from finalization applies to this criteria).
|
|
162
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
163
|
+
* @param ceremonyId <string> - the unique identifier of the ceremony.
|
|
164
|
+
* @param circuitId <string> - the unique identifier of the circuit.
|
|
165
|
+
* @param participantId <string> - the unique identifier of the participant.
|
|
166
|
+
* @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the circuit contributions from contributor.
|
|
167
|
+
*/
|
|
168
|
+
export const getCircuitContributionsFromContributor = async (
|
|
169
|
+
firestoreDatabase: Firestore,
|
|
170
|
+
ceremonyId: string,
|
|
171
|
+
circuitId: string,
|
|
172
|
+
participantId: string
|
|
173
|
+
): Promise<Array<FirebaseDocumentInfo>> => {
|
|
174
|
+
const participantContributionsQuerySnap = await queryCollection(
|
|
175
|
+
firestoreDatabase,
|
|
176
|
+
getContributionsCollectionPath(ceremonyId, circuitId),
|
|
177
|
+
[where(commonTerms.collections.contributions.fields.participantId, "==", participantId)]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return fromQueryToFirebaseDocumentInfo(participantContributionsQuerySnap.docs)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Query for the active timeout from given participant for a given ceremony (if any).
|
|
185
|
+
* @param ceremonyId <string> - the identifier of the ceremony.
|
|
186
|
+
* @param participantId <string> - the identifier of the participant.
|
|
187
|
+
* @returns <Promise<Array<FirebaseDocumentInfo>>> - the document info about the current active participant timeout.
|
|
188
|
+
*/
|
|
189
|
+
export const getCurrentActiveParticipantTimeout = async (
|
|
190
|
+
firestoreDatabase: Firestore,
|
|
191
|
+
ceremonyId: string,
|
|
192
|
+
participantId: string
|
|
193
|
+
): Promise<Array<FirebaseDocumentInfo>> => {
|
|
194
|
+
const participantTimeoutQuerySnap = await queryCollection(
|
|
195
|
+
firestoreDatabase,
|
|
196
|
+
getTimeoutsCollectionPath(ceremonyId, participantId),
|
|
197
|
+
[where(commonTerms.collections.timeouts.fields.endDate, ">=", Timestamp.now().toMillis())]
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Query for the closed ceremonies.
|
|
205
|
+
* @notice a ceremony is closed when the period for receiving new contributions has ended.
|
|
206
|
+
* @dev when the ceremony is closed it becomes ready for finalization.
|
|
207
|
+
* @param firestoreDatabase <Firestore> - the Firestore service instance associated to the current Firebase application.
|
|
208
|
+
* @returns <Promise<Array<FirebaseDocumentInfo>>> - the list of closed ceremonies.
|
|
209
|
+
*/
|
|
210
|
+
export const getClosedCeremonies = async (firestoreDatabase: Firestore): Promise<Array<FirebaseDocumentInfo>> => {
|
|
211
|
+
const closedCeremoniesQuerySnap = await queryCollection(
|
|
212
|
+
firestoreDatabase,
|
|
213
|
+
commonTerms.collections.ceremonies.name,
|
|
214
|
+
[
|
|
215
|
+
where(commonTerms.collections.ceremonies.fields.state, "==", CeremonyState.CLOSED),
|
|
216
|
+
where(commonTerms.collections.ceremonies.fields.endDate, "<=", Date.now())
|
|
217
|
+
]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return fromQueryToFirebaseDocumentInfo(closedCeremoniesQuerySnap.docs)
|
|
221
|
+
}
|