diff --git a/web/src/helpers/p0tion.ts b/web/src/helpers/p0tion.ts index ff95fe0..c796f2d 100644 --- a/web/src/helpers/p0tion.ts +++ b/web/src/helpers/p0tion.ts @@ -1,6 +1,6 @@ import { firebaseApp, firebaseAuth, getCeremonyCircuits, getCircuitBySequencePosition, getCircuitContributionsFromContributor, getCurrentActiveParticipantTimeout, getDocumentById, getLatestUpdatesFromParticipant } from "./firebase"; import { Contribution, FirebaseDocumentInfo, ParticipantContributionStep, ParticipantStatus } from "./interfaces"; -import { getAuth, GithubAuthProvider, signInWithPopup, signOut } from "firebase/auth" +import { getAuth, GithubAuthProvider, signInWithPopup, signOut } from "firebase/auth" import { DocumentData, DocumentSnapshot, onSnapshot } from "firebase/firestore"; import { checkParticipantForCeremony, permanentlyStoreCurrentContributionTimeAndHash, progressToNextCircuitForContribution, progressToNextContributionStep, resumeContributionAfterTimeoutExpiration, verifyContribution } from "./functions"; import { checkGitHubReputation, convertToDoubleDigits, downloadCeremonyArtifact, formatHash, formatZkeyIndex, generatePublicAttestation, getBucketName, getGithubProviderUserId, getParticipantsCollectionPath, getSecondsMinutesHoursFromMillis, getZkeyStorageFilePath, handleTweetGeneration, multiPartUpload, publishGist, sleep } from "./utils"; @@ -9,10 +9,10 @@ import randomf from "randomf" declare global { interface Window { - snarkjs: any; + snarkjs: any; } - } - +} + const snarkjs = window.snarkjs; // the zKey generated by snarkjs @@ -64,16 +64,17 @@ export const signOutWithGitHub = async () => { * @returns */ export const contribute = async (ceremonyId: string, setStatus: (message: string, loading?: boolean, attestationLink?: string) => void) => { - const user = getAuth(firebaseApp).currentUser + const auth = getAuth(firebaseApp) + const user = auth.currentUser if (user === null) { setStatus("Not authenticated, please authenticate first") - return + return } const token = localStorage.getItem("token") if (!token) { setStatus("No token, auth first") - return + return } // check if the user passes the GitHub reputation checks @@ -85,7 +86,7 @@ export const contribute = async (ceremonyId: string, setStatus: (message: string follower${minFollowers > 1 ? "s" : ""}, and follow ${minFollowing} user${minFollowers > 1 ? "s" : ""}. Please fulfil the requirements and login again.`) - return + return } // we are sure to get this cause the user is authenticated @@ -103,7 +104,7 @@ export const contribute = async (ceremonyId: string, setStatus: (message: string if (!activeTimeout.data) { setStatus("There seems to be an error with the timeout, please try again or contact an administrator", false) - return + return } // Extract data. @@ -116,11 +117,11 @@ export const contribute = async (ceremonyId: string, setStatus: (message: string setStatus(`You are timed out. Timeout will end in ${convertToDoubleDigits(days)}:${convertToDoubleDigits(hours)}:${convertToDoubleDigits( minutes )}:${convertToDoubleDigits(seconds)} (dd:hh:mm:ss)`, false) - return + return } else { // check if the user already contributed, or is timed out setStatus("You cannot participate to this ceremony", false) - return + return } } @@ -154,7 +155,7 @@ export const handleStartOrResumeContribution = async ( if (!ceremonyData) { setStatus("There is no such ceremony", false) - return + return } const { prefix: ceremonyPrefix } = ceremonyData @@ -164,8 +165,8 @@ export const handleStartOrResumeContribution = async ( const updatedParticipant = await getDocumentById(`ceremonies/${ceremonyId}/participants`, participant.id) if (!updatedParticipant.data()) { setStatus("The participant document seems to not have any data, please try again.", false) - return - } + return + } setStatus(`You are contributing at circuit #${sequencePosition}`, true) @@ -184,68 +185,61 @@ export const handleStartOrResumeContribution = async ( // Get ceremony bucket name. const bucketName = getBucketName(ceremonyPrefix, bucketPostfix) - let blob: Uint8Array = new Uint8Array() + setStatus("Downloading zKey", true) + let blob: Uint8Array = await downloadCeremonyArtifact(bucketName, lastZkeyStorageFilePath, setStatus) + setStatus("Downloaded zKey", false) if (updatedParticipantData.contributionStep === ParticipantContributionStep.DOWNLOADING) { - setStatus("Downloading zKey", true) - blob = await downloadCeremonyArtifact(bucketName, lastZkeyStorageFilePath, setStatus) - - setStatus("Downloaded zKey", false) - // progress to the next step - await progressToNextContributionStep(ceremonyId) - - await sleep(10000) - - updatedParticipantData = await getLatestUpdatesFromParticipant(ceremonyId, participant.id) + updatedParticipantData = await transitionWithUserInfo(participant.id, updatedParticipantData.contributionStep, ceremonyId, setStatus) } - if (updatedParticipantData.contributionStep === ParticipantContributionStep.COMPUTING) { - setStatus("Computing contribution", true) + setStatus("Computing contribution", true) - // time - const start = new Date().getTime(); - let output: any - // contribute - try { - output = await snarkjs.zKey.contribute( - blob, - nextZKey, - contributorIdentifier, - Array(32) + // time + const start = new Date().getTime(); + let output: any + // contribute + try { + output = await snarkjs.zKey.contribute( + blob, + nextZKey, + contributorIdentifier, + Array(32) .fill(null) .map(() => randomf(2n ** 256n)) .join(''), - ) - } catch (error: any) { - setStatus(`Error computing, ${error.toString()}`, false) - - } + ) + } catch (error: any) { + setStatus(`Error computing, ${error.toString()}`, false) + } - // take hash - const hash = formatHash(output, "Contribution Hash: ") + // take hash + const hash = formatHash(output, "Contribution Hash: ") - const end = new Date().getTime(); - const time = end - start; - setStatus(`Computed zKey in: ${time}ms`, false); + const end = new Date().getTime(); + const time = end - start; + setStatus(`Computed zKey in: ${time}ms`, false); + await sleep(1000) + setStatus(`Uploading hash and time taken`, true); - // upload hash and time taken + // upload hash and time taken + try { await permanentlyStoreCurrentContributionTimeAndHash( ceremony.id, time, hash ) + } catch (error: any) { + setStatus(`Error uploading hash and time taken, ${error.toString()}, (Trying to continue...)`, false) + } - await sleep(5000) - - await progressToNextContributionStep(ceremony.id) - await sleep(1000) - - // Refresh most up-to-date data from the participant document. - updatedParticipantData = await getLatestUpdatesFromParticipant(ceremony.id, participant.id) + await sleep(5000) + if (updatedParticipantData.contributionStep === ParticipantContributionStep.COMPUTING) { + updatedParticipantData = await transitionWithUserInfo(participant.id, updatedParticipantData.contributionStep, ceremonyId, setStatus) } - if (updatedParticipantData.contributionStep === ParticipantContributionStep.UPLOADING) { - setStatus("Uploading contribution", true) + setStatus("Uploading contribution", true) + try { await multiPartUpload( bucketName, nextZkeyStorageFilePath, @@ -257,12 +251,14 @@ export const handleStartOrResumeContribution = async ( setStatus("Uploaded contribution", false) await sleep(1000) + } catch (error: any) { + setStatus(`Error uploading contribution. Please reload and try again. Error: ${error.toString()}`, false) + throw error + } - await progressToNextContributionStep(ceremonyId) - await sleep(1000) - // Refresh most up-to-date data from the participant document. - updatedParticipantData = await getLatestUpdatesFromParticipant(ceremonyId, participant.id) + if (updatedParticipantData.contributionStep === ParticipantContributionStep.UPLOADING) { + updatedParticipantData = await transitionWithUserInfo(participant.id, updatedParticipantData.contributionStep, ceremonyId, setStatus) } if (updatedParticipantData.contributionStep === ParticipantContributionStep.VERIFYING) { @@ -284,6 +280,31 @@ export const handleStartOrResumeContribution = async ( } +/** + * Progress to the next contribution step + * @param participantId - the unique identifier of the participant. + * @param currentContributionStep - the current contribution step. + * @param ceremonyId - the unique identifier of the ceremony. + * @param setStatus - the function to set the status. + * @returns - the updated participant data. + */ +async function transitionWithUserInfo( + participantId: string, + currentContributionStep: string, + ceremonyId: string, + setStatus: (message: string, loading?: boolean, attestationLink?: string) => void +) { + setStatus(`Progressing to the next contribution step (from: ${currentContributionStep})`, true) + await progressToNextContributionStep(ceremonyId) + + await sleep(5000) + + const updatedParticipantData = await getLatestUpdatesFromParticipant(ceremonyId, participantId) + setStatus(`Step after update: ${updatedParticipantData.contributionStep}`, false) + + return updatedParticipantData +} + /** * Listen to circuit document changes. * @notice the circuit is the one for which the participant wants to contribute. @@ -305,7 +326,7 @@ export const listenToCeremonyCircuitDocumentChanges = ( // Check data. if (!circuit.data || !changedCircuit.data()) { setStatus(`There is no circuit data for circuit ${circuit.id}`, false) - return + return } // Extract data. @@ -321,15 +342,15 @@ export const listenToCeremonyCircuitDocumentChanges = ( // Check data. if (!circuitCurrentContributor.data()) { setStatus("No circuit current contributor data. Please try again") - return + return } const latestParticipantPositionInQueue = waitingQueue.contributors.indexOf(participantId) + 1 const newEstimatedWaitingTime = - fullContribution <= 0 && verifyCloudFunction <= 0 - ? 0 - : (fullContribution + verifyCloudFunction) * (latestParticipantPositionInQueue - 1) + fullContribution <= 0 && verifyCloudFunction <= 0 + ? 0 + : (fullContribution + verifyCloudFunction) * (latestParticipantPositionInQueue - 1) const { seconds, minutes, hours, days } = getSecondsMinutesHoursFromMillis(newEstimatedWaitingTime) // Check if the participant is now the new current contributor for the circuit. @@ -341,12 +362,11 @@ export const listenToCeremonyCircuitDocumentChanges = ( } else if (latestParticipantPositionInQueue !== cachedLatestPosition) { setStatus(`You are at position ${latestParticipantPositionInQueue} in the queue`, false) setStatus( - `You will have to wait for ${latestParticipantPositionInQueue - 1} contributors (~${ - newEstimatedWaitingTime > 0 - ? - `${convertToDoubleDigits(days)}:${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}` + `You will have to wait for ${latestParticipantPositionInQueue - 1} contributors (~${newEstimatedWaitingTime > 0 + ? + `${convertToDoubleDigits(days)}:${convertToDoubleDigits(hours)}:${convertToDoubleDigits(minutes)}:${convertToDoubleDigits(seconds)}` : `no time` - } (dd/hh/mm/ss))`, false) + } (dd/hh/mm/ss))`, false) cachedLatestPosition = latestParticipantPositionInQueue } @@ -411,7 +431,7 @@ export const listenToParticipantDocumentChanges = async ( const ceremonyData = ceremonyDodc.data() if (!ceremonyData) { setStatus(`There is no such ceremony`, false) - return + return } const unsubscribe = onSnapshot(participant.ref, async (changedParticipant: DocumentSnapshot) => { // Extract data. @@ -446,9 +466,9 @@ export const listenToParticipantDocumentChanges = async ( } if (changedContributionProgress > 0 && changedContributionProgress <= circuits.length) { - const circuit = circuits[changedContributionProgress-1] + const circuit = circuits[changedContributionProgress - 1] - if (!circuit.data) return + if (!circuit.data) return const { waitingQueue } = circuit.data @@ -475,10 +495,10 @@ export const listenToParticipantDocumentChanges = async ( const timeoutExpired = changedStatus === ParticipantStatus.EXHUMED const alreadyContributedToEveryCeremonyCircuit = - changedStatus === ParticipantStatus.DONE && - changedContributionStep === ParticipantContributionStep.COMPLETED && - changedContributionProgress === circuits.length && - changedContributions.length === circuits.length + changedStatus === ParticipantStatus.DONE && + changedContributionStep === ParticipantContributionStep.COMPLETED && + changedContributionProgress >= circuits.length && + changedContributions.length >= circuits.length const noTemporaryContributionData = !prevTempContributionData && !changedTempContributionData @@ -507,9 +527,9 @@ export const listenToParticipantDocumentChanges = async ( !!prevTempContributionData && !!changedTempContributionData && JSON.stringify(Object.keys(prevTempContributionData).sort()) === - JSON.stringify(Object.keys(changedTempContributionData).sort()) && + JSON.stringify(Object.keys(changedTempContributionData).sort()) && JSON.stringify(Object.values(prevTempContributionData).sort()) === - JSON.stringify(Object.values(changedTempContributionData).sort()) + JSON.stringify(Object.values(changedTempContributionData).sort()) const startingOrResumingContribution = // Pre-condition W => contribute / resume when contribution step = DOWNLOADING. @@ -526,7 +546,7 @@ export const listenToParticipantDocumentChanges = async ( // Pre-condition Z => contribute / resume when contribution step = UPLOADING w/ some pre-uploaded chunk. (!noTemporaryContributionData && resumingWithSameTemporaryData) - + if (isCurrentContributor && hasResumableStep && startingOrResumingContribution) { setStatus("Starting or resuming contribution", true) await handleStartOrResumeContribution(ceremonyId, circuit, changedParticipant, participantProviderId, setStatus) @@ -545,9 +565,9 @@ export const listenToParticipantDocumentChanges = async ( if (progressToNextContribution && noStatusChanges && (changedStatus === ParticipantStatus.DONE || changedStatus === ParticipantStatus.CONTRIBUTED)) { - // get the latest verification result - const res = await getLatestVerificationResult(ceremonyId, circuit.id, participant.id) - setStatus(`Result of previous contribution - verified = ${res}`, false) + // get the latest verification result + const res = await getLatestVerificationResult(ceremonyId, circuit.id, participant.id) + setStatus(`Result of previous contribution - verified = ${res}`, false) } // check timeout @@ -589,7 +609,7 @@ export const listenToParticipantDocumentChanges = async ( } const nextCircuit = timeoutExpired ? getCircuitBySequencePosition(circuits, changedContributionProgress) : getCircuitBySequencePosition(circuits, changedContributionProgress + 1) - + if (!timeoutExpired) { // progress to next circuit setStatus(`Progressing to the next circuit at position #${nextCircuit.data.sequencePosition}`, true) @@ -628,5 +648,5 @@ export const getLatestVerificationResult = async (ceremonyId: string, circuitId: const contribution = circuitContributionsFromContributor[0] - return contribution?.data.valid + return contribution?.data.valid }