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: