Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 106 additions & 86 deletions web/src/helpers/p0tion.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
}
}

Expand Down Expand Up @@ -154,7 +155,7 @@ export const handleStartOrResumeContribution = async (

if (!ceremonyData) {
setStatus("There is no such ceremony", false)
return
return
}
const { prefix: ceremonyPrefix } = ceremonyData

Expand All @@ -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)

Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -284,6 +280,31 @@ export const handleStartOrResumeContribution = async (

}

/**
* Progress to the next contribution step
* @param participantId <string> - the unique identifier of the participant.
* @param currentContributionStep <string> - the current contribution step.
* @param ceremonyId <string> - the unique identifier of the ceremony.
* @param setStatus <function> - the function to set the status.
* @returns <FirebaseDocumentInfo> - 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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -628,5 +648,5 @@ export const getLatestVerificationResult = async (ceremonyId: string, circuitId:

const contribution = circuitContributionsFromContributor[0]

return contribution?.data.valid
return contribution?.data.valid
}