Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rerunable tally task #706

Merged
merged 7 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/finalize-round.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
echo "MACI_START_BLOCK:" $MACI_START_BLOCK
# tally and finalize
cd contracts
yarn hardhat run --network "${NETWORK}" scripts/tally.ts
yarn hardhat tally --round-address "${ROUND_ADDRESS}" --network "${NETWORK}"
curl --location --request POST 'https://api.pinata.cloud/pinning/pinFileToIPFS' \
--header "Authorization: Bearer ${{ secrets.PINATA_JWT }}" \
--form 'file=@"tally.json"'
Expand Down
2 changes: 1 addition & 1 deletion contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"deployTestRound:local": "hardhat run --network localhost scripts/deployTestRound.ts",
"contribute:local": "hardhat run --network localhost scripts/contribute.ts",
"vote:local": "hardhat run --network localhost scripts/vote.ts",
"tally:local": "hardhat run --network localhost scripts/tally.ts",
"tally:local": "hardhat --network localhost tally",
"finalize:local": "hardhat run --network localhost scripts/finalize.ts",
"claim:local": "hardhat run --network localhost scripts/claim.ts",
"test": "hardhat test",
Expand Down
123 changes: 0 additions & 123 deletions contracts/scripts/tally.ts

This file was deleted.

1 change: 1 addition & 0 deletions contracts/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ import './mergeAllocations'
import './setDurations'
import './deploySponsor'
import './loadUsers'
import './tally'
226 changes: 226 additions & 0 deletions contracts/tasks/tally.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import { task, types } from 'hardhat/config'
import fs from 'fs'
import { Contract, Wallet } from 'ethers'
import { genProofs, proveOnChain, fetchLogs } from 'maci-cli'

import { getIpfsHash } from '../utils/ipfs'
import { addTallyResultsBatch } from '../utils/maci'

type TallyArgs = {
fundingRound: Contract
coordinatorMaciPrivKey: string
coordinator: Wallet
startBlock: number
numBlocksPerRequest: number
batchSize: number
logsFile: string
maciStateFile: string
providerUrl: string
voteOptionTreeDepth: number
}

async function main(args: TallyArgs) {
const {
fundingRound,
coordinatorMaciPrivKey,
coordinator,
batchSize,
logsFile,
maciStateFile,
providerUrl,
voteOptionTreeDepth,
} = args

console.log('funding round address', fundingRound.address)
const maciAddress = await fundingRound.maci()
console.log('maci address', maciAddress)

const publishedTallyHash = await fundingRound.tallyHash()

let tally

if (!publishedTallyHash) {
// Process messages and tally votes
const results = await genProofs({
contract: maciAddress,
eth_provider: providerUrl,
privkey: coordinatorMaciPrivKey,
tally_file: 'tally.json',
output: 'proofs.json',
logs_file: logsFile,
macistate: maciStateFile,
})
if (!results) {
throw new Error('generation of proofs failed')
}
const { proofs } = results
tally = results.tally

// Submit proofs to MACI contract
await proveOnChain({
contract: maciAddress,
eth_privkey: coordinator.privateKey,
eth_provider: providerUrl,
privkey: coordinatorMaciPrivKey,
proof_file: proofs,
})

// Publish tally hash
const tallyHash = await getIpfsHash(tally)
await fundingRound.publishTallyHash(tallyHash)
console.log(`Tally hash is ${tallyHash}`)
} else {
// read the tally.json file
console.log(`Tally hash is ${publishedTallyHash}`)
try {
console.log(`Reading tally.json file...`)
const tallyStr = fs.readFileSync('tally.json').toString()
tally = JSON.parse(tallyStr)
} catch (err) {
console.log('Failed to get tally file', publishedTallyHash, err)
throw err
}
}

// Submit results to the funding round contract
const startIndex = await fundingRound.totalTallyResults()
const total = tally.results.tally.length
console.log('Uploading tally results in batches of', batchSize)
const addTallyGas = await addTallyResultsBatch(
fundingRound,
voteOptionTreeDepth,
tally,
batchSize,
startIndex.toNumber(),
(processed: number) => {
console.log(`Processed ${processed} / ${total}`)
}
)
console.log('Tally results uploaded. Gas used:', addTallyGas.toString())
}

task('tally', 'Tally votes for the current round')
.addParam(
'roundAddress',
'The funding round contract address',
'',
types.string
)
.addParam(
'batchSize',
'Number of tally result to submit on chain per batch',
20,
types.int
)
.addParam(
'numBlocksPerRequest',
'The number of blocks to fetch for each get log request',
200000,
types.int
)
.addParam(
'startBlock',
'The first block containing the MACI events',
0,
types.int
)
.addOptionalParam('maciLogs', 'The file path containing the MACI logs')
.addOptionalParam(
'maciStateFile',
'The MACI state file, genProof will continue from it last run'
)
.setAction(
async (
{
roundAddress,
maciLogs,
maciStateFile,
batchSize,
startBlock,
numBlocksPerRequest,
},
{ ethers, network }
) => {
let fundingRoundAddress = roundAddress
let coordinatorMaciPrivKey = process.env.COORDINATOR_PK || ''
let coordinatorEthPrivKey =
process.env.COORDINATOR_ETH_PK || process.env.WALLET_PRIVATE_KEY || ''
const providerUrl = (network.config as any).url

if (network.name === 'localhost') {
const stateStr = fs.readFileSync('state.json').toString()
const state = JSON.parse(stateStr)
fundingRoundAddress = state.fundingRound
coordinatorMaciPrivKey = state.coordinatorPrivKey
// default to the first account
coordinatorEthPrivKey = coordinatorEthPrivKey
? coordinatorEthPrivKey
: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
} else {
if (!coordinatorEthPrivKey) {
throw Error(
`Please set the environment variable COORDINATOR_ETH_PK, the coordinator's wallet private key`
)
}

if (!coordinatorMaciPrivKey) {
throw Error(
`Please set the environment variable COORDINATOR_PK, the coordinator's MACI private key`
)
}
}

if (!fundingRoundAddress) {
throw Error(`The '--round-address' parameter is required`)
}

console.log('Funding round address: ', fundingRoundAddress)
const coordinator = new Wallet(coordinatorEthPrivKey, ethers.provider)
console.log('Coordinator address: ', coordinator.address)

const fundingRound = await ethers.getContractAt(
'FundingRound',
fundingRoundAddress,
coordinator
)

const maciAddress = await fundingRound.maci()
const maci = await ethers.getContractAt('MACI', maciAddress, coordinator)
const [, , voteOptionTreeDepth] = await maci.treeDepths()
console.log('Vote option tree depth', voteOptionTreeDepth)

const timeMs = new Date().getTime()
const logsFile = maciLogs ? maciLogs : `maci_logs_${timeMs}.json`
if (!maciLogs) {
const maciAddress = await fundingRound.maci()
console.log('maci address', maciAddress)

// Fetch Maci logs
console.log('Fetching MACI logs from block', startBlock)
await fetchLogs({
contract: maciAddress,
eth_provider: (network.config as any).url,
privkey: coordinatorMaciPrivKey,
start_block: startBlock,
num_blocks_per_request: numBlocksPerRequest,
output: logsFile,
})
console.log('MACI logs generated at', logsFile)
}

await main({
fundingRound,
coordinatorMaciPrivKey,
coordinator,
startBlock,
numBlocksPerRequest,
batchSize,
voteOptionTreeDepth: Number(voteOptionTreeDepth),
logsFile,
providerUrl,
maciStateFile: maciStateFile
? maciStateFile
: `maci_state_${timeMs}.json`,
})
}
)
4 changes: 2 additions & 2 deletions contracts/utils/ipfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export async function getIpfsHash(object: any): Promise<string> {
}

export class Ipfs {
static async fetchJson(hash: string): Promise<any> {
const url = `${IPFS_BASE_URL}/ipfs/${hash}`
static async fetchJson(hash: string, gatewayUrl?: string): Promise<any> {
const url = `${gatewayUrl || IPFS_BASE_URL}/ipfs/${hash}`
const result = utils.fetchJson(url)
return result
}
Expand Down
Loading