Skip to content

Commit

Permalink
refactor(contribute): refactoring the contribute command
Browse files Browse the repository at this point in the history
ctrlc03 committed Dec 19, 2022

Partially verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
1 parent 549ba5e commit 69947b5
Showing 18 changed files with 787 additions and 374 deletions.
191 changes: 190 additions & 1 deletion packages/actions/src/core/contribute/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Firestore, where } from "firebase/firestore"
import { CeremonyCollectionField, CeremonyState, Collections, FirebaseDocumentInfo } from "../../../types/index"
import { queryCollection, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs } from "../../helpers/query"
import { queryCollection, fromQueryToFirebaseDocumentInfo, getAllCollectionDocs } from "../..//helpers/query"
import { Functions, httpsCallable, httpsCallableFromURL } from "firebase/functions"
import { convertToGB } from "../../helpers/storage"

/**
* Query for opened ceremonies documents and return their data (if any).
@@ -31,3 +33,190 @@ export const getCeremonyCircuits = async (
fromQueryToFirebaseDocumentInfo(
await getAllCollectionDocs(firestoreDatabase, `${Collections.CEREMONIES}/${ceremonyId}/${Collections.CIRCUITS}`)
).sort((a: FirebaseDocumentInfo, b: FirebaseDocumentInfo) => a.data.sequencePosition - b.data.sequencePosition)


/**
* Calls the cloud function checkParticipantForCeremony
* @param functions <Functions> - the Firebase functions
* @param ceremonyId <string> - the ceremony ID for which to query participants
* @returns
*/
export const checkParticipantForCeremony = async (
functions: Functions,
ceremonyId: string
): Promise<any> => {
const cf = httpsCallable(functions, 'checkParticipantForCeremony')
const { data } = await cf({ ceremonyId: ceremonyId })
return data
}


/**
* Return the next circuit where the participant needs to compute or has computed the contribution.
* @param circuits <Array<FirebaseDocumentInfo>> - the ceremony circuits document.
* @param nextCircuitPosition <number> - the position in the sequence of circuits where the next contribution must be done.
* @returns <FirebaseDocumentInfo>
*/
export const getNextCircuitForContribution = (
circuits: Array<FirebaseDocumentInfo>,
nextCircuitPosition: number
): FirebaseDocumentInfo => {
// Filter for sequence position (should match contribution progress).
const filteredCircuits = circuits.filter(
(circuit: FirebaseDocumentInfo) => circuit.data.sequencePosition === nextCircuitPosition
)

// There must be only one.
if (filteredCircuits.length !== 1) throw new Error('Contribute-0001: Something went wrong when retrieving the data from the database')

return filteredCircuits.at(0)!
}

/**
* Calls the permanentlyStoreCurrentContributionTimeAndHash cloud function
* @param functions <Functions> - the firebase functions
* @param ceremonyId <string> - the ceremony id
* @param contributionComputationTime <number> - the time when it was computed
* @param contributingHash <string> - the hash of the contribution
*/
export const permanentlyStoreCurrentContributionTimeAndHash = async (
functions: Functions,
ceremonyId: string,
contributionComputationTime: number,
contributionHash: string
) => {
const cf = httpsCallable(functions, 'permanentlyStoreCurrentContributionTimeAndHash')
await cf({
ceremonyId: ceremonyId,
contributionComputationTime,
contributionHash
})
}


/**
* Call the makeProgressToNextContribution cloud function
* @param functions <Functions> - the cloud functions
* @param ceremonyId <string> - the ceremony Id
*/
export const makeProgressToNextContribution = async (
functions: Functions,
ceremonyId: string
) => {
const cf = httpsCallable(functions, 'makeProgressToNextContribution')
await cf({
ceremonyId
})
}

/**
* Call the resumeContributionAfterTimeoutExpiration cloud function
* @param functions <Functions> - the cloud functions.
* @param ceremonyId <string> - the ceremony Id.
*/
export const resumeContributionAfterTimeoutExpiration = async (
functions: Functions,
ceremonyId: string
) => {
const cf = httpsCallable(functions, 'resumeContributionAfterTimeoutExpiration')
await cf({
ceremonyId
})
}

/**
* Call the progressToNextContributionStep cloud function
* @param ceremonyId <string> - the ceremony ID to which we want to contribute to.
*/
export const progressToNextContributionStep = async (
functions: Functions,
ceremonyId: string
) => {
const cf = httpsCallable(functions, 'progressToNextContributionStep')
await cf({
ceremonyId
})
}

/**
* Call the verifyContribution cloud function
* @param functions <Functions> - the cloud functions.
* @param verifyContributionURL <string> - the url for the contribution verification.
* @param ceremonyId <string> - the ID of the ceremony.
* @param circuitId <string> - the ID of the circuit to which the user contribute.
* @param username <string> - the
*/
export const verifyContribution = async (
functions: Functions,
verifyContributionURL: string,
ceremonyId: string,
circuitId: string,
username: string,
bucketName: string
): Promise<any> => {
const cf = httpsCallableFromURL(
functions,
verifyContributionURL,
{
timeout: 3600000
}
)

const response = await cf({
ceremonyId: ceremonyId,
circuitId: circuitId,
username,
bucketName: bucketName
})


return response
}

/**
* Calls the temporaryStoreCurrentContributionMultiPartUploadId cloud function
* @param functions <Functions> - the cloud functions.
* @param ceremonyId <string> - the ID of the ceremony.
* @param uploadIdZKey <string> - the upload identifier.
*/
export const temporaryStoreCurrentContributionMultiPartUploadId = async (
functions: Functions,
ceremonyId: string,
uploadIdZkey: string
) => {
const cf = httpsCallable(functions, 'temporaryStoreCurrentContributionMultiPartUploadId')
await cf({
ceremonyId,
uploadId: uploadIdZkey
})
}

/**
* Call the temporaryStoreCurrentContributionUploadedChunk cloud function
* @param functions <Functions> - the cloud functions.
* @param ceremonyId <string> - the ceremony ID.
* @param eTag <string> - the eTag.
* @param partNumber <number> - the part number.
*/
export const temporaryStoreCurrentContributionUploadedChunk = async (
functions: Functions,
ceremonyId: string,
eTag: string,
partNumber: number
) => {
const cf = httpsCallable(functions, 'temporaryStoreCurrentContributionUploadedChunkData')
await cf({
ceremonyId,
eTag,
partNumber
})
}

/**
* Return the memory space requirement for a zkey in GB.
* @param zKeySizeInBytes <number> - the size of the zkey in bytes.
* @returns <number>
*/
export const getZkeysSpaceRequirementsForContributionInGB = (zKeySizeInBytes: number): number =>
// nb. mul per 2 is necessary because download latest + compute newest.
convertToGB(zKeySizeInBytes * 2, true)
36 changes: 36 additions & 0 deletions packages/actions/src/core/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import open from "open"
// import clipboard from "clipboardy" // TODO: need a substitute.
import { Verification } from "@octokit/auth-oauth-device/dist-types/types"
import { OAuthCredential, GithubAuthProvider } from "firebase/auth"
import { firstZkeyIndex } from '../../helpers/constants'

/**
* @dev TODO: needs refactoring.
@@ -61,3 +62,38 @@ export const onVerification = async (verification: Verification): Promise<void>
*/
export const exchangeGithubTokenForFirebaseCredentials = (token: string): OAuthCredential =>
GithubAuthProvider.credential(token)


/**
* Get the powers from pot file name
* @dev the pot files must follow these convention (i_am_a_pot_file_09.ptau) where the numbers before '.ptau' are the powers.
* @param potFileName <string>
* @returns <number>
*/
export const extractPoTFromFilename = (potFileName: string): number =>
Number(potFileName.split("_").pop()?.split(".").at(0))

/**
* Extract a prefix (like_this) from a provided string with special characters and spaces.
* @dev replaces all symbols and whitespaces with underscore.
* @param str <string>
* @returns <string>
*/
export const extractPrefix = (str: string): string =>
// eslint-disable-next-line no-useless-escape
str.replace(/[`\s~!@#$%^&*()|+\-=?;:'",.<>\{\}\[\]\\\/]/gi, "-").toLowerCase()

/**
* Format the next zkey index.
* @param progress <number> - the progression in zkey index (= contributions).
* @returns <string>
*/
export const formatZkeyIndex = (progress: number): string => {
let index = progress.toString()

while (index.length < firstZkeyIndex.length) {
index = `0${index}`
}

return index
}
26 changes: 26 additions & 0 deletions packages/actions/src/helpers/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** Firebase */
export const collections = {
users: "users",
participants: "participants",
ceremonies: "ceremonies",
circuits: "circuits",
contributions: "contributions",
timeouts: "timeouts"
}

export const contributionsCollectionFields = {
contributionTime: "contributionTime",
files: "files",
lastUpdated: "lastUpdated",
participantId: "participantId",
valid: "valid",
verificationTime: "verificationTime",
zkeyIndex: "zKeyIndex"
}

export const firstZkeyIndex = `00000`

export const timeoutsCollectionFields = {
startDate: "startDate",
endDate: "endDate"
}
70 changes: 69 additions & 1 deletion packages/actions/src/helpers/query.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {
collection as collectionRef,
doc,
DocumentData,
DocumentSnapshot,
Firestore,
getDoc,
getDocs,
query,
QueryConstraint,
QueryDocumentSnapshot,
QuerySnapshot
QuerySnapshot,
Timestamp,
where
} from "firebase/firestore"
import { FirebaseDocumentInfo } from "../../types/index"
import { collections, contributionsCollectionFields, timeoutsCollectionFields } from "./constants"

/**
* Helper for query a collection based on certain constraints.
@@ -54,3 +60,65 @@ export const getAllCollectionDocs = async (
collection: string
): Promise<Array<QueryDocumentSnapshot<DocumentData>>> =>
(await getDocs(collectionRef(firestoreDatabase, collection))).docs


/**
* Get a specific document from database.
* @param firestoreDatabase <Firestore> - the firestore db.
* @param collection <string> - the name of the collection.
* @param documentUID <string> - the unique identifier of the document in the collection.
* @returns <Promise<DocumentSnapshot<DocumentData>>> - return the document from Firestore.
*/
export const getDocumentById = async (
firestoreDatabase: Firestore,
collection: string,
documentUID: string
): Promise<DocumentSnapshot<DocumentData>> => {
const docRef = doc(firestoreDatabase, collection, documentUID)

return getDoc(docRef)
}


/**
* Query for contribution from given participant for a given circuit (if any).
* @param firestoreDatabase <Firestore> - the database to query.
* @param ceremonyId <string> - the identifier of the ceremony.
* @param circuitId <string> - the identifier of the circuit.
* @param participantId <string> - the identifier of the participant.
* @returns <Promise<Array<FirebaseDocumentInfo>>>
*/
export const getCurrentContributorContribution = async (
firestoreDatabase: Firestore,
ceremonyId: string,
circuitId: string,
participantId: string
): Promise<Array<FirebaseDocumentInfo>> => {
const participantContributionQuerySnap = await queryCollection(
firestoreDatabase,
`${collections.ceremonies}/${ceremonyId}/${collections.circuits}/${circuitId}/${collections.contributions}`,
[where(contributionsCollectionFields.participantId, "==", participantId)]
)

return fromQueryToFirebaseDocumentInfo(participantContributionQuerySnap.docs)
}

/**
* Query for the active timeout from given participant for a given ceremony (if any).
* @param ceremonyId <string> - the identifier of the ceremony.
* @param participantId <string> - the identifier of the participant.
* @returns Promise<Array<FirebaseDocumentInfo>>
*/
export const getCurrentActiveParticipantTimeout = async (
firestoreDatabase: Firestore,
ceremonyId: string,
participantId: string
): Promise<Array<FirebaseDocumentInfo>> => {
const participantTimeoutQuerySnap = await queryCollection(
firestoreDatabase,
`${collections.ceremonies}/${ceremonyId}/${collections.participants}/${participantId}/${collections.timeouts}`,
[where(timeoutsCollectionFields.endDate, ">=", Timestamp.now().toMillis())]
)

return fromQueryToFirebaseDocumentInfo(participantTimeoutQuerySnap.docs)
}
Loading

0 comments on commit 69947b5

Please sign in to comment.