@chainwayxyz/phase2cli
Version:
All-in-one interactive command-line for interfacing with zkSNARK Phase 2 Trusted Setup ceremonies
198 lines (159 loc) • 7.5 kB
text/typescript
import {
FirebaseDocumentInfo,
getCeremonyCircuits,
getCircuitContributionsFromContributor,
getOpenedCeremonies,
isCoordinator,
convertToDoubleDigits
} from "@p0tion/actions"
import { Firestore } from "firebase/firestore"
import logSymbols from "log-symbols"
import readline from "readline"
import { COMMAND_ERRORS, GENERIC_ERRORS, showError } from "../lib/errors.js"
import { promptForCeremonySelection } from "../lib/prompts.js"
import { bootstrapCommandExecutionAndServices, checkAuth } from "../lib/services.js"
import theme from "../lib/theme.js"
import { customSpinner, getSecondsMinutesHoursFromMillis, sleep } from "../lib/utils.js"
/**
* Clean cursor lines from current position back to root (default: zero).
* @param currentCursorPos - the current position of the cursor.
* @returns <number>
*/
export const cleanCursorPosBackToRoot = (currentCursorPos: number) => {
while (currentCursorPos < 0) {
// Get back and clean line by line.
readline.cursorTo(process.stdout, 0)
readline.clearLine(process.stdout, 0)
readline.moveCursor(process.stdout, -1, -1)
currentCursorPos += 1
}
return currentCursorPos
}
/**
* Show the latest updates for the given circuit.
* @param firestoreDatabase <Firestore> - the Firestore database to query from.
* @param ceremony <FirebaseDocumentInfo> - the Firebase document containing info about the ceremony.
* @param circuit <FirebaseDocumentInfo> - the Firebase document containing info about the circuit.
* @returns Promise<number> return the current position of the cursor (i.e., number of lines displayed).
*/
export const displayLatestCircuitUpdates = async (
firestoreDatabase: Firestore,
ceremony: FirebaseDocumentInfo,
circuit: FirebaseDocumentInfo
): Promise<number> => {
let observation = theme.text.bold(`- Circuit # ${theme.colors.magenta(circuit.data.sequencePosition)}`) // Observation output.
let cursorPos = -1 // Current cursor position (nb. decrease every time there's a new line!).
const { waitingQueue } = circuit.data
// Get info from circuit.
const { currentContributor } = waitingQueue
const { completedContributions } = waitingQueue
if (!currentContributor) {
observation += `\n> Nobody's currently waiting to contribute ${theme.emojis.eyes}`
cursorPos -= 1
} else {
// Search for currentContributor' contribution.
const contributions = await getCircuitContributionsFromContributor(
firestoreDatabase,
ceremony.id,
circuit.id,
currentContributor
)
if (!contributions.length) {
// The contributor is currently contributing.
observation += `\n> Participant ${theme.text.bold(`#${completedContributions + 1}`)} (${theme.text.bold(
currentContributor
)}) is currently contributing ${theme.emojis.fire}`
cursorPos -= 1
} else {
// The contributor has contributed.
observation += `\n> Participant ${theme.text.bold(`#${completedContributions}`)} (${theme.text.bold(
currentContributor
)}) has completed the contribution ${theme.emojis.tada}`
cursorPos -= 1
// The contributor has finished the contribution.
const contributionData = contributions.at(0)?.data
if (!contributionData) showError(GENERIC_ERRORS.GENERIC_ERROR_RETRIEVING_DATA, true)
// Convert times to seconds.
const {
seconds: contributionTimeSeconds,
minutes: contributionTimeMinutes,
hours: contributionTimeHours
} = getSecondsMinutesHoursFromMillis(contributionData?.contributionComputationTime)
const {
seconds: verificationTimeSeconds,
minutes: verificationTimeMinutes,
hours: verificationTimeHours
} = getSecondsMinutesHoursFromMillis(contributionData?.verificationComputationTime)
observation += `\n> The ${theme.text.bold("computation")} took ${theme.text.bold(
`${convertToDoubleDigits(contributionTimeHours)}:${convertToDoubleDigits(
contributionTimeMinutes
)}:${convertToDoubleDigits(contributionTimeSeconds)}`
)}`
observation += `\n> The ${theme.text.bold("verification")} took ${theme.text.bold(
`${convertToDoubleDigits(verificationTimeHours)}:${convertToDoubleDigits(
verificationTimeMinutes
)}:${convertToDoubleDigits(verificationTimeSeconds)}`
)}`
observation += `\n> Contribution ${
contributionData?.valid
? `${theme.text.bold("VALID")} ${theme.symbols.success}`
: `${theme.text.bold("INVALID")} ${theme.symbols.error}`
}`
cursorPos -= 3
}
}
// Show observation for circuit.
process.stdout.write(`${observation}\n\n`)
cursorPos -= 1
return cursorPos
}
/**
* Observe command.
*/
const observe = async () => {
// @todo to be moved as command configuration parameter.
const observationWaitingTimeInMillis = 3000
try {
// Initialize services.
const { firebaseApp, firestoreDatabase } = await bootstrapCommandExecutionAndServices()
// Handle current authenticated user sign in.
const { user } = await checkAuth(firebaseApp)
// Preserve command execution only for coordinators].
if (!(await isCoordinator(user))) showError(COMMAND_ERRORS.COMMAND_NOT_COORDINATOR, true)
// Get running ceremonies info (if any).
const runningCeremoniesDocs = await getOpenedCeremonies(firestoreDatabase)
// Ask to select a ceremony.
const ceremony = await promptForCeremonySelection(
runningCeremoniesDocs,
false,
"Which ceremony would you like to observe?"
)
console.log(`${logSymbols.info} Refresh rate set to ~3 seconds for waiting queue updates\n`)
let cursorPos = 0 // Keep track of current cursor position.
const spinner = customSpinner(`Getting ready...`, "clock")
spinner.start()
// Get circuit updates every 3 seconds.
setInterval(async () => {
// Clean cursor position back to root.
cursorPos = cleanCursorPosBackToRoot(cursorPos)
spinner.stop()
spinner.text = `Updating...`
spinner.start()
// Get updates from circuits.
const circuits = await getCeremonyCircuits(firestoreDatabase, ceremony.id)
await sleep(observationWaitingTimeInMillis / 10) // Just for a smoother UX/UI experience.
spinner.stop()
// Observe changes for each circuit
for await (const circuit of circuits)
cursorPos += await displayLatestCircuitUpdates(firestoreDatabase, ceremony, circuit)
process.stdout.write(`Press CTRL+C to exit`)
await sleep(1000) // Just for a smoother UX/UI experience.
}, observationWaitingTimeInMillis)
await sleep(observationWaitingTimeInMillis) // Wait until the first update.
spinner.stop()
} catch (err: any) {
showError(`Something went wrong: ${err.toString()}`, true)
}
}
export default observe