diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index ec1a4d969d..eb45a454cb 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -7,6 +7,10 @@ on: pull_request: workflow_dispatch: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: benchmarks: timeout-minutes: 30 diff --git a/.github/workflows/build-action.yml b/.github/workflows/build-action.yml index 23039aadd0..1e3e7a3895 100644 --- a/.github/workflows/build-action.yml +++ b/.github/workflows/build-action.yml @@ -9,6 +9,10 @@ on: pull_request: workflow_dispatch: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Prepare: runs-on: ubuntu-latest diff --git a/.github/workflows/build-bindings.yml b/.github/workflows/build-bindings.yml new file mode 100644 index 0000000000..d1b6772a06 --- /dev/null +++ b/.github/workflows/build-bindings.yml @@ -0,0 +1,36 @@ +# Purpose: We want to build the o1js bindings in CI so that people in the +# community can change them without being scared of breaking things, or +# needing to do the complicated (without nix) build setup. + +name: Build o1js bindings + +on: + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + nix-build: + name: build-bindings-ubuntu + runs-on: [sdk-self-hosted-linux-amd64-build-system] + steps: + - name: Set up Nix + run: echo "PATH=$PATH:/nix/var/nix/profiles/default/bin" >> $GITHUB_ENV + - name: Disable smudging + run: echo "GIT_LFS_SKIP_SMUDGE=1" >> $GITHUB_ENV + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build the o1js bindings + run: | + set -Eeu + ./pin.sh + nix develop o1js --command bash -c "npm run build:update-bindings" + - name: Cleanup the Nix store + run: | + nix-env --delete-generations old + nix-collect-garbage -d --quiet + nix-store --gc --print-dead + nix-store --optimise diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 6bd4433615..b4f7eb790a 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -4,6 +4,10 @@ on: branches: - main +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: Build-Doc: runs-on: ubuntu-latest diff --git a/.github/workflows/live-tests.yml b/.github/workflows/live-tests.yml index a4be3529f4..dc7db700a1 100644 --- a/.github/workflows/live-tests.yml +++ b/.github/workflows/live-tests.yml @@ -12,6 +12,10 @@ on: - v1 workflow_dispatch: {} +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: master: timeout-minutes: 45 diff --git a/CHANGELOG.md b/CHANGELOG.md index 15f6a0b8f3..641a9f4a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/e1bac02...HEAD) +### Fixed + +- Compiling stuck in the browser for recursive zkprograms https://github.com/o1-labs/o1js/pull/1906 + ## [2.1.0](https://github.com/o1-labs/o1js/compare/b04520d...e1bac02) - 2024-11-13 ### Added diff --git a/package-lock.json b/package-lock.json index 3a94644315..ab82906a48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2671,12 +2671,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2908,9 +2908,9 @@ "dev": true }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -3518,9 +3518,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5708,12 +5708,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { diff --git a/src/bindings b/src/bindings index 2634cc0b11..2c62a9a755 160000 --- a/src/bindings +++ b/src/bindings @@ -1 +1 @@ -Subproject commit 2634cc0b113e02ffc692828568ed2fe4c6c65b6a +Subproject commit 2c62a9a755f1b128f89cc2131814df7157f68109 diff --git a/src/build/e2e-tests-build-helper.js b/src/build/e2e-tests-build-helper.js index 3709c514e8..f21d3f0fff 100644 --- a/src/build/e2e-tests-build-helper.js +++ b/src/build/e2e-tests-build-helper.js @@ -1,14 +1,31 @@ import replace from 'replace-in-file'; -const options = { - files: './dist/web/examples/zkapps/**/*.js', - from: /from 'o1js'/g, - to: "from '../../../index.js'", -}; - -try { - const results = await replace(options); - console.log('Replacement results:', results); -} catch (error) { - console.error('Error occurred:', error); +const replaceOptions = [ + { + files: './dist/web/examples/zkapps/**/*.js', + from: /from 'o1js'/g, + to: "from '../../../index.js'", + }, + { + files: './dist/web/examples/zkprogram/*.js', + from: /from 'o1js'/g, + to: "from '../../index.js'", + }, +]; + +async function performReplacement(options) { + try { + const results = await replace(options); + console.log(`Replacement results for ${options.files}:`, results); + } catch (error) { + console.error(`Error occurred while replacing in ${options.files}:`, error); + } +} + +async function main() { + for (const options of replaceOptions) { + await performReplacement(options); + } } + +main(); diff --git a/src/examples/zkprogram/recursive-program.ts b/src/examples/zkprogram/recursive-program.ts new file mode 100644 index 0000000000..0308266cd8 --- /dev/null +++ b/src/examples/zkprogram/recursive-program.ts @@ -0,0 +1,23 @@ +import { SelfProof, Field, ZkProgram } from 'o1js'; + +export const RecursiveProgram = ZkProgram({ + name: 'recursive-program', + publicInput: Field, + + methods: { + baseCase: { + privateInputs: [], + async method(input: Field) { + input.assertEquals(Field(0)); + }, + }, + + inductiveCase: { + privateInputs: [SelfProof], + async method(input: Field, earlierProof: SelfProof) { + earlierProof.verify(); + earlierProof.publicInput.add(1).assertEquals(input); + }, + }, + }, +}); diff --git a/tests/artifacts/html/on-chain-state-mgmt-zkapp-ui.html b/tests/artifacts/html/on-chain-state-mgmt-zkapp-ui.html index b9bad32a17..980822b28c 100644 --- a/tests/artifacts/html/on-chain-state-mgmt-zkapp-ui.html +++ b/tests/artifacts/html/on-chain-state-mgmt-zkapp-ui.html @@ -25,6 +25,10 @@

UI for the On-Chain State Management zkApp


+

zkProgram Management

+
+ +

zkApp Management

diff --git a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js index bfeed49252..86392c0d0c 100644 --- a/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js +++ b/tests/artifacts/javascript/on-chain-state-mgmt-zkapp-ui.js @@ -3,8 +3,10 @@ import { adminPrivateKey, HelloWorld, } from './examples/zkapps/hello-world/hello-world.js'; +import { RecursiveProgram } from './examples/zkprogram/recursive-program.js'; import { AccountUpdate, Field, Mina, verify } from './index.js'; +const compileButton = document.querySelector('#compileButton'); const deployButton = document.querySelector('#deployButton'); const updateButton = document.querySelector('#updateButton'); const clearEventsButton = document.querySelector('#clearEventsButton'); @@ -25,6 +27,22 @@ const contractAddress = Mina.TestPublicKey.random(); const contract = new HelloWorld(contractAddress); let verificationKey = null; +compileButton.addEventListener('click', async () => { + compileButton.disabled = true; + + logEvents('Compiling ZkProgram', eventsContainer); + + try { + await RecursiveProgram.compile(); + logEvents('ZkProgram compiled successfully!', eventsContainer); + } catch (exception) { + logEvents(`Compilation failure: ${exception.message}`, eventsContainer); + console.log(exception); + } + + compileButton.disabled = false; +}); + deployButton.addEventListener('click', async () => { deployButton.disabled = true; diff --git a/tests/on-chain-state-mgmt-zkapp-ui.spec.ts b/tests/on-chain-state-mgmt-zkapp-ui.spec.ts index 7fba7fd26d..76a7c72e4b 100644 --- a/tests/on-chain-state-mgmt-zkapp-ui.spec.ts +++ b/tests/on-chain-state-mgmt-zkapp-ui.spec.ts @@ -8,6 +8,16 @@ test.describe('On-Chain State Management zkApp UI', () => { await onChainStateMgmtZkAppPage.checkO1jsInitialization(); }); + test('should compile zkProgram', async ({ + onChainStateMgmtZkAppPage, + }) => { + await onChainStateMgmtZkAppPage.goto(); + await onChainStateMgmtZkAppPage.checkO1jsInitialization(); + await onChainStateMgmtZkAppPage.compileZkProgram(); + await onChainStateMgmtZkAppPage.checkZkProgramCompilation(); + }); + + test('should fail to update account state since zkApp was not yet deployed', async ({ onChainStateMgmtZkAppPage, }) => { diff --git a/tests/pages/on-chain-state-mgmt-zkapp.ts b/tests/pages/on-chain-state-mgmt-zkapp.ts index bf1a8027d9..d9e2e9b45b 100644 --- a/tests/pages/on-chain-state-mgmt-zkapp.ts +++ b/tests/pages/on-chain-state-mgmt-zkapp.ts @@ -2,6 +2,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; export class OnChainStateMgmtZkAppPage { readonly page: Page; + readonly compileButton: Locator; readonly deployButton: Locator; readonly updateButton: Locator; readonly clearEventsButton: Locator; @@ -11,6 +12,7 @@ export class OnChainStateMgmtZkAppPage { constructor(page: Page) { this.page = page; + this.compileButton = page.locator('button[id="compileButton"]'); this.deployButton = page.locator('button[id="deployButton"]'); this.updateButton = page.locator('button[id="updateButton"]'); this.clearEventsButton = page.locator('button[id="clearEventsButton"]'); @@ -23,6 +25,10 @@ export class OnChainStateMgmtZkAppPage { await this.page.goto('/on-chain-state-mgmt-zkapp-ui.html'); } + async compileZkProgram() { + await this.compileButton.click(); + } + async compileAndDeployZkApp() { await this.deployButton.click(); } @@ -40,6 +46,13 @@ export class OnChainStateMgmtZkAppPage { await expect(this.eventsContainer).toContainText('o1js initialized after'); } + async checkZkProgramCompilation() { + await expect(this.eventsContainer).toContainText('Compiling ZkProgram'); + await expect(this.eventsContainer).toContainText( + 'ZkProgram compiled successfully!' + ); + } + async checkDeployedZkApp() { await expect(this.eventsContainer).toContainText('Deploying'); await expect(this.eventsContainer).toContainText('Initial state: 2');