Skip to content

Commit

Permalink
Merge pull request #121 from privacy-scaling-explorations/fix/verific…
Browse files Browse the repository at this point in the history
…ation

fix(verification): set concurrency to 1 and ensure that files are downloaded before proceeding
  • Loading branch information
baumstern authored Jul 24, 2023
2 parents 9dd2cb6 + 45b8fc5 commit 70263f2
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 74 deletions.
64 changes: 44 additions & 20 deletions packages/actions/src/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,39 +185,63 @@ export const parseCeremonyFile = async (path: string, cleanup: boolean = false):
// check that the timeout is provided for the correct configuration
let dynamicThreshold: number | undefined
let fixedTimeWindow: number | undefined

let circuit: CircuitDocument | CircuitInputData = {} as CircuitDocument | CircuitInputData

if (data.timeoutMechanismType === CeremonyTimeoutType.DYNAMIC) {
if (circuitData.dynamicThreshold <= 0)
throw new Error("The dynamic threshold should be > 0.")
dynamicThreshold = circuitData.dynamicThreshold

// the Circuit data for the ceremony setup
circuit = {
name: circuitData.name,
description: circuitData.description,
prefix: circuitPrefix,
sequencePosition: i+1,
metadata: metadata,
files: files,
template: template,
compiler: compiler,
verification: verification,
dynamicThreshold: dynamicThreshold,
avgTimings: {
contributionComputation: 0,
fullContribution: 0,
verifyCloudFunction: 0
},

}
}

if (data.timeoutMechanismType === CeremonyTimeoutType.FIXED) {
if (circuitData.fixedTimeWindow <= 0)
throw new Error("The fixed time window threshold should be > 0.")
fixedTimeWindow = circuitData.fixedTimeWindow
}

// the Circuit data for the ceremony setup
const circuit: CircuitDocument | CircuitInputData = {
name: circuitData.name,
description: circuitData.description,
prefix: circuitPrefix,
sequencePosition: i+1,
metadata: metadata,
files: files,
template: template,
compiler: compiler,
verification: verification,
fixedTimeWindow: fixedTimeWindow,
// dynamicThreshold: dynamicThreshold,
avgTimings: {
contributionComputation: 0,
fullContribution: 0,
verifyCloudFunction: 0
},


// the Circuit data for the ceremony setup
circuit = {
name: circuitData.name,
description: circuitData.description,
prefix: circuitPrefix,
sequencePosition: i+1,
metadata: metadata,
files: files,
template: template,
compiler: compiler,
verification: verification,
fixedTimeWindow: fixedTimeWindow,
avgTimings: {
contributionComputation: 0,
fullContribution: 0,
verifyCloudFunction: 0
},

}
}


circuits.push(circuit)

// remove the local r1cs download (if used for verifying the config only vs setup)
Expand Down
145 changes: 93 additions & 52 deletions packages/backend/src/functions/circuit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { zKey } from "snarkjs"
import { CommandInvocationStatus, SSMClient } from "@aws-sdk/client-ssm"
import { FinalizeCircuitData, VerifyContributionData } from "../types/index"
import { LogLevel } from "../types/enums"
import { COMMON_ERRORS, logAndThrowError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
import { COMMON_ERRORS, logAndThrowError, makeError, printLog, SPECIFIC_ERRORS } from "../lib/errors"
import {
createEC2Client,
createSSMClient,
Expand Down Expand Up @@ -236,9 +236,6 @@ const waitForVMCommandExecution = (
if (cmdStatus === CommandInvocationStatus.SUCCESS) {
printLog(`Command ${commandId} successfully completed`, LogLevel.DEBUG)

// Clear the interval.
clearInterval(interval)

// Resolve the promise.
resolve()
} else if (cmdStatus === CommandInvocationStatus.FAILED) {
Expand Down Expand Up @@ -268,6 +265,62 @@ const waitForVMCommandExecution = (
}, 60000) // 1 minute.
}

/**
* Wait until the artifacts have been downloaded.
* @param {any} resolve the promise.
* @param {any} reject the promise.
* @param {string} potTempFilePath the tmp path to the locally downloaded pot file.
* @param {string} firstZkeyTempFilePath the tmp path to the locally downloaded first zkey file.
* @param {string} lastZkeyTempFilePath the tmp path to the locally downloaded last zkey file.
*/
const waitForFileDownload = (
resolve: any,
reject: any,
potTempFilePath: string,
firstZkeyTempFilePath: string,
lastZkeyTempFilePath: string,
circuitId: string,
participantId: string
) => {
const maxWaitTime = 5 * 60 * 1000 // 5 minutes
// every second check if the file download was completed
const interval = setInterval(async () => {
printLog(`Verifying that the artifacts were downloaded for circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG)
try {
// check if files have been downloaded
if (!fs.existsSync(potTempFilePath)) {
printLog(`Pot file not found at ${potTempFilePath}`, LogLevel.DEBUG)
}
if (!fs.existsSync(firstZkeyTempFilePath)) {
printLog(`First zkey file not found at ${firstZkeyTempFilePath}`, LogLevel.DEBUG)
}
if (!fs.existsSync(lastZkeyTempFilePath)) {
printLog(`Last zkey file not found at ${lastZkeyTempFilePath}`, LogLevel.DEBUG)
}

// if all files were downloaded
if (fs.existsSync(potTempFilePath) && fs.existsSync(firstZkeyTempFilePath) && fs.existsSync(lastZkeyTempFilePath)) {
printLog(`All required files are present on disk.`, LogLevel.INFO)
// resolve the promise
resolve()
}
} catch (error: any) {
// if we have an error then we print it as a warning and reject
printLog(`Error while downloading files: ${error}`, LogLevel.WARN)
reject()
} finally {
printLog(`Clearing the interval for file download. Circuit ${circuitId} and participant ${participantId}`, LogLevel.DEBUG)
clearInterval(interval)
}
}, 5000)

// we want to clean in 5 minutes in case
setTimeout(() => {
clearInterval(interval)
reject(new Error('Timeout exceeded while waiting for files to be downloaded.'))
}, maxWaitTime)
}

/**
* This method is used to coordinate the waiting queues of ceremony circuits.
* @dev this cloud function is triggered whenever an update of a document related to a participant of a ceremony occurs.
Expand Down Expand Up @@ -444,7 +497,7 @@ const checkIfVMRunning = async (
* 2) Send all updates atomically to the Firestore database.
*/
export const verifycontribution = functionsV2.https.onCall(
{ memory: "16GiB", timeoutSeconds: 3600, region: 'europe-west1' },
{ memory: "16GiB", timeoutSeconds: 3600, region: 'europe-west1', concurrency: 1 },
async (request: functionsV2.https.CallableRequest<VerifyContributionData>): Promise<any> => {
if (!request.auth || (!request.auth.token.participant && !request.auth.token.coordinator))
logAndThrowError(SPECIFIC_ERRORS.SE_AUTH_NO_CURRENT_AUTH_USER)
Expand Down Expand Up @@ -828,55 +881,43 @@ export const verifycontribution = functionsV2.https.onCall(

await sleep(10000)

// check if files have been downloaded
if (!fs.existsSync(potTempFilePath)) {
printLog(`Pot file not found at ${potTempFilePath}`, LogLevel.DEBUG)
// retry once
printLog(`Retrying to download pot file from ${potStoragePath} to ${potTempFilePath}`, LogLevel.DEBUG)
await downloadArtifactFromS3Bucket(bucketName, potStoragePath, potTempFilePath)
}
if (!fs.existsSync(firstZkeyTempFilePath)) {
printLog(`First zkey file not found at ${firstZkeyTempFilePath}`, LogLevel.DEBUG)
// retry once
printLog(`Retrying to download first zkey file from ${firstZkeyStoragePath} to ${firstZkeyTempFilePath}`, LogLevel.DEBUG)
await downloadArtifactFromS3Bucket(bucketName, firstZkeyStoragePath, firstZkeyTempFilePath)
}
if (!fs.existsSync(lastZkeyTempFilePath)) {
printLog(`Last zkey file not found at ${lastZkeyTempFilePath}`, LogLevel.DEBUG)
// retry once
printLog(`Retrying to download last zkey file from ${lastZkeyStoragePath} to ${lastZkeyTempFilePath}`, LogLevel.DEBUG)
await downloadArtifactFromS3Bucket(bucketName, lastZkeyStoragePath, lastZkeyTempFilePath)
}
// wait until the files are actually downloaded
return new Promise<void>((resolve, reject) =>
waitForFileDownload(resolve, reject, potTempFilePath, firstZkeyTempFilePath, lastZkeyTempFilePath, circuitId, participantDoc.id)
)
.then(async () => {
printLog(`Downloads from AWS S3 bucket completed - ceremony ${ceremonyId}`, LogLevel.DEBUG)

// Step (1.A.4).
isContributionValid = await zKey.verifyFromInit(
firstZkeyTempFilePath,
potTempFilePath,
lastZkeyTempFilePath,
transcriptLogger
)

// Compute contribution hash.
lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)

printLog(`Downloads from AWS S3 bucket completed - ceremony ${ceremonyId}`, LogLevel.DEBUG)
await completeVerification()

// Step (1.A.4).
try {
isContributionValid = await zKey.verifyFromInit(
firstZkeyTempFilePath,
potTempFilePath,
lastZkeyTempFilePath,
transcriptLogger
)
} catch (error: any) {
printLog(`Error while verifying contribution - Error ${error}`, LogLevel.WARN)
isContributionValid = false
}

// Compute contribution hash.
lastZkeyBlake2bHash = await blake512FromPath(lastZkeyTempFilePath)

await completeVerification()

// Free resources by unlinking temporary folders.
// Do not free-up verification transcript path here.
try {
fs.unlinkSync(potTempFilePath)
fs.unlinkSync(firstZkeyTempFilePath)
fs.unlinkSync(lastZkeyTempFilePath)
} catch (error: any) {
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
}
// Free resources by unlinking temporary folders.
// Do not free-up verification transcript path here.
try {
fs.unlinkSync(potTempFilePath)
fs.unlinkSync(firstZkeyTempFilePath)
fs.unlinkSync(lastZkeyTempFilePath)
} catch (error: any) {
printLog(`Error while unlinking temporary files - Error ${error}`, LogLevel.WARN)
}
})
.catch((error: any) => {
// Throw the new error
const commonError = COMMON_ERRORS.CM_INVALID_REQUEST
const additionalDetails = error.toString()

logAndThrowError(makeError(commonError.code, commonError.message, additionalDetails))
})
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions packages/backend/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import admin from "firebase-admin"
import dotenv from "dotenv"
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
import { createWriteStream } from "node:fs"
import { createWriteStream, fstat } from "node:fs"
import { pipeline } from "node:stream"
import { promisify } from "node:util"
import { readFileSync } from "fs"
Expand Down Expand Up @@ -213,8 +213,13 @@ export const downloadArtifactFromS3Bucket = async (bucketName: string, objectKey
if (response.status !== 200 || !response.ok) logAndThrowError(SPECIFIC_ERRORS.SE_STORAGE_DOWNLOAD_FAILED)

// Write the file locally using streams.
const writeStream = createWriteStream(localFilePath)
const streamPipeline = promisify(pipeline)
await streamPipeline(response.body, createWriteStream(localFilePath))
await streamPipeline(response.body, writeStream)

writeStream.on('finish', () => {
writeStream.end()
})
}

/**
Expand Down

0 comments on commit 70263f2

Please sign in to comment.