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

feat: separate gen proofs and submit tasks #1888

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions packages/contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ docs
.env
deploy-config.json
deployed-contracts.json
proofs
1 change: 1 addition & 0 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import "./tasks/runner/deployFull";
import "./tasks/runner/deployPoll";
import "./tasks/runner/merge";
import "./tasks/runner/prove";
import "./tasks/runner/submitOnChain";
import "./tasks/runner/verifyFull";

dotenv.config();
Expand Down
16 changes: 15 additions & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"verify": "hardhat verify-full",
"merge": "hardhat merge",
"prove": "hardhat prove",
"submitOnChain": "hardhat submitOnChain",
"benchmark": "hardhat benchmark",
"deploy:localhost": "pnpm run deploy",
"deploy:sepolia": "pnpm run deploy --network sepolia",
Expand Down Expand Up @@ -124,12 +125,25 @@
"prove:gnosis-chiado": "pnpm run prove --network gnosis_chiado",
"prove:polygon": "pnpm run prove --network polygon",
"prove:polygon-amoy": "pnpm run prove --network polygon_amoy",
"submitOnChain:localhost": "pnpm run submitOnChain --network localhost",
"submitOnChain:sepolia": "pnpm run submitOnChain --network sepolia",
"submitOnChain:optimism-sepolia": "pnpm run submitOnChain --network optimism_sepolia",
"submitOnChain:optimism": "pnpm run submitOnChain --network optimism",
"submitOnChain:scroll": "pnpm run submitOnChain --network scroll",
"submitOnChain:scroll-sepolia": "pnpm run submitOnChain --network scroll_sepolia",
"submitOnChain:arbitrum": "pnpm run submitOnChain --network arbitrum",
"submitOnChain:arbitrum-sepolia": "pnpm run submitOnChain --network arbitrum_sepolia",
"submitOnChain:base": "pnpm run submitOnChain --network base",
"submitOnChain:base-sepolia": "pnpm run submitOnChain --network base_sepolia",
"submitOnChain:gnosis": "pnpm run submitOnChain --network gnosis",
"submitOnChain:gnosis-chiado": "pnpm run submitOnChain --network gnosis_chiado",
"submitOnChain:polygon": "pnpm run submitOnChain --network polygon",
"submitOnChain:polygon-amoy": "pnpm run submitOnChain --network polygon_amoy",
"verify:sepolia": "pnpm run verify --network sepolia",
"verify:optimism-sepolia": "pnpm run verify --network optimism_sepolia",
"verify:optimism": "pnpm run verify --network optimism",
"verify:scroll": "pnpm run verify --network scroll",
"verify:scroll-sepolia": "pnpm run verify --network scroll_sepolia",
"verify:arbitrum": "pnpm run verify --network arbitrum",
"verify:arbitrum-sepolia": "pnpm run verify --network arbitrum_sepolia",
"verify:base": "pnpm run verify --network base",
"verify:base-sepolia": "pnpm run verify --network base_sepolia",
Expand Down
24 changes: 20 additions & 4 deletions packages/contracts/tasks/helpers/Prover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class Prover {
*
* @param proofs tally proofs
*/
async proveTally(proofs: Proof[], tallyData: TallyData): Promise<void> {
async proveTally(proofs: Proof[]): Promise<void> {
const [treeDepths, numSignUpsAndMessages, tallyBatchNumber, mode, stateTreeDepth] = await Promise.all([
this.pollContract.treeDepths(),
this.pollContract.numSignUpsAndMessages(),
Expand Down Expand Up @@ -310,11 +310,25 @@ export class Prover {
if (tallyBatchNum === totalTallyBatches) {
console.log("All vote tallying proofs have been submitted.");
}
}

/**
* Submit tally results on chain
*
* @param tallyData - tally data
* @param recipients - number of recipients
*/
async submitResults(tallyData: TallyData, recipients?: number): Promise<void> {
console.log("Submitting results...");

const tallyResults = tallyData.results.tally.map((t) => BigInt(t));
const tallyResultProofs = tallyData.results.tally.map((_, index) =>
genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)),
);

const [treeDepths] = await Promise.all([this.pollContract.treeDepths()]);

const resultLength = recipients ?? tallyResults.length;
const tallyResultProofs = tallyResults
.slice(0, resultLength)
.map((_, index) => genTreeProof(index, tallyResults, Number(treeDepths.voteOptionTreeDepth)));

await this.tallyContract
.addTallyResults({
Expand All @@ -329,6 +343,8 @@ export class Prover {
perVOSpentVoiceCreditsHash: tallyData.perVOSpentVoiceCredits?.commitment ?? 0n,
})
.then((tx) => tx.wait());

console.log("Results have been submitted.");
}

/**
Expand Down
20 changes: 20 additions & 0 deletions packages/contracts/tasks/helpers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ export interface IProveParams {
transactionHash?: string;
}

/**
* Interface that represents task submitOnChain params
*/
export interface ISubmitOnChainParams {
/**
* The poll id
*/
poll: BigNumberish;

/**
* The directory where proofs are stored
*/
outputDir: string;

/**
* The file to store the tally proof
*/
tallyFile: string;
}

/**
* Interface that represents prove generator params
*/
Expand Down
45 changes: 8 additions & 37 deletions packages/contracts/tasks/runner/prove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ import { Keypair, PrivKey } from "maci-domainobjs";

import fs from "fs";

import type { Proof } from "../../ts/types";
import type { VkRegistry, Verifier, MACI, Poll, AccQueue, MessageProcessor, Tally } from "../../typechain-types";
import type { MACI, Poll, AccQueue } from "../../typechain-types";

import { ContractStorage } from "../helpers/ContractStorage";
import { Deployment } from "../helpers/Deployment";
import { ProofGenerator } from "../helpers/ProofGenerator";
import { Prover } from "../helpers/Prover";
import { EContracts, type IProveParams } from "../helpers/types";

/**
* Prove hardhat task for generating off-chain proofs and sending them on-chain
*/
task("prove", "Command to generate proof and prove the result of a poll on-chain")
task("prove", "Command to generate proofs")
.addParam("poll", "The poll id", undefined, types.string)
.addParam("outputDir", "Output directory for proofs", undefined, types.string)
.addParam("coordinatorPrivateKey", "Coordinator maci private key", undefined, types.string)
Expand Down Expand Up @@ -71,8 +69,6 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain

const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name);
const maciContract = await deployment.getContract<MACI>({ name: EContracts.MACI, address: maciContractAddress });
const vkRegistryContract = await deployment.getContract<VkRegistry>({ name: EContracts.VkRegistry });
const verifierContract = await deployment.getContract<Verifier>({ name: EContracts.Verifier });

const pollContracts = await maciContract.polls(poll);
const pollContract = await deployment.getContract<Poll>({ name: EContracts.Poll, address: pollContracts.poll });
Expand Down Expand Up @@ -127,16 +123,6 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain
throw new Error(`Poll ${poll} not found`);
}

const mpContract = await deployment.getContract<MessageProcessor>({
name: EContracts.MessageProcessor,
address: pollContracts.messageProcessor,
});

// get the tally contract based on the useQuadraticVoting flag
const tallyContract = await deployment.getContract<Tally>({
name: EContracts.Tally,
address: pollContracts.tally,
});
const useQuadraticVoting =
deployment.getDeployConfigField<boolean | null>(EContracts.Poll, "useQuadraticVoting") ?? false;
const mode = useQuadraticVoting ? "qv" : "nonQv";
Expand Down Expand Up @@ -176,31 +162,16 @@ task("prove", "Command to generate proof and prove the result of a poll on-chain
useQuadraticVoting,
});

const data = {
processProofs: [] as Proof[],
tallyProofs: [] as Proof[],
};

const prover = new Prover({
maciContract,
messageAqContract,
mpContract,
pollContract,
vkRegistryContract,
verifierContract,
tallyContract,
});

data.processProofs = await proofGenerator.generateMpProofs();
await prover.proveMessageProcessing(data.processProofs);

const { proofs: tallyProofs, tallyData } = await proofGenerator.generateTallyProofs(network);
data.tallyProofs = tallyProofs;
await prover.proveTally(data.tallyProofs, tallyData);
await proofGenerator.generateMpProofs();
await proofGenerator.generateTallyProofs(network);

const endBalance = await signer.provider.getBalance(signer);

console.log("End balance: ", Number(endBalance / 10n ** 12n) / 1e6);
console.log("Prove expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6);

console.log(
"Please make sure that you do not delete the proofs from the proof directory until they are all submitted on-chain.\nRegenerating proofs will result in overwriting the existing proofs and commitments which will be different due to the use of random salts.",
);
},
);
132 changes: 132 additions & 0 deletions packages/contracts/tasks/runner/submitOnChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-console */
import { task, types } from "hardhat/config";

import fs from "fs";

import type { Proof } from "../../ts/types";
import type { VkRegistry, Verifier, MACI, Poll, AccQueue, MessageProcessor, Tally } from "../../typechain-types";

import { ContractStorage } from "../helpers/ContractStorage";
import { Deployment } from "../helpers/Deployment";
import { Prover } from "../helpers/Prover";
import { EContracts, TallyData, type ISubmitOnChainParams } from "../helpers/types";

/**
* Prove hardhat task for submitting proofs on-chain as well as uploading tally results
*/
task("submitOnChain", "Command to prove the result of a poll on-chain")
.addParam("poll", "The poll id", undefined, types.string)
.addParam("outputDir", "Output directory for proofs", undefined, types.string)
.addParam("tallyFile", "The file to store the tally proof", undefined, types.string)
.setAction(async ({ outputDir, poll, tallyFile }: ISubmitOnChainParams, hre) => {
const deployment = Deployment.getInstance();
deployment.setHre(hre);
const storage = ContractStorage.getInstance();
// if we do not have the output directory just create it
const isOutputDirExists = fs.existsSync(outputDir);

if (!isOutputDirExists) {
// Create the directory
throw new Error(
`Output directory ${outputDir} does not exist. You must provide a valid directory containing the poll zk-SNARK proofs.`,
);
}

const signer = await deployment.getDeployer();
const { network } = hre;

const startBalance = await signer.provider.getBalance(signer);

console.log("Start balance: ", Number(startBalance / 10n ** 12n) / 1e6);

const maciContractAddress = storage.mustGetAddress(EContracts.MACI, network.name);
const maciContract = await deployment.getContract<MACI>({ name: EContracts.MACI, address: maciContractAddress });
const vkRegistryContract = await deployment.getContract<VkRegistry>({ name: EContracts.VkRegistry });
const verifierContract = await deployment.getContract<Verifier>({ name: EContracts.Verifier });

const pollContracts = await maciContract.polls(poll);
const pollContract = await deployment.getContract<Poll>({ name: EContracts.Poll, address: pollContracts.poll });

const [, messageAqContractAddress] = await pollContract.extContracts();
const messageAqContract = await deployment.getContract<AccQueue>({
name: EContracts.AccQueue,
address: messageAqContractAddress,
});
const isStateAqMerged = await pollContract.stateMerged();

// Check that the state and message trees have been merged for at least the first poll
if (!isStateAqMerged && poll.toString() === "0") {
throw new Error("The state tree has not been merged yet. Please use the mergeSignups subcommand to do so.");
}

const messageTreeDepth = await pollContract.treeDepths().then((depths) => Number(depths[2]));

// check that the main root is set
const mainRoot = await messageAqContract.getMainRoot(messageTreeDepth.toString());

if (mainRoot.toString() === "0") {
throw new Error("The message tree has not been merged yet. Please use the mergeMessages subcommand to do so.");
}

const mpContract = await deployment.getContract<MessageProcessor>({
name: EContracts.MessageProcessor,
address: pollContracts.messageProcessor,
});

// get the tally contract based on the useQuadraticVoting flag
const tallyContract = await deployment.getContract<Tally>({
name: EContracts.Tally,
address: pollContracts.tally,
});

const data = {
processProofs: [] as Proof[],
tallyProofs: [] as Proof[],
};

// read the proofs from the output directory
const files = fs.readdirSync(outputDir);

// Read process proofs
const processProofFiles = files.filter((f) => f.startsWith("process_") && f.endsWith(".json"));
await Promise.all(
processProofFiles.sort().map(async (file) => {
const proofData = JSON.parse(await fs.promises.readFile(`${outputDir}/${file}`, "utf8")) as Proof;
data.processProofs.push(proofData);
}),
);

// Read tally proofs
const tallyProofFiles = files.filter((f) => f.startsWith("tally_") && f.endsWith(".json"));
await Promise.all(
tallyProofFiles.sort().map(async (file) => {
const proofData = JSON.parse(await fs.promises.readFile(`${outputDir}/${file}`, "utf8")) as Proof;
data.tallyProofs.push(proofData);
}),
);

const prover = new Prover({
maciContract,
messageAqContract,
mpContract,
pollContract,
vkRegistryContract,
verifierContract,
tallyContract,
});

await prover.proveMessageProcessing(data.processProofs);

// read tally data
const tallyData = JSON.parse(await fs.promises.readFile(tallyFile, "utf8")) as unknown as TallyData;

await prover.proveTally(data.tallyProofs);

await prover.submitResults(tallyData);

const endBalance = await signer.provider.getBalance(signer);

console.log("End balance: ", Number(endBalance / 10n ** 12n) / 1e6);
console.log("Prove expenses: ", Number((startBalance - endBalance) / 10n ** 12n) / 1e6);
});
4 changes: 4 additions & 0 deletions packages/contracts/testScriptLocalhost.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ pnpm run prove:localhost --poll 0 \
--coordinator-private-key "macisk.1751146b59d32e3c0d7426de411218172428263f93b2fc4d981c036047a4d8c0" \
--tally-file ../cli/tally.json \
--output-dir ../cli/proofs/

pnpm run submitOnChain:localhost --poll 0 \
--tally-file ../cli/tally.json \
--output-dir ../cli/proofs/
Loading