From 62680933f352bbfc4cb6ca3140847573cba19c2c Mon Sep 17 00:00:00 2001 From: yuetloo Date: Sat, 30 Mar 2024 13:57:40 -0400 Subject: [PATCH 01/14] do not wait for synchronization with subgraph --- vue-app/src/locales/cn.json | 4 ++-- vue-app/src/locales/en.json | 4 ++-- vue-app/src/locales/es.json | 4 ++-- vue-app/src/utils/contracts.ts | 33 ------------------------------ vue-app/src/views/JoinView.vue | 10 +++------ vue-app/src/views/ProjectAdded.vue | 22 ++++++++------------ 6 files changed, 17 insertions(+), 60 deletions(-) diff --git a/vue-app/src/locales/cn.json b/vue-app/src/locales/cn.json index 693ba2260..a95b93b08 100644 --- a/vue-app/src/locales/cn.json +++ b/vue-app/src/locales/cn.json @@ -728,8 +728,8 @@ "div1": "您快要参加这筹款活动了。", "li1": "您的项目只需要经过一些最终检查来确保它符合筹款标准。您可以在这里", "link1": "了解更多关于注册流程的信息。", - "li2": "完成后,您的项目页面将上线。", - "li3": " 如果您的项目未通过检查,我们将电邮通知您并退回您的押金。", + "li2": "完成后,您的项目页面将上线, 我们将退还您的押金。", + "li3": " 如果您的项目未通过检查,我们将退回您的押金。", "linkProjects": "查看项目", "link2": "查看项目", "link3": "回首页" diff --git a/vue-app/src/locales/en.json b/vue-app/src/locales/en.json index 89dd5781e..8db5b2e14 100644 --- a/vue-app/src/locales/en.json +++ b/vue-app/src/locales/en.json @@ -728,8 +728,8 @@ "div1": "You’re almost on board this funding round.", "li1": "Your project just needs to go through some final checks to ensure it meets round criteria. You can", "link1": "learn more about the registration process here.", - "li2": "Once that's complete, your project page will go live.", - "li3": " If your project fails any checks, we'll let you know by email and return your deposit.", + "li2": "Once that's complete, your project page will go live and we'll refund your deposit.", + "li3": " If your project fails any checks, we'll return your deposit.", "linkProjects": "View projects", "link2": "View project", "link3": "Go home" diff --git a/vue-app/src/locales/es.json b/vue-app/src/locales/es.json index 54b0f04dc..704996105 100644 --- a/vue-app/src/locales/es.json +++ b/vue-app/src/locales/es.json @@ -728,8 +728,8 @@ "div1": "Estás casi listo para unirte a esta ronda de financiamiento.", "li1": "Tu proyecto solo necesita pasar por algunas verificaciones finales para asegurarse de que cumpla con los criterios de la ronda. Puedes", "link1": "obtener más información sobre el proceso de registro aquí.", - "li2": "Una vez que se complete, la página de tu proyecto se publicará.", - "li3": "Si tu proyecto no pasa alguna verificación, te lo haremos saber por correo electrónico y te devolveremos tu depósito.", + "li2": "Una vez que se complete, la página de tu proyecto se publicará y te devolveremos tu depósito.", + "li3": "Si tu proyecto no pasa alguna verificación, te devolveremos tu depósito.", "linkProjects": "Ver proyectos", "link2": "Ver proyecto", "link3": "Ir a inicio" diff --git a/vue-app/src/utils/contracts.ts b/vue-app/src/utils/contracts.ts index 6df726b50..640152451 100644 --- a/vue-app/src/utils/contracts.ts +++ b/vue-app/src/utils/contracts.ts @@ -47,39 +47,6 @@ export async function waitForTransaction( return transactionReceipt } -/** - * Wait for transaction to be mined and available on the subgraph - * @param pendingTransaction transaction to wait and check for - * @param checkFn the check function - * @param onTransactionHash callback function with the transaction hash - * @returns transaction receipt - */ -export async function waitForTransactionAndCheck( - pendingTransaction: Promise, - checkFn: (receipt: TransactionReceipt) => Promise, - onTransactionHash?: (hash: string) => void, -): Promise { - const receipt = await waitForTransaction(pendingTransaction, onTransactionHash) - - return new Promise(resolve => { - async function checkAndWait(depth = 0) { - if (await checkFn(receipt)) { - resolve(receipt) - } else { - if (depth > MAX_WAIT_DEPTH) { - throw new Error('Time out waiting for transaction ' + receipt.hash) - } - - const timeoutMs = 2 ** depth * 10 - await new Promise(res => setTimeout(res, timeoutMs)) - checkAndWait(depth + 1) - } - } - - checkAndWait() - }) -} - export async function isTransactionMined(hash: string): Promise { const receipt = await provider.getTransactionReceipt(hash) return !!receipt diff --git a/vue-app/src/views/JoinView.vue b/vue-app/src/views/JoinView.vue index 547658557..d87736942 100644 --- a/vue-app/src/views/JoinView.vue +++ b/vue-app/src/views/JoinView.vue @@ -717,12 +717,11 @@ import { useVuelidate } from '@vuelidate/core' import { required, requiredIf, email, maxLength, url, helpers } from '@vuelidate/validators' import type { RecipientApplicationData } from '@/api/types' import type { Project } from '@/api/projects' -import { isTransactionInSubgraph } from '@/api/subgraph' import { formToProjectInterface } from '@/api/projects' -import { chain, showComplianceRequirement, isOptimisticRecipientRegistry } from '@/api/core' +import { chain, showComplianceRequirement } from '@/api/core' import { DateTime } from 'luxon' import { useRecipientStore, useAppStore, useUserStore } from '@/stores' -import { waitForTransactionAndCheck } from '@/utils/contracts' +import { waitForTransaction } from '@/utils/contracts' import { addRecipient as _addRecipient } from '@/api/recipient-registry' import { isValidEthAddress, resolveEns } from '@/utils/accounts' import { toReactive } from '@vueuse/core' @@ -942,11 +941,8 @@ async function addRecipient() { } const signer = await userStore.getSigner() - await waitForTransactionAndCheck( + await waitForTransaction( _addRecipient(recipientRegistryAddress.value, recipient.value, recipientRegistryInfo.value.deposit, signer), - receipt => { - return isOptimisticRecipientRegistry ? isTransactionInSubgraph(receipt) : Promise.resolve(true) - }, hash => (txHash.value = hash), ) diff --git a/vue-app/src/views/ProjectAdded.vue b/vue-app/src/views/ProjectAdded.vue index 59fe6290c..f59065d37 100644 --- a/vue-app/src/views/ProjectAdded.vue +++ b/vue-app/src/views/ProjectAdded.vue @@ -2,9 +2,9 @@
- +
- +
🎉
@@ -23,12 +23,6 @@
- - {{ $t('projectAdded.linkProjects') }} {{ $t('projectAdded.link3') }}
@@ -121,7 +115,7 @@ ul { height: calc(100vh - 113px); @media (max-width: $breakpoint-m) { padding: 2rem 0rem; - padding-bottom: 16rem; + flex-direction: column; } img { @@ -130,21 +124,21 @@ ul { right: 0; width: 66%; @media (max-width: $breakpoint-m) { + position: relative; right: 0; - width: 100%; + width: 90%; } } .content { position: relative; z-index: 1; - padding: $content-space; - width: min(100%, 512px); + width: min(80%, 512px); margin-left: 2rem; margin-top: 3rem; @media (max-width: $breakpoint-m) { - width: 100%; - margin: 0; + width: 90%; + padding: 0; } .flex-title { From 71b9fbdae26cd3cfebcda315addaa6a28dce267a Mon Sep 17 00:00:00 2001 From: yuetloo Date: Sat, 30 Mar 2024 14:00:42 -0400 Subject: [PATCH 02/14] remove unused translation --- vue-app/src/locales/cn.json | 2 -- vue-app/src/locales/en.json | 2 -- vue-app/src/locales/es.json | 2 -- 3 files changed, 6 deletions(-) diff --git a/vue-app/src/locales/cn.json b/vue-app/src/locales/cn.json index a95b93b08..dbc945f34 100644 --- a/vue-app/src/locales/cn.json +++ b/vue-app/src/locales/cn.json @@ -730,8 +730,6 @@ "link1": "了解更多关于注册流程的信息。", "li2": "完成后,您的项目页面将上线, 我们将退还您的押金。", "li3": " 如果您的项目未通过检查,我们将退回您的押金。", - "linkProjects": "查看项目", - "link2": "查看项目", "link3": "回首页" }, "projectList": { diff --git a/vue-app/src/locales/en.json b/vue-app/src/locales/en.json index 8db5b2e14..3811acfbc 100644 --- a/vue-app/src/locales/en.json +++ b/vue-app/src/locales/en.json @@ -730,8 +730,6 @@ "link1": "learn more about the registration process here.", "li2": "Once that's complete, your project page will go live and we'll refund your deposit.", "li3": " If your project fails any checks, we'll return your deposit.", - "linkProjects": "View projects", - "link2": "View project", "link3": "Go home" }, "projectList": { diff --git a/vue-app/src/locales/es.json b/vue-app/src/locales/es.json index 704996105..ae103f13e 100644 --- a/vue-app/src/locales/es.json +++ b/vue-app/src/locales/es.json @@ -730,8 +730,6 @@ "link1": "obtener más información sobre el proceso de registro aquí.", "li2": "Una vez que se complete, la página de tu proyecto se publicará y te devolveremos tu depósito.", "li3": "Si tu proyecto no pasa alguna verificación, te devolveremos tu depósito.", - "linkProjects": "Ver proyectos", - "link2": "Ver proyecto", "link3": "Ir a inicio" }, "projectList": { From d20877af26e06e719589a662a967d11e49e3cfda Mon Sep 17 00:00:00 2001 From: yuetloo Date: Tue, 2 Apr 2024 23:30:29 -0400 Subject: [PATCH 03/14] break the tally script into multiple smaller scripts --- contracts/tasks/index.ts | 4 + contracts/tasks/runners/genProofs.ts | 185 ++++++++++++++++++ contracts/tasks/runners/proveOnChain.ts | 99 ++++++++++ .../tasks/runners/publishTallyResults.ts | 163 +++++++++++++++ contracts/tasks/runners/resetTally.ts | 51 +++++ contracts/utils/contracts.ts | 29 ++- 6 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 contracts/tasks/runners/genProofs.ts create mode 100644 contracts/tasks/runners/proveOnChain.ts create mode 100644 contracts/tasks/runners/publishTallyResults.ts create mode 100644 contracts/tasks/runners/resetTally.ts diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 5bcc7e4fc..934d30612 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -23,3 +23,7 @@ import './runners/addRecipients' import './runners/findStorageSlot' import './runners/verifyTallyFile' import './runners/verifyAll' +import './runners/genProofs' +import './runners/proveOnChain' +import './runners/publishTallyResults' +import './runners/resetTally' diff --git a/contracts/tasks/runners/genProofs.ts b/contracts/tasks/runners/genProofs.ts new file mode 100644 index 000000000..55c9d5521 --- /dev/null +++ b/contracts/tasks/runners/genProofs.ts @@ -0,0 +1,185 @@ +/** + * Script for generating MACI proofs + * + * Pass --maci-tx-hash if this is the first time running the script and you + * want to get MACI logs starting from the block as recorded in the MACI creation + * transaction hash + * + * Pass --maci-state-file if you have previously ran the script and have + * the maci-state file (maci-state.json) + * + * Make sure to set the following environment variables in the .env file + * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC + * - coordinator's wallet private key to interact with contracts + * 2) COORDINATOR_MACISK - coordinator's MACI private key to decrypt messages + * + * Sample usage: + * + * yarn hardhat tally --clrfund --proof-dir \ + * --maci-tx-hash --network + * + */ +import { getNumber, NonceManager } from 'ethers' +import { task, types } from 'hardhat/config' + +import { + DEFAULT_GET_LOG_BATCH_SIZE, + DEFAULT_SR_QUEUE_OPS, +} from '../../utils/constants' +import { + getGenProofArgs, + genProofs, + genLocalState, + mergeMaciSubtrees, +} from '../../utils/maci' +import { getMaciStateFilePath } from '../../utils/misc' +import { EContracts } from '../../utils/types' +import { Subtask } from '../helpers/Subtask' +import { getCurrentFundingRoundContract } from '../../utils/contracts' + +task('gen-proofs', 'Generate MACI proofs offchain') + .addParam('clrfund', 'FundingRound contract address') + .addParam('proofDir', 'The proof output directory') + .addOptionalParam('maciTxHash', 'MACI creation transaction hash') + .addOptionalParam( + 'maciStartBlock', + 'MACI creation block', + undefined, + types.int + ) + .addOptionalParam('maciStateFile', 'MACI state file') + .addFlag('manageNonce', 'Whether to manually manage transaction nonce') + .addOptionalParam('rapidsnark', 'The rapidsnark prover path') + .addOptionalParam( + 'blocksPerBatch', + 'The number of blocks per batch of logs to fetch on-chain', + DEFAULT_GET_LOG_BATCH_SIZE, + types.int + ) + .addOptionalParam( + 'numQueueOps', + 'The number of operations for MACI tree merging', + getNumber(DEFAULT_SR_QUEUE_OPS), + types.int + ) + .addOptionalParam('sleep', 'Number of seconds to sleep between log fetch') + .addOptionalParam( + 'quiet', + 'Whether to disable verbose logging', + false, + types.boolean + ) + .setAction( + async ( + { + clrfund, + maciStartBlock, + maciTxHash, + quiet, + maciStateFile, + proofDir, + blocksPerBatch, + rapidsnark, + numQueueOps, + sleep, + manageNonce, + }, + hre + ) => { + console.log('Verbose logging enabled:', !quiet) + + const { ethers, network } = hre + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + if (!maciStateFile && !maciTxHash && maciStartBlock == undefined) { + throw new Error('Please provide --maci-start-block or --maci-tx-hash') + } + + const [coordinatorSigner] = await ethers.getSigners() + if (!coordinatorSigner) { + throw new Error('Env. variable WALLET_PRIVATE_KEY not set') + } + const coordinator = manageNonce + ? new NonceManager(coordinatorSigner) + : coordinatorSigner + console.log('Coordinator address: ', await coordinator.getAddress()) + + const coordinatorMacisk = process.env.COORDINATOR_MACISK + if (!coordinatorMacisk) { + throw new Error('Env. variable COORDINATOR_MACISK not set') + } + + const circuit = subtask.getConfigField( + EContracts.VkRegistry, + 'circuit' + ) + const circuitDirectory = subtask.getConfigField( + EContracts.VkRegistry, + 'paramsDirectory' + ) + + await subtask.logStart() + + const fundingRoundContract = await getCurrentFundingRoundContract( + clrfund, + coordinator, + ethers + ) + console.log('Funding round contract', fundingRoundContract.target) + + const pollId = await fundingRoundContract.pollId() + console.log('PollId', pollId) + + const maciAddress = await fundingRoundContract.maci() + + const providerUrl = (network.config as any).url + + await mergeMaciSubtrees({ + maciAddress, + pollId, + numQueueOps, + signer: coordinator, + quiet, + }) + + const maciStateFilePath = maciStateFile + ? maciStateFile + : getMaciStateFilePath(proofDir) + + if (!maciStateFile) { + await genLocalState({ + quiet, + outputPath: maciStateFilePath, + pollId, + maciContractAddress: maciAddress, + coordinatorPrivateKey: coordinatorMacisk, + ethereumProvider: providerUrl, + transactionHash: maciTxHash, + startBlock: maciStartBlock, + blockPerBatch: blocksPerBatch, + signer: coordinator, + sleep, + }) + } + + const genProofArgs = getGenProofArgs({ + maciAddress, + pollId, + coordinatorMacisk, + rapidsnark, + circuitType: circuit, + circuitDirectory, + outputDir: proofDir, + blocksPerBatch: getNumber(blocksPerBatch), + maciTxHash, + maciStateFile: maciStateFilePath, + signer: coordinator, + quiet, + }) + await genProofs(genProofArgs) + + const success = true + await subtask.finish(success) + } + ) diff --git a/contracts/tasks/runners/proveOnChain.ts b/contracts/tasks/runners/proveOnChain.ts new file mode 100644 index 000000000..b1eb499de --- /dev/null +++ b/contracts/tasks/runners/proveOnChain.ts @@ -0,0 +1,99 @@ +/** + * Prove on chain the MACI proofs generated using genProofs + * + * Make sure to set the following environment variables in the .env file + * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC + * - coordinator's wallet private key to interact with contracts + * + * Sample usage: + * + * yarn hardhat prove-on-chain --clrfund --proof-dir --network + * + */ +import { BaseContract, NonceManager } from 'ethers' +import { task, types } from 'hardhat/config' + +import { proveOnChain } from '../../utils/maci' +import { Tally } from '../../typechain-types' +import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' +import { EContracts } from '../../utils/types' +import { Subtask } from '../helpers/Subtask' +import { getCurrentFundingRoundContract } from '../../utils/contracts' + +/** + * Get the message processor contract address from the tally contract + * @param tallyAddress Tally contract address + * @param ethers Hardhat ethers helper + * @returns Message processor contract address + */ +async function getMessageProcessorAddress( + tallyAddress: string, + ethers: HardhatEthersHelpers +): Promise { + const tallyContract = (await ethers.getContractAt( + EContracts.Tally, + tallyAddress + )) as BaseContract as Tally + + const messageProcessorAddress = await tallyContract.messageProcessor() + return messageProcessorAddress +} + +task('prove-on-chain', 'Prove on chain with the MACI proofs') + .addParam('clrfund', 'ClrFund contract address') + .addParam('proofDir', 'The proof output directory') + .addFlag('manageNonce', 'Whether to manually manage transaction nonce') + .addOptionalParam( + 'quiet', + 'Whether to disable verbose logging', + false, + types.boolean + ) + .setAction(async ({ clrfund, quiet, manageNonce, proofDir }, hre) => { + console.log('Verbose logging enabled:', !quiet) + + const { ethers } = hre + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + const [coordinatorSigner] = await ethers.getSigners() + if (!coordinatorSigner) { + throw new Error('Env. variable WALLET_PRIVATE_KEY not set') + } + const coordinator = manageNonce + ? new NonceManager(coordinatorSigner) + : coordinatorSigner + console.log('Coordinator address: ', await coordinator.getAddress()) + + await subtask.logStart() + + const fundingRoundContract = await getCurrentFundingRoundContract( + clrfund, + coordinator, + ethers + ) + console.log('Funding round contract', fundingRoundContract.target) + + const pollId = await fundingRoundContract.pollId() + const maciAddress = await fundingRoundContract.maci() + const tallyAddress = await fundingRoundContract.tally() + const messageProcessorAddress = await getMessageProcessorAddress( + tallyAddress, + ethers + ) + + // proveOnChain if not already processed + await proveOnChain({ + pollId, + proofDir, + subsidyEnabled: false, + maciAddress, + messageProcessorAddress, + tallyAddress, + signer: coordinator, + quiet, + }) + + const success = true + await subtask.finish(success) + }) diff --git a/contracts/tasks/runners/publishTallyResults.ts b/contracts/tasks/runners/publishTallyResults.ts new file mode 100644 index 000000000..46f535f8b --- /dev/null +++ b/contracts/tasks/runners/publishTallyResults.ts @@ -0,0 +1,163 @@ +/** + * Script for tallying votes which involves fetching MACI logs, generating proofs, + * and proving on chain + * + * Make sure to set the following environment variables in the .env file + * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC + * - coordinator's wallet private key to interact with contracts + * + * Sample usage: + * + * yarn hardhat publish-tally-results --clrfund + * --proof-dir --network + * + */ +import { BaseContract, getNumber, NonceManager } from 'ethers' +import { task, types } from 'hardhat/config' + +import { getIpfsHash } from '../../utils/ipfs' +import { JSONFile } from '../../utils/JSONFile' +import { addTallyResultsBatch, TallyData, verify } from '../../utils/maci' +import { FundingRound, Poll } from '../../typechain-types' +import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' +import { EContracts } from '../../utils/types' +import { Subtask } from '../helpers/Subtask' +import { getCurrentFundingRoundContract } from '../../utils/contracts' +import { getTalyFilePath } from '../../utils/misc' + +/** + * Publish the tally IPFS hash on chain if it's not already published + * @param fundingRoundContract Funding round contract + * @param tallyData Tally data + */ +async function publishTallyHash( + fundingRoundContract: FundingRound, + tallyData: TallyData +) { + const tallyHash = await getIpfsHash(tallyData) + console.log(`Tally hash is ${tallyHash}`) + + const tallyHashOnChain = await fundingRoundContract.tallyHash() + if (tallyHashOnChain !== tallyHash) { + const tx = await fundingRoundContract.publishTallyHash(tallyHash) + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to publish tally hash on chain') + } + + console.log('Published tally hash on chain') + } +} +/** + * Submit tally data to funding round contract + * @param fundingRoundContract Funding round contract + * @param batchSize Number of tally results per batch + * @param tallyData Tally file content + */ +async function submitTallyResults( + fundingRoundContract: FundingRound, + recipientTreeDepth: number, + tallyData: TallyData, + batchSize: number +) { + const startIndex = await fundingRoundContract.totalTallyResults() + const total = tallyData.results.tally.length + console.log('Uploading tally results in batches of', batchSize) + const addTallyGas = await addTallyResultsBatch( + fundingRoundContract, + recipientTreeDepth, + tallyData, + getNumber(batchSize), + getNumber(startIndex), + (processed: number) => { + console.log(`Processed ${processed} / ${total}`) + } + ) + console.log('Tally results uploaded. Gas used:', addTallyGas.toString()) +} + +/** + * Get the recipient tree depth (aka vote option tree depth) + * @param fundingRoundContract Funding round conract + * @param ethers Hardhat Ethers Helper + * @returns Recipient tree depth + */ +async function getRecipientTreeDepth( + fundingRoundContract: FundingRound, + ethers: HardhatEthersHelpers +): Promise { + const pollAddress = await fundingRoundContract.poll() + const pollContract = await ethers.getContractAt(EContracts.Poll, pollAddress) + const treeDepths = await (pollContract as BaseContract as Poll).treeDepths() + const voteOptionTreeDepth = treeDepths.voteOptionTreeDepth + return getNumber(voteOptionTreeDepth) +} + +task('publish-tally-results', 'Publish tally results') + .addParam('clrfund', 'ClrFund contract address') + .addParam('proofDir', 'The proof output directory') + .addOptionalParam( + 'batchSize', + 'The batch size to upload tally result on-chain', + 10, + types.int + ) + .addFlag('manageNonce', 'Whether to manually manage transaction nonce') + .addFlag('quiet', 'Whether to log on the console') + .setAction( + async ({ clrfund, proofDir, batchSize, manageNonce, quiet }, hre) => { + const { ethers } = hre + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + const [signer] = await ethers.getSigners() + if (!signer) { + throw new Error('Env. variable WALLET_PRIVATE_KEY not set') + } + const coordinator = manageNonce ? new NonceManager(signer) : signer + console.log('Coordinator address: ', await coordinator.getAddress()) + + await subtask.logStart() + + const fundingRoundContract = await getCurrentFundingRoundContract( + clrfund, + coordinator, + ethers + ) + console.log('Funding round contract', fundingRoundContract.target) + + const recipientTreeDepth = await getRecipientTreeDepth( + fundingRoundContract, + ethers + ) + + const tallyFile = getTalyFilePath(proofDir) + const tallyData = JSONFile.read(tallyFile) + const tallyAddress = await fundingRoundContract.tally() + + await verify({ + pollId: BigInt(tallyData.pollId), + subsidyEnabled: false, + tallyData, + maciAddress: tallyData.maci, + tallyAddress, + signer: coordinator, + quiet, + }) + + // Publish tally hash if it is not already published + await publishTallyHash(fundingRoundContract, tallyData) + + // Submit tally results to the funding round contract + // This function can be re-run from where it left off + await submitTallyResults( + fundingRoundContract, + recipientTreeDepth, + tallyData, + batchSize + ) + + const success = true + await subtask.finish(success) + } + ) diff --git a/contracts/tasks/runners/resetTally.ts b/contracts/tasks/runners/resetTally.ts new file mode 100644 index 000000000..e3283434d --- /dev/null +++ b/contracts/tasks/runners/resetTally.ts @@ -0,0 +1,51 @@ +/** + * WARNING: + * This script will create a new instance of the tally contract in the funding round contract + * + * Usage: + * hardhat resetTally --funding-round --network + * + * Note: + * 1) This script needs to be run by the coordinator + * 2) It can only be run if the funding round hasn't been finalized + */ +import { task } from 'hardhat/config' +import { getCurrentFundingRoundContract } from '../../utils/contracts' +import { Subtask } from '../helpers/Subtask' + +task('reset-tally', 'Reset the tally contract') + .addParam('clrfund', 'The clrfund contract address') + .setAction(async ({ clrfund }, hre) => { + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + let success = false + try { + const [coordinator] = await hre.ethers.getSigners() + console.log('Coordinator address: ', await coordinator.getAddress()) + + const fundingRoundContract = await getCurrentFundingRoundContract( + clrfund, + coordinator, + hre.ethers + ) + + const tx = await fundingRoundContract.resetTally() + const receipt = await tx.wait() + if (receipt?.status !== 1) { + throw new Error('Failed to reset the tally contract') + } + + subtask.logTransaction(tx) + success = true + } catch (err) { + console.error( + '\n=========================================================\nERROR:', + err, + '\n' + ) + success = false + } + + await subtask.finish(success) + }) diff --git a/contracts/utils/contracts.ts b/contracts/utils/contracts.ts index e60d11a76..45250e3fb 100644 --- a/contracts/utils/contracts.ts +++ b/contracts/utils/contracts.ts @@ -2,6 +2,7 @@ import { BaseContract, ContractTransactionResponse, TransactionResponse, + Signer, } from 'ethers' import { getEventArg } from '@clrfund/common' import { EContracts } from './types' @@ -9,7 +10,7 @@ import { DeployContractOptions, HardhatEthersHelpers, } from '@nomicfoundation/hardhat-ethers/types' -import { VkRegistry } from '../typechain-types' +import { VkRegistry, FundingRound } from '../typechain-types' import { MaciParameters } from './maciParameters' import { IVerifyingKeyStruct } from 'maci-contracts' @@ -89,4 +90,30 @@ export async function getTxFee( return receipt ? BigInt(receipt.gasUsed) * BigInt(receipt.gasPrice) : 0n } +/** + * Return the current funding round contract handle + * @param clrfund ClrFund contract address + * @param signer Signer who will interact with the funding round contract + * @param hre Hardhat runtime environment + */ +export async function getCurrentFundingRoundContract( + clrfund: string, + signer: Signer, + ethers: HardhatEthersHelpers +): Promise { + const clrfundContract = await ethers.getContractAt( + EContracts.ClrFund, + clrfund, + signer + ) + + const fundingRound = await clrfundContract.getCurrentRound() + const fundingRoundContract = await ethers.getContractAt( + EContracts.FundingRound, + fundingRound, + signer + ) + + return fundingRoundContract as BaseContract as FundingRound +} export { getEventArg } From 4b513e87de8590d26bf8a28cbd718cd37d4173cc Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 00:34:31 -0400 Subject: [PATCH 04/14] adjust tally scripts for testing purposes --- contracts/sh/runScriptTests.sh | 10 ++++++---- contracts/tasks/runners/genProofs.ts | 10 +++++++--- contracts/tasks/runners/proveOnChain.ts | 10 +++++++--- contracts/tasks/runners/publishTallyResults.ts | 10 +++++++--- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/contracts/sh/runScriptTests.sh b/contracts/sh/runScriptTests.sh index f03a47bf9..b17de7b2f 100755 --- a/contracts/sh/runScriptTests.sh +++ b/contracts/sh/runScriptTests.sh @@ -33,13 +33,15 @@ yarn hardhat contribute --network ${HARDHAT_NETWORK} yarn hardhat time-travel --seconds ${ROUND_DURATION} --network ${HARDHAT_NETWORK} -# run the tally script +# tally the votes NODE_OPTIONS="--max-old-space-size=4096" -yarn hardhat tally \ +yarn hardhat gen-proofs \ --rapidsnark ${RAPID_SNARK} \ - --batch-size 8 \ - --output-dir ${OUTPUT_DIR} \ + --proof-dir ${OUTPUT_DIR} \ + --maci-start-block 0 \ --network "${HARDHAT_NETWORK}" +yarn hardhat prove-on-chain --proof-dir ${OUTPUT_DIR} --network "${HARDHAT_NETWORK}" +yarn hardhat publish-tally-results --proof-dir ${OUTPUT_DIR} --network "${HARDHAT_NETWORK}" # finalize the round yarn hardhat finalize --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} diff --git a/contracts/tasks/runners/genProofs.ts b/contracts/tasks/runners/genProofs.ts index 55c9d5521..c9da580f3 100644 --- a/contracts/tasks/runners/genProofs.ts +++ b/contracts/tasks/runners/genProofs.ts @@ -15,7 +15,7 @@ * * Sample usage: * - * yarn hardhat tally --clrfund --proof-dir \ + * yarn hardhat gen-proofs --clrfund --proof-dir \ * --maci-tx-hash --network * */ @@ -36,9 +36,10 @@ import { getMaciStateFilePath } from '../../utils/misc' import { EContracts } from '../../utils/types' import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' +import { ContractStorage } from '../helpers/ContractStorage' task('gen-proofs', 'Generate MACI proofs offchain') - .addParam('clrfund', 'FundingRound contract address') + .addOptionalParam('clrfund', 'FundingRound contract address') .addParam('proofDir', 'The proof output directory') .addOptionalParam('maciTxHash', 'MACI creation transaction hash') .addOptionalParam( @@ -89,6 +90,7 @@ task('gen-proofs', 'Generate MACI proofs offchain') console.log('Verbose logging enabled:', !quiet) const { ethers, network } = hre + const storage = ContractStorage.getInstance() const subtask = Subtask.getInstance(hre) subtask.setHre(hre) @@ -121,8 +123,10 @@ task('gen-proofs', 'Generate MACI proofs offchain') await subtask.logStart() + const clrfundContractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) const fundingRoundContract = await getCurrentFundingRoundContract( - clrfund, + clrfundContractAddress, coordinator, ethers ) diff --git a/contracts/tasks/runners/proveOnChain.ts b/contracts/tasks/runners/proveOnChain.ts index b1eb499de..af777ed12 100644 --- a/contracts/tasks/runners/proveOnChain.ts +++ b/contracts/tasks/runners/proveOnChain.ts @@ -19,6 +19,7 @@ import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' import { EContracts } from '../../utils/types' import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' +import { ContractStorage } from '../helpers/ContractStorage' /** * Get the message processor contract address from the tally contract @@ -40,7 +41,7 @@ async function getMessageProcessorAddress( } task('prove-on-chain', 'Prove on chain with the MACI proofs') - .addParam('clrfund', 'ClrFund contract address') + .addOptionalParam('clrfund', 'ClrFund contract address') .addParam('proofDir', 'The proof output directory') .addFlag('manageNonce', 'Whether to manually manage transaction nonce') .addOptionalParam( @@ -52,7 +53,8 @@ task('prove-on-chain', 'Prove on chain with the MACI proofs') .setAction(async ({ clrfund, quiet, manageNonce, proofDir }, hre) => { console.log('Verbose logging enabled:', !quiet) - const { ethers } = hre + const { ethers, network } = hre + const storage = ContractStorage.getInstance() const subtask = Subtask.getInstance(hre) subtask.setHre(hre) @@ -67,8 +69,10 @@ task('prove-on-chain', 'Prove on chain with the MACI proofs') await subtask.logStart() + const clrfundContractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) const fundingRoundContract = await getCurrentFundingRoundContract( - clrfund, + clrfundContractAddress, coordinator, ethers ) diff --git a/contracts/tasks/runners/publishTallyResults.ts b/contracts/tasks/runners/publishTallyResults.ts index 46f535f8b..d2fef14f1 100644 --- a/contracts/tasks/runners/publishTallyResults.ts +++ b/contracts/tasks/runners/publishTallyResults.ts @@ -24,6 +24,7 @@ import { EContracts } from '../../utils/types' import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' import { getTalyFilePath } from '../../utils/misc' +import { ContractStorage } from '../helpers/ContractStorage' /** * Publish the tally IPFS hash on chain if it's not already published @@ -94,7 +95,7 @@ async function getRecipientTreeDepth( } task('publish-tally-results', 'Publish tally results') - .addParam('clrfund', 'ClrFund contract address') + .addOptionalParam('clrfund', 'ClrFund contract address') .addParam('proofDir', 'The proof output directory') .addOptionalParam( 'batchSize', @@ -106,7 +107,8 @@ task('publish-tally-results', 'Publish tally results') .addFlag('quiet', 'Whether to log on the console') .setAction( async ({ clrfund, proofDir, batchSize, manageNonce, quiet }, hre) => { - const { ethers } = hre + const { ethers, network } = hre + const storage = ContractStorage.getInstance() const subtask = Subtask.getInstance(hre) subtask.setHre(hre) @@ -119,8 +121,10 @@ task('publish-tally-results', 'Publish tally results') await subtask.logStart() + const clrfundContractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) const fundingRoundContract = await getCurrentFundingRoundContract( - clrfund, + clrfundContractAddress, coordinator, ethers ) From 2a8fa9097f2043c3063d6393c136ae4fbf163f9d Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 00:52:18 -0400 Subject: [PATCH 05/14] add missing start --- contracts/tasks/runners/resetTally.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/tasks/runners/resetTally.ts b/contracts/tasks/runners/resetTally.ts index e3283434d..adc9ebb4e 100644 --- a/contracts/tasks/runners/resetTally.ts +++ b/contracts/tasks/runners/resetTally.ts @@ -21,6 +21,8 @@ task('reset-tally', 'Reset the tally contract') let success = false try { + await subtask.logStart() + const [coordinator] = await hre.ethers.getSigners() console.log('Coordinator address: ', await coordinator.getAddress()) From 83023b7c5a0a1429ed4c076fd92cc825c2219c49 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 02:01:15 -0400 Subject: [PATCH 06/14] update documentation with new commands for votes tallying --- docs/tally-verify.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/tally-verify.md b/docs/tally-verify.md index b2edd2a50..a2d2d66b0 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -20,22 +20,34 @@ WALLET_MNEMONIC= WALLET_PRIVATE_KEY ``` -Decrypt messages and tally the votes: +Decrypt messages, tally the votes and generate proofs: ``` -yarn hardhat tally --rapidsnark {RAPID_SNARK} --output-dir {OUTPUT_DIR} --network {network} +yarn hardhat gen-proofs --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-tx-hash {MACI_CREATION_TRANSACTION_HASH} --proof-dir {OUTPUT_DIR} --rapidsnark {RAPID_SNARK} --network {network} ``` You only need to provide `--rapidsnark` if you are running the `tally` command on an intel chip. +If `gen-proofs` failed, you can rerun the command with the same parameters. If the maci-state.json file has been created, you can skip fetching MACI logs by providing the MACI state file as follow: -If there's error and the tally task was stopped prematurely, it can be resumed by passing 2 additional parameters, '--tally-file' and/or '--maci-state-file', if the files were generated. +``` +yarn hardhat gen-proofs --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-state-file {MACI_STATE_FILE_PATH} --proof-dir {OUTPUT_DIR} --rapidsnark {RAPID_SNARK} --network {network} +``` + + +** Make a backup of the {OUTPUT_DIR} before continuing to the next step ** + +Upload the proofs on chain: ``` -# for rerun -yarn hardhat tally --maci-state-file {maci-state.json} --tally-file {tally.json} --output-dir {OUTPUT_DIR} --network {network} +yarn hardhat prove-on-chain --clrfund {CLRFUND_CONTRACT_ADDRESS} --proof-dir {OUTPUT_DIR} --network {network} +yarn hardhat publish-tally-results --clrfund {CLRFUND_CONTRACT_ADDRESS} --proof-dir {OUTPUT_DIR} --network localhost ``` -Result will be saved to `tally.json` file, which must then be published via IPFS. +If there's error running `prove-on-chain` or `publish-tally-resuls`, simply rerun the commands with the same parameters. + + + +Result will be saved to `{OUTPUT_DIR}/tally.json` file, which must then be published via IPFS. **Using [command line](https://docs.ipfs.tech/reference/kubo/cli/#ipfs)** From 214e3958ebbebdc1e274d1f59c8ebae6fbf6e389 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 02:35:08 -0400 Subject: [PATCH 07/14] upload smaller batches to avoid socket error on linux --- contracts/tasks/runners/publishTallyResults.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tasks/runners/publishTallyResults.ts b/contracts/tasks/runners/publishTallyResults.ts index d2fef14f1..91699fa94 100644 --- a/contracts/tasks/runners/publishTallyResults.ts +++ b/contracts/tasks/runners/publishTallyResults.ts @@ -100,7 +100,7 @@ task('publish-tally-results', 'Publish tally results') .addOptionalParam( 'batchSize', 'The batch size to upload tally result on-chain', - 10, + 8, types.int ) .addFlag('manageNonce', 'Whether to manually manage transaction nonce') From 8e3a1a25d179f9d9dae220dd1dbab0010d2b9e5a Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 09:56:24 -0400 Subject: [PATCH 08/14] add --round-address optino --- contracts/tasks/runners/claim.ts | 86 +++++++++++++++++--------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/contracts/tasks/runners/claim.ts b/contracts/tasks/runners/claim.ts index acce6d3a2..bf90576e2 100644 --- a/contracts/tasks/runners/claim.ts +++ b/contracts/tasks/runners/claim.ts @@ -18,6 +18,7 @@ import { EContracts } from '../../utils/types' import { ContractStorage } from '../helpers/ContractStorage' task('claim', 'Claim funnds for test recipients') + .addOptionalParam('roundAddress', 'Funding round contract address') .addParam( 'recipient', 'The recipient index in the tally file', @@ -25,52 +26,55 @@ task('claim', 'Claim funnds for test recipients') types.int ) .addParam('tallyFile', 'The tally file') - .setAction(async ({ tallyFile, recipient }, { ethers, network }) => { - if (!isPathExist(tallyFile)) { - throw new Error(`Path ${tallyFile} does not exist`) - } + .setAction( + async ({ tallyFile, recipient, roundAddress }, { ethers, network }) => { + if (!isPathExist(tallyFile)) { + throw new Error(`Path ${tallyFile} does not exist`) + } - if (recipient <= 0) { - throw new Error('Recipient must be greater than 0') - } + if (recipient <= 0) { + throw new Error('Recipient must be greater than 0') + } - const storage = ContractStorage.getInstance() - const fundingRound = storage.mustGetAddress( - EContracts.FundingRound, - network.name - ) + const storage = ContractStorage.getInstance() + const fundingRound = + roundAddress ?? + storage.mustGetAddress(EContracts.FundingRound, network.name) - const tally = JSONFile.read(tallyFile) + const tally = JSONFile.read(tallyFile) - const fundingRoundContract = await ethers.getContractAt( - EContracts.FundingRound, - fundingRound - ) + const fundingRoundContract = await ethers.getContractAt( + EContracts.FundingRound, + fundingRound + ) - const recipientStatus = await fundingRoundContract.recipients(recipient) - if (recipientStatus.fundsClaimed) { - throw new Error(`Recipient already claimed funds`) - } + const recipientStatus = await fundingRoundContract.recipients(recipient) + if (recipientStatus.fundsClaimed) { + throw new Error(`Recipient already claimed funds`) + } - const pollAddress = await fundingRoundContract.poll() - console.log('pollAddress', pollAddress) + const pollAddress = await fundingRoundContract.poll() + console.log('pollAddress', pollAddress) - const poll = await ethers.getContractAt(EContracts.Poll, pollAddress) - const treeDepths = await poll.treeDepths() - const recipientTreeDepth = getNumber(treeDepths.voteOptionTreeDepth) + const poll = await ethers.getContractAt(EContracts.Poll, pollAddress) + const treeDepths = await poll.treeDepths() + const recipientTreeDepth = getNumber(treeDepths.voteOptionTreeDepth) - // Claim funds - const recipientClaimData = getRecipientClaimData( - recipient, - recipientTreeDepth, - tally - ) - const claimTx = await fundingRoundContract.claimFunds(...recipientClaimData) - const claimedAmount = await getEventArg( - claimTx, - fundingRoundContract, - 'FundsClaimed', - '_amount' - ) - console.log(`Recipient ${recipient} claimed ${claimedAmount} tokens.`) - }) + // Claim funds + const recipientClaimData = getRecipientClaimData( + recipient, + recipientTreeDepth, + tally + ) + const claimTx = await fundingRoundContract.claimFunds( + ...recipientClaimData + ) + const claimedAmount = await getEventArg( + claimTx, + fundingRoundContract, + 'FundsClaimed', + '_amount' + ) + console.log(`Recipient ${recipient} claimed ${claimedAmount} tokens.`) + } + ) From 7b705835da7c8e25b13cd7b3aa80e7de192d8ca5 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 20:18:57 -0400 Subject: [PATCH 09/14] use consistent network name to get etherscan api key --- contracts/hardhat.config.ts | 2 +- contracts/utils/providers/EtherscanProvider.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index e9fcb6931..0a30c06b2 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -59,7 +59,7 @@ export default { 'https://sepolia-rollup.arbitrum.io/rpc', accounts, }, - optimism: { + optimisticEthereum: { url: process.env.JSONRPC_HTTP_URL || 'https://mainnet.optimism.io', accounts, }, diff --git a/contracts/utils/providers/EtherscanProvider.ts b/contracts/utils/providers/EtherscanProvider.ts index 5a35ac557..efa0cadb0 100644 --- a/contracts/utils/providers/EtherscanProvider.ts +++ b/contracts/utils/providers/EtherscanProvider.ts @@ -6,7 +6,7 @@ const EtherscanApiUrl: Record = { arbitrum: 'https://api.arbiscan.io', 'arbitrum-goerli': 'https://api-goerli.arbiscan.io', 'arbitrum-sepolia': 'https://api-sepolia.arbiscan.io', - optimism: 'https://api-optimistic.etherscan.io', + optimisticEthereum: 'https://api-optimistic.etherscan.io', 'optimism-sepolia': 'https://api-sepolia-optimistic.etherscan.io', } From e7df7a8a0311f5853b567b13551f97945e7f246c Mon Sep 17 00:00:00 2001 From: yuetloo Date: Wed, 3 Apr 2024 20:56:38 -0400 Subject: [PATCH 10/14] map hardhat network name to etherscan name to retrieve the etherscan api key --- contracts/hardhat.config.ts | 2 +- contracts/tasks/runners/exportRound.ts | 12 +++++++++++- contracts/utils/providers/EtherscanProvider.ts | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 0a30c06b2..e9fcb6931 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -59,7 +59,7 @@ export default { 'https://sepolia-rollup.arbitrum.io/rpc', accounts, }, - optimisticEthereum: { + optimism: { url: process.env.JSONRPC_HTTP_URL || 'https://mainnet.optimism.io', accounts, }, diff --git a/contracts/tasks/runners/exportRound.ts b/contracts/tasks/runners/exportRound.ts index 4eadf9818..da14269c1 100644 --- a/contracts/tasks/runners/exportRound.ts +++ b/contracts/tasks/runners/exportRound.ts @@ -29,6 +29,11 @@ type RoundListEntry = { isFinalized: boolean } +// Map hardhat network name to the etherscan api network name in the hardhat.config +const ETHERSCAN_NETWORKS: Record = { + optimism: 'optimisticEthereum', +} + const toUndefined = () => undefined const toString = (val: bigint) => BigInt(val).toString() const toZero = () => BigInt(0) @@ -41,13 +46,18 @@ function roundListFileName(directory: string): string { return path.join(directory, 'rounds.json') } +function toEtherscanNetworkName(network: string): string { + return ETHERSCAN_NETWORKS[network] ?? network +} + function getEtherscanApiKey(config: any, network: string): string { let etherscanApiKey = '' if (config.etherscan?.apiKey) { if (typeof config.etherscan.apiKey === 'string') { etherscanApiKey = config.etherscan.apiKey } else { - etherscanApiKey = config.etherscan.apiKey[network] + const etherscanNetwork = toEtherscanNetworkName(network) + etherscanApiKey = config.etherscan.apiKey[etherscanNetwork] } } diff --git a/contracts/utils/providers/EtherscanProvider.ts b/contracts/utils/providers/EtherscanProvider.ts index efa0cadb0..5a35ac557 100644 --- a/contracts/utils/providers/EtherscanProvider.ts +++ b/contracts/utils/providers/EtherscanProvider.ts @@ -6,7 +6,7 @@ const EtherscanApiUrl: Record = { arbitrum: 'https://api.arbiscan.io', 'arbitrum-goerli': 'https://api-goerli.arbiscan.io', 'arbitrum-sepolia': 'https://api-sepolia.arbiscan.io', - optimisticEthereum: 'https://api-optimistic.etherscan.io', + optimism: 'https://api-optimistic.etherscan.io', 'optimism-sepolia': 'https://api-sepolia-optimistic.etherscan.io', } From 3df43e0763d01224e86ce9376a7cc948ce1203b8 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Thu, 4 Apr 2024 14:03:12 -0400 Subject: [PATCH 11/14] remove tally script --- .github/workflows/finalize-round.yml | 18 +- contracts/.env.example | 2 +- contracts/tasks/index.ts | 1 - contracts/tasks/runners/tally.ts | 353 --------------------------- docs/tally-verify.md | 2 +- 5 files changed, 15 insertions(+), 361 deletions(-) delete mode 100644 contracts/tasks/runners/tally.ts diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index adfcdfdfc..a1d903269 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -79,14 +79,22 @@ jobs: export BLOCKS_PER_BATCH=${{ github.event.inputs.blocks_per_batch }} export RAPID_SNARK="$GITHUB_WORKSPACE/rapidsnark/package/bin/prover" export CIRCUIT_DIRECTORY=$GITHUB_WORKSPACE/params + export PROOF_OUTPUT_DIR=./proof_output # tally and finalize cd monorepo/contracts - mkdir -p proof_output - yarn hardhat tally --clrfund "${CLRFUND_ADDRESS}" --network "${NETWORK}" \ - --rapidsnark ${RAPID_SNARK} \ - --circuit-directory ${CIRCUIT_DIRECTORY} \ + mkdir -p ${PROOF_OUTPUT_DIR} + yarn gen-proofs --clrfund "${CLRFUND_ADDRESS}" \ --blocks-per-batch ${BLOCKS_PER_BATCH} \ - --maci-tx-hash "${MACI_TX_HASH}" --output-dir "./proof_output" + --rapidsnark ${RAPID_SNARK} \ + --maci-tx-hash "${MACI_TX_HASH}" \ + --proof-dir ${PROOF_OUTPUT_DIR} \ + --network "${NETWORK}" + yarn hardhat prove-on-chain --clrfund "${CLRFUND_ADDRESS}" \ + --proof-dir ${PROOF_OUTPUT_DIR} \ + --network "${NETWORK}" + yarn hardhat publish-tally-results --clrfund "${CLRFUND_ADDRESS}" \ + --proof-dir ${PROOF_OUTPUT_DIR} \ + --network "${NETWORK}" curl --location --request POST 'https://api.pinata.cloud/pinning/pinFileToIPFS' \ --header "Authorization: Bearer ${{ secrets.PINATA_JWT }}" \ --form 'file=@"./proof_output/tally.json"' diff --git a/contracts/.env.example b/contracts/.env.example index 5ace336a7..bdede7da1 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -6,7 +6,7 @@ JSONRPC_HTTP_URL=https://eth-goerli.alchemyapi.io/v2/ADD_API_KEY WALLET_MNEMONIC= WALLET_PRIVATE_KEY= -# The coordinator MACI private key, required by the tally script +# The coordinator MACI private key, required by the gen-proofs script COORDINATOR_MACISK= # API key used to verify contracts on arbitrum chain (including testnet) diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 934d30612..227256785 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -10,7 +10,6 @@ import './runners/setMaciParameters' import './runners/setToken' import './runners/setUserRegistry' import './runners/setStorageRoot' -import './runners/tally' import './runners/finalize' import './runners/claim' import './runners/cancel' diff --git a/contracts/tasks/runners/tally.ts b/contracts/tasks/runners/tally.ts deleted file mode 100644 index eb135b3e8..000000000 --- a/contracts/tasks/runners/tally.ts +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Script for tallying votes which involves fetching MACI logs, generating proofs, - * and proving on chain - * - * This script can be rerun by passing in --maci-state-file and --tally-file - * If the --maci-state-file is passed, it will skip MACI log fetching - * If the --tally-file is passed, it will skip MACI log fetching and proof generation - * - * Make sure to set the following environment variables in the .env file - * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC - * - coordinator's wallet private key to interact with contracts - * 2) COORDINATOR_MACISK - coordinator's MACI private key to decrypt messages - * - * Sample usage: - * - * yarn hardhat tally --clrfund --maci-tx-hash --network - * - * To rerun: - * - * yarn hardhat tally --clrfund --maci-state-file \ - * --tally-file --network - */ -import { BaseContract, getNumber, Signer, NonceManager } from 'ethers' -import { task, types } from 'hardhat/config' - -import { - DEFAULT_SR_QUEUE_OPS, - DEFAULT_GET_LOG_BATCH_SIZE, -} from '../../utils/constants' -import { getIpfsHash } from '../../utils/ipfs' -import { JSONFile } from '../../utils/JSONFile' -import { - getGenProofArgs, - genProofs, - proveOnChain, - addTallyResultsBatch, - mergeMaciSubtrees, - genLocalState, - TallyData, -} from '../../utils/maci' -import { getMaciStateFilePath, getDirname } from '../../utils/misc' -import { FundingRound, Poll, Tally } from '../../typechain-types' -import { HardhatEthersHelpers } from '@nomicfoundation/hardhat-ethers/types' -import { EContracts } from '../../utils/types' -import { ContractStorage } from '../helpers/ContractStorage' -import { Subtask } from '../helpers/Subtask' - -/** - * Publish the tally IPFS hash on chain if it's not already published - * @param fundingRoundContract Funding round contract - * @param tallyData Tally data - */ -async function publishTallyHash( - fundingRoundContract: FundingRound, - tallyData: TallyData -) { - const tallyHash = await getIpfsHash(tallyData) - console.log(`Tally hash is ${tallyHash}`) - - const tallyHashOnChain = await fundingRoundContract.tallyHash() - if (tallyHashOnChain !== tallyHash) { - const tx = await fundingRoundContract.publishTallyHash(tallyHash) - const receipt = await tx.wait() - if (receipt?.status !== 1) { - throw new Error('Failed to publish tally hash on chain') - } - - console.log('Published tally hash on chain') - } -} -/** - * Submit tally data to funding round contract - * @param fundingRoundContract Funding round contract - * @param batchSize Number of tally results per batch - * @param tallyData Tally file content - */ -async function submitTallyResults( - fundingRoundContract: FundingRound, - recipientTreeDepth: number, - tallyData: TallyData, - batchSize: number -) { - const startIndex = await fundingRoundContract.totalTallyResults() - const total = tallyData.results.tally.length - console.log('Uploading tally results in batches of', batchSize) - const addTallyGas = await addTallyResultsBatch( - fundingRoundContract, - recipientTreeDepth, - tallyData, - getNumber(batchSize), - getNumber(startIndex), - (processed: number) => { - console.log(`Processed ${processed} / ${total}`) - } - ) - console.log('Tally results uploaded. Gas used:', addTallyGas.toString()) -} - -/** - * Return the current funding round contract handle - * @param clrfund ClrFund contract address - * @param coordinator Signer who will interact with the funding round contract - * @param hre Hardhat runtime environment - */ -async function getFundingRound( - clrfund: string, - coordinator: Signer, - ethers: HardhatEthersHelpers -): Promise { - const clrfundContract = await ethers.getContractAt( - EContracts.ClrFund, - clrfund, - coordinator - ) - - const fundingRound = await clrfundContract.getCurrentRound() - const fundingRoundContract = await ethers.getContractAt( - EContracts.FundingRound, - fundingRound, - coordinator - ) - - return fundingRoundContract as BaseContract as FundingRound -} - -/** - * Get the recipient tree depth (aka vote option tree depth) - * @param fundingRoundContract Funding round conract - * @param ethers Hardhat Ethers Helper - * @returns Recipient tree depth - */ -async function getRecipientTreeDepth( - fundingRoundContract: FundingRound, - ethers: HardhatEthersHelpers -): Promise { - const pollAddress = await fundingRoundContract.poll() - const pollContract = await ethers.getContractAt(EContracts.Poll, pollAddress) - const treeDepths = await (pollContract as BaseContract as Poll).treeDepths() - const voteOptionTreeDepth = treeDepths.voteOptionTreeDepth - return getNumber(voteOptionTreeDepth) -} - -/** - * Get the message processor contract address from the tally contract - * @param tallyAddress Tally contract address - * @param ethers Hardhat ethers helper - * @returns Message processor contract address - */ -async function getMessageProcessorAddress( - tallyAddress: string, - ethers: HardhatEthersHelpers -): Promise { - const tallyContract = (await ethers.getContractAt( - EContracts.Tally, - tallyAddress - )) as BaseContract as Tally - - const messageProcessorAddress = await tallyContract.messageProcessor() - return messageProcessorAddress -} - -task('tally', 'Tally votes') - .addOptionalParam('clrfund', 'ClrFund contract address') - .addOptionalParam('maciTxHash', 'MACI creation transaction hash') - .addOptionalParam('maciStateFile', 'MACI state file') - .addFlag('manageNonce', 'Whether to manually manage transaction nonce') - .addOptionalParam('tallyFile', 'The tally file path') - .addOptionalParam( - 'batchSize', - 'The batch size to upload tally result on-chain', - 10, - types.int - ) - .addParam('outputDir', 'The proof output directory', './proof_output') - .addOptionalParam('rapidsnark', 'The rapidsnark prover path') - .addOptionalParam( - 'numQueueOps', - 'The number of operations for MACI tree merging', - getNumber(DEFAULT_SR_QUEUE_OPS), - types.int - ) - .addOptionalParam( - 'blocksPerBatch', - 'The number of blocks per batch of logs to fetch on-chain', - DEFAULT_GET_LOG_BATCH_SIZE, - types.int - ) - .addOptionalParam('sleep', 'Number of seconds to sleep between log fetch') - .addOptionalParam( - 'quiet', - 'Whether to disable verbose logging', - false, - types.boolean - ) - .setAction( - async ( - { - clrfund, - maciTxHash, - quiet, - maciStateFile, - outputDir, - numQueueOps, - tallyFile, - blocksPerBatch, - rapidsnark, - sleep, - batchSize, - manageNonce, - }, - hre - ) => { - console.log('Verbose logging enabled:', !quiet) - - const { ethers, network } = hre - const storage = ContractStorage.getInstance() - const subtask = Subtask.getInstance(hre) - subtask.setHre(hre) - - const [coordinatorSigner] = await ethers.getSigners() - if (!coordinatorSigner) { - throw new Error('Env. variable WALLET_PRIVATE_KEY not set') - } - const coordinator = manageNonce - ? new NonceManager(coordinatorSigner) - : coordinatorSigner - console.log('Coordinator address: ', await coordinator.getAddress()) - - const coordinatorMacisk = process.env.COORDINATOR_MACISK - if (!coordinatorMacisk) { - throw new Error('Env. variable COORDINATOR_MACISK not set') - } - - const circuit = subtask.getConfigField( - EContracts.VkRegistry, - 'circuit' - ) - const circuitDirectory = subtask.getConfigField( - EContracts.VkRegistry, - 'paramsDirectory' - ) - - await subtask.logStart() - - const clrfundContractAddress = - clrfund ?? storage.mustGetAddress(EContracts.ClrFund, network.name) - const fundingRoundContract = await getFundingRound( - clrfundContractAddress, - coordinator, - ethers - ) - console.log('Funding round contract', fundingRoundContract.target) - - const recipientTreeDepth = await getRecipientTreeDepth( - fundingRoundContract, - ethers - ) - - const pollId = await fundingRoundContract.pollId() - console.log('PollId', pollId) - - const maciAddress = await fundingRoundContract.maci() - const maciTransactionHash = - maciTxHash ?? storage.getTxHash(maciAddress, network.name) - console.log('MACI address', maciAddress) - - const tallyAddress = await fundingRoundContract.tally() - const messageProcessorAddress = await getMessageProcessorAddress( - tallyAddress, - ethers - ) - - const providerUrl = (network.config as any).url - - const outputPath = maciStateFile - ? maciStateFile - : getMaciStateFilePath(outputDir) - - await mergeMaciSubtrees({ - maciAddress, - pollId, - numQueueOps, - signer: coordinator, - quiet, - }) - - let tallyFilePath: string = tallyFile || '' - if (!tallyFile) { - if (!maciStateFile) { - await genLocalState({ - quiet, - outputPath, - pollId, - maciContractAddress: maciAddress, - coordinatorPrivateKey: coordinatorMacisk, - ethereumProvider: providerUrl, - transactionHash: maciTransactionHash, - blockPerBatch: blocksPerBatch, - signer: coordinator, - sleep, - }) - } - - const genProofArgs = getGenProofArgs({ - maciAddress, - pollId, - coordinatorMacisk, - rapidsnark, - circuitType: circuit, - circuitDirectory, - outputDir, - blocksPerBatch: getNumber(blocksPerBatch), - maciTxHash: maciTransactionHash, - maciStateFile: outputPath, - signer: coordinator, - quiet, - }) - await genProofs(genProofArgs) - tallyFilePath = genProofArgs.tallyFile - } - - const tally = JSONFile.read(tallyFilePath) as TallyData - const proofDir = getDirname(tallyFilePath) - console.log('Proof directory', proofDir) - - // proveOnChain if not already processed - await proveOnChain({ - pollId, - proofDir, - subsidyEnabled: false, - maciAddress, - messageProcessorAddress, - tallyAddress, - signer: coordinator, - quiet, - }) - - // Publish tally hash if it is not already published - await publishTallyHash(fundingRoundContract, tally) - - // Submit tally results to the funding round contract - // This function can be re-run from where it left off - await submitTallyResults( - fundingRoundContract, - recipientTreeDepth, - tally, - batchSize - ) - - const success = true - await subtask.finish(success) - } - ) diff --git a/docs/tally-verify.md b/docs/tally-verify.md index a2d2d66b0..61139360e 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -34,7 +34,7 @@ yarn hardhat gen-proofs --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-state-file { ``` -** Make a backup of the {OUTPUT_DIR} before continuing to the next step ** +**Make a backup of the {OUTPUT_DIR} before continuing to the next step** Upload the proofs on chain: From ac777d71bafb6eb60573ee49909d97cff16e0498 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Fri, 5 Apr 2024 13:45:55 -0400 Subject: [PATCH 12/14] add tally script back --- .github/workflows/finalize-round.yml | 18 ++-- contracts/tasks/index.ts | 1 + contracts/tasks/runners/genProofs.ts | 115 +++++++++++++------------ contracts/tasks/runners/tally.ts | 122 +++++++++++++++++++++++++++ contracts/utils/maci.ts | 5 +- contracts/utils/misc.ts | 9 +- 6 files changed, 195 insertions(+), 75 deletions(-) create mode 100644 contracts/tasks/runners/tally.ts diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index a1d903269..d0af1ab9f 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -79,22 +79,14 @@ jobs: export BLOCKS_PER_BATCH=${{ github.event.inputs.blocks_per_batch }} export RAPID_SNARK="$GITHUB_WORKSPACE/rapidsnark/package/bin/prover" export CIRCUIT_DIRECTORY=$GITHUB_WORKSPACE/params - export PROOF_OUTPUT_DIR=./proof_output # tally and finalize cd monorepo/contracts - mkdir -p ${PROOF_OUTPUT_DIR} - yarn gen-proofs --clrfund "${CLRFUND_ADDRESS}" \ - --blocks-per-batch ${BLOCKS_PER_BATCH} \ + mkdir -p proof_output + yarn hardhat tally --clrfund "${CLRFUND_ADDRESS}" --network "${NETWORK}" \ --rapidsnark ${RAPID_SNARK} \ - --maci-tx-hash "${MACI_TX_HASH}" \ - --proof-dir ${PROOF_OUTPUT_DIR} \ - --network "${NETWORK}" - yarn hardhat prove-on-chain --clrfund "${CLRFUND_ADDRESS}" \ - --proof-dir ${PROOF_OUTPUT_DIR} \ - --network "${NETWORK}" - yarn hardhat publish-tally-results --clrfund "${CLRFUND_ADDRESS}" \ - --proof-dir ${PROOF_OUTPUT_DIR} \ - --network "${NETWORK}" + --params-dir ${CIRCUIT_DIRECTORY} \ + --blocks-per-batch ${BLOCKS_PER_BATCH} \ + --maci-tx-hash "${MACI_TX_HASH}" --output-dir "./proof_output" curl --location --request POST 'https://api.pinata.cloud/pinning/pinFileToIPFS' \ --header "Authorization: Bearer ${{ secrets.PINATA_JWT }}" \ --form 'file=@"./proof_output/tally.json"' diff --git a/contracts/tasks/index.ts b/contracts/tasks/index.ts index 227256785..934d30612 100644 --- a/contracts/tasks/index.ts +++ b/contracts/tasks/index.ts @@ -10,6 +10,7 @@ import './runners/setMaciParameters' import './runners/setToken' import './runners/setUserRegistry' import './runners/setStorageRoot' +import './runners/tally' import './runners/finalize' import './runners/claim' import './runners/cancel' diff --git a/contracts/tasks/runners/genProofs.ts b/contracts/tasks/runners/genProofs.ts index c9da580f3..a0656e650 100644 --- a/contracts/tasks/runners/genProofs.ts +++ b/contracts/tasks/runners/genProofs.ts @@ -1,13 +1,6 @@ /** * Script for generating MACI proofs * - * Pass --maci-tx-hash if this is the first time running the script and you - * want to get MACI logs starting from the block as recorded in the MACI creation - * transaction hash - * - * Pass --maci-state-file if you have previously ran the script and have - * the maci-state file (maci-state.json) - * * Make sure to set the following environment variables in the .env file * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC * - coordinator's wallet private key to interact with contracts @@ -32,11 +25,17 @@ import { genLocalState, mergeMaciSubtrees, } from '../../utils/maci' -import { getMaciStateFilePath } from '../../utils/misc' +import { + getMaciStateFilePath, + getTalyFilePath, + isPathExist, + makeDirectory, +} from '../../utils/misc' import { EContracts } from '../../utils/types' import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' import { ContractStorage } from '../helpers/ContractStorage' +import { DEFAULT_CIRCUIT } from '../../utils/circuits' task('gen-proofs', 'Generate MACI proofs offchain') .addOptionalParam('clrfund', 'FundingRound contract address') @@ -48,9 +47,9 @@ task('gen-proofs', 'Generate MACI proofs offchain') undefined, types.int ) - .addOptionalParam('maciStateFile', 'MACI state file') .addFlag('manageNonce', 'Whether to manually manage transaction nonce') .addOptionalParam('rapidsnark', 'The rapidsnark prover path') + .addParam('paramsDir', 'The circuit zkeys directory', './params') .addOptionalParam( 'blocksPerBatch', 'The number of blocks per batch of logs to fetch on-chain', @@ -77,8 +76,8 @@ task('gen-proofs', 'Generate MACI proofs offchain') maciStartBlock, maciTxHash, quiet, - maciStateFile, proofDir, + paramsDir, blocksPerBatch, rapidsnark, numQueueOps, @@ -94,10 +93,6 @@ task('gen-proofs', 'Generate MACI proofs offchain') const subtask = Subtask.getInstance(hre) subtask.setHre(hre) - if (!maciStateFile && !maciTxHash && maciStartBlock == undefined) { - throw new Error('Please provide --maci-start-block or --maci-tx-hash') - } - const [coordinatorSigner] = await ethers.getSigners() if (!coordinatorSigner) { throw new Error('Env. variable WALLET_PRIVATE_KEY not set') @@ -112,14 +107,15 @@ task('gen-proofs', 'Generate MACI proofs offchain') throw new Error('Env. variable COORDINATOR_MACISK not set') } - const circuit = subtask.getConfigField( - EContracts.VkRegistry, - 'circuit' - ) - const circuitDirectory = subtask.getConfigField( - EContracts.VkRegistry, - 'paramsDirectory' - ) + const circuit = + subtask.tryGetConfigField(EContracts.VkRegistry, 'circuit') || + DEFAULT_CIRCUIT + + const circuitDirectory = + subtask.tryGetConfigField( + EContracts.VkRegistry, + 'paramsDirectory' + ) || paramsDir await subtask.logStart() @@ -136,9 +132,6 @@ task('gen-proofs', 'Generate MACI proofs offchain') console.log('PollId', pollId) const maciAddress = await fundingRoundContract.maci() - - const providerUrl = (network.config as any).url - await mergeMaciSubtrees({ maciAddress, pollId, @@ -147,42 +140,54 @@ task('gen-proofs', 'Generate MACI proofs offchain') quiet, }) - const maciStateFilePath = maciStateFile - ? maciStateFile - : getMaciStateFilePath(proofDir) + if (!isPathExist(proofDir)) { + makeDirectory(proofDir) + } + + const tallyFile = getTalyFilePath(proofDir) + const maciStateFile = getMaciStateFilePath(proofDir) + const providerUrl = (network.config as any).url - if (!maciStateFile) { - await genLocalState({ - quiet, - outputPath: maciStateFilePath, + if (!isPathExist(tallyFile)) { + if (!isPathExist(maciStateFile)) { + if (!maciTxHash && maciStartBlock == null) { + throw new Error( + 'Please provide a value for --maci-tx-hash or --maci-start-block' + ) + } + + await genLocalState({ + quiet, + outputPath: maciStateFile, + pollId, + maciContractAddress: maciAddress, + coordinatorPrivateKey: coordinatorMacisk, + ethereumProvider: providerUrl, + transactionHash: maciTxHash, + startBlock: maciStartBlock, + blockPerBatch: blocksPerBatch, + signer: coordinator, + sleep, + }) + } + + const genProofArgs = getGenProofArgs({ + maciAddress, pollId, - maciContractAddress: maciAddress, - coordinatorPrivateKey: coordinatorMacisk, - ethereumProvider: providerUrl, - transactionHash: maciTxHash, - startBlock: maciStartBlock, - blockPerBatch: blocksPerBatch, + coordinatorMacisk, + rapidsnark, + circuitType: circuit, + circuitDirectory, + outputDir: proofDir, + blocksPerBatch: getNumber(blocksPerBatch), + maciStateFile, + tallyFile, signer: coordinator, - sleep, + quiet, }) + await genProofs(genProofArgs) } - const genProofArgs = getGenProofArgs({ - maciAddress, - pollId, - coordinatorMacisk, - rapidsnark, - circuitType: circuit, - circuitDirectory, - outputDir: proofDir, - blocksPerBatch: getNumber(blocksPerBatch), - maciTxHash, - maciStateFile: maciStateFilePath, - signer: coordinator, - quiet, - }) - await genProofs(genProofArgs) - const success = true await subtask.finish(success) } diff --git a/contracts/tasks/runners/tally.ts b/contracts/tasks/runners/tally.ts new file mode 100644 index 000000000..f524dda72 --- /dev/null +++ b/contracts/tasks/runners/tally.ts @@ -0,0 +1,122 @@ +/** + * Script for tallying votes which involves fetching MACI logs, generating proofs, + * proving on chain, and uploading tally results on chain + * + * Sample usage: + * yarn hardhat tally --clrfund --maci-tx-hash --network + * + * This script can be re-run with the same input parameters + */ +import { getNumber } from 'ethers' +import { task, types } from 'hardhat/config' + +import { + DEFAULT_SR_QUEUE_OPS, + DEFAULT_GET_LOG_BATCH_SIZE, +} from '../../utils/constants' +import { EContracts } from '../../utils/types' +import { ContractStorage } from '../helpers/ContractStorage' +import { Subtask } from '../helpers/Subtask' + +task('tally', 'Tally votes') + .addOptionalParam('clrfund', 'ClrFund contract address') + .addOptionalParam('maciTxHash', 'MACI creation transaction hash') + .addOptionalParam( + 'maciStartBlock', + 'MACI creation block', + undefined, + types.int + ) + .addFlag('manageNonce', 'Whether to manually manage transaction nonce') + .addOptionalParam( + 'batchSize', + 'The batch size to upload tally result on-chain', + 8, + types.int + ) + .addParam('proofDir', 'The proof output directory', './proof_output') + .addParam('paramsDir', 'The circuit zkeys directory', './params') + .addOptionalParam('rapidsnark', 'The rapidsnark prover path') + .addOptionalParam( + 'numQueueOps', + 'The number of operations for MACI tree merging', + getNumber(DEFAULT_SR_QUEUE_OPS), + types.int + ) + .addOptionalParam( + 'blocksPerBatch', + 'The number of blocks per batch of logs to fetch on-chain', + DEFAULT_GET_LOG_BATCH_SIZE, + types.int + ) + .addOptionalParam('sleep', 'Number of seconds to sleep between log fetch') + .addOptionalParam( + 'quiet', + 'Whether to disable verbose logging', + false, + types.boolean + ) + .setAction( + async ( + { + clrfund, + maciTxHash, + maciStartBlock, + quiet, + proofDir, + paramsDir, + numQueueOps, + blocksPerBatch, + rapidsnark, + sleep, + batchSize, + manageNonce, + }, + hre + ) => { + console.log('Verbose logging enabled:', !quiet) + + const storage = ContractStorage.getInstance() + const subtask = Subtask.getInstance(hre) + subtask.setHre(hre) + + await subtask.logStart() + + const clrfundContractAddress = + clrfund ?? storage.mustGetAddress(EContracts.ClrFund, hre.network.name) + + await hre.run('gen-proofs', { + clrfund: clrfundContractAddress, + maciStartBlock, + maciTxHash, + numQueueOps, + blocksPerBatch, + rapidsnark, + sleep, + proofDir, + paramsDir, + manageNonce, + quiet, + }) + + // proveOnChain if not already processed + await hre.run('prove-on-chain', { + clrfund: clrfundContractAddress, + proofDir, + manageNonce, + quiet, + }) + + // Publish tally hash if it is not already published + await hre.run('publish-tally-results', { + clrfund: clrfundContractAddress, + proofDir, + batchSize, + manageNonce, + quiet, + }) + + const success = true + await subtask.finish(success) + } + ) diff --git a/contracts/utils/maci.ts b/contracts/utils/maci.ts index 6065f383d..bb3e3bb0b 100644 --- a/contracts/utils/maci.ts +++ b/contracts/utils/maci.ts @@ -183,6 +183,8 @@ type getGenProofArgsInput = { endBlock?: number // MACI state file maciStateFile?: string + // Tally output file + tallyFile: string // transaction signer signer: Signer // flag to turn on verbose logging in MACI cli @@ -206,12 +208,11 @@ export function getGenProofArgs(args: getGenProofArgsInput): GenProofsArgs { startBlock, endBlock, maciStateFile, + tallyFile, signer, quiet, } = args - const tallyFile = getTalyFilePath(outputDir) - const { processZkFile, tallyZkFile, diff --git a/contracts/utils/misc.ts b/contracts/utils/misc.ts index 34bb53482..029a455de 100644 --- a/contracts/utils/misc.ts +++ b/contracts/utils/misc.ts @@ -29,10 +29,9 @@ export function isPathExist(path: string): boolean { } /** - * Returns the directory of the path - * @param file The file path - * @returns The directory of the file + * Create a directory + * @param directory The directory to create */ -export function getDirname(file: string): string { - return path.dirname(file) +export function makeDirectory(directory: string): void { + fs.mkdirSync(directory) } From d70d4147285420f06706168907ee34bc6f6df684 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Fri, 5 Apr 2024 22:06:02 -0400 Subject: [PATCH 13/14] refactor the tally script to re-run without extra inputs --- .github/workflows/finalize-round.yml | 9 +- contracts/.env.example | 4 + contracts/e2e/index.ts | 4 + contracts/package.json | 1 + contracts/sh/runScriptTests.sh | 10 +- contracts/tasks/runners/claim.ts | 30 +++--- contracts/tasks/runners/finalize.ts | 26 +++--- contracts/tasks/runners/genProofs.ts | 92 ++++++++++++------- .../tasks/runners/publishTallyResults.ts | 28 ++++-- contracts/tasks/runners/tally.ts | 33 ++++++- contracts/utils/ipfs.ts | 28 +++++- contracts/utils/misc.ts | 25 ++++- docs/tally-verify.md | 41 ++------- yarn.lock | 69 +++++++++++++- 14 files changed, 287 insertions(+), 113 deletions(-) diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index d0af1ab9f..fd972749f 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -86,8 +86,7 @@ jobs: --rapidsnark ${RAPID_SNARK} \ --params-dir ${CIRCUIT_DIRECTORY} \ --blocks-per-batch ${BLOCKS_PER_BATCH} \ - --maci-tx-hash "${MACI_TX_HASH}" --output-dir "./proof_output" - curl --location --request POST 'https://api.pinata.cloud/pinning/pinFileToIPFS' \ - --header "Authorization: Bearer ${{ secrets.PINATA_JWT }}" \ - --form 'file=@"./proof_output/tally.json"' - yarn hardhat --network "${NETWORK}" finalize --clrfund "${CLRFUND_ADDRESS}" + --maci-tx-hash "${MACI_TX_HASH}" \ + --proof-dir "./proof_output" + yarn hardhat --network "${NETWORK}" finalize --clrfund "${CLRFUND_ADDRESS}" \ + --proof-dir "./proof_output" diff --git a/contracts/.env.example b/contracts/.env.example index bdede7da1..2db7006e4 100644 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -13,6 +13,10 @@ COORDINATOR_MACISK= # Update the etherscan section in hardhat.config to add API key for other chains ARBISCAN_API_KEY= +# PINATE credentials to upload tally.json file to IPFS; used by the tally script +PINATA_API_KEY= +PINATA_SECRET_API_KEY= + # these are used in the e2e testing CIRCUIT_TYPE= CIRCUIT_DIRECTORY= diff --git a/contracts/e2e/index.ts b/contracts/e2e/index.ts index 5a22205dd..76e21525c 100644 --- a/contracts/e2e/index.ts +++ b/contracts/e2e/index.ts @@ -36,6 +36,7 @@ import path from 'path' import { FundingRound } from '../typechain-types' import { JSONFile } from '../utils/JSONFile' import { EContracts } from '../utils/types' +import { getTalyFilePath } from '../utils/misc' type VoteData = { recipientIndex: number; voiceCredits: bigint } type ClaimData = { [index: number]: bigint } @@ -359,6 +360,8 @@ describe('End-to-end Tests', function () { mkdirSync(outputDir, { recursive: true }) } + const tallyFile = getTalyFilePath(outputDir) + // past an end block that's later than the MACI start block const genProofArgs = getGenProofArgs({ maciAddress, @@ -368,6 +371,7 @@ describe('End-to-end Tests', function () { circuitType: circuit, circuitDirectory, outputDir, + tallyFile, blocksPerBatch: DEFAULT_GET_LOG_BATCH_SIZE, maciTxHash: maciTransactionHash, signer: coordinator, diff --git a/contracts/package.json b/contracts/package.json index 7bbbb8133..a52b79d4f 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@openzeppelin/contracts": "4.9.0", + "@pinata/sdk": "^2.1.0", "dotenv": "^8.2.0", "maci-contracts": "^1.2.0", "solidity-rlp": "2.0.8" diff --git a/contracts/sh/runScriptTests.sh b/contracts/sh/runScriptTests.sh index b17de7b2f..11cde09a2 100755 --- a/contracts/sh/runScriptTests.sh +++ b/contracts/sh/runScriptTests.sh @@ -35,17 +35,15 @@ yarn hardhat time-travel --seconds ${ROUND_DURATION} --network ${HARDHAT_NETWORK # tally the votes NODE_OPTIONS="--max-old-space-size=4096" -yarn hardhat gen-proofs \ +yarn hardhat tally \ --rapidsnark ${RAPID_SNARK} \ --proof-dir ${OUTPUT_DIR} \ --maci-start-block 0 \ --network "${HARDHAT_NETWORK}" -yarn hardhat prove-on-chain --proof-dir ${OUTPUT_DIR} --network "${HARDHAT_NETWORK}" -yarn hardhat publish-tally-results --proof-dir ${OUTPUT_DIR} --network "${HARDHAT_NETWORK}" # finalize the round -yarn hardhat finalize --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} +yarn hardhat finalize --proof-dir ${OUTPUT_DIR} --network ${HARDHAT_NETWORK} # claim funds -yarn hardhat claim --recipient 1 --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} -yarn hardhat claim --recipient 2 --tally-file ${TALLY_FILE} --network ${HARDHAT_NETWORK} +yarn hardhat claim --recipient 1 --proof-dir ${OUTPUT_DIR} --network ${HARDHAT_NETWORK} +yarn hardhat claim --recipient 2 --proof-dir ${OUTPUT_DIR} --network ${HARDHAT_NETWORK} diff --git a/contracts/tasks/runners/claim.ts b/contracts/tasks/runners/claim.ts index bf90576e2..e5135aa24 100644 --- a/contracts/tasks/runners/claim.ts +++ b/contracts/tasks/runners/claim.ts @@ -2,16 +2,17 @@ * Claim funds. This script is mainly used by e2e testing * * Sample usage: - * yarn hardhat claim \ - * --tally-file \ - * --recipient \ - * --network + * yarn hardhat claim --recipient --network */ import { getEventArg } from '../../utils/contracts' import { getRecipientClaimData } from '@clrfund/common' import { JSONFile } from '../../utils/JSONFile' -import { isPathExist } from '../../utils/misc' +import { + getProofDirForRound, + getTalyFilePath, + isPathExist, +} from '../../utils/misc' import { getNumber } from 'ethers' import { task, types } from 'hardhat/config' import { EContracts } from '../../utils/types' @@ -25,13 +26,9 @@ task('claim', 'Claim funnds for test recipients') undefined, types.int ) - .addParam('tallyFile', 'The tally file') + .addParam('proofDir', 'The proof output directory', './proof_output') .setAction( - async ({ tallyFile, recipient, roundAddress }, { ethers, network }) => { - if (!isPathExist(tallyFile)) { - throw new Error(`Path ${tallyFile} does not exist`) - } - + async ({ proofDir, recipient, roundAddress }, { ethers, network }) => { if (recipient <= 0) { throw new Error('Recipient must be greater than 0') } @@ -41,6 +38,17 @@ task('claim', 'Claim funnds for test recipients') roundAddress ?? storage.mustGetAddress(EContracts.FundingRound, network.name) + const proofDirForRound = getProofDirForRound( + proofDir, + network.name, + fundingRound + ) + + const tallyFile = getTalyFilePath(proofDirForRound) + if (!isPathExist(tallyFile)) { + throw new Error(`Path ${tallyFile} does not exist`) + } + const tally = JSONFile.read(tallyFile) const fundingRoundContract = await ethers.getContractAt( diff --git a/contracts/tasks/runners/finalize.ts b/contracts/tasks/runners/finalize.ts index 0240ba16b..e48a6c2b6 100644 --- a/contracts/tasks/runners/finalize.ts +++ b/contracts/tasks/runners/finalize.ts @@ -6,7 +6,7 @@ * - clrfund owner's wallet private key to interact with the contract * * Sample usage: - * yarn hardhat finalize --clrfund --tally-file --network + * yarn hardhat finalize --clrfund --network */ import { JSONFile } from '../../utils/JSONFile' @@ -16,20 +16,13 @@ import { task } from 'hardhat/config' import { EContracts } from '../../utils/types' import { ContractStorage } from '../helpers/ContractStorage' import { Subtask } from '../helpers/Subtask' +import { getProofDirForRound, getTalyFilePath } from '../../utils/misc' task('finalize', 'Finalize a funding round') .addOptionalParam('clrfund', 'The ClrFund contract address') - .addOptionalParam( - 'tallyFile', - 'The tally file path', - './proof_output/tally.json' - ) - .setAction(async ({ clrfund, tallyFile }, hre) => { + .addParam('proofDir', 'The proof output directory', './proof_output') + .setAction(async ({ clrfund, proofDir }, hre) => { const { ethers, network } = hre - const tally = JSONFile.read(tallyFile) - if (!tally.maci) { - throw Error('Bad tally file ' + tallyFile) - } const storage = ContractStorage.getInstance() const subtask = Subtask.getInstance(hre) @@ -63,6 +56,17 @@ task('finalize', 'Finalize a funding round') const treeDepths = await pollContract.treeDepths() console.log('voteOptionTreeDepth', treeDepths.voteOptionTreeDepth) + const currentRoundProofDir = getProofDirForRound( + proofDir, + network.name, + currentRoundAddress + ) + const tallyFile = getTalyFilePath(currentRoundProofDir) + const tally = JSONFile.read(tallyFile) + if (!tally.maci) { + throw Error('Bad tally file ' + tallyFile) + } + const totalSpent = tally.totalSpentVoiceCredits.spent const totalSpentSalt = tally.totalSpentVoiceCredits.salt diff --git a/contracts/tasks/runners/genProofs.ts b/contracts/tasks/runners/genProofs.ts index a0656e650..ed3061edb 100644 --- a/contracts/tasks/runners/genProofs.ts +++ b/contracts/tasks/runners/genProofs.ts @@ -36,6 +36,29 @@ import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' import { ContractStorage } from '../helpers/ContractStorage' import { DEFAULT_CIRCUIT } from '../../utils/circuits' +import { JSONFile } from '../../utils/JSONFile' + +/** + * Check if the tally file with the maci contract address exists + * @param tallyFile The tally file path + * @param maciAddress The MACI contract address + * @returns true if the file exists and it contains the MACI contract address + */ +function tallyFileExists(tallyFile: string, maciAddress: string): boolean { + if (!isPathExist(tallyFile)) { + return false + } + try { + const tallyData = JSONFile.read(tallyFile) + return ( + tallyData.maci && + tallyData.maci.toLowerCase() === maciAddress.toLowerCase() + ) + } catch { + // in case the file does not have the expected format/field + return false + } +} task('gen-proofs', 'Generate MACI proofs offchain') .addOptionalParam('clrfund', 'FundingRound contract address') @@ -148,46 +171,49 @@ task('gen-proofs', 'Generate MACI proofs offchain') const maciStateFile = getMaciStateFilePath(proofDir) const providerUrl = (network.config as any).url - if (!isPathExist(tallyFile)) { - if (!isPathExist(maciStateFile)) { - if (!maciTxHash && maciStartBlock == null) { - throw new Error( - 'Please provide a value for --maci-tx-hash or --maci-start-block' - ) - } - - await genLocalState({ - quiet, - outputPath: maciStateFile, - pollId, - maciContractAddress: maciAddress, - coordinatorPrivateKey: coordinatorMacisk, - ethereumProvider: providerUrl, - transactionHash: maciTxHash, - startBlock: maciStartBlock, - blockPerBatch: blocksPerBatch, - signer: coordinator, - sleep, - }) + if (tallyFileExists(tallyFile, maciAddress)) { + console.log('The tally file has already been generated.') + return + } + + if (!isPathExist(maciStateFile)) { + if (!maciTxHash && maciStartBlock == null) { + throw new Error( + 'Please provide a value for --maci-tx-hash or --maci-start-block' + ) } - const genProofArgs = getGenProofArgs({ - maciAddress, + await genLocalState({ + quiet, + outputPath: maciStateFile, pollId, - coordinatorMacisk, - rapidsnark, - circuitType: circuit, - circuitDirectory, - outputDir: proofDir, - blocksPerBatch: getNumber(blocksPerBatch), - maciStateFile, - tallyFile, + maciContractAddress: maciAddress, + coordinatorPrivateKey: coordinatorMacisk, + ethereumProvider: providerUrl, + transactionHash: maciTxHash, + startBlock: maciStartBlock, + blockPerBatch: blocksPerBatch, signer: coordinator, - quiet, + sleep, }) - await genProofs(genProofArgs) } + const genProofArgs = getGenProofArgs({ + maciAddress, + pollId, + coordinatorMacisk, + rapidsnark, + circuitType: circuit, + circuitDirectory, + outputDir: proofDir, + blocksPerBatch: getNumber(blocksPerBatch), + maciStateFile, + tallyFile, + signer: coordinator, + quiet, + }) + await genProofs(genProofArgs) + const success = true await subtask.finish(success) } diff --git a/contracts/tasks/runners/publishTallyResults.ts b/contracts/tasks/runners/publishTallyResults.ts index 91699fa94..5651ac353 100644 --- a/contracts/tasks/runners/publishTallyResults.ts +++ b/contracts/tasks/runners/publishTallyResults.ts @@ -5,6 +5,8 @@ * Make sure to set the following environment variables in the .env file * 1) WALLET_PRIVATE_KEY or WALLET_MNEMONIC * - coordinator's wallet private key to interact with contracts + * 2) PINATA_API_KEY - The Pinata api key for pinning file to IPFS + * 3) PINATA_SECRET_API_KEY - The Pinata secret api key for pinning file to IPFS * * Sample usage: * @@ -15,7 +17,7 @@ import { BaseContract, getNumber, NonceManager } from 'ethers' import { task, types } from 'hardhat/config' -import { getIpfsHash } from '../../utils/ipfs' +import { Ipfs } from '../../utils/ipfs' import { JSONFile } from '../../utils/JSONFile' import { addTallyResultsBatch, TallyData, verify } from '../../utils/maci' import { FundingRound, Poll } from '../../typechain-types' @@ -25,17 +27,17 @@ import { Subtask } from '../helpers/Subtask' import { getCurrentFundingRoundContract } from '../../utils/contracts' import { getTalyFilePath } from '../../utils/misc' import { ContractStorage } from '../helpers/ContractStorage' +import { PINATA_PINNING_URL } from '../../utils/constants' /** * Publish the tally IPFS hash on chain if it's not already published * @param fundingRoundContract Funding round contract - * @param tallyData Tally data + * @param tallyHash Tally hash */ async function publishTallyHash( fundingRoundContract: FundingRound, - tallyData: TallyData + tallyHash: string ) { - const tallyHash = await getIpfsHash(tallyData) console.log(`Tally hash is ${tallyHash}`) const tallyHashOnChain = await fundingRoundContract.tallyHash() @@ -63,7 +65,9 @@ async function submitTallyResults( ) { const startIndex = await fundingRoundContract.totalTallyResults() const total = tallyData.results.tally.length - console.log('Uploading tally results in batches of', batchSize) + if (startIndex < total) { + console.log('Uploading tally results in batches of', batchSize) + } const addTallyGas = await addTallyResultsBatch( fundingRoundContract, recipientTreeDepth, @@ -119,6 +123,16 @@ task('publish-tally-results', 'Publish tally results') const coordinator = manageNonce ? new NonceManager(signer) : signer console.log('Coordinator address: ', await coordinator.getAddress()) + const apiKey = process.env.PINATA_API_KEY + if (!apiKey) { + throw new Error('Env. variable PINATA_API_KEY not set') + } + + const secretApiKey = process.env.PINATA_SECRET_API_KEY + if (!secretApiKey) { + throw new Error('Env. variable PINATA_SECRET_API_KEY not set') + } + await subtask.logStart() const clrfundContractAddress = @@ -149,8 +163,10 @@ task('publish-tally-results', 'Publish tally results') quiet, }) + const tallyHash = await Ipfs.pinFile(tallyFile, apiKey, secretApiKey) + // Publish tally hash if it is not already published - await publishTallyHash(fundingRoundContract, tallyData) + await publishTallyHash(fundingRoundContract, tallyHash) // Submit tally results to the funding round contract // This function can be re-run from where it left off diff --git a/contracts/tasks/runners/tally.ts b/contracts/tasks/runners/tally.ts index f524dda72..94482b679 100644 --- a/contracts/tasks/runners/tally.ts +++ b/contracts/tasks/runners/tally.ts @@ -9,11 +9,13 @@ */ import { getNumber } from 'ethers' import { task, types } from 'hardhat/config' +import { ClrFund } from '../../typechain-types' import { DEFAULT_SR_QUEUE_OPS, DEFAULT_GET_LOG_BATCH_SIZE, } from '../../utils/constants' +import { getProofDirForRound } from '../../utils/misc' import { EContracts } from '../../utils/types' import { ContractStorage } from '../helpers/ContractStorage' import { Subtask } from '../helpers/Subtask' @@ -76,6 +78,16 @@ task('tally', 'Tally votes') ) => { console.log('Verbose logging enabled:', !quiet) + const apiKey = process.env.PINATA_API_KEY + if (!apiKey) { + throw new Error('Env. variable PINATA_API_KEY not set') + } + + const secretApiKey = process.env.PINATA_SECRET_API_KEY + if (!secretApiKey) { + throw new Error('Env. variable PINATA_SECRET_API_KEY not set') + } + const storage = ContractStorage.getInstance() const subtask = Subtask.getInstance(hre) subtask.setHre(hre) @@ -85,6 +97,21 @@ task('tally', 'Tally votes') const clrfundContractAddress = clrfund ?? storage.mustGetAddress(EContracts.ClrFund, hre.network.name) + const clrfundContract = subtask.getContract({ + name: EContracts.ClrFund, + address: clrfundContractAddress, + }) + + const fundingRoundContractAddress = await ( + await clrfundContract + ).getCurrentRound() + + const outputDir = getProofDirForRound( + proofDir, + hre.network.name, + fundingRoundContractAddress + ) + await hre.run('gen-proofs', { clrfund: clrfundContractAddress, maciStartBlock, @@ -93,7 +120,7 @@ task('tally', 'Tally votes') blocksPerBatch, rapidsnark, sleep, - proofDir, + proofDir: outputDir, paramsDir, manageNonce, quiet, @@ -102,7 +129,7 @@ task('tally', 'Tally votes') // proveOnChain if not already processed await hre.run('prove-on-chain', { clrfund: clrfundContractAddress, - proofDir, + proofDir: outputDir, manageNonce, quiet, }) @@ -110,7 +137,7 @@ task('tally', 'Tally votes') // Publish tally hash if it is not already published await hre.run('publish-tally-results', { clrfund: clrfundContractAddress, - proofDir, + proofDir: outputDir, batchSize, manageNonce, quiet, diff --git a/contracts/utils/ipfs.ts b/contracts/utils/ipfs.ts index 8fc5de275..092d9a876 100644 --- a/contracts/utils/ipfs.ts +++ b/contracts/utils/ipfs.ts @@ -2,7 +2,9 @@ const Hash = require('ipfs-only-hash') import { FetchRequest } from 'ethers' import { DEFAULT_IPFS_GATEWAY } from './constants' - +import fs from 'fs' +import path from 'path' +import pinataSDK from '@pinata/sdk' /** * Get the ipfs hash for the input object * @param object a json object to get the ipfs hash for @@ -26,4 +28,28 @@ export class Ipfs { const resp = await req.send() return resp.bodyJson } + + /** + * Pin a file to IPFS + * @param file The file path to be uploaded to IPFS + * @param apiKey Pinata api key + * @param secretApiKey Pinata secret api key + * @returns IPFS hash + */ + static async pinFile( + file: string, + apiKey: string, + secretApiKey: string + ): Promise { + const pinata = new pinataSDK(apiKey, secretApiKey) + const data = fs.createReadStream(file) + const name = path.basename(file) + const options = { + pinataMetadata: { + name, + }, + } + const res = await pinata.pinFileToIPFS(data, options) + return res.IpfsHash + } } diff --git a/contracts/utils/misc.ts b/contracts/utils/misc.ts index 029a455de..9f7a34297 100644 --- a/contracts/utils/misc.ts +++ b/contracts/utils/misc.ts @@ -19,6 +19,29 @@ export function getMaciStateFilePath(directory: string) { return path.join(directory, 'maci-state.json') } +/** + * Return the proof output directory + * @param directory The root directory + * @param network The network + * @param roundAddress The funding round contract address + * @returns The proofs output directory + */ +export function getProofDirForRound( + directory: string, + network: string, + roundAddress: string +) { + try { + return path.join( + directory, + network.toLowerCase(), + roundAddress.toLowerCase() + ) + } catch { + return directory + } +} + /** * Check if the path exist * @param path The path to check for existence @@ -33,5 +56,5 @@ export function isPathExist(path: string): boolean { * @param directory The directory to create */ export function makeDirectory(directory: string): void { - fs.mkdirSync(directory) + fs.mkdirSync(directory, { recursive: true }) } diff --git a/docs/tally-verify.md b/docs/tally-verify.md index 61139360e..9ce6bc7dd 100644 --- a/docs/tally-verify.md +++ b/docs/tally-verify.md @@ -18,46 +18,23 @@ COORDINATOR_MACISK= # private key for interacting with contracts WALLET_MNEMONIC= WALLET_PRIVATE_KEY -``` - -Decrypt messages, tally the votes and generate proofs: -``` -yarn hardhat gen-proofs --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-tx-hash {MACI_CREATION_TRANSACTION_HASH} --proof-dir {OUTPUT_DIR} --rapidsnark {RAPID_SNARK} --network {network} +# credential to upload tally result to IPFS +PINATA_API_KEY= +PINATA_SECRET_API_KEY= ``` -You only need to provide `--rapidsnark` if you are running the `tally` command on an intel chip. -If `gen-proofs` failed, you can rerun the command with the same parameters. If the maci-state.json file has been created, you can skip fetching MACI logs by providing the MACI state file as follow: +Decrypt messages, tally the votes: ``` -yarn hardhat gen-proofs --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-state-file {MACI_STATE_FILE_PATH} --proof-dir {OUTPUT_DIR} --rapidsnark {RAPID_SNARK} --network {network} +yarn hardhat tally --clrfund {CLRFUND_CONTRACT_ADDRESS} --maci-tx-hash {MACI_CREATION_TRANSACTION_HASH} --proof-dir {OUTPUT_DIR} --rapidsnark {RAPID_SNARK} --network {network} ``` - -**Make a backup of the {OUTPUT_DIR} before continuing to the next step** - - -Upload the proofs on chain: -``` -yarn hardhat prove-on-chain --clrfund {CLRFUND_CONTRACT_ADDRESS} --proof-dir {OUTPUT_DIR} --network {network} -yarn hardhat publish-tally-results --clrfund {CLRFUND_CONTRACT_ADDRESS} --proof-dir {OUTPUT_DIR} --network localhost -``` - -If there's error running `prove-on-chain` or `publish-tally-resuls`, simply rerun the commands with the same parameters. - - - -Result will be saved to `{OUTPUT_DIR}/tally.json` file, which must then be published via IPFS. - -**Using [command line](https://docs.ipfs.tech/reference/kubo/cli/#ipfs)** - +You only need to provide `--rapidsnark` if you are running the `tally` command on an intel chip. +If the `tally` script failed, you can rerun the command with the same parameters. ``` -# start ipfs daemon in one terminal -ipfs daemon -# in a diff terminal, go to `/contracts` (or where you have the file) and publish the file -ipfs add tally.json -``` +Result will be saved to `{OUTPUT_DIR}/{network}-{fundingRoundAddress}/tally.json` file, which is also available on IPFS at `https://{ipfs-gateway-host}/ipfs/{tally-hash}`. ### Finalize round @@ -72,7 +49,7 @@ WALLET_PRIVATE_KEY= Once you have the `tally.json` from the tally script, run: ``` -yarn hardhat finalize --tally-file {tally.json} --network {network} +yarn hardhat finalize --clrfund {CLRFUND_CONTRACT_ADDRESS} --proof-dir {OUTPUT_DIR} --network {network} ``` # How to verify the tally results diff --git a/yarn.lock b/yarn.lock index 11e8b7f78..86e13a1f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3409,6 +3409,16 @@ tslib "^2.5.0" webcrypto-core "^1.7.7" +"@pinata/sdk@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@pinata/sdk/-/sdk-2.1.0.tgz#d61aa8f21ec1206e867f4b65996db52b70316945" + integrity sha512-hkS0tcKtsjf9xhsEBs2Nbey5s+Db7x5rlOH9TaWHBXkJ7IwwOs2xnEDigNaxAHKjYAwcw+m2hzpO5QgOfeF7Zw== + dependencies: + axios "^0.21.1" + form-data "^2.3.3" + is-ipfs "^0.6.0" + path "^0.12.7" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -6830,7 +6840,7 @@ browserslist@^4.21.10, browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -bs58@^4.0.0: +bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== @@ -10984,7 +10994,7 @@ form-data-encoder@^2.1.2: resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-2.1.4.tgz#261ea35d2a70d48d30ec7a9603130fa5515e9cd5" integrity sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw== -form-data@^2.2.0: +form-data@^2.2.0, form-data@^2.3.3: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -12507,6 +12517,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + ini@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" @@ -13200,6 +13215,18 @@ is-ip@^3.1.0: dependencies: ip-regex "^4.0.0" +is-ipfs@^0.6.0: + version "0.6.3" + resolved "https://registry.yarnpkg.com/is-ipfs/-/is-ipfs-0.6.3.tgz#82a5350e0a42d01441c40b369f8791e91404c497" + integrity sha512-HyRot1dvLcxImtDqPxAaY1miO6WsiP/z7Yxpg2qpaLWv5UdhAPtLvHJ4kMLM0w8GSl8AFsVF23PHe1LzuWrUlQ== + dependencies: + bs58 "^4.0.1" + cids "~0.7.0" + mafmt "^7.0.0" + multiaddr "^7.2.1" + multibase "~0.6.0" + multihashes "~0.4.13" + is-lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/is-lower-case/-/is-lower-case-2.0.2.tgz#1c0884d3012c841556243483aa5d522f47396d2a" @@ -14968,6 +14995,13 @@ macos-release@^3.1.0: resolved "https://registry.yarnpkg.com/macos-release/-/macos-release-3.2.0.tgz#dcee82b6a4932971b1538dbf6f3aabc4a903b613" integrity sha512-fSErXALFNsnowREYZ49XCdOHF8wOPWuFOGQrAhP7x5J/BqQv+B02cNsTykGpDgRVx43EKg++6ANmTaGTtW+hUA== +mafmt@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/mafmt/-/mafmt-7.1.0.tgz#4126f6d0eded070ace7dbbb6fb04977412d380b5" + integrity sha512-vpeo9S+hepT3k2h5iFxzEHvvR0GPBx9uKaErmnRzYNcaKb03DgOArjEMlgG4a9LcuZZ89a3I8xbeto487n26eA== + dependencies: + multiaddr "^7.3.0" + magic-string@^0.26.7: version "0.26.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f" @@ -15646,6 +15680,18 @@ multiaddr@^10.0.0: uint8arrays "^3.0.0" varint "^6.0.0" +multiaddr@^7.2.1, multiaddr@^7.3.0: + version "7.5.0" + resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-7.5.0.tgz#976c88e256e512263445ab03b3b68c003d5f485e" + integrity sha512-GvhHsIGDULh06jyb6ev+VfREH9evJCFIRnh3jUt9iEZ6XDbyoisZRFEI9bMvK/AiR6y66y6P+eoBw9mBYMhMvw== + dependencies: + buffer "^5.5.0" + cids "~0.8.0" + class-is "^1.1.0" + is-ip "^3.1.0" + multibase "^0.7.0" + varint "^5.0.0" + multibase@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/multibase/-/multibase-0.7.0.tgz#1adfc1c50abe05eefeb5091ac0c2728d6b84581b" @@ -15690,7 +15736,7 @@ multiformats@^9.4.13, multiformats@^9.4.2, multiformats@^9.4.5, multiformats@^9. resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== -multihashes@^0.4.15, multihashes@~0.4.15: +multihashes@^0.4.15, multihashes@~0.4.13, multihashes@~0.4.15: version "0.4.21" resolved "https://registry.yarnpkg.com/multihashes/-/multihashes-0.4.21.tgz#dc02d525579f334a7909ade8a122dabb58ccfcb5" integrity sha512-uVSvmeCWf36pU2nB4/1kzYZjsXD9vofZKpgudqkceYY5g2aZZXJ5r9lxuzoRLl1OAp28XljXsEJ/X/85ZsKmKw== @@ -17063,6 +17109,14 @@ path-type@^5.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8" integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg== +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== + dependencies: + process "^0.11.1" + util "^0.10.3" + pathe@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.2.0.tgz#30fd7bbe0a0d91f0e60bae621f5d19e9e225c339" @@ -17431,7 +17485,7 @@ process-warning@^3.0.0: resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== -process@^0.11.10: +process@^0.11.1, process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== @@ -21003,6 +21057,13 @@ util.promisify@^1.0.0: object.getownpropertydescriptors "^2.1.6" safe-array-concat "^1.0.0" +util@^0.10.3: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + util@^0.12.4, util@^0.12.5: version "0.12.5" resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" From 88f8898c1666a33a310a0600070e69cfa3d82ec8 Mon Sep 17 00:00:00 2001 From: yuetloo Date: Fri, 5 Apr 2024 22:32:36 -0400 Subject: [PATCH 14/14] add PINATA env variables --- .github/workflows/finalize-round.yml | 2 ++ .github/workflows/test-scripts.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/finalize-round.yml b/.github/workflows/finalize-round.yml index fd972749f..0109651d5 100644 --- a/.github/workflows/finalize-round.yml +++ b/.github/workflows/finalize-round.yml @@ -29,6 +29,8 @@ env: CIRCUIT_TYPE: micro ZKEYS_DOWNLOAD_SCRIPT: "download-6-9-2-3.sh" JSONRPC_HTTP_URL: ${{ github.event.inputs.jsonrpc_url }} + PINATA_API_KEY: ${{ secrets.PINATA_API_KEY }} + PINATA_SECRET_API_KEY: ${{ secrets.PINATA_SECRET_API_KEY }} jobs: finalize: diff --git a/.github/workflows/test-scripts.yml b/.github/workflows/test-scripts.yml index 5ebe7d346..05082aa5e 100644 --- a/.github/workflows/test-scripts.yml +++ b/.github/workflows/test-scripts.yml @@ -10,6 +10,8 @@ on: env: NODE_VERSION: 20.x ZKEYS_DOWNLOAD_SCRIPT: "download-6-9-2-3.sh" + PINATA_API_KEY: ${{ secrets.PINATA_API_KEY }} + PINATA_SECRET_API_KEY: ${{ secrets.PINATA_SECRET_API_KEY }} jobs: script-tests: