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

refactor(optimisedmt): remove dependency and implement locally #1031

Merged
merged 1 commit into from
Jan 17, 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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ module.exports = {
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: ["**/*.test.ts"],
devDependencies: ["**/*.test.ts", "**/__benchmarks__/**"],
},
],
"no-debugger": isProduction ? "error" : "off",
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Benchmarks

on:
push:
branches: [dev]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: latest

- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"

- name: Install
run: |
pnpm install --frozen-lockfile --prefer-offline

- name: Build
run: |
pnpm build

- name: Benchmarks
run: pnpm benchmarks
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,6 @@ circuits/circom/test
publish

# typedoc
docs/typedoc/*
docs/typedoc/*

**/ts/__benchmarks__/results/**
4 changes: 2 additions & 2 deletions circuits/ts/__tests__/IncrementalQuinTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,13 @@ describe("IncrementalQuinTree circuit", function test() {
tree.insert(leaf);
});

const proof = tree.genMerklePath(2);
const proof = tree.genProof(2);

const circuitInputs = {
root: tree.root,
leaf: 3n,
path_elements: proof.pathElements,
path_index: proof.indices,
path_index: proof.pathIndices,
};

const witness = await circuitLeafExists.calculateWitness(circuitInputs);
Expand Down
1 change: 0 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"dependencies": {
"@commander-js/extra-typings": "^11.1.0",
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"big-integer": "^1.6.52",
"commander": "^11.1.0",
"dotenv": "^16.3.1",
"ethers": "^6.10.0",
Expand Down
8 changes: 6 additions & 2 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"scripts": {
"watch": "tsc --watch",
"build": "tsc -p tsconfig.build.json",
"benchmarks": "ts-node ts/__benchmarks__/index.ts",
"types": "tsc -p tsconfig.json --noEmit",
"test:processMessage": "ts-mocha --exit ts/__tests__/ProcessMessage.test.ts",
"test:maciState": "ts-mocha --exit ts/__tests__/MaciState.test.ts",
"test:e2e": "ts-mocha --exit ts/__tests__/e2e.test.ts",
"test:utils": "ts-mocha --exit ts/__tests__/utils.test.ts",
Expand All @@ -23,10 +23,12 @@
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.2",
"benny": "^3.7.1",
"chai": "^4.3.10",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"ts-mocha": "^10.0.0"
"ts-mocha": "^10.0.0",
"ts-node": "^10.9.1"
},
"nyc": {
"reporter": [
Expand All @@ -38,6 +40,8 @@
],
"all": true,
"exclude": [
"ts/__benchmarks__/**",
"**/__tests__/**",
"**/__tests__/*.ts",
"**/*.js",
"**/*.d.ts"
Expand Down
39 changes: 17 additions & 22 deletions core/ts/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,6 @@ export class Poll implements IPoll {
*/
copyStateFromMaci = (): void => {
// Copy the state tree, ballot tree, state leaves, and ballot leaves
assert(this.maciStateRef.stateLeaves.length === this.maciStateRef.stateTree.nextIndex);

this.stateLeaves = this.maciStateRef.stateLeaves.map((x) => x.copy());
this.stateTree = this.maciStateRef.stateTree.copy();

Expand Down Expand Up @@ -277,18 +275,18 @@ export class Poll implements IPoll {
// calculate the path elements for the state tree given the original state tree (before any changes)
// changes could effectively be made by this new vote - either a key change or vote change
// would result in a different state leaf
const originalStateLeafPathElements = this.stateTree?.genMerklePath(Number(stateLeafIndex)).pathElements;
const originalStateLeafPathElements = this.stateTree?.genProof(Number(stateLeafIndex)).pathElements;
// calculate the path elements for the ballot tree given the original ballot tree (before any changes)
// changes could effectively be made by this new ballot
const originalBallotPathElements = this.ballotTree?.genMerklePath(Number(stateLeafIndex)).pathElements;
const originalBallotPathElements = this.ballotTree?.genProof(Number(stateLeafIndex)).pathElements;

// create a new quinary tree where we insert the votes of the origin (up until this message is processed) ballot
const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5);
for (let i = 0; i < this.ballots[0].votes.length; i += 1) {
vt.insert(ballot.votes[i]);
}
// calculate the path elements for the vote option tree given the original vote option tree (before any changes)
const originalVoteWeightsPathElements = vt.genMerklePath(voteOptionIndex).pathElements;
const originalVoteWeightsPathElements = vt.genProof(voteOptionIndex).pathElements;
// we return the data which is then to be used in the processMessage circuit
// to generate a proof of processing
return {
Expand Down Expand Up @@ -524,10 +522,10 @@ export class Poll implements IPoll {
if (e instanceof ProcessMessageError) {
// Since the command is invalid, use a blank state leaf
currentStateLeaves.unshift(this.stateLeaves[0].copy());
currentStateLeavesPathElements.unshift(this.stateTree!.genMerklePath(0).pathElements);
currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements);
// since the command is invliad we use the blank ballot
currentBallots.unshift(this.ballots[0].copy());
currentBallotsPathElements.unshift(this.ballotTree!.genMerklePath(0).pathElements);
currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements);

// Since the command is invalid, we use a zero vote weight
currentVoteWeights.unshift(this.ballots[0].votes[0]);
Expand All @@ -536,7 +534,7 @@ export class Poll implements IPoll {
const vt = new IncrementalQuinTree(this.treeDepths.voteOptionTreeDepth, 0n, STATE_TREE_ARITY, hash5);
vt.insert(this.ballots[0].votes[0]);
// get the path elements for this empty vote weight leaf
currentVoteWeightsPathElements.unshift(vt.genMerklePath(0).pathElements);
currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
} else {
throw e;
}
Expand All @@ -550,7 +548,7 @@ export class Poll implements IPoll {
const amount = message.data[0] >= BigInt(this.ballots.length) ? 0n : message.data[1];

currentStateLeaves.unshift(this.stateLeaves[stateIndex].copy());
currentStateLeavesPathElements.unshift(this.stateTree!.genMerklePath(stateIndex).pathElements);
currentStateLeavesPathElements.unshift(this.stateTree!.genProof(stateIndex).pathElements);

// create a copy of the state leaf
const newStateLeaf = this.stateLeaves[stateIndex].copy();
Expand All @@ -564,7 +562,7 @@ export class Poll implements IPoll {
// we still need them as placeholder for vote command
const currentBallot = this.ballots[stateIndex].copy();
currentBallots.unshift(currentBallot);
currentBallotsPathElements.unshift(this.ballotTree!.genMerklePath(Number(stateIndex)).pathElements);
currentBallotsPathElements.unshift(this.ballotTree!.genProof(Number(stateIndex)).pathElements);
currentVoteWeights.unshift(currentBallot.votes[0]);

// create a quinary tree to fill with the votes of the current ballot
Expand All @@ -575,7 +573,7 @@ export class Poll implements IPoll {
}

// add to the first position the path elements of the vote weight tree
currentVoteWeightsPathElements.unshift(vt.genMerklePath(0).pathElements);
currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
} catch (e) {
// eslint-disable-next-line no-console
console.log("Error processing topup message: ", (e as Error).message);
Expand All @@ -588,10 +586,10 @@ export class Poll implements IPoll {
} else {
// Since we don't have a command at that position, use a blank state leaf
currentStateLeaves.unshift(this.stateLeaves[0].copy());
currentStateLeavesPathElements.unshift(this.stateTree!.genMerklePath(0).pathElements);
currentStateLeavesPathElements.unshift(this.stateTree!.genProof(0).pathElements);
// since the command is invliad we use the blank ballot
currentBallots.unshift(this.ballots[0].copy());
currentBallotsPathElements.unshift(this.ballotTree!.genMerklePath(0).pathElements);
currentBallotsPathElements.unshift(this.ballotTree!.genProof(0).pathElements);

// Since the command is invalid, we use a zero vote weight
currentVoteWeights.unshift(this.ballots[0].votes[0]);
Expand All @@ -601,7 +599,7 @@ export class Poll implements IPoll {
vt.insert(this.ballots[0].votes[0]);

// get the path elements for this empty vote weight leaf
currentVoteWeightsPathElements.unshift(vt.genMerklePath(0).pathElements);
currentVoteWeightsPathElements.unshift(vt.genProof(0).pathElements);
}
}

Expand Down Expand Up @@ -690,12 +688,9 @@ export class Poll implements IPoll {
}

// generate the path to the subroot of the message tree for this batch
const messageSubrootPath = this.messageTree.genMerkleSubrootPath(index, index + messageBatchSize);
const messageSubrootPath = this.messageTree.genSubrootProof(index, index + messageBatchSize);

assert(
IncrementalQuinTree.verifyMerklePath(messageSubrootPath, this.messageTree.hashFunc),
"The message subroot path is invalid",
);
assert(this.messageTree.verifyProof(messageSubrootPath), "The message subroot path is invalid");

// validate that the batch index is correct, if not fix it
// this means that the end will be the last message
Expand Down Expand Up @@ -815,8 +810,8 @@ export class Poll implements IPoll {
const colStartIndex = this.cbi * batchSize;
const [ballots1, ballots2] = this.subsidyCalculation(rowStartIndex, colStartIndex);

const ballotSubrootProof1 = this.ballotTree?.genMerkleSubrootPath(rowStartIndex, rowStartIndex + batchSize);
const ballotSubrootProof2 = this.ballotTree?.genMerkleSubrootPath(colStartIndex, colStartIndex + batchSize);
const ballotSubrootProof1 = this.ballotTree?.genSubrootProof(rowStartIndex, rowStartIndex + batchSize);
const ballotSubrootProof2 = this.ballotTree?.genSubrootProof(colStartIndex, colStartIndex + batchSize);

const newSubsidySalt = genRandomSalt();
saltIndex = `${this.rbi.toString()}-${this.cbi.toString()}`;
Expand Down Expand Up @@ -1087,7 +1082,7 @@ export class Poll implements IPoll {
const packedVals = packTallyVotesSmallVals(batchStartIndex, batchSize, this.maciStateRef.numSignUps);
const inputHash = sha256Hash([packedVals, sbCommitment, currentTallyCommitment, newTallyCommitment]);

const ballotSubrootProof = this.ballotTree?.genMerkleSubrootPath(batchStartIndex, batchStartIndex + batchSize);
const ballotSubrootProof = this.ballotTree?.genSubrootProof(batchStartIndex, batchStartIndex + batchSize);

const votes = ballots.map((x) => x.votes);

Expand Down
110 changes: 110 additions & 0 deletions core/ts/__benchmarks__/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import benny from "benny";
import { Keypair, PCommand } from "maci-domainobjs";

import { MaciState } from "..";

import {
COORDINATOR_KEYPAIR,
DURATION,
MAX_VALUES,
MESSAGE_BATCH_SIZE,
STATE_TREE_DEPTH,
TREE_DEPTHS,
VOICE_CREDIT_BALANCE,
} from "./utils/constants";

const NAME = "maci-core";

export default function runCore(): void {
benny.suite(
NAME,

benny.add(`maci-core - Generate circuit inputs for 10 signups and 50 messages`, () => {
const voteWeight = 9n;

const users: Keypair[] = [];

const maciState = new MaciState(STATE_TREE_DEPTH);
// Sign up and vote
for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) {
const userKeypair = new Keypair();
users.push(userKeypair);

maciState.signUp(userKeypair.pubKey, VOICE_CREDIT_BALANCE, BigInt(Math.floor(Date.now() / 1000)));
}

const pollId = maciState.deployPoll(
BigInt(Math.floor(Date.now() / 1000) + DURATION),
MAX_VALUES,
TREE_DEPTHS,
MESSAGE_BATCH_SIZE,
COORDINATOR_KEYPAIR,
);
const poll = maciState.polls[pollId];

// 24 valid votes
for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) {
const userKeypair = users[i];

const command = new PCommand(
BigInt(i + 1),
userKeypair.pubKey,
BigInt(i), // vote option index
voteWeight,
1n,
BigInt(pollId),
);

const signature = command.sign(userKeypair.privKey);

const ecdhKeypair = new Keypair();
const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, COORDINATOR_KEYPAIR.pubKey);
const message = command.encrypt(signature, sharedKey);
poll.publishMessage(message, ecdhKeypair.pubKey);
}

// 24 invalid votes
for (let i = 0; i < MESSAGE_BATCH_SIZE - 1; i += 1) {
const userKeypair = users[i];
const command = new PCommand(
BigInt(i + 1),
userKeypair.pubKey,
BigInt(i), // vote option index
VOICE_CREDIT_BALANCE * 2n, // invalid vote weight
1n,
BigInt(pollId),
);

const signature = command.sign(userKeypair.privKey);

const ecdhKeypair = new Keypair();
const sharedKey = Keypair.genEcdhSharedKey(ecdhKeypair.privKey, COORDINATOR_KEYPAIR.pubKey);
const message = command.encrypt(signature, sharedKey);
poll.publishMessage(message, ecdhKeypair.pubKey);
}

// Process messages
poll.processMessages(pollId);

// Process messages
poll.processMessages(pollId);

// Test processAllMessages
poll.processAllMessages();
}),

benny.cycle(),
benny.complete((results) => {
results.results.forEach((result) => {
// eslint-disable-next-line no-console
console.log(`${result.name}: mean time: ${result.details.mean.toFixed(2)}`);
});
}),

benny.save({ folder: "ts/__benchmarks__/results", file: NAME, version: "1.0.0", details: true }),
benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "chart.html", details: true }),
benny.save({ folder: "ts/__benchmarks__/results", file: NAME, format: "table.html", details: true }),
);
}

runCore();
19 changes: 19 additions & 0 deletions core/ts/__benchmarks__/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Keypair } from "maci-domainobjs";

export const VOICE_CREDIT_BALANCE = 100n;
export const DURATION = 30;
export const MESSAGE_BATCH_SIZE = 25;
export const COORDINATOR_KEYPAIR = new Keypair();
export const STATE_TREE_DEPTH = 10;
export const MAX_VALUES = {
maxUsers: 25,
maxMessages: 25,
maxVoteOptions: 25,
};

export const TREE_DEPTHS = {
intStateTreeDepth: 2,
messageTreeDepth: 3,
messageTreeSubDepth: 2,
voteOptionTreeDepth: 4,
};
Loading
Loading