Skip to content

Commit

Permalink
Merge pull request #4144 from ethereum/circuit-compiler
Browse files Browse the repository at this point in the history
Circuit compiler UI
  • Loading branch information
ioedeveloper authored Oct 31, 2023
2 parents 7b17e83 + 1849efb commit 50d745d
Show file tree
Hide file tree
Showing 41 changed files with 1,524 additions and 158 deletions.
4 changes: 2 additions & 2 deletions apps/circuit-compiler/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/circuit-compiler/src",
"projectType": "application",
"implicitDependencies": ["remixd"],
"implicitDependencies": [],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
Expand All @@ -17,7 +17,7 @@
"main": "apps/circuit-compiler/src/main.tsx",
"polyfills": "apps/circuit-compiler/src/polyfills.ts",
"tsConfig": "apps/circuit-compiler/tsconfig.app.json",
"assets": ["apps/circuit-compiler/src/profile.json"],
"assets": ["apps/circuit-compiler/src/profile.json", "apps/circuit-compiler/src/snarkjs.min.js"],
"styles": ["apps/circuit-compiler/src/css/app.css"],
"scripts": [],
"webpackConfig": "apps/circuit-compiler/webpack.config.js"
Expand Down
43 changes: 43 additions & 0 deletions apps/circuit-compiler/src/app/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { CircomPluginClient } from "../services/circomPluginClient"
import { Actions, AppState } from "../types"

export const compileCircuit = async (plugin: CircomPluginClient, appState: AppState) => {
try {
if (appState.status !== "compiling") {
await plugin.compile(appState.filePath, { version: appState.version, prime: appState.primeValue })
} else {
console.log('Exisiting circuit compilation in progress')
}
} catch (e) {
plugin.internalEvents.emit('circuit_compiling_errored', e)
console.error(e)
}
}

export const generateR1cs = async (plugin: CircomPluginClient, appState: AppState) => {
try {
if (appState.status !== "generating") {
await plugin.generateR1cs(appState.filePath, { version: appState.version, prime: appState.primeValue })
} else {
console.log('Exisiting r1cs generation in progress')
}
} catch (e) {
plugin.internalEvents.emit('circuit_generating_r1cs_errored', e)
console.error('Generating R1CS failed: ', e)
}
}

export const computeWitness = async (plugin: CircomPluginClient, status: string, witnessValues: Record<string, string>) => {
try {
if (status !== "computing") {
const input = JSON.stringify(witnessValues)

await plugin.computeWitness(input)
} else {
console.log('Exisiting witness computation in progress')
}
} catch (e) {
plugin.internalEvents.emit('circuit_computing_witness_errored', e)
console.error('Computing witness failed: ', e)
}
}
138 changes: 132 additions & 6 deletions apps/circuit-compiler/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,143 @@
import React, { useEffect } from 'react'
import React, {useEffect, useReducer, useState} from 'react'
import {RenderIf} from '@remix-ui/helper'
import {IntlProvider} from 'react-intl'

import { CircomPluginClient } from './services/circomPluginClient'
import { Container } from './components/container'
import {CircuitAppContext} from './contexts'
import {appInitialState, appReducer} from './reducers/state'
import {CircomPluginClient} from './services/circomPluginClient'
import { compileCircuit } from './actions'

const plugin = new CircomPluginClient()

function App() {
const [appState, dispatch] = useReducer(appReducer, appInitialState)
const [locale, setLocale] = useState<{code: string; messages: any}>({
code: 'en',
messages: null
})
const [isContentChanged, setIsContentChanged] = useState<boolean>(false)
const [isPluginActivated, setIsPluginActivated] = useState<boolean>(false)

useEffect(() => {
new CircomPluginClient()
plugin.internalEvents.on('circom_activated', () => {
// @ts-ignore
plugin.on('locale', 'localeChanged', (locale: any) => {
setLocale(locale)
})
plugin.on('fileManager', 'currentFileChanged', (filePath) => {
if (filePath.endsWith('.circom')) {
dispatch({ type: 'SET_FILE_PATH', payload: filePath })
plugin.parse(filePath)
} else {
dispatch({ type: 'SET_FILE_PATH', payload: '' })
}
})
// @ts-ignore
plugin.on('editor', 'contentChanged', async (path: string, content: string) => {
setIsContentChanged(true)
if (path.endsWith('.circom')) {
plugin.parse(path, content)
}
})
setIsPluginActivated(true)
})

// compiling events
plugin.internalEvents.on('circuit_compiling_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'compiling' }))
plugin.internalEvents.on('circuit_compiling_done', (signalInputs: string[]) => {
signalInputs = (signalInputs || []).filter(input => input)
dispatch({ type: 'SET_SIGNAL_INPUTS', payload: signalInputs })
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
})
plugin.internalEvents.on('circuit_compiling_errored', compilerErrored)

// r1cs events
plugin.internalEvents.on('circuit_generating_r1cs_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'generating' }))
plugin.internalEvents.on('circuit_generating_r1cs_done', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' }))
plugin.internalEvents.on('circuit_generating_r1cs_errored', compilerErrored)

// witness events
plugin.internalEvents.on('circuit_computing_witness_start', () => dispatch({ type: 'SET_COMPILER_STATUS', payload: 'computing' }))
plugin.internalEvents.on('circuit_computing_witness_done', () => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'idle' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_computing_witness_errored', compilerErrored)

// parsing events
plugin.internalEvents.on('circuit_parsing_done', (_, filePathToId) => {
dispatch({ type: 'SET_FILE_PATH_TO_ID', payload: filePathToId })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: null })
})
plugin.internalEvents.on('circuit_parsing_errored', (report) => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
})
plugin.internalEvents.on('circuit_parsing_warning', (report) => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'warning' })
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
})
}, [])


useEffect(() => {
if (isContentChanged) {
(async () => {
if (appState.autoCompile) await compileCircuit(plugin, appState)
})()
setIsContentChanged(false)
}
}, [appState.autoCompile, isContentChanged])

useEffect(() => {
if (isPluginActivated) {
setCurrentLocale()
}
}, [isPluginActivated])

useEffect(() => {
if (appState.filePath) {
(async () => {
if (appState.autoCompile) await compileCircuit(plugin, appState)
})()
}
}, [appState.filePath])

const setCurrentLocale = async () => {
// @ts-ignore
const currentLocale = await plugin.call('locale', 'currentLocale')

setLocale(currentLocale)
}

const compilerErrored = (err: ErrorEvent) => {
dispatch({ type: 'SET_COMPILER_STATUS', payload: 'errored' })
try {
const report = JSON.parse(err.message)

dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: report })
} catch (e) {
dispatch({ type: 'SET_COMPILER_FEEDBACK', payload: err.message })
}
}

const value = {
appState,
dispatch,
plugin
}

return (
<div className="App">
<div className="circuit_compiler_app">
<RenderIf condition={locale.messages}>
<IntlProvider locale={locale.code} messages={locale.messages}>
<CircuitAppContext.Provider value={value}>
<Container />
</CircuitAppContext.Provider>
</IntlProvider>
</RenderIf>
</div>
)
}

export default App
export default App
11 changes: 11 additions & 0 deletions apps/circuit-compiler/src/app/components/actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CompileBtn } from "./compileBtn";
import { R1CSBtn } from "./r1csBtn";

export function CircuitActions () {
return (
<div className="pb-3">
<CompileBtn />
<R1CSBtn />
</div>
)
}
51 changes: 51 additions & 0 deletions apps/circuit-compiler/src/app/components/compileBtn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { CustomTooltip, RenderIf, RenderIfNot, extractNameFromKey } from "@remix-ui/helper";
import { useContext } from "react";
import { CircuitAppContext } from "../contexts";
import { FormattedMessage } from "react-intl";
import { compileCircuit } from "../actions";

export function CompileBtn () {
const { plugin, appState } = useContext(CircuitAppContext)

return (
<button
className="btn btn-primary btn-block d-block w-100 text-break mb-1 mt-3"
onClick={() => { compileCircuit(plugin, appState) }}
disabled={(appState.filePath === "") || (appState.status === "compiling") || (appState.status === "generating")}
>
<CustomTooltip
placement="auto"
tooltipId="overlay-tooltip-compile"
tooltipText={
<div className="text-left">
<div>
<b>Ctrl+S</b> to compile {appState.filePath}
</div>
</div>
}
>
<div className="d-flex align-items-center justify-content-center">
<RenderIf condition={appState.status === 'compiling'}>
<i className="fas fa-sync fa-spin mr-2" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={appState.status === 'compiling'}>
<i className="fas fa-sync mr-2" aria-hidden="true"></i>
</RenderIfNot>
<div className="text-truncate overflow-hidden text-nowrap">
<span>
<FormattedMessage id="circuit.compile" />
</span>
<span className="ml-1 text-nowrap">
<RenderIf condition={appState.filePath === ""}>
<FormattedMessage id="circuit.noFileSelected" />
</RenderIf>
<RenderIfNot condition={appState.filePath === ""}>
<>{extractNameFromKey(appState.filePath)}</>
</RenderIfNot>
</span>
</div>
</div>
</CustomTooltip>
</button>
)
}
36 changes: 36 additions & 0 deletions apps/circuit-compiler/src/app/components/configToggler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useState } from "react"
import { FormattedMessage } from "react-intl"
import { RenderIf, RenderIfNot } from "@remix-ui/helper"

export function ConfigToggler ({ children }: { children: JSX.Element }) {
const [toggleExpander, setToggleExpander] = useState<boolean>(false)

const toggleConfigurations = () => {
setToggleExpander(!toggleExpander)
}

return (
<div>
<div className="d-flex circuit_config_section justify-content-between" onClick={toggleConfigurations}>
<div className="d-flex">
<label className="mt-1 circuit_config_section">
<FormattedMessage id="circuit.advancedConfigurations" />
</label>
</div>
<div>
<span data-id="scConfigExpander" onClick={toggleConfigurations}>
<RenderIf condition={toggleExpander}>
<i className="fas fa-angle-down" aria-hidden="true"></i>
</RenderIf>
<RenderIfNot condition={toggleExpander}>
<i className="fas fa-angle-right" aria-hidden="true"></i>
</RenderIfNot>
</span>
</div>
</div>
<RenderIf condition={toggleExpander}>
{ children }
</RenderIf>
</div>
)
}
38 changes: 38 additions & 0 deletions apps/circuit-compiler/src/app/components/configurations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { CustomTooltip } from "@remix-ui/helper"
import { FormattedMessage } from "react-intl"
import { ConfigurationsProps, PrimeValue } from "../types"

export function Configurations ({primeValue, setPrimeValue}: ConfigurationsProps) {
return (
<div className="pb-2 border-bottom flex-column">
<div className="flex-column d-flex">
<div className="mb-2 ml-0">
<label className="circuit_inner_label form-check-label" htmlFor="circuitPrimeSelector">
<FormattedMessage id="circuit.prime" />
</label>
<CustomTooltip
placement={"auto"}
tooltipId="circuitPrimeLabelTooltip"
tooltipClasses="text-nowrap"
tooltipText={<span>{'To choose the prime number to use to generate the circuit. Receives the name of the curve (bn128, bls12381, goldilocks) [default: bn128]'}</span>}
>
<div>
<select
onChange={(e) => setPrimeValue(e.target.value as PrimeValue)}
value={primeValue}
className="custom-select"
style={{
pointerEvents: 'auto'
}}
>
<option value="bn128">bn128</option>
<option value="bls12381">bls12381</option>
<option value="goldilocks">goldilocks</option>
</select>
</div>
</CustomTooltip>
</div>
</div>
</div>
)
}
Loading

0 comments on commit 50d745d

Please sign in to comment.